From 1aec195e2b970cbe76c81c7eec184fd1173c2550 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Thu, 26 Nov 2009 21:15:34 +0000 Subject: [PATCH] Fixes the implementation of MediaDevice to not cause undesired disconnects from CaptureDevice. --- .../impl/neomedia/MediaStreamImpl.java | 28 +- .../impl/neomedia/conference/AudioMixer.java | 18 +- .../device/AudioMixerMediaDevice.java | 2 +- .../impl/neomedia/device/MediaDeviceImpl.java | 337 +++--------------- .../neomedia/device/MediaDeviceSession.java | 65 +++- .../media/protocol/portaudio/DataSource.java | 97 +---- 6 files changed, 161 insertions(+), 386 deletions(-) diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java index 7bba7863b..c53c077a2 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java @@ -136,6 +136,13 @@ public class MediaStreamImpl */ private String localSourceID = null; + /** + * The indicator which determines whether this MediaStream is set + * to transmit "silence" instead of the actual media fed from its + * MediaDevice. + */ + private boolean mute = false; + /** * Initializes a new MediaStreamImpl instance which will use the * specified MediaDevice for both capture and playback of media @@ -586,12 +593,9 @@ private RTPManager getRTPManager() */ public boolean isMute() { - MediaDevice device = getDevice(); + MediaDeviceSession deviceSession = getDeviceSession(); - return - (device instanceof MediaDeviceImpl) - ? ((MediaDeviceImpl) device).isMute() - : false; + return (deviceSession == null) ? mute : deviceSession.isMute(); } /** @@ -716,6 +720,7 @@ public void setDevice(MediaDevice device) if (deviceSession != null) { + deviceSession.setMute(mute); deviceSession.start(startedDirection); synchronized (receiveStreams) @@ -806,12 +811,15 @@ public void setFormat(MediaFormat format) */ public void setMute(boolean mute) { - MediaDevice device = getDevice(); + if (this.mute != mute) + { + this.mute = mute; - if (device instanceof MediaDeviceImpl) - ((MediaDeviceImpl) device).setMute(mute); - else - throw new IllegalStateException("device"); + MediaDeviceSession deviceSession = getDeviceSession(); + + if (deviceSession != null) + deviceSession.setMute(this.mute); + } } /** diff --git a/src/net/java/sip/communicator/impl/neomedia/conference/AudioMixer.java b/src/net/java/sip/communicator/impl/neomedia/conference/AudioMixer.java index 435e10057..11bce3fab 100644 --- a/src/net/java/sip/communicator/impl/neomedia/conference/AudioMixer.java +++ b/src/net/java/sip/communicator/impl/neomedia/conference/AudioMixer.java @@ -263,7 +263,23 @@ void connect() { if (connected == 0) for (InputDataSourceDesc inputDataSourceDesc : inputDataSources) - inputDataSourceDesc.getEffectiveInputDataSource().connect(); + try + { + inputDataSourceDesc + .getEffectiveInputDataSource().connect(); + } + catch (IOException ioe) + { + logger + .error( + "Failed to connect to inputDataSource " + + MediaStreamImpl + .toString( + inputDataSourceDesc + .inputDataSource), + ioe); + throw ioe; + } connected++; } 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 5f609e7ca..907cfe77e 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java @@ -109,7 +109,7 @@ public synchronized MediaDeviceSession createSession() private AudioMixer getAudioMixer() { if (audioMixer == null) - audioMixer = new AudioMixer(device.getCaptureDevice()) + audioMixer = new AudioMixer(device.createCaptureDevice()) { @Override protected void readCaptureDeviceStream( diff --git a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceImpl.java b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceImpl.java index dc48fbc9e..e08136986 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceImpl.java @@ -38,23 +38,11 @@ public class MediaDeviceImpl private static final Logger logger = Logger.getLogger(MediaDeviceImpl.class); - /** - * The JMF CaptureDevice this instance wraps and provides an - * implementation of MediaDevice for. - */ - private CaptureDevice captureDevice; - /** * The CaptureDeviceInfo of {@link #captureDevice}. */ private CaptureDeviceInfo captureDeviceInfo; - /** - * The indicator which determines whether {@link DataSource#connect()} has - * been successfully executed on {@link #captureDevice}. - */ - private boolean captureDeviceIsConnected; - /** * The MediaType of this instance and the CaptureDevice * that it wraps. @@ -70,32 +58,10 @@ public class MediaDeviceImpl */ public MediaDeviceImpl(MediaType mediaType) { - this.captureDevice = null; this.captureDeviceInfo = null; this.mediaType = mediaType; } - /** - * Initializes a new MediaDeviceImpl instance which is to provide - * an implementation of MediaDevice for a specific - * CaptureDevice with a specific MediaType. - * - * @param captureDevice the JMF CaptureDevice the new instance is - * to provide an implementation of MediaDevice for - * @param mediaType the MediaType of the new instance - */ - public MediaDeviceImpl(CaptureDevice captureDevice, MediaType mediaType) - { - if (captureDevice == null) - throw new NullPointerException("captureDevice"); - if (mediaType == null) - throw new NullPointerException("mediaType"); - - this.mediaType = mediaType; - - setCaptureDevice(captureDevice); - } - /** * Initializes a new MediaDeviceImpl instance which is to provide * an implementation of MediaDevice for a CaptureDevice @@ -116,30 +82,60 @@ public MediaDeviceImpl( if (mediaType == null) throw new NullPointerException("mediaType"); - this.captureDevice = null; this.captureDeviceInfo = captureDeviceInfo; this.mediaType = mediaType; } /** - * Notifies this instance that its captureDevice (the JMF - * CaptureDevice this instance wraps and provides an implementation - * of MediaDevice for) property has changed its value from - * oldValue to newValue. Allows extenders to override in - * order to perform additional processing of the new captureDevice - * once it is clear that it is set into this instance. + * Creates the JMF CaptureDevice this instance represents and + * provides an implementation of MediaDevice for. * - * @param oldValue the JMF CaptureDevice which was the value of the - * captureDevice property of this instance before newValue - * was set - * @param newValue the JMF CaptureDevice which is the value of the - * captureDevice property of this instance and which replaced - * oldValue + * @return the JMF CaptureDevice this instance represents and + * provides an implementation of MediaDevice for; null + * if the creation fails */ - protected void captureDeviceChanged( - CaptureDevice oldValue, - CaptureDevice newValue) + CaptureDevice createCaptureDevice() { + CaptureDevice captureDevice = null; + + if (getDirection().allowsSending()) + { + Throwable exception = null; + + try + { + captureDevice + = (CaptureDevice) + Manager + .createDataSource(captureDeviceInfo.getLocator()); + } + catch (IOException ioe) + { + // TODO + exception = ioe; + } + catch (NoDataSourceException ndse) + { + // TODO + exception = ndse; + } + + if (exception != null) + logger + .error( + "Failed to create CaptureDevice" + + "from CaptureDeviceInfo " + + captureDeviceInfo, + exception); + else + { + // Try to enable tracing on captureDevice. + if (logger.isTraceEnabled()) + captureDevice + = createTracingCaptureDevice(captureDevice, logger); + } + } + return captureDevice; } /** @@ -154,7 +150,7 @@ DataSource createOutputDataSource() { return getDirection().allowsSending() - ? (DataSource) getConnectedCaptureDevice() + ? (DataSource) createCaptureDevice() : null; } @@ -236,79 +232,6 @@ public void stop() return captureDevice; } - /** - * Gets the JMF CaptureDevice this instance wraps and provides an - * implementation of MediaDevice for. - * - * @return the JMF CaptureDevice this instance wraps and provides - * an implementation of MediaDevice for - */ - public CaptureDevice getCaptureDevice() - { - return getCaptureDevice(true); - } - - /** - * Gets the JMF CaptureDevice this instance wraps and provides an - * implementation of MediaDevice for and, optionally, creates it if - * it does not exist yet. - * - * @param create true to create the CaptureDevice this - * instance provides an implementation of MediaDevice for it it - * does not exist yet; false to not create it and return - * null if it does not exist yet - * @return the JMF CaptureDevice this instance wraps and provides - * an implementation of MediaDevice for if it exists or - * create is true and its creation succeeds; null - * if it does not exist yet and create is false or its - * creation fails - */ - protected CaptureDevice getCaptureDevice(boolean create) - { - if (getDirection().allowsSending() && (captureDevice == null) && create) - { - CaptureDevice captureDevice = null; - Throwable exception = null; - - try - { - captureDevice - = (CaptureDevice) - Manager - .createDataSource(captureDeviceInfo.getLocator()); - } - catch (IOException ioe) - { - // TODO - exception = ioe; - } - catch (NoDataSourceException ndse) - { - // TODO - exception = ndse; - } - - if (exception != null) - logger - .error( - "Failed to create CaptureDevice DataSource " - + "from CaptureDeviceInfo " - + captureDeviceInfo, - exception); - else - { - - // Try to enable tracing on captureDevice. - if (logger.isTraceEnabled()) - captureDevice - = createTracingCaptureDevice(captureDevice, logger); - - setCaptureDevice(captureDevice); - } - } - return captureDevice; - } - /** * Gets the CaptureDeviceInfo of the JMF CaptureDevice * represented by this instance. @@ -321,83 +244,6 @@ public CaptureDeviceInfo getCaptureDeviceInfo() return captureDeviceInfo; } - /** - * Gets the JMF CaptureDevice this instance wraps and provides an - * implementation of MediaDevice for in a connected state. If the - * CaptureDevice is not connected to yet, first tries to connect to - * it. Returns null if this instance has failed to create a - * CaptureDevice instance or to connect to it. - * - * @return the JMF CaptureDevice this instance wraps and provides - * an implementation of MediaDevice for in a connected state; - * null if this instance has failed to create a - * CaptureDevice instance or to connect to it - */ - private CaptureDevice getConnectedCaptureDevice() - { - CaptureDevice captureDevice = getCaptureDevice(); - - if ((captureDevice != null) && !captureDeviceIsConnected) - { - Throwable exception = null; - - try - { - captureDevice.connect(); - } - catch (IOException ioe) - { - // TODO - exception = ioe; - } - catch (NullPointerException npe) - { - /* - * TODO The old media says it happens when the operating system - * does not support the operation. - */ - exception = npe; - } - - if (exception == null) - { - captureDeviceIsConnected = true; - - /* - * 1. Changing buffer size. The default buffer size (for - * javasound) is 125 milliseconds - 1/8 sec. On MacOS this leads - * to an exception and no audio capture. A value of 30 for the - * buffer fixes the problem and is OK when using some pstn - * gateways. - * - * 2. Changing to 60. When it is 30 there are some issues with - * asterisk and nat (we don't start to send stream and so - * asterisk rtp part doesn't notice that we are behind nat) - * - * 3. Do not set buffer length on linux as it completely breaks - * audio capture. - */ - String osName = System.getProperty("os.name"); - - if ((osName == null) || !osName.toLowerCase().contains("linux")) - { - Control bufferControl - = (Control) - ((DataSource) captureDevice) - .getControl( - "javax.media.control.BufferControl"); - - if (bufferControl != null) - ((BufferControl) bufferControl) - .setBufferLength(60); // in milliseconds - } - } - else - captureDevice = null; - } - return captureDevice; - } - /** * Returns the MediaDirection supported by this device. * @@ -409,19 +255,13 @@ private CaptureDevice getConnectedCaptureDevice() */ public MediaDirection getDirection() { - if ((captureDeviceInfo != null) || (captureDevice != null)) + if (captureDeviceInfo != null) return MediaDirection.SENDRECV; else - { - /* - * If there is no audio CaptureDevice, then even play back is not - * possible. - */ return MediaType.AUDIO.equals(getMediaType()) ? MediaDirection.INACTIVE : MediaDirection.RECVONLY; - } } /** @@ -434,7 +274,7 @@ public MediaDirection getDirection() */ public MediaFormat getFormat() { - CaptureDevice captureDevice = getCaptureDevice(); + CaptureDevice captureDevice = createCaptureDevice(); if (captureDevice != null) { @@ -488,85 +328,6 @@ public List getSupportedFormats() return supportedFormats; } - /** - * Determines whether this MediaDevice will provide silence instead - * of actual captured data next time it is read. - * - * @return true if this MediaDevice will provide silence - * instead of actual captured data next time it is read; false, - * otherwise - */ - public boolean isMute() - { - CaptureDevice captureDevice = getCaptureDevice(false); - - if (captureDevice instanceof MutePushBufferDataSource) - return ((MutePushBufferDataSource) captureDevice).isMute(); - - /* - * If there is no underlying CaptureDevice, this instance is mute - * because it cannot capture any media. - */ - return !getDirection().allowsSending(); - } - - /** - * Sets the JMF CaptureDevice this instance wraps and provides a - * MediaDevice implementation for. Allows extenders to override in - * order to customize captureDevice including to replace it before - * it is set into this instance. - * - * @param captureDevice the JMF CaptureDevice this instance is to - * wrap and provide a MediaDevice implementation for - */ - protected void setCaptureDevice(CaptureDevice captureDevice) - { - /* - * Translate PullBufferDataSource into PushBufferDataSource because - * other functionality such as MutePushBufferDataSource may depend on - * it. - */ -// if (captureDevice instanceof PullBufferDataSource) -// captureDevice -// = new PushBufferDataSourceAdapter( -// (PullBufferDataSource) captureDevice); - - // Try to enable mute support on the specified CaptureDevice. - if (captureDevice instanceof PushBufferDataSource) - captureDevice - = new MutePushBufferDataSource( - (PushBufferDataSource) captureDevice); - - if (this.captureDevice != captureDevice) - { - CaptureDevice oldValue = this.captureDevice; - - this.captureDevice = captureDevice; - this.captureDeviceInfo = captureDevice.getCaptureDeviceInfo(); - - CaptureDevice newValue = captureDevice; - - captureDeviceChanged(oldValue, newValue); - } - } - - /** - * Sets the indicator which determines whether this MediaDevice - * will start providing silence instead of actual captured data next time it - * is read. - * - * @param mute true to have this MediaDevice start - * providing silence instead of actual captured data next time it is read; - * otherwise, false - */ - public void setMute(boolean mute) - { - CaptureDevice captureDevice = getCaptureDevice(); - - if (captureDevice instanceof MutePushBufferDataSource) - ((MutePushBufferDataSource) captureDevice).setMute(mute); - } - /** * Gets a human-readable String representation of this instance. * 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 f663c83a3..96834341e 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java @@ -19,6 +19,7 @@ import net.java.sip.communicator.impl.neomedia.*; import net.java.sip.communicator.impl.neomedia.codec.audio.*; import net.java.sip.communicator.impl.neomedia.format.*; +import net.java.sip.communicator.impl.neomedia.protocol.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.neomedia.device.*; import net.java.sip.communicator.service.neomedia.event.*; @@ -76,6 +77,13 @@ public class MediaDeviceSession */ private Format format; + /** + * The indicator which determines whether this MediaDeviceSession + * is set to output "silence" instead of the actual media captured from + * {@link #captureDevice}. + */ + private boolean mute = false; + /** * The ControllerListener which listens to the Player * instances in {@link #players} for ControllerEvents. @@ -464,7 +472,20 @@ private void closeProcessor() */ protected DataSource createCaptureDevice() { - return getDevice().createOutputDataSource(); + DataSource captureDevice = getDevice().createOutputDataSource(); + + // Try to enable muting. + if (captureDevice instanceof PushBufferDataSource) + { + MutePushBufferDataSource mutePushBufferDataSource + = new MutePushBufferDataSource( + (PushBufferDataSource) captureDevice); + + mutePushBufferDataSource.setMute(mute); + captureDevice = mutePushBufferDataSource; + } + + return captureDevice; } /** @@ -888,6 +909,26 @@ public List getSupportedFormats() return supportedMediaFormats; } + /** + * Determines whether this MediaDeviceSession is set to output + * "silence" instead of the actual media fed from its + * CaptureDevice. + * + * @return true if this MediaDeviceSession is set to + * output "silence" instead of the actual media fed from its + * CaptureDevice; otherwise, false + */ + public boolean isMute() + { + DataSource captureDevice = this.captureDevice; + + if (captureDevice == null) + return mute; + if (captureDevice instanceof MutePushBufferDataSource) + return ((MutePushBufferDataSource) captureDevice).isMute(); + return false; + } + /** * Gets notified about ControllerEvents generated by * {@link #processor}. @@ -1160,6 +1201,28 @@ else if (logger.isTraceEnabled()) } } + /** + * Sets the indicator which determines whether this + * MediaDeviceSession is set to output "silence" instead of the + * actual media fed from its CaptureDevice. + * + * @param mute true to set this MediaDeviceSession to + * output "silence" instead of the actual media fed from its + * CaptureDevice; otherwise, false + */ + public void setMute(boolean mute) + { + if (this.mute != mute) + { + this.mute = mute; + + DataSource captureDevice = this.captureDevice; + + if (captureDevice instanceof MutePushBufferDataSource) + ((MutePushBufferDataSource) captureDevice).setMute(this.mute); + } + } + /** * Sets the JMF Processor which is to transcode * {@link #captureDevice} into the format of this instance. diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/portaudio/DataSource.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/portaudio/DataSource.java index 25af0349e..0ff94cd5c 100644 --- a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/portaudio/DataSource.java +++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/protocol/portaudio/DataSource.java @@ -14,8 +14,10 @@ import javax.media.format.*; import javax.media.protocol.*; -import net.java.sip.communicator.util.*; +import net.java.sip.communicator.impl.neomedia.*; +import net.java.sip.communicator.impl.neomedia.control.*; import net.java.sip.communicator.impl.neomedia.portaudio.*; +import net.java.sip.communicator.util.*; /** * Implements DataSource and CaptureDevice for PortAudio. @@ -83,6 +85,8 @@ public void connect() return; connected = true; + if (logger.isTraceEnabled()) + logger.trace("Connected " + MediaStreamImpl.toString(this)); } /** @@ -106,6 +110,8 @@ public void disconnect() } connected = false; + if (logger.isTraceEnabled()) + logger.trace("Disconnected " + MediaStreamImpl.toString(this)); } /** @@ -274,6 +280,8 @@ public void start() } started = true; + if (logger.isTraceEnabled()) + logger.trace("Started " + MediaStreamImpl.toString(this)); } /** @@ -299,6 +307,8 @@ public void stop() } started = false; + if (logger.isTraceEnabled()) + logger.trace("Stopped " + MediaStreamImpl.toString(this)); } /** @@ -307,30 +317,9 @@ public void stop() * important because, for example, AudioMixer will ask for it. */ private class FormatControlImpl - implements FormatControl + extends AbstractFormatControl { - /** - * The indicator which determines whether this track is enabled. I don't - * known what it means for DataSource implementations but - * at least the choice of the caller is remembered and reported. - */ - private boolean enabled; - - /** - * Implements {@link Controls#getControlComponent()}. Since - * DataSource does not export any UI, returns null. - * - * @return a Component which represents UI associated with this - * DataSource and this FormatControl if any; - * otherwise, null - */ - public java.awt.Component getControlComponent() - { - // No Component is exported for this DataSource. - return null; - } - /** * Implements {@link FormatControl#getFormat()}. * @@ -352,67 +341,5 @@ public Format[] getSupportedFormats() { return new Format[] { getCaptureFormat() }; } - - /** - * Implements {@link FormatControl#isEnabled()}. Does not mean anything - * to this DataSource at the time of this writing. - * - * @return true if this track is enabled; otherwise, - * false - */ - public boolean isEnabled() - { - return enabled; - } - - /** - * Implements {@link FormatControl#setEnabled(boolean)}. Does not mean - * anything to this DataSource at the time of this writing. - * - * @param enabled true if this track is to be enabled; - * otherwise, false - */ - public void setEnabled(boolean enabled) - { - this.enabled = enabled; - } - - /** - * Implements {@link FormatControl#setFormat(Format)}. Not supported at - * this time and just returns the currently set format if the specified - * Format is supported and null if it is not - * supported. - * - * @param format the Format in which this DataSource - * is to output - * @return the currently set Format after the attempt to set it - * as the output format of this DataSource if format - * is supported by this DataSource and regardless of whether it - * was actually set; null if format is not supported - * by this DataSource - */ - public Format setFormat(Format format) - { - /* - * Determine whether the specified format is supported by this - * DataSource because we have to return null if it is not supported. - * Or at least that is what I gather from the respective javadoc. - */ - boolean formatIsSupported = false; - - if (format != null) - for (Format supportedFormat : getSupportedFormats()) - if (supportedFormat.matches(format)) - { - formatIsSupported = true; - break; - } - - /* - * We do not actually support setFormat so we have to return the - * currently set format if the specified format is supported. - */ - return (formatIsSupported) ? getFormat() : null; - } } }