diff options
author | Sebastien Vincent <seb@jitsi.org> | 2012-01-18 16:48:29 +0000 |
---|---|---|
committer | Sebastien Vincent <seb@jitsi.org> | 2012-01-18 16:48:29 +0000 |
commit | eadc63024f6e89e5c34a0caa77423511685e5076 (patch) | |
tree | e6f8cf2d45eefeeed0636c7bba83e29a79c294db /src/net/java/sip/communicator/impl | |
parent | 78cefa3a27f81ea48ee6017a390db627191b50ed (diff) | |
download | jitsi-eadc63024f6e89e5c34a0caa77423511685e5076.zip jitsi-eadc63024f6e89e5c34a0caa77423511685e5076.tar.gz jitsi-eadc63024f6e89e5c34a0caa77423511685e5076.tar.bz2 |
Adds support for audio devices hotplug as well as to change input/output audio devices during a call.
Diffstat (limited to 'src/net/java/sip/communicator/impl')
19 files changed, 722 insertions, 166 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java index acd9393..cd73947 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceFocusPanel.java @@ -13,7 +13,6 @@ import javax.swing.*; import net.java.sip.communicator.impl.gui.main.call.*; import net.java.sip.communicator.impl.gui.main.call.CallPeerAdapter; -import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.skin.*; diff --git a/src/net/java/sip/communicator/impl/neomedia/DeviceConfigurationComboBoxModel.java b/src/net/java/sip/communicator/impl/neomedia/DeviceConfigurationComboBoxModel.java index ee86f0d..f16c52d 100644 --- a/src/net/java/sip/communicator/impl/neomedia/DeviceConfigurationComboBoxModel.java +++ b/src/net/java/sip/communicator/impl/neomedia/DeviceConfigurationComboBoxModel.java @@ -14,12 +14,14 @@ import javax.swing.event.*; import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.impl.neomedia.device.*;
+import net.java.sip.communicator.impl.neomedia.portaudio.*;
/**
* @author Lubomir Marinov
*/
public class DeviceConfigurationComboBoxModel
- implements ComboBoxModel
+ implements ComboBoxModel,
+ PortAudioDeviceChangedCallback
{
/**
* Encapsulates CaptureDeviceInfo
@@ -133,6 +135,11 @@ public class DeviceConfigurationComboBoxModel this.deviceConfiguration = deviceConfiguration;
this.type = type;
+
+ if(type == AUDIO)
+ {
+ PortAudio.addDeviceChangedCallback(this);
+ }
}
public void addListDataListener(ListDataListener listener)
@@ -169,10 +176,9 @@ public class DeviceConfigurationComboBoxModel */
private CaptureDevice[] getDevices()
{
- if (devices != null)
+ if (type != AUDIO && devices != null)
return devices;
-
CaptureDeviceInfo[] infos;
switch (type)
{
@@ -229,7 +235,6 @@ public class DeviceConfigurationComboBoxModel throw new IllegalStateException("type");
}
-
for (CaptureDevice device : getDevices())
{
if (CaptureDevice.equals(device.info, info))
@@ -332,4 +337,45 @@ public class DeviceConfigurationComboBoxModel devices = null;
}
}
+
+ /**
+ * Reinitializes audio devices.
+ */
+ public void reinitAudio()
+ {
+ if(type == AUDIO)
+ {
+ devices = null;
+ // only for PortAudio
+ if(deviceConfiguration.getAudioSystem().equals(
+ DeviceConfiguration.AUDIO_SYSTEM_PORTAUDIO))
+ {
+ String systemName = DeviceConfiguration.AUDIO_NONE;
+
+ deviceConfiguration.setAudioSystem(systemName, null, false);
+ fireContentsChanged(-1, -1);
+ setSelectedItem(DeviceConfiguration.AUDIO_SYSTEM_PORTAUDIO);
+ fireContentsChanged(-1, -1);
+ }
+ }
+ }
+
+ /**
+ * Callback when PortAudio device changed.
+ */
+ public void deviceChanged()
+ {
+ if(!SwingUtilities.isEventDispatchThread())
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ deviceChanged();
+ }
+ });
+ return;
+ }
+ reinitAudio();
+ }
}
diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaConfiguration.java b/src/net/java/sip/communicator/impl/neomedia/MediaConfiguration.java index cbcac53..b52def9 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaConfiguration.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaConfiguration.java @@ -85,7 +85,7 @@ public class MediaConfiguration * Creates the ui controls for portaudio.
* @param portAudioPanel the panel
*/
- private static void createPortAudioControls(JPanel portAudioPanel)
+ static void createPortAudioControls(JPanel portAudioPanel)
{
GridBagConstraints constraints = new GridBagConstraints();
constraints.insets = new Insets(3, 0, 3, 5);
@@ -182,13 +182,23 @@ public class MediaConfiguration }
/**
- * Creates all the controls for a type(AUDIO or VIDEO)
+ * Creates basic controls for a type(AUDIO or VIDEO)
* @param type the type.
* @return the build Component.
*/
- private static Component createControls(int type)
+ public static Component createBasicControls(int type)
+ {
+ return createBasicControls(type, true);
+ }
+
+ /**
+ * Creates basic controls for a type(AUDIO or VIDEO)
+ * @param type the type.
+ * @param addTypeCbo add the type combobox
+ * @return the build Component.
+ */
+ public static Component createBasicControls(int type, boolean addTypeCbo)
{
- SIPCommTabbedPane container = new SIPCommTabbedPane();
final JComboBox cboDevice = new JComboBox();
cboDevice.setEditable(false);
cboDevice.setModel( new DeviceConfigurationComboBoxModel(
@@ -232,18 +242,22 @@ public class MediaConfiguration else
portAudioPanel = null;
- JLabel label = new JLabel(getLabelText(type));
- label.setDisplayedMnemonic(getDisplayedMnemonic(type));
- label.setLabelFor(cboDevice);
+ JPanel pnlDeviceAndDetails = new TransparentPanel(new BorderLayout());
- Container pnlDevice
- = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));
- pnlDevice.setMaximumSize(new Dimension(WIDTH, 25));
- pnlDevice.add(label);
- pnlDevice.add(cboDevice);
+ if(addTypeCbo)
+ {
+ Container pnlDevice
+ = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));
+ JLabel label = new JLabel(getLabelText(type));
- JPanel pnlDeviceAndDetails = new TransparentPanel(new BorderLayout());
- pnlDeviceAndDetails.add(pnlDevice, BorderLayout.NORTH);
+ label.setDisplayedMnemonic(getDisplayedMnemonic(type));
+ label.setLabelFor(cboDevice);
+
+ pnlDevice.setMaximumSize(new Dimension(WIDTH, 25));
+ pnlDevice.add(label);
+ pnlDevice.add(cboDevice);
+ pnlDeviceAndDetails.add(pnlDevice, BorderLayout.NORTH);
+ }
// if creating controls for audio will add devices panel
// otherwise it is video controls and will add preview panel
@@ -258,6 +272,19 @@ public class MediaConfiguration );
}
+ return pnlDeviceAndDetails;
+ }
+
+ /**
+ * Creates all the controls (including encoding) for a type(AUDIO or VIDEO)
+ * @param type the type.
+ * @return the build Component.
+ */
+ private static Component createControls(int type)
+ {
+ SIPCommTabbedPane container = new SIPCommTabbedPane();
+ Component pnlDeviceAndDetails = createBasicControls(type);
+
ResourceManagementService R = NeomediaActivator.getResources();
container.insertTab(
R.getI18NString("impl.media.configform.DEVICES"),
@@ -265,7 +292,7 @@ public class MediaConfiguration container.insertTab(
R.getI18NString("impl.media.configform.ENCODINGS"),
null, createEncodingControls(type), null, 1);
- if (portAudioPanel == null)
+ if (type == DeviceConfigurationComboBoxModel.VIDEO)
container.insertTab(
R.getI18NString("impl.media.configform.VIDEO_MORE_SETTINGS"),
null, createVideoAdvancedSettings(), null, 2);
diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java index c5e8b35..932923e 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java @@ -23,6 +23,7 @@ import net.java.sip.communicator.impl.neomedia.codec.*; import net.java.sip.communicator.impl.neomedia.codec.video.*; import net.java.sip.communicator.impl.neomedia.device.*; import net.java.sip.communicator.impl.neomedia.format.*; +import net.java.sip.communicator.impl.neomedia.portaudio.*; import net.java.sip.communicator.impl.neomedia.protocol.*; import net.java.sip.communicator.impl.neomedia.transform.sdes.*; import net.java.sip.communicator.service.configuration.*; @@ -39,7 +40,8 @@ import net.java.sip.communicator.util.swing.*; * @author Dmitri Melnikov */ public class MediaServiceImpl - implements MediaService + implements MediaService, + PortAudioDeviceChangedCallback { /** * The logger. @@ -154,6 +156,11 @@ public class MediaServiceImpl new ArrayList<Recorder.Listener>(); /** + * Audio configuration panel. + */ + private SIPCommDialog audioConfiguration = null; + + /** * Create a <tt>MediaStream</tt> which will use a specific * <tt>MediaDevice</tt> for capture and playback of media. The new instance * will not have a <tt>StreamConnector</tt> at the time of its construction @@ -559,6 +566,52 @@ public class MediaServiceImpl encodingConfiguration.initializeFormatPreferences(); encodingConfiguration.registerCustomPackages(); encodingConfiguration.registerCustomCodecs(); + + if(!OSUtils.IS_ANDROID) + { + final Component panel = MediaConfiguration.createBasicControls( + DeviceConfigurationComboBoxModel.AUDIO, false); + + audioConfiguration = new SIPCommDialog() + { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * {@inheritDoc} + */ + @Override + protected void close(boolean isEscaped) + { + setVisible(false); + } + }; + + TransparentPanel mainPanel = new TransparentPanel(new + BorderLayout()); + TransparentPanel btnPanel = new TransparentPanel(new + FlowLayout(FlowLayout.RIGHT)); + JButton btn = new JButton(NeomediaActivator.getResources(). + getI18NString("service.gui.CLOSE")); + btn.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + audioConfiguration.setVisible(false); + } + }); + btnPanel.add(btn); + mainPanel.add(panel, BorderLayout.CENTER); + mainPanel.add(btnPanel, BorderLayout.SOUTH); + + audioConfiguration.add(mainPanel); + audioConfiguration.validate(); + audioConfiguration.pack(); + + PortAudio.addDeviceChangedCallback(this); + } } /** @@ -567,6 +620,7 @@ public class MediaServiceImpl */ void stop() { + PortAudio.removeDeviceChangedCallback(this); } /** @@ -582,7 +636,7 @@ public class MediaServiceImpl /** * Creates <tt>SDesControl</tt> used to control all SDes options. - * + * * @return SDesControl instance. */ public SDesControl createSDesControl() @@ -1318,4 +1372,45 @@ public class MediaServiceImpl { return recorderListeners.iterator(); } + + /** + * Callback called from native PortAudio side that notify device changed. + */ + public void deviceChanged() + { + showAudioConfiguration(); + } + + /** + * Show audio configuration panel when media devices change. + */ + private void showAudioConfiguration() + { + if(audioConfiguration == null) + { + return; + } + + if (!SwingUtilities.isEventDispatchThread()) + { + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + showAudioConfiguration(); + } + }); + return; + } + + SwingUtilities.updateComponentTreeUI( + audioConfiguration.getComponent(0)); + audioConfiguration.pack(); + audioConfiguration.repaint(); + + if(!audioConfiguration.isVisible()) + { + audioConfiguration.setVisible(true); + } + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java index 2b8e720..f6d7c21 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java @@ -45,7 +45,8 @@ public class MediaStreamImpl implements ReceiveStreamListener, SendStreamListener, SessionListener, - RemoteListener + RemoteListener, + PropertyChangeListener { /** * The <tt>Logger</tt> used by the <tt>MediaStreamImpl</tt> class and its @@ -223,6 +224,11 @@ public class MediaStreamImpl private StatisticsEngine statisticsEngine = null; /** + * If the device session has been reinited. + */ + private boolean deviceSessionReinit = false; + + /** * Initializes a new <tt>MediaStreamImpl</tt> instance which will use the * specified <tt>MediaDevice</tt> for both capture and playback of media. * The new instance will not have an associated <tt>StreamConnector</tt> and @@ -266,6 +272,9 @@ public class MediaStreamImpl */ setDevice(device); + NeomediaActivator.getMediaServiceImpl().getDeviceConfiguration(). + addPropertyChangeListener(this); + //TODO add option to disable ZRTP, e.g. by implementing a NullControl this.srtpControl = (srtpControl == null) ? new ZrtpControlImpl() : srtpControl; @@ -499,6 +508,8 @@ public class MediaStreamImpl */ public void close() { + NeomediaActivator.getMediaServiceImpl().getDeviceConfiguration(). + removePropertyChangeListener(this); stop(); closeSendStreams(); @@ -1359,9 +1370,14 @@ public class MediaStreamImpl deviceSessionPropertyChangeListener); // keep player active - deviceSession.setDisposePlayerOnClose(false); + deviceSession.setDisposePlayerOnClose( + (deviceSession instanceof VideoMediaDeviceSession) + == false); deviceSession.close(); + + deviceSession.getDevice().close(); deviceSession = null; + deviceSessionReinit = true; } else { @@ -1404,7 +1420,9 @@ public class MediaStreamImpl synchronized (receiveStreamSyncRoot) { if (receiveStream != null) + { deviceSession.setReceiveStream(receiveStream); + } } } } @@ -2346,7 +2364,8 @@ public class MediaStreamImpl buff = new StringBuilder(StatisticsEngine.RTP_STAT_PREFIX); buff.append("call stats for incoming ") - .append(getFormat().getMediaType()).append(" stream SSRC:") + .append(getFormat() != null ? getFormat().getMediaType() : "") + .append(" stream SSRC:") .append(getRemoteSourceID()) .append("\n").append(StatisticsEngine.RTP_STAT_PREFIX) .append("packets received: ").append(rs.getPacketsRecd()) @@ -2394,4 +2413,39 @@ public class MediaStreamImpl logger.error("Error writing statistics", t); } } + + /** + * {@inheritDoc} + */ + public void propertyChange(PropertyChangeEvent evt) + { + // for the moment only handle audio device + if(this instanceof VideoMediaStreamImpl) + { + return; + } + + String prop = evt.getPropertyName(); + + if(prop.equals(DeviceConfiguration.AUDIO_CAPTURE_DEVICE)) + { + if(evt.getOldValue() == evt.getNewValue()) + return; + + if(deviceSessionReinit == false) + firePropertyChange("CHANGE_CAPTURE_DEV", evt.getOldValue(), + evt.getNewValue()); + + deviceSessionReinit = false; +/* + MediaServiceImpl mediaService = + NeomediaActivator.getMediaServiceImpl(); + MediaFormat format = getFormat(); + this.setDevice(mediaService.createMixer( + mediaService.getDefaultDevice(MediaType.AUDIO, + MediaUseCase.CALL))); + this.setFormat(format); +*/ + } + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/ZrtpControlImpl.java b/src/net/java/sip/communicator/impl/neomedia/ZrtpControlImpl.java index cef3cb4..cd24643 100644 --- a/src/net/java/sip/communicator/impl/neomedia/ZrtpControlImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/ZrtpControlImpl.java @@ -205,7 +205,7 @@ public class ZrtpControlImpl * calls this method to start the multi-stream ZRTP sessions. * * enable auto-start mode (auto-sensing) to the engine. - * @param multiStreamData + * @param master master SRTP data */ public void setMultistream(SrtpControl master) { @@ -258,7 +258,7 @@ public class ZrtpControlImpl /* * (non-Javadoc) - * + * * @see * net.java.sip.communicator.service.neomedia.ZrtpControl#getSecurityString * () @@ -270,7 +270,7 @@ public class ZrtpControlImpl /* * (non-Javadoc) - * + * * @see * net.java.sip.communicator.service.neomedia.ZrtpControl#isSecurityVerified * () @@ -282,7 +282,7 @@ public class ZrtpControlImpl /** * Returns false, ZRTP exchanges is keys over the media path. - * + * * @return false */ public boolean requiresSecureSignalingTransport() diff --git a/src/net/java/sip/communicator/impl/neomedia/audiolevel/AudioLevelEffect.java b/src/net/java/sip/communicator/impl/neomedia/audiolevel/AudioLevelEffect.java index 5f4d99e..7d6e6d1 100644 --- a/src/net/java/sip/communicator/impl/neomedia/audiolevel/AudioLevelEffect.java +++ b/src/net/java/sip/communicator/impl/neomedia/audiolevel/AudioLevelEffect.java @@ -55,6 +55,11 @@ public class AudioLevelEffect private Format[] supportedAudioFormats; /** + * Audio level listener. + */ + private SimpleAudioLevelListener audioLevelListener = null; + + /** * The minimum and maximum values of the scale */ public AudioLevelEffect() @@ -87,8 +92,19 @@ public class AudioLevelEffect */ public void setAudioLevelListener(SimpleAudioLevelListener listener) { + audioLevelListener = listener; eventDispatcher.setAudioLevelListener(listener); } + + /** + * Returns audio level listener. + * + * @return audio level listener or <tt>null</tt> if not exist + */ + public SimpleAudioLevelListener getAudioLevelListener() + { + return audioLevelListener; + } /** * Lists all of the input formats that this codec accepts. diff --git a/src/net/java/sip/communicator/impl/neomedia/conference/AudioMixingPushBufferStream.java b/src/net/java/sip/communicator/impl/neomedia/conference/AudioMixingPushBufferStream.java index 8b8afc6..dc2bd55 100644 --- a/src/net/java/sip/communicator/impl/neomedia/conference/AudioMixingPushBufferStream.java +++ b/src/net/java/sip/communicator/impl/neomedia/conference/AudioMixingPushBufferStream.java @@ -19,7 +19,7 @@ import net.java.sip.communicator.util.*; /** * Represents a <tt>PushBufferStream</tt> containing the result of the audio * mixing of <tt>DataSource</tt>s. - * + * * @author Lyubomir Marinov */ public class AudioMixingPushBufferStream @@ -84,7 +84,7 @@ public class AudioMixingPushBufferStream * input data of a specific <tt>AudioMixerPushBufferStream</tt> and * excluding from the mix the audio contributions of a specific * <tt>AudioMixingPushBufferDataSource</tt>. - * + * * @param audioMixerStream the <tt>AudioMixerPushBufferStream</tt> reading * data from input <tt>DataSource</tt>s and to push it to the new * <tt>AudioMixingPushBufferStream</tt> @@ -147,7 +147,7 @@ public class AudioMixingPushBufferStream * Gets the <tt>AudioMixingPushBufferDataSource</tt> which created and owns * this instance and defines the input data which is to not be mixed in the * output of this <tt>PushBufferStream</tt>. - * + * * @return the <tt>AudioMixingPushBufferDataSource</tt> which created and * owns this instance and defines the input data which is to not be mixed in * the output of this <tt>PushBufferStream</tt> @@ -173,7 +173,7 @@ public class AudioMixingPushBufferStream /** * Gets the maximum possible value for an audio sample of a specific * <tt>AudioFormat</tt>. - * + * * @param outputFormat the <tt>AudioFormat</tt> of which to get the maximum * possible value for an audio sample * @return the maximum possible value for an audio sample of the specified @@ -205,9 +205,9 @@ public class AudioMixingPushBufferStream * Mixes as in audio mixing a specified collection of audio sample sets and * returns the resulting mix audio sample set in a specific * <tt>AudioFormat</tt>. - * + * * @param inputSamples the collection of audio sample sets to be mixed into - * one audio sample set in the sense of audio mixing + * one audio sample set in the sense of audio mixing * @param outputFormat the <tt>AudioFormat</tt> in which the resulting mix * audio sample set is to be produced * @param outputSampleCount the size of the resulting mix audio sample set @@ -240,7 +240,7 @@ public class AudioMixingPushBufferStream } int maxOutputSample; - + try { maxOutputSample = getMaxOutputSample(outputFormat); @@ -249,23 +249,23 @@ public class AudioMixingPushBufferStream { throw new UnsupportedOperationException(ufex); } - + for (int[] inputStreamSamples : inputSamples) { - + if (inputStreamSamples == null) continue; - + int inputStreamSampleCount = inputStreamSamples.length; - + if (inputStreamSampleCount <= 0) continue; - + for (int i = 0; i < inputStreamSampleCount; i++) { int inputStreamSample = inputStreamSamples[i]; int outputSample = outputSamples[i]; - + outputSamples[i] = inputStreamSample + outputSample @@ -318,7 +318,7 @@ public class AudioMixingPushBufferStream AudioFormat outputFormat = getFormat(); int[] outputSamples = mix(inputSamples, outputFormat, maxInputSampleCount); - + Class<?> outputDataType = outputFormat.getDataType(); if (Format.byteArray.equals(outputDataType)) @@ -361,7 +361,7 @@ public class AudioMixingPushBufferStream * Sets the collection of audio sample sets to be mixed in the sense of * audio mixing by this stream when data is read from it. Triggers a push to * the clients of this stream. - * + * * @param inputSamples the collection of audio sample sets to be mixed by * this stream when data is read from it * @param maxInputSampleCount the maximum number of per-stream audio samples @@ -442,7 +442,7 @@ public class AudioMixingPushBufferStream * Converts an integer to a series of bytes and writes the result into a * specific output array of bytes starting the writing at a specific offset * in it. - * + * * @param input the integer to be written out as a series of bytes * @param output the output to receive the conversion of the specified * integer to a series of bytes diff --git a/src/net/java/sip/communicator/impl/neomedia/device/AbstractMediaDevice.java b/src/net/java/sip/communicator/impl/neomedia/device/AbstractMediaDevice.java index 5e5b60c..3d22b92 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/AbstractMediaDevice.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/AbstractMediaDevice.java @@ -133,4 +133,11 @@ public abstract class AbstractMediaDevice { return null; } + + /** + * Closes this <tt>MediaDevice</tt>. + */ + public void close() + { + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/device/AudioMediaDeviceSession.java b/src/net/java/sip/communicator/impl/neomedia/device/AudioMediaDeviceSession.java index 4de2de3..bb368c6 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/AudioMediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/AudioMediaDeviceSession.java @@ -258,4 +258,18 @@ public class AudioMediaDeviceSession { return -1; } + + /** + * Transfer rendering part from <tt>session</tt> to this instance. + * + * @param session <tt>MediaDeviceSession</tt> to transfer data from + */ + protected void transferRenderingSession(MediaDeviceSession session) + { + AudioMediaDeviceSession amds = (AudioMediaDeviceSession)session; + this.setStreamAudioLevelListener( + amds.streamAudioLevelEffect.getAudioLevelListener()); + this.setLocalUserAudioLevelListener( + amds.localUserAudioLevelEffect.getAudioLevelListener()); + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java b/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java index d3806ca..9a38148 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java @@ -153,6 +153,24 @@ public class AudioMixerMediaDevice } /** + * Closes this <tt>MediaDevice</tt> and removes all of the + * <tt>MediaDeviceSession</tt> of its <tt>AudioMixerMediaDeviceSession</tt>. + */ + @Override + public void close() + { + List<MediaDeviceSession> sessions = new ArrayList<MediaDeviceSession>(); + for(MediaStreamMediaDeviceSession s : + deviceSession.mediaStreamMediaDeviceSessions) + { + sessions.add(s); + } + + for(MediaDeviceSession s : sessions) + s.close(); + } + + /** * Connects to a specific <tt>CaptureDevice</tt> given in the form of a * <tt>DataSource</tt>. * @@ -212,11 +230,35 @@ public class AudioMixerMediaDevice public synchronized MediaDeviceSession createSession() { if (deviceSession == null) + { deviceSession = new AudioMixerMediaDeviceSession(); + } return new MediaStreamMediaDeviceSession(deviceSession); } /** + * Creates a new <tt>MediaDeviceSession</tt> instance which is to represent + * the use of this <tt>MediaDevice</tt> by a <tt>MediaStream</tt> and the + * rendering part of a previous <tt>MediaDeviceSession</tt>. + * + * @param oldSession previous <tt>MediaDeviceSession</tt> + * @return a new <tt>MediaDeviceSession</tt> instance which is to represent + * the use of this <tt>MediaDevice</tt> by a <tt>MediaStream</tt> + */ + @Override + public MediaDeviceSession createSession(MediaDeviceSession oldSession) + { + MediaStreamMediaDeviceSession session = + (MediaStreamMediaDeviceSession)createSession(); + MediaStreamMediaDeviceSession old = + (MediaStreamMediaDeviceSession)oldSession; + + session.setStreamAudioLevelListener(old.streamAudioLevelListener); + session.setLocalUserAudioLevelListener(old.localUserAudioLevelListener); + return session; + } + + /** * Notifies all currently registered <tt>SimpleAudioLevelListener</tt>s * that our local media now has audio level <tt>level</tt>. * @@ -271,7 +313,9 @@ public class AudioMixerMediaDevice if (inputDataSource == captureDevice) AudioMixerMediaDevice.this.connect(dataSource); else + { super.connect(dataSource, inputDataSource); + } } @Override @@ -326,9 +370,11 @@ public class AudioMixerMediaDevice && buffer.getData() != null) { if(! streamEventDispatcher.isRunning()) + { new Thread(streamEventDispatcher, "StreamAudioLevelDispatcher (Mixer Edition)") .start(); + } streamEventDispatcher.addData(buffer); } } @@ -713,7 +759,9 @@ public class AudioMixerMediaDevice if (mediaStreamMediaDeviceSessions .remove(mediaStreamMediaDeviceSession) && mediaStreamMediaDeviceSessions.isEmpty()) + { close(); + } } } } diff --git a/src/net/java/sip/communicator/impl/neomedia/device/DeviceConfiguration.java b/src/net/java/sip/communicator/impl/neomedia/device/DeviceConfiguration.java index 823b9b0..8ec5c51 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/DeviceConfiguration.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/DeviceConfiguration.java @@ -19,6 +19,7 @@ import net.java.sip.communicator.impl.neomedia.*; import net.java.sip.communicator.impl.neomedia.codec.*; import net.java.sip.communicator.impl.neomedia.codec.video.*; import net.java.sip.communicator.impl.neomedia.jmfext.media.renderer.audio.*; +import net.java.sip.communicator.impl.neomedia.portaudio.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.util.*; @@ -35,7 +36,8 @@ import net.java.sip.communicator.util.*; @SuppressWarnings("unchecked") public class DeviceConfiguration extends PropertyChangeNotifier - implements PropertyChangeListener + implements PropertyChangeListener, + PortAudioDeviceChangedCallback { /** @@ -324,6 +326,7 @@ public class DeviceConfiguration } registerCustomRenderers(); + PortAudio.addDeviceChangedCallback(this); } /** @@ -348,114 +351,8 @@ public class DeviceConfiguration */ private void extractConfiguredCaptureDevices() { - ConfigurationService config - = NeomediaActivator.getConfigurationService(); - - if (logger.isInfoEnabled()) - logger.info("Scanning for configured Audio Devices."); - CaptureDeviceInfo[] audioCaptureDevices = - getAvailableAudioCaptureDevices(); - if (config.getBoolean(PROP_AUDIO_DEVICE_IS_DISABLED, false)) - { - audioCaptureDevice = null; - audioSystem = AUDIO_NONE; - } - else if (audioCaptureDevices.length < 1) - { - logger.warn("No Audio Device was found."); - audioCaptureDevice = null; - audioSystem = AUDIO_NONE; - } - else - { - if (logger.isDebugEnabled()) - logger.debug("Found " + audioCaptureDevices.length - + " capture devices: " + audioCaptureDevices); - - String audioDevName = config.getString(PROP_AUDIO_DEVICE); - - if(audioDevName == null || audioDevName.equals(AUDIO_NONE)) - { - // the default behaviour if nothing set is to use PortAudio - // this will also choose the capture device - if(PortAudioAuto.isSupported()) - { - setAudioSystem(AUDIO_SYSTEM_PORTAUDIO, null, false); - if(audioDevName != null - && audioDevName.equals(AUDIO_NONE)) - setAudioCaptureDevice(null, false); - } - else - { - setAudioPlaybackDevice(null, false); - setAudioNotifyDevice(null, false); - if (OSUtils.IS_ANDROID) - { - setAudioCaptureDevice(audioCaptureDevices[0], false); - } - else - { - setAudioCaptureDevice(null, false); - setAudioSystem(AUDIO_SYSTEM_JAVASOUND, null, false); - } - } - } - else - { - for (CaptureDeviceInfo captureDeviceInfo : audioCaptureDevices) - { - if (audioDevName.equals(captureDeviceInfo.getName())) - { - setAudioSystem(getAudioSystem(captureDeviceInfo), - captureDeviceInfo, false); - break; - } - } - - if(getAudioSystem() == null || !PortAudioAuto.isSupported()) - { - logger.warn("Computer sound config changed or " + - "there is a problem since last config was saved, " + - "will back to default"); - setAudioPlaybackDevice(null, false); - setAudioNotifyDevice(null, false); - setAudioCaptureDevice(null, false); - setAudioSystem(AUDIO_SYSTEM_PORTAUDIO, null, false); - } - } - if (audioCaptureDevice != null) - if (logger.isInfoEnabled()) - logger.info("Found " + audioCaptureDevice.getName() - + " as an audio capture device."); - } - - if (config.getBoolean(PROP_VIDEO_DEVICE_IS_DISABLED, false)) - videoCaptureDevice = null; - else - { - if (logger.isInfoEnabled()) - logger.info("Scanning for configured Video Devices."); - - Format[] formats - = new Format[] - { - new AVFrameFormat(), - new VideoFormat(VideoFormat.RGB), - new VideoFormat(VideoFormat.YUV), - new VideoFormat(Constants.H264) - }; - - for (Format format : formats) - { - videoCaptureDevice - = extractConfiguredVideoCaptureDevice(format); - if (videoCaptureDevice != null) - break; - } - if (videoCaptureDevice == null) - if (logger.isInfoEnabled()) - logger.info("No Video Device was found."); - } + extractConfiguredAudioCaptureDevices(); + extractConfiguredVideoCaptureDevices(); } /** @@ -1400,6 +1297,141 @@ public class DeviceConfiguration { videoMaxBandwidth = -1; } + } + + /** + * Detects audio capture devices configured through JMF and disable audio if + * none was found. + */ + private void extractConfiguredAudioCaptureDevices() + { + ConfigurationService config + = NeomediaActivator.getConfigurationService(); + + if (logger.isInfoEnabled()) + logger.info("Scanning for configured Audio Devices."); + + CaptureDeviceInfo[] audioCaptureDevices = + getAvailableAudioCaptureDevices(); + if (config.getBoolean(PROP_AUDIO_DEVICE_IS_DISABLED, false)) + { + audioCaptureDevice = null; + audioSystem = AUDIO_NONE; + } + else if (audioCaptureDevices.length < 1) + { + logger.warn("No Audio Device was found."); + audioCaptureDevice = null; + audioSystem = AUDIO_NONE; + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Found " + audioCaptureDevices.length + + " capture devices: " + audioCaptureDevices); + + String audioDevName = config.getString(PROP_AUDIO_DEVICE); + + if(audioDevName == null || audioDevName.equals(AUDIO_NONE)) + { + // the default behaviour if nothing set is to use PortAudio + // this will also choose the capture device + if(PortAudioAuto.isSupported()) + { + setAudioSystem(AUDIO_SYSTEM_PORTAUDIO, null, false); + if(audioDevName != null + && audioDevName.equals(AUDIO_NONE)) + setAudioCaptureDevice(null, false); + } + else + { + setAudioPlaybackDevice(null, false); + setAudioNotifyDevice(null, false); + if (OSUtils.IS_ANDROID) + { + setAudioCaptureDevice(audioCaptureDevices[0], false); + } + else + { + setAudioCaptureDevice(null, false); + setAudioSystem(AUDIO_SYSTEM_JAVASOUND, null, false); + } + } + } + else + { + for (CaptureDeviceInfo captureDeviceInfo : audioCaptureDevices) + { + if (audioDevName.equals(captureDeviceInfo.getName())) + { + setAudioSystem(getAudioSystem(captureDeviceInfo), + captureDeviceInfo, false); + break; + } + } + + if(getAudioSystem() == null || !PortAudioAuto.isSupported()) + { + logger.warn("Computer sound config changed or " + + "there is a problem since last config was saved, " + + "will back to default"); + setAudioPlaybackDevice(null, false); + setAudioNotifyDevice(null, false); + setAudioCaptureDevice(null, false); + setAudioSystem(AUDIO_SYSTEM_PORTAUDIO, null, false); + } + } + if (audioCaptureDevice != null) + if (logger.isInfoEnabled()) + logger.info("Found " + audioCaptureDevice.getName() + + " as an audio capture device."); + } + } + + /** + * Detects video capture devices configured through JMF and disable video if + * none was found. + */ + private void extractConfiguredVideoCaptureDevices() + { + ConfigurationService config + = NeomediaActivator.getConfigurationService(); + + if (config.getBoolean(PROP_VIDEO_DEVICE_IS_DISABLED, false)) + videoCaptureDevice = null; + else + { + if (logger.isInfoEnabled()) + logger.info("Scanning for configured Video Devices."); + + Format[] formats + = new Format[] + { + new AVFrameFormat(), + new VideoFormat(VideoFormat.RGB), + new VideoFormat(VideoFormat.YUV), + new VideoFormat(Constants.H264) + }; + for (Format format : formats) + { + videoCaptureDevice + = extractConfiguredVideoCaptureDevice(format); + if (videoCaptureDevice != null) + break; + } + if (videoCaptureDevice == null) + if (logger.isInfoEnabled()) + logger.info("No Video Device was found."); + } + } + + /** + * Callback when PortAudio device changed. + */ + public void deviceChanged() + { + JmfDeviceDetector.reinitializePortAudio(); + extractConfiguredAudioCaptureDevices(); } } diff --git a/src/net/java/sip/communicator/impl/neomedia/device/JmfDeviceDetector.java b/src/net/java/sip/communicator/impl/neomedia/device/JmfDeviceDetector.java index c4e5505..2e1a1f5 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/JmfDeviceDetector.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/JmfDeviceDetector.java @@ -62,6 +62,11 @@ public class JmfDeviceDetector = "sip-communicator.org"; /** + * PortAudioAuto reference. + */ + private static PortAudioAuto portaudioAuto = null; + + /** * Default constructor - does nothing. */ public JmfDeviceDetector() @@ -187,7 +192,7 @@ public class JmfDeviceDetector } try { - new PortAudioAuto(); + portaudioAuto = new PortAudioAuto(); } catch (Throwable exc) { @@ -347,4 +352,21 @@ public class JmfDeviceDetector { new JmfDeviceDetector().reinitializeVideo(); } + + /** + * Reinitialize PortAudio devices. + */ + public static void reinitializePortAudio() + { + if(portaudioAuto != null) + { + try + { + portaudioAuto.reinit(); + } + catch(Exception e) + { + } + } + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java index 90ac280..f7dee1e 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java @@ -340,6 +340,10 @@ public class MediaDeviceSession // playback if (disposePlayerOnClose) disposePlayer(); + + processor = null; + player = null; + captureDevice = null; } /** diff --git a/src/net/java/sip/communicator/impl/neomedia/device/PortAudioAuto.java b/src/net/java/sip/communicator/impl/neomedia/device/PortAudioAuto.java index d8f4302..93eaf9d 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/PortAudioAuto.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/PortAudioAuto.java @@ -66,6 +66,38 @@ public class PortAudioAuto PortAudioAuto() throws Exception { + reinit(); + + // now add it as available audio system to DeviceConfiguration + DeviceConfiguration.addAudioSystem( + DeviceConfiguration.AUDIO_SYSTEM_PORTAUDIO); + + supported = true; + } + + /** + * PortAudio device changed callback. + */ + public void deviceChanged() + { + try + { + reinit(); + } + catch(Exception e) + { + logger.warn("Error while reinitialize PortAudio devices", e); + } + } + + /** + * Reinitializes PortAudio system. + * @throws Exception if anything wrong happens while creating the PortAudio + * capture devices + */ + public void reinit() + throws Exception + { // if PortAudio has a problem initializing like missing native // components it will trow exception here and PortAudio rendering will // not be inited. @@ -73,10 +105,14 @@ public class PortAudioAuto int defaultInputDeviceIx = PortAudio.Pa_GetDefaultInputDevice(); int defaultOutputDeviceIx = PortAudio.Pa_GetDefaultOutputDevice(); + + playbackDevices = null; + defaultPlaybackDevice = null; + defaultCaptureDevice = null; /* // for windows we will search for directsound devices, to set them as - // defualt, we need info for defualt devices host api + // default, we need info for default devices host api PortAudio.PaHostApiTypeId defaultInputHostApi = PortAudio.PaHostApiTypeId.undefined; PortAudio.PaHostApiTypeId defaultOutputHostApi @@ -97,6 +133,42 @@ public class PortAudioAuto } */ + AudioFormat fmt = new AudioFormat( + AudioFormat.LINEAR, + Format.NOT_SPECIFIED, + Format.NOT_SPECIFIED, + Format.NOT_SPECIFIED, + AudioFormat.LITTLE_ENDIAN, + AudioFormat.SIGNED, + Format.NOT_SPECIFIED /* frameSizeInBits */, + Format.NOT_SPECIFIED /* frameRate */, + Format.byteArray); + + Iterator<?> it = CaptureDeviceManager.getDeviceList(fmt).iterator(); + List<CaptureDeviceInfo> devicesToRemove = + new ArrayList<CaptureDeviceInfo>(); + + while(it.hasNext()) + { + CaptureDeviceInfo ifo = (CaptureDeviceInfo)it.next(); + + if(!ifo.getLocator().getProtocol().equals("portaudio")) + { + continue; + } + else + { + devicesToRemove.add(ifo); + } + } + + for(CaptureDeviceInfo info : devicesToRemove) + { + CaptureDeviceManager.removeDevice(info); + } + if(devicesToRemove.size() > 0) + CaptureDeviceManager.commit(); + Vector<CaptureDeviceInfo> playbackDevVector = new Vector<CaptureDeviceInfo>(); int channels = 1; @@ -201,14 +273,7 @@ public class PortAudioAuto } playbackDevices = playbackDevVector.toArray(new CaptureDeviceInfo[0]); - CaptureDeviceManager.commit(); - - // now add it as available audio system to DeviceConfiguration - DeviceConfiguration.addAudioSystem( - DeviceConfiguration.AUDIO_SYSTEM_PORTAUDIO); - - supported = true; } /** diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/audio/PortAudioRenderer.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/audio/PortAudioRenderer.java index e48aa61..44014bd 100644 --- a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/audio/PortAudioRenderer.java +++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/audio/PortAudioRenderer.java @@ -89,6 +89,11 @@ public class PortAudioRenderer private static MediaLocator defaultLocator; /** + * Value to indicate if default locator has changed. + */ + private static boolean changeDefaultLocator = false; + + /** * The audio samples left unwritten by a previous call to * {@link #process(Buffer)}. As {@link #bytesPerBuffer} number of * bytes are always written, the number of the unwritten audio samples is @@ -437,6 +442,24 @@ public class PortAudioRenderer } /** + * Update default locator. + */ + private void updateDefaultLocator() + { + try + { + stop(); + close(); + open(); + start(); + } + catch(ResourceUnavailableException e) + { + logger.info("Error reinit default portaudio locator", e); + } + } + + /** * Renders the audio data contained in a specific <tt>Buffer</tt> onto the * PortAudio device represented by this <tt>Renderer</tt>. * @@ -447,6 +470,12 @@ public class PortAudioRenderer */ public int process(Buffer buffer) { + if(changeDefaultLocator && locator == null) + { + updateDefaultLocator(); + changeDefaultLocator = false; + } + synchronized (this) { if (!started || (stream == 0)) @@ -568,6 +597,8 @@ public class PortAudioRenderer return; PortAudioRenderer.defaultLocator = defaultLocator; + + changeDefaultLocator = true; } /** diff --git a/src/net/java/sip/communicator/impl/neomedia/portaudio/PortAudio.java b/src/net/java/sip/communicator/impl/neomedia/portaudio/PortAudio.java index f4bd7c2..e20ffe6 100644 --- a/src/net/java/sip/communicator/impl/neomedia/portaudio/PortAudio.java +++ b/src/net/java/sip/communicator/impl/neomedia/portaudio/PortAudio.java @@ -9,6 +9,7 @@ package net.java.sip.communicator.impl.neomedia.portaudio; import java.io.*; import java.lang.reflect.*; import java.nio.charset.*; +import java.util.*; import net.java.sip.communicator.impl.neomedia.*; import net.java.sip.communicator.impl.neomedia.codec.*; @@ -19,16 +20,27 @@ import net.java.sip.communicator.util.*; * * @author Lyubomir Marinov * @author Damian Minkov + * @author Sebastien Vincent */ public final class PortAudio { - /** * The <tt>Logger</tt> used by the <tt>PortAudio</tt> class for logging * output. */ private static final Logger logger = Logger.getLogger(PortAudio.class); + /** + * List of device changed callbacks. + */ + public static final List<PortAudioDeviceChangedCallback> callbacks = + new Vector<PortAudioDeviceChangedCallback>(); + + /** + * Synchronization object. + */ + private static final Object syncRoot = new Object(); + static { System.loadLibrary("jnportaudio"); @@ -82,6 +94,9 @@ public final class PortAudio */ public static final double LATENCY_UNSPECIFIED = 0d; + /** + * PortAudio "no device" constant. + */ public static final int paNoDevice = -1; /** @@ -499,6 +514,15 @@ public final class PortAudio int numberOfWrites) throws PortAudioException; + /** + * Gets the human-readable name of the <tt>PaDeviceInfo</tt> specified by a + * pointer to it. + * + * @param deviceInfo the pointer to the <tt>PaDeviceInfo</tt> to get the + * human-readable name of + * @return the human-readable name of the <tt>PaDeviceInfo</tt> pointed to + * by <tt>deviceInfo</tt> + */ public static String PaDeviceInfo_getName(long deviceInfo) { byte[] nameBytes = PaDeviceInfo_getNameBytes(deviceInfo); @@ -648,6 +672,12 @@ public final class PortAudio */ public static native int PaHostApiInfo_getType(long hostApiInfo); + /** + * Free StreamParameters resources specified by a pointer to it. + * + * @param streamParameters the pointer to the <tt>PaStreamParameters</tt> + * to free + */ public static void PaStreamParameters_free(long streamParameters) { try @@ -734,6 +764,52 @@ public final class PortAudio long echoFilterLengthInMillis); /** + * Updates available device lists in PortAudio. + */ + private static native void updateAvailableDeviceList(); + + /** + * Adds a device changed callback. + * + * @param cb callback that will be called if device are added/removed + */ + public static void addDeviceChangedCallback( + PortAudioDeviceChangedCallback cb) + { + if(!callbacks.contains(cb)) + callbacks.add(cb); + } + + /** + * Removes a device changed callback. + * + * @param cb callback that will be called if device are added/removed + */ + public static void removeDeviceChangedCallback( + PortAudioDeviceChangedCallback cb) + { + if(callbacks.contains(cb)) + callbacks.remove(cb); + } + + + /** + * Callback called from native PortAudio side that notify device changed. + */ + public static void deviceChanged() + { + synchronized(syncRoot) + { + updateAvailableDeviceList(); + + for(PortAudioDeviceChangedCallback cb : callbacks) + { + cb.deviceChanged(); + } + } + } + + /** * Prevents the creation of <tt>PortAudio</tt> instances. */ private PortAudio() diff --git a/src/net/java/sip/communicator/impl/neomedia/portaudio/PortAudioDeviceChangedCallback.java b/src/net/java/sip/communicator/impl/neomedia/portaudio/PortAudioDeviceChangedCallback.java new file mode 100644 index 0000000..6c6bdcf --- /dev/null +++ b/src/net/java/sip/communicator/impl/neomedia/portaudio/PortAudioDeviceChangedCallback.java @@ -0,0 +1,20 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.neomedia.portaudio; + +/** + * Interface to be notified by PortAudio device changed callback. + * + * @author Sebastien Vincent + */ +public interface PortAudioDeviceChangedCallback +{ + /** + * Callback when PortAudio device changed. + */ + public void deviceChanged(); +} diff --git a/src/net/java/sip/communicator/impl/neomedia/transform/csrc/CsrcTransformEngine.java b/src/net/java/sip/communicator/impl/neomedia/transform/csrc/CsrcTransformEngine.java index bad9afc..fc992d4 100644 --- a/src/net/java/sip/communicator/impl/neomedia/transform/csrc/CsrcTransformEngine.java +++ b/src/net/java/sip/communicator/impl/neomedia/transform/csrc/CsrcTransformEngine.java @@ -137,9 +137,9 @@ public class CsrcTransformEngine */ public synchronized RawPacket transform(RawPacket pkt) { - // if somebody has modified the packet and added an extension + // if somebody has modified the packet and added an extension // don't process it. As ZRTP creates special rtp packets carring no - // rtp data and those packets are used only by zrtp we don't use them. + // rtp data and those packets are used only by zrtp we don't use them. if(pkt.getExtensionBit()) return pkt; |