Fixes the implementation of MediaDevice to not cause undesired disconnects from CaptureDevice.

cusax-fix
Lyubomir Marinov 16 years ago
parent 670e6a34ee
commit 1aec195e2b

@ -136,6 +136,13 @@ public class MediaStreamImpl
*/
private String localSourceID = null;
/**
* The indicator which determines whether this <tt>MediaStream</tt> is set
* to transmit "silence" instead of the actual media fed from its
* <tt>MediaDevice</tt>.
*/
private boolean mute = false;
/**
* Initializes a new <tt>MediaStreamImpl</tt> instance which will use the
* specified <tt>MediaDevice</tt> 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);
}
}
/**

@ -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++;
}

@ -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(

@ -38,23 +38,11 @@ public class MediaDeviceImpl
private static final Logger logger
= Logger.getLogger(MediaDeviceImpl.class);
/**
* The JMF <tt>CaptureDevice</tt> this instance wraps and provides an
* implementation of <tt>MediaDevice</tt> for.
*/
private CaptureDevice captureDevice;
/**
* The <tt>CaptureDeviceInfo</tt> 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 <tt>MediaType</tt> of this instance and the <tt>CaptureDevice</tt>
* 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 <tt>MediaDeviceImpl</tt> instance which is to provide
* an implementation of <tt>MediaDevice</tt> for a specific
* <tt>CaptureDevice</tt> with a specific <tt>MediaType</tt>.
*
* @param captureDevice the JMF <tt>CaptureDevice</tt> the new instance is
* to provide an implementation of <tt>MediaDevice</tt> for
* @param mediaType the <tt>MediaType</tt> 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 <tt>MediaDeviceImpl</tt> instance which is to provide
* an implementation of <tt>MediaDevice</tt> for a <tt>CaptureDevice</tt>
@ -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 <tt>captureDevice</tt> (the JMF
* <tt>CaptureDevice</tt> this instance wraps and provides an implementation
* of <tt>MediaDevice</tt> for) property has changed its value from
* <tt>oldValue</tt> to <tt>newValue</tt>. Allows extenders to override in
* order to perform additional processing of the new <tt>captureDevice</tt>
* once it is clear that it is set into this instance.
* Creates the JMF <tt>CaptureDevice</tt> this instance represents and
* provides an implementation of <tt>MediaDevice</tt> for.
*
* @param oldValue the JMF <tt>CaptureDevice</tt> which was the value of the
* <tt>captureDevice</tt> property of this instance before <tt>newValue</tt>
* was set
* @param newValue the JMF <tt>CaptureDevice</tt> which is the value of the
* <tt>captureDevice</tt> property of this instance and which replaced
* <tt>oldValue</tt>
* @return the JMF <tt>CaptureDevice</tt> this instance represents and
* provides an implementation of <tt>MediaDevice</tt> for; <tt>null</tt>
* 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 <tt>CaptureDevice</tt> this instance wraps and provides an
* implementation of <tt>MediaDevice</tt> for.
*
* @return the JMF <tt>CaptureDevice</tt> this instance wraps and provides
* an implementation of <tt>MediaDevice</tt> for
*/
public CaptureDevice getCaptureDevice()
{
return getCaptureDevice(true);
}
/**
* Gets the JMF <tt>CaptureDevice</tt> this instance wraps and provides an
* implementation of <tt>MediaDevice</tt> for and, optionally, creates it if
* it does not exist yet.
*
* @param create <tt>true</tt> to create the <tt>CaptureDevice</tt> this
* instance provides an implementation of <tt>MediaDevice</tt> for it it
* does not exist yet; <tt>false</tt> to not create it and return
* <tt>null</tt> if it does not exist yet
* @return the JMF <tt>CaptureDevice</tt> this instance wraps and provides
* an implementation of <tt>MediaDevice</tt> for if it exists or
* <tt>create</tt> is <tt>true</tt> and its creation succeeds; <tt>null</tt>
* if it does not exist yet and <tt>create</tt> is <tt>false</tt> 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 <tt>CaptureDeviceInfo</tt> of the JMF <tt>CaptureDevice</tt>
* represented by this instance.
@ -321,83 +244,6 @@ public CaptureDeviceInfo getCaptureDeviceInfo()
return captureDeviceInfo;
}
/**
* Gets the JMF <tt>CaptureDevice</tt> this instance wraps and provides an
* implementation of <tt>MediaDevice</tt> for in a connected state. If the
* <tt>CaptureDevice</tt> is not connected to yet, first tries to connect to
* it. Returns <tt>null</tt> if this instance has failed to create a
* <tt>CaptureDevice</tt> instance or to connect to it.
*
* @return the JMF <tt>CaptureDevice</tt> this instance wraps and provides
* an implementation of <tt>MediaDevice</tt> for in a connected state;
* <tt>null</tt> if this instance has failed to create a
* <tt>CaptureDevice</tt> 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 <tt>MediaDirection</tt> 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<MediaFormat> getSupportedFormats()
return supportedFormats;
}
/**
* Determines whether this <tt>MediaDevice</tt> will provide silence instead
* of actual captured data next time it is read.
*
* @return <tt>true</tt> if this <tt>MediaDevice</tt> will provide silence
* instead of actual captured data next time it is read; <tt>false</tt>,
* 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 <tt>CaptureDevice</tt> this instance wraps and provides a
* <tt>MediaDevice</tt> implementation for. Allows extenders to override in
* order to customize <tt>captureDevice</tt> including to replace it before
* it is set into this instance.
*
* @param captureDevice the JMF <tt>CaptureDevice</tt> this instance is to
* wrap and provide a <tt>MediaDevice</tt> 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 <tt>MediaDevice</tt>
* will start providing silence instead of actual captured data next time it
* is read.
*
* @param mute <tt>true</tt> to have this <tt>MediaDevice</tt> start
* providing silence instead of actual captured data next time it is read;
* otherwise, <tt>false</tt>
*/
public void setMute(boolean mute)
{
CaptureDevice captureDevice = getCaptureDevice();
if (captureDevice instanceof MutePushBufferDataSource)
((MutePushBufferDataSource) captureDevice).setMute(mute);
}
/**
* Gets a human-readable <tt>String</tt> representation of this instance.
*

@ -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 <tt>MediaDeviceSession</tt>
* is set to output "silence" instead of the actual media captured from
* {@link #captureDevice}.
*/
private boolean mute = false;
/**
* The <tt>ControllerListener</tt> which listens to the <tt>Player</tt>
* instances in {@link #players} for <tt>ControllerEvent</tt>s.
@ -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<MediaFormat> getSupportedFormats()
return supportedMediaFormats;
}
/**
* Determines whether this <tt>MediaDeviceSession</tt> is set to output
* "silence" instead of the actual media fed from its
* <tt>CaptureDevice</tt>.
*
* @return <tt>true</tt> if this <tt>MediaDeviceSession</tt> is set to
* output "silence" instead of the actual media fed from its
* <tt>CaptureDevice</tt>; otherwise, <tt>false</tt>
*/
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 <tt>ControllerEvent</tt>s generated by
* {@link #processor}.
@ -1160,6 +1201,28 @@ else if (logger.isTraceEnabled())
}
}
/**
* Sets the indicator which determines whether this
* <tt>MediaDeviceSession</tt> is set to output "silence" instead of the
* actual media fed from its <tt>CaptureDevice</tt>.
*
* @param mute <tt>true</tt> to set this <tt>MediaDeviceSession</tt> to
* output "silence" instead of the actual media fed from its
* <tt>CaptureDevice</tt>; otherwise, <tt>false</tt>
*/
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 <tt>Processor</tt> which is to transcode
* {@link #captureDevice} into the format of this instance.

@ -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 <tt>DataSource</tt> and <tt>CaptureDevice</tt> 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, <tt>AudioMixer</tt> 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 <tt>DataSource</tt> implementations but
* at least the choice of the caller is remembered and reported.
*/
private boolean enabled;
/**
* Implements {@link Controls#getControlComponent()}. Since
* <tt>DataSource</tt> does not export any UI, returns <tt>null</tt>.
*
* @return a <tt>Component</tt> which represents UI associated with this
* <tt>DataSource</tt> and this <tt>FormatControl</tt> if any;
* otherwise, <tt>null</tt>
*/
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 <tt>DataSource</tt> at the time of this writing.
*
* @return <tt>true</tt> if this track is enabled; otherwise,
* <tt>false</tt>
*/
public boolean isEnabled()
{
return enabled;
}
/**
* Implements {@link FormatControl#setEnabled(boolean)}. Does not mean
* anything to this <tt>DataSource</tt> at the time of this writing.
*
* @param enabled <tt>true</tt> if this track is to be enabled;
* otherwise, <tt>false</tt>
*/
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
* <tt>Format</tt> is supported and <tt>null</tt> if it is not
* supported.
*
* @param format the <tt>Format</tt> in which this <tt>DataSource</tt>
* is to output
* @return the currently set <tt>Format</tt> after the attempt to set it
* as the output format of this <tt>DataSource</tt> if <tt>format</tt>
* is supported by this <tt>DataSource</tt> and regardless of whether it
* was actually set; <tt>null</tt> if <tt>format</tt> is not supported
* by this <tt>DataSource</tt>
*/
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;
}
}
}

Loading…
Cancel
Save