diff --git a/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java index da6a4a615..76a7084f6 100644 --- a/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java @@ -6,11 +6,11 @@ */ package net.java.sip.communicator.impl.neomedia; +import javax.media.*; import javax.media.format.*; import javax.media.rtp.*; import net.java.sip.communicator.impl.neomedia.codec.*; -import net.java.sip.communicator.impl.neomedia.device.*; import net.java.sip.communicator.service.neomedia.*; import net.java.sip.communicator.service.neomedia.device.*; import net.java.sip.communicator.service.neomedia.event.*; @@ -52,7 +52,7 @@ public class AudioMediaStreamImpl 8000, 8, 1, - -1, + Format.NOT_SPECIFIED, AudioFormat.SIGNED) }; @@ -109,20 +109,6 @@ public void addSoundLevelListener(SoundLevelListener listener) // TODO Auto-generated method stub } - /** - * Determines whether this AudioMediaStream is set to transmit - * silence instead of the audio being fed from its MediaDevice. - * - * @return true if this AudioMediaStream is set to - * transmit silence instead of the audio fed from its MediaDevice; - * false, otherwise - * @see AudioMediaStream#isMute() - */ - public boolean isMute() - { - return ((AudioMediaDeviceImpl) getDevice()).isMute(); - } - /** * Registers {@link #CUSTOM_CODEC_FORMATS} with a specific * RTPManager. @@ -194,38 +180,6 @@ public void removeSoundLevelListener(SoundLevelListener listener) // TODO Auto-generated method stub } - /** - * Sets the MediaDevice that this stream should use to play back - * and capture media. Asserts that the specified device is an - * AudioMediaDeviceImpl because the implementation of - * AudioMediaStreamImpl depends on it. - * - * @param device the MediaDevice that this stream should use to - * play back and capture media - * @see MediaStreamImpl#setDevice(MediaDevice) - */ - @Override - public void setDevice(MediaDevice device) - { - super.setDevice((AudioMediaDeviceImpl) device); - } - - /** - * Causes this AudioMediaStream to stop transmitting the audio - * being fed from this stream's MediaDevice and transmit silence - * instead. - * - * @param mute true to have this AudioMediaStream transmit - * silence instead of the actual audio data that it captures from its - * MediaDevice; false to transmit actual audio data - * captured from the MediaDevice of this AudioMediaStream - * @see AudioMediaStream#setMute(boolean) - */ - public void setMute(boolean mute) - { - ((AudioMediaDeviceImpl) getDevice()).setMute(mute); - } - /** * Starts sending the specified DTMFTone until the * stopSendingDTMF() method is called. Callers should keep in mind diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java index f386de5dd..dfcb143cc 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java @@ -274,8 +274,6 @@ public List getDevices(MediaType mediaType) switch (mediaType) { case AUDIO: - device = new AudioMediaDeviceImpl(captureDeviceInfo); - break; case VIDEO: device = new MediaDeviceImpl(captureDeviceInfo, mediaType); break; @@ -363,7 +361,7 @@ public MediaFormatFactory getFormatFactory() private MediaDevice getNonSendAudioDevice() { if (nonSendAudioDevice == null) - nonSendAudioDevice = new AudioMediaDeviceImpl(); + nonSendAudioDevice = new MediaDeviceImpl(MediaType.AUDIO); return nonSendAudioDevice; } @@ -392,6 +390,7 @@ void start() deviceConfiguration.initialize(); encodingConfiguration.initializeFormatPreferences(); encodingConfiguration.registerCustomPackages(); + encodingConfiguration.registerCustomCodecs(); } /** diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java index 0eb87e9cb..f2fa6dc31 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java @@ -55,6 +55,13 @@ public class MediaStreamImpl */ private MediaDeviceSession deviceSession; + /** + * The PropertyChangeListener which listens to + * {@link #deviceSession} and changes in the values of its + * {@link MediaDeviceSession#OUTPUT_DATA_SOURCE} property. + */ + private PropertyChangeListener deviceSessionPropertyChangeListener; + /** * The MediaDirection in which this MediaStream is allowed * to stream media. @@ -66,8 +73,8 @@ public class MediaStreamImpl * RTPManager it utilizes of (dynamic) RTP payload types to * MediaFormats. */ - private final Map dynamicRTPPayloadTypes - = new HashMap(); + private final Map dynamicRTPPayloadTypes + = new HashMap(); /** * The ReceiveStreams this instance plays back on its associated @@ -148,7 +155,7 @@ public MediaStreamImpl(StreamConnector connector, MediaDevice device) * @see MediaStream#addDynamicRTPPayloadType(int, MediaFormat) */ public void addDynamicRTPPayloadType( - int rtpPayloadType, + byte rtpPayloadType, MediaFormat format) { MediaFormatImpl mediaFormatImpl @@ -156,7 +163,7 @@ public void addDynamicRTPPayloadType( synchronized (dynamicRTPPayloadTypes) { - dynamicRTPPayloadTypes.put(Integer.valueOf(rtpPayloadType), format); + dynamicRTPPayloadTypes.put(Byte.valueOf(rtpPayloadType), format); if (rtpManager != null) rtpManager @@ -206,7 +213,8 @@ private void closeSendStreams() private void createSendStreams() { RTPManager rtpManager = getRTPManager(); - DataSource dataSource = getDeviceSession().getOutputDataSource(); + MediaDeviceSession deviceSession = getDeviceSession(); + DataSource dataSource = deviceSession.getOutputDataSource(); int streamCount; if (dataSource instanceof PushBufferDataSource) @@ -247,6 +255,13 @@ else if (dataSource instanceof PullDataSource) try { rtpManager.createSendStream(dataSource, streamIndex); + if (logger.isTraceEnabled()) + logger + .trace( + "Created send stream for data source " + + dataSource + + " and stream index " + + streamIndex); } catch (IOException ioe) { @@ -270,6 +285,19 @@ else if (dataSource instanceof PullDataSource) } } sendStreamsAreCreated = true; + + if (deviceSessionPropertyChangeListener == null) + deviceSessionPropertyChangeListener = new PropertyChangeListener() + { + public void propertyChange(PropertyChangeEvent event) + { + if (MediaDeviceSession + .OUTPUT_DATA_SOURCE.equals(event.getPropertyName())) + deviceSessionOutputDataSourceChanged(); + } + }; + deviceSession + .addPropertyChangeListener(deviceSessionPropertyChangeListener); } /** @@ -288,12 +316,19 @@ protected void deviceSessionChanged( MediaDeviceSession oldValue, MediaDeviceSession newValue) { - if (sendStreamsAreCreated) - { - closeSendStreams(); - if ((newValue != null) && (rtpManager != null)) - createSendStreams(); - } + recreateSendStreams(); + } + + /** + * Notifies this instance that the output DataSource of its + * MediaDeviceSession has changed. Recreates the + * SendStreams of this instance as necessary so that it, for + * example, continues streaming after the change if it was streaming before + * the change. + */ + private void deviceSessionOutputDataSourceChanged() + { + recreateSendStreams(); } /** @@ -350,20 +385,20 @@ public MediaDirection getDirection() * well-known associations reported by * {@link MediaFormat#getRTPPayloadType()}. * - * @return a Map of RTP payload type expressed as Integer - * to MediaFormat describing the existing (dynamic) associations in + * @return a Map of RTP payload type expressed as Byte to + * MediaFormat describing the existing (dynamic) associations in * this instance of RTP payload types to MediaFormats. The * Map represents a snapshot of the existing associations at the * time of the getDynamicRTPPayloadTypes() method call and * modifications to it are not reflected on the internal storage * @see MediaStream#getDynamicRTPPayloadTypes() */ - public Map getDynamicRTPPayloadTypes() + public Map getDynamicRTPPayloadTypes() { synchronized (dynamicRTPPayloadTypes) { return - new HashMap(dynamicRTPPayloadTypes); + new HashMap(dynamicRTPPayloadTypes); } } @@ -511,7 +546,12 @@ private RTPManager getRTPManager() */ public boolean isMute() { - return false; + MediaDevice device = getDevice(); + + return + (device instanceof MediaDeviceImpl) + ? ((MediaDeviceImpl) device).isMute() + : false; } /** @@ -529,6 +569,29 @@ public boolean isStarted() return started; } + /** + * Recreates the SendStreams of this instance (i.e. of its + * RTPManager) as necessary. For example, if there was no attempt + * to create the SendStreams prior to the call, does nothing. If + * they were created prior to the call, closes them and creates them again. + * If they were not started prior to the call, does not start them after + * recreating them. + */ + private void recreateSendStreams() + { + if (sendStreamsAreCreated) + { + closeSendStreams(); + if ((getDeviceSession() != null) && (rtpManager != null)) + { + createSendStreams(); + if (MediaDirection.SENDONLY.equals(startedDirection) + || MediaDirection.SENDRECV.equals(startedDirection)) + startSendStreams(); + } + } + } + /** * Registers any custom JMF Formats with a specific * RTPManager. Extenders should override in order to register their @@ -544,7 +607,7 @@ protected void registerCustomCodecFormats(RTPManager rtpManager) { synchronized (dynamicRTPPayloadTypes) { - for (Map.Entry dynamicRTPPayloadType + for (Map.Entry dynamicRTPPayloadType : dynamicRTPPayloadTypes.entrySet()) { MediaFormatImpl mediaFormatImpl @@ -577,6 +640,7 @@ public void setDevice(MediaDevice device) if (device == null) throw new NullPointerException("device"); + // Require AbstractMediaDevice for MediaDeviceSession support. AbstractMediaDevice abstractMediaDevice = (AbstractMediaDevice) device; if ((deviceSession == null) || (deviceSession.getDevice() != device)) @@ -585,6 +649,10 @@ public void setDevice(MediaDevice device) if (deviceSession != null) { + if (deviceSessionPropertyChangeListener != null) + deviceSession + .removePropertyChangeListener( + deviceSessionPropertyChangeListener); deviceSession.close(); deviceSession = null; } @@ -691,6 +759,12 @@ public void setFormat(MediaFormat format) */ public void setMute(boolean mute) { + MediaDevice device = getDevice(); + + if (device instanceof MediaDeviceImpl) + ((MediaDeviceImpl) device).setMute(mute); + else + throw new IllegalStateException("device"); } /** @@ -764,22 +838,7 @@ private void start(MediaDirection direction) && (!MediaDirection.SENDRECV.equals(startedDirection) && !MediaDirection.SENDONLY.equals(startedDirection))) { - RTPManager rtpManager = getRTPManager(); - Iterable sendStreams = rtpManager.getSendStreams(); - - if (sendStreams != null) - for (SendStream sendStream : sendStreams) - try - { - // TODO Are we sure we want to connect here? - sendStream.getDataSource().connect(); - sendStream.start(); - } - catch (IOException ioe) - { - logger - .warn("Failed to start stream " + sendStream, ioe); - } + startSendStreams(); getDeviceSession().start(MediaDirection.SENDONLY); @@ -844,6 +903,31 @@ else if (startedDirection == null) } } + /** + * Starts the SendStreams of the RTPManager of this + * MediaStream. + */ + private void startSendStreams() + { + RTPManager rtpManager = getRTPManager(); + @SuppressWarnings("unchecked") + Iterable sendStreams = rtpManager.getSendStreams(); + + if (sendStreams != null) + for (SendStream sendStream : sendStreams) + try + { + // TODO Are we sure we want to connect here? + sendStream.getDataSource().connect(); + sendStream.start(); + } + catch (IOException ioe) + { + logger + .warn("Failed to start stream " + sendStream, ioe); + } + } + /** * Stops all streaming and capturing in this MediaStream and closes * and releases all open/allocated devices/resources. Has no effect if this diff --git a/src/net/java/sip/communicator/impl/neomedia/ProcessorUtility.java b/src/net/java/sip/communicator/impl/neomedia/ProcessorUtility.java index 87fdf2661..353ecf2f7 100644 --- a/src/net/java/sip/communicator/impl/neomedia/ProcessorUtility.java +++ b/src/net/java/sip/communicator/impl/neomedia/ProcessorUtility.java @@ -15,30 +15,43 @@ * * @author Emil Ivov * @author Ken Larson + * @author Lubomir Marinov */ public class ProcessorUtility implements ControllerListener { - private final Logger logger = Logger.getLogger(ProcessorUtility.class); /** - * The object that we use for syncing when waiting for a processor - * to enter a specific state. + * The Logger used by the ProcessorUtility class and its + * instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(ProcessorUtility.class); + + /** + * The Object used for syncing when waiting for a processor to + * enter a specific state. */ private final Object stateLock = new Object(); + /** + * The indicator which determines whether the waiting of this instance on a + * processor for it to enter a specific state has failed. + */ private boolean failed = false; /** - * Default constructor, creates an instance of the of the Processor utility. + * Initializes a new ProcessorUtility instance. */ public ProcessorUtility() { } /** - * Returns the object that we use for syncing when waiting for a processor + * Gets the Object to use for syncing when waiting for a processor * to enter a specific state. - * @return Integer + * + * @return the Object to use for syncing when waiting for a + * processor to enter a specific state */ private Object getStateLock() { @@ -125,11 +138,20 @@ else if (state == Processor.Realized) } catch (InterruptedException ie) { + logger + .warn( + "Failed while waiting on Processor " + + processor + + " for state " + + state, + ie); + processor.removeControllerListener(this); return false; } } } + processor.removeControllerListener(this); return !failed; } } diff --git a/src/net/java/sip/communicator/impl/neomedia/device/AudioMediaDeviceImpl.java b/src/net/java/sip/communicator/impl/neomedia/device/AudioMediaDeviceImpl.java deleted file mode 100644 index 172a2578f..000000000 --- a/src/net/java/sip/communicator/impl/neomedia/device/AudioMediaDeviceImpl.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SIP Communicator, 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.device; - -import javax.media.*; -import javax.media.protocol.*; - -import net.java.sip.communicator.impl.neomedia.*; -import net.java.sip.communicator.service.neomedia.*; - -/** - * Extends MediaDeviceImpl with audio-specific functionality. - * - * @author Lubomir Marinov - */ -public class AudioMediaDeviceImpl - extends MediaDeviceImpl -{ - - /** - * Initializes a new AudioMediaDeviceImpl instance with - * MediaDirection which does not allow sending i.e. the new - * instance cannot be used to capture audio. - */ - public AudioMediaDeviceImpl() - { - super(MediaType.AUDIO); - } - - /** - * Initializes a new AudioMediaDeviceImpl instance which is to - * provide an implementation of MediaDevice for a specific audio - * CaptureDevice. - * - * @param captureDevice the audio CaptureDevice the new instance is - * to provide an implementation of MediaDevice for - */ - public AudioMediaDeviceImpl(CaptureDevice captureDevice) - { - super(captureDevice, MediaType.AUDIO); - } - - /** - * Initializes a new AudioMediaDeviceImpl instance which is to - * provide an implementation of MediaDevice for an audio - * CaptureDevice with a specific CaptureDeviceInfo. - * - * @param captureDeviceInfo the CaptureDeviceInfo of the audio - * CaptureDevice the new instance is to provide an implementation - * of MediaDevice for - */ - public AudioMediaDeviceImpl(CaptureDeviceInfo captureDeviceInfo) - { - super(captureDeviceInfo, MediaType.AUDIO); - } - - /** - * 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 audio. - */ - return !getDirection().allowsSending(); - } - - /** - * Sets the JMF CaptureDevice this instance wraps and provides a - * MediaDevice implementation for. Tries to enable muting. - * - * @param captureDevice the JMF CaptureDevice this instance is to - * wrap and provide a MediaDevice implementation for - * @see MediaDeviceImpl#setCaptureDevice(CaptureDevice) - */ - @Override - protected void setCaptureDevice(CaptureDevice captureDevice) - { - if (captureDevice instanceof PushBufferDataSource) - captureDevice - = new MutePushBufferDataSource( - (PushBufferDataSource) captureDevice); - - super.setCaptureDevice(captureDevice); - } - - /** - * 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); - } -} 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 d8bc00e2f..240a6420c 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java @@ -33,21 +33,23 @@ public class AudioMixerMediaDevice private AudioMixer audioMixer; /** - * The actual AudioMediaDeviceImpl wrapped by this instance for the + * The actual MediaDeviceImpl wrapped by this instance for the * purposes of audio mixing and used by {@link #audioMixer} as its * CaptureDevice. */ - private final AudioMediaDeviceImpl device; + private final MediaDeviceImpl device; /** * Initializes a new AudioMixerMediaDevice instance which is to - * enable audio mixing on a specific AudioMediaDeviceImpl. + * enable audio mixing on a specific MediaDeviceImpl. * - * @param device the AudioMediaDeviceImpl which the new instance is - * to enable audio mixing on + * @param device the MediaDeviceImpl which the new instance is to + * enable audio mixing on */ - public AudioMixerMediaDevice(AudioMediaDeviceImpl device) + public AudioMixerMediaDevice(MediaDeviceImpl device) { + if (!MediaType.AUDIO.equals(device.getMediaType())) + throw new IllegalArgumentException("device"); /* * AudioMixer is initialized with a CaptureDevice so we have to be sure 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 3895d6987..69b2976d0 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceImpl.java @@ -401,6 +401,28 @@ 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 @@ -412,6 +434,12 @@ public List getSupportedFormats() */ protected void setCaptureDevice(CaptureDevice 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; @@ -425,6 +453,23 @@ protected void setCaptureDevice(CaptureDevice captureDevice) } } + /** + * 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 842edc202..cafbdcde3 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java @@ -30,7 +30,7 @@ * @author Lubomir Marinov */ public class MediaDeviceSession - implements ControllerListener + extends PropertyChangeNotifier { /** @@ -40,6 +40,14 @@ public class MediaDeviceSession private static final Logger logger = Logger.getLogger(MediaDeviceSession.class); + /** + * The name of the MediaDeviceSession instance property the value + * of which represents the output DataSource of the + * MediaDeviceSession instance which provides the captured (RTP) + * data to be sent by MediaStream to MediaStreamTarget. + */ + public static final String OUTPUT_DATA_SOURCE = "OUTPUT_DATA_SOURCE"; + /** * The JMF DataSource of {@link #device} through which this * instance accesses the media captured by it. @@ -58,6 +66,19 @@ public class MediaDeviceSession */ private final AbstractMediaDevice device; + /** + * The last JMF Format set to this instance by a call to its + * {@link #setFormat(MediaFormat) and to be set as the output format of + * {@link #processor}. + */ + private Format format; + + /** + * The ControllerListener which listens to the Player + * instances in {@link #players} for ControllerEvents. + */ + private ControllerListener playerControllerListener; + /** * The Players rendering ReceiveStreams on the * MediaDevice represented by this instance. Associated with @@ -75,6 +96,32 @@ public class MediaDeviceSession */ private Processor processor; + /** + * The ControllerListener which listens to {@link #processor} for + * ControllerEvents. + */ + private ControllerListener processorControllerListener; + + /** + * The indicator which determines whether {@link #processor} has received + * a ControllerClosedEvent at an unexpected time in its execution. + * A value of false does not mean that processor exists + * or that it is not closed, it just means that if processor failed + * to be initialized or it received a ControllerClosedEvent, it was + * at an expected time of its execution and that the fact in question was + * reflected, for example, by setting processor to null. + * If there is no processorIsPrematurelyClosed field and + * processor is set to null or left existing after the + * receipt of ControllerClosedEvent, it will either lead to not + * firing a PropertyChangeEvent for OUTPUT_DATA_SOURCE + * when it has actually changed and, consequently, cause the + * SendStreams of MediaStreamImpl to not be recreated or + * it will be impossible to detect that processor cannot have its + * format set and will thus be left broken even for subsequent calls to + * {@link #setFormat(MediaFormat)}. + */ + private boolean processorIsPrematurelyClosed; + /** * The ReceiveStreams rendered by this instance on its associated * MediaDevice. Mapped to DataSource because extenders may @@ -170,7 +217,28 @@ protected void addReceiveStream( exception); else { - player.addControllerListener(this); + if (playerControllerListener == null) + playerControllerListener = new ControllerListener() + { + + /** + * Notifies this ControllerListener that + * the Controller which it is registered + * with has generated an event. + * + * @param event the ControllerEvent + * specifying the Controller which is the + * source of the event and the very type of the + * event + * @see ControllerListener#controllerUpdate( + * ControllerEvent) + */ + public void controllerUpdate(ControllerEvent event) + { + playerControllerUpdate(event); + } + }; + player.addControllerListener(playerControllerListener); player.realize(); players.put(receiveStreamDataSource, player); @@ -260,6 +328,45 @@ public void close() { disposePlayers(); + disconnectCaptureDevice(); + closeProcessor(); + } + + /** + * Makes sure {@link #processor} is closed. + */ + private void closeProcessor() + { + if (processor != null) + { + if (processorControllerListener != null) + processor.removeControllerListener(processorControllerListener); + + processor.stop(); + if (processor.getState() == Processor.Realized) + { + DataSource dataOutput = processor.getDataOutput(); + + if (dataOutput != null) + dataOutput.disconnect(); + } + processor.deallocate(); + processor.close(); + processorIsPrematurelyClosed = false; + + /* + * Once the processor uses the captureDevice, the captureDevice has + * to be reconnected on its next use. + */ + disconnectCaptureDevice(); + } + } + + /** + * Makes sure {@link #captureDevice} is disconnected. + */ + private void disconnectCaptureDevice() + { if (captureDevice != null) { /* @@ -287,43 +394,6 @@ public void close() captureDevice.disconnect(); captureDeviceIsConnected = false; } - if (processor != null) - { - processor.stop(); - if (processor.getState() == Processor.Realized) - { - DataSource dataOutput = processor.getDataOutput(); - - if (dataOutput != null) - dataOutput.disconnect(); - } - processor.deallocate(); - processor.close(); - } - } - - /** - * Notifies this ControllerListener that the Controller - * which it is registered with has generated an event. - * - * @param event the ControllerEvent specifying the - * Controller which is the source of the event and the very type of - * the event - * @see ControllerListener#controllerUpdate(ControllerEvent) - */ - public void controllerUpdate(ControllerEvent event) - { - if (event instanceof RealizeCompleteEvent) - { - Player player = (Player) event.getSourceController(); - - if (player != null) - { - player.start(); - - realizeComplete(player); - } - } } /** @@ -346,6 +416,8 @@ protected void disposePlayer(Player player) break; } + if (playerControllerListener != null) + player.removeControllerListener(playerControllerListener); player.stop(); player.deallocate(); player.close(); @@ -383,8 +455,15 @@ private static Format findFirstMatchingFormat( Format format) { for (Format match : formats) + { + /* + * TODO Is the encoding enough? We've been explicitly told what + * format to use so it may be that its non-encoding attributes which + * have been specified are also necessary. + */ if (match.isSameEncoding(format)) return match; + } return null; } @@ -468,7 +547,9 @@ public MediaFormat getFormat() { Processor processor = getProcessor(); - if (processor != null) + if ((processor != null) + && (this.processor == processor) + && !processorIsPrematurelyClosed) { MediaType mediaType = getMediaType(); @@ -580,33 +661,46 @@ private Processor getProcessor() .error( "Failed to create Processor for " + captureDevice, exception); - else if (waitForState(processor, Processor.Configured)) + else { - try + if (processorControllerListener == null) + processorControllerListener = new ControllerListener() + { + + /** + * Notifies this ControllerListener that + * the Controller which it is registered + * with has generated an event. + * + * @param event the ControllerEvent + * specifying the Controller which is the + * source of the event and the very type of the + * event + * @see ControllerListener#controllerUpdate( + * ControllerEvent) + */ + public void controllerUpdate(ControllerEvent event) + { + processorControllerUpdate(event); + } + }; + processor + .addControllerListener(processorControllerListener); + + if (waitForState(processor, Processor.Configured)) { - exception = null; - processor - .setContentDescriptor( - new ContentDescriptor( - ContentDescriptor.RAW_RTP)); + this.processor = processor; + processorIsPrematurelyClosed = false; } - catch (NotConfiguredError nce) + else { - // TODO - exception = nce; + if (processorControllerListener != null) + processor + .removeControllerListener( + processorControllerListener); processor = null; } - - if (exception != null) - logger - .error( - "Failed to set ContentDescriptor to Processor.", - exception); - else - this.processor = processor; } - else - processor = null; } } return processor; @@ -624,7 +718,9 @@ public List getSupportedFormats() Processor processor = getProcessor(); Set supportedFormats = new HashSet(); - if (processor != null) + if ((processor != null) + && (this.processor == processor) + && !processorIsPrematurelyClosed) { MediaType mediaType = getMediaType(); @@ -656,6 +752,80 @@ public List getSupportedFormats() return supportedMediaFormats; } + /** + * Gets notified about ControllerEvents generated by the + * Player instances in {@link #players}. + * + * @param event the ControllerEvent specifying the + * Controller which is the source of the event and the very type of + * the event + */ + private void playerControllerUpdate(ControllerEvent event) + { + if (event instanceof RealizeCompleteEvent) + { + Player player = (Player) event.getSourceController(); + + if (player != null) + { + player.start(); + + realizeComplete(player); + } + } + } + + /** + * Gets notified about ControllerEvents generated by + * {@link #processor}. + * + * @param event the ControllerEvent specifying the + * Controller which is the source of the event and the very type of + * the event + */ + private void processorControllerUpdate(ControllerEvent event) + { + if (event instanceof ConfigureCompleteEvent) + { + Processor processor = (Processor) event.getSourceController(); + + if (processor != null) + { + try + { + processor + .setContentDescriptor( + new ContentDescriptor( + ContentDescriptor.RAW_RTP)); + } + catch (NotConfiguredError nce) + { + logger + .error( + "Failed to set ContentDescriptor to Processor.", + nce); + } + + if (format != null) + setFormat(processor, format); + } + } + else if (event instanceof ControllerClosedEvent) + { + Processor processor = (Processor) event.getSourceController(); + + /* + * If everything goes according to plan, we should've removed the + * ControllerListener from the processor by now. + */ + logger.warn(event); + + // TODO Should the access to processor be synchronized? + if ((processor != null) && (this.processor == processor)) + processorIsPrematurelyClosed = true; + } + } + /** * Notifies this instance that a specific Player of remote content * has generated a RealizeCompleteEvent. Allows extenders to carry @@ -714,72 +884,162 @@ public void setFormat(MediaFormat format) MediaFormatImpl mediaFormatImpl = (MediaFormatImpl) format; - Processor processor = getProcessor(); + this.format = mediaFormatImpl.getFormat(); + /* + * If the processor is after Configured, setting a different format will + * silently fail. Recreate the processor in order to be able to set the + * different format. + */ if (processor != null) { - if ((processor.getState() < Processor.Configured) - && !waitForState(processor, Processor.Configured)) - { - // TODO - return; - } + int processorState = processor.getState(); + + if (processorState == Processor.Configured) + setFormat(processor, this.format); + else if (processorIsPrematurelyClosed + || ((processorState > Processor.Configured) + && !format.equals(getFormat()))) + setProcessor(null); + } + } - for (TrackControl trackControl : processor.getTrackControls()) - { - if (!trackControl.isEnabled()) - continue; + /** + * Sets the JMF Format in which a specific Processor is to + * output media data. + * + * @param processor the Processor to set the output Format + * of + * @param format the JMF Format to set to processor + */ + private void setFormat(Processor processor, Format format) + { + TrackControl[] trackControls = processor.getTrackControls(); + MediaType mediaType = getMediaType(); - Format[] supportedFormats = trackControl.getSupportedFormats(); + for (int trackIndex = 0; + trackIndex < trackControls.length; + trackIndex++) + { + TrackControl trackControl = trackControls[trackIndex]; - if ((supportedFormats == null) || (supportedFormats.length < 1)) - { - trackControl.setEnabled(false); - continue; - } + if (!trackControl.isEnabled()) + continue; - Format supportedFormat = null; + Format[] supportedFormats = trackControl.getSupportedFormats(); - switch (mediaType) + if ((supportedFormats == null) || (supportedFormats.length < 1)) + { + trackControl.setEnabled(false); + continue; + } + + Format supportedFormat = null; + + switch (mediaType) + { + case AUDIO: + if (supportedFormats[0] instanceof AudioFormat) { - case AUDIO: - if (supportedFormats[0] instanceof AudioFormat) - { - if (FMJConditionals.FORCE_AUDIO_FORMAT != null) - trackControl - .setFormat(FMJConditionals.FORCE_AUDIO_FORMAT); - else - { - supportedFormat - = findFirstMatchingFormat( - supportedFormats, - mediaFormatImpl.getFormat()); - } - } - break; - case VIDEO: - if (supportedFormats[0] instanceof VideoFormat) + if (FMJConditionals.FORCE_AUDIO_FORMAT != null) + trackControl + .setFormat(FMJConditionals.FORCE_AUDIO_FORMAT); + else { supportedFormat - = findFirstMatchingFormat( - supportedFormats, - mediaFormatImpl.getFormat()); - - if (supportedFormat != null) - supportedFormat - = assertSize((VideoFormat) supportedFormat); + = findFirstMatchingFormat(supportedFormats, format); + + /* + * We've failed to find a supported format so try to use + * whatever we've been told and, if it fails, the caller + * will at least know why. + */ + if (supportedFormat == null) + supportedFormat = format; } - break; } + break; + case VIDEO: + if (supportedFormats[0] instanceof VideoFormat) + { + supportedFormat + = findFirstMatchingFormat(supportedFormats, format); + + /* + * We've failed to find a supported format so try to use + * whatever we've been told and, if it fails, the caller + * will at least know why. + */ + if (supportedFormat == null) + supportedFormat = format; + + if (supportedFormat != null) + supportedFormat + = assertSize((VideoFormat) supportedFormat); + } + break; + } - if (supportedFormat == null) - trackControl.setEnabled(false); - else - trackControl.setFormat(supportedFormat); + if (supportedFormat == null) + trackControl.setEnabled(false); + else + { + Format setFormat = trackControl.setFormat(supportedFormat); + + if (setFormat == null) + logger + .error( + "Failed to set format of track " + + trackIndex + + " to " + + supportedFormat + + ". Processor is in state " + + processor.getState()); + else if (setFormat != supportedFormat) + logger + .warn( + "Failed to change format of track " + + trackIndex + + " from " + + setFormat + + " to " + + supportedFormat + + ". Processor is in state " + + processor.getState()); + else if (logger.isTraceEnabled()) + logger + .trace( + "Set format of track " + + trackIndex + + " to " + + setFormat); } } } + /** + * Sets the JMF Processor which is to transcode + * {@link #captureDevice} into the format of this instance. + * + * @param processor the JMF Processor which is to transcode + * {@link #captureDevice} into the format of this instance + */ + private void setProcessor(Processor processor) + { + if (this.processor != processor) + { + closeProcessor(); + + this.processor = processor; + + /* + * Since the processor has changed, its output DataSource known to + * the public has also changed. + */ + firePropertyChange(OUTPUT_DATA_SOURCE, null, null); + } + } + /** * Starts the processing of media in this instance in a specific direction. * @@ -822,8 +1082,8 @@ public void stop(MediaDirection direction) if (MediaDirection.SENDRECV.equals(direction) || MediaDirection.SENDONLY.equals(direction)) if ((processor != null) - && (processor.getState() == Processor.Started)) - processor.start(); + && (processor.getState() > Processor.Configured)) + processor.stop(); } /** diff --git a/src/net/java/sip/communicator/service/neomedia/MediaStream.java b/src/net/java/sip/communicator/service/neomedia/MediaStream.java index bafade083..34b10066b 100644 --- a/src/net/java/sip/communicator/service/neomedia/MediaStream.java +++ b/src/net/java/sip/communicator/service/neomedia/MediaStream.java @@ -176,7 +176,7 @@ public interface MediaStream * MediaStream with rtpPayloadType */ public void addDynamicRTPPayloadType( - int rtpPayloadType, + byte rtpPayloadType, MediaFormat format); /** @@ -187,14 +187,14 @@ public void addDynamicRTPPayloadType( * well-known associations reported by * {@link MediaFormat#getRTPPayloadType()}. * - * @return a Map of RTP payload type expressed as Integer - * to MediaFormat describing the existing (dynamic) associations in + * @return a Map of RTP payload type expressed as Byte to + * MediaFormat describing the existing (dynamic) associations in * this instance of RTP payload types to MediaFormats. The * Map represents a snapshot of the existing associations at the * time of the getDynamicRTPPayloadTypes() method call and * modifications to it are not reflected on the internal storage */ - public Map getDynamicRTPPayloadTypes(); + public Map getDynamicRTPPayloadTypes(); /** * Sets the direction in which media in this MediaStream is to be