/*
* 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;
import java.util.*;
import javax.media.*;
import javax.media.control.*;
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.impl.neomedia.transform.dtmf.*;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.neomedia.device.*;
import net.java.sip.communicator.service.neomedia.event.*;
import net.java.sip.communicator.util.*;
/**
* Extends MediaStreamImpl in order to provide an implementation of
* AudioMediaStream.
*
* @author Lubomir Marinov
* @author Emil Ivov
*/
public class AudioMediaStreamImpl
extends MediaStreamImpl
implements AudioMediaStream
{
/**
* The Logger used by the AudioMediaStreamImpl class and
* its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(AudioMediaStreamImpl.class);
/**
* The transformer that we use for sending and receiving DTMF packets.
*/
private DtmfTransformEngine dtmfTransfrmEngine ;
/**
* List of DTMF listeners;
*/
private List dtmfListeners = new ArrayList();
/**
* List of RTP format strings which are supported by SIP Communicator in
* addition to the JMF standard formats.
*
* @see #registerCustomCodecFormats(RTPManager)
*/
private static final AudioFormat[] CUSTOM_CODEC_FORMATS
= new AudioFormat[]
{
/*
* these formats are specific, since RTP uses format numbers
* with no parameters.
*/
new AudioFormat(
Constants.ALAW_RTP,
8000,
8,
1,
Format.NOT_SPECIFIED,
AudioFormat.SIGNED),
new AudioFormat(
Constants.G722_RTP,
8000,
Format.NOT_SPECIFIED /* sampleSizeInBits */,
1)
};
/**
* The listener that gets notified of changes in the audio level of
* remote conference participants.
*/
private CsrcAudioLevelListener csrcAudioLevelListener = null;
/**
* Initializes a new AudioMediaStreamImpl instance which will use
* the specified MediaDevice for both capture and playback of audio
* exchanged via the specified StreamConnector.
*
* @param connector the StreamConnector the new instance is to use
* for sending and receiving audio
* @param device the MediaDevice the new instance is to use for
* both capture and playback of audio exchanged via the specified
* StreamConnector
* @param zrtpControl a control which is already created, used to control
* the zrtp operations.
*/
public AudioMediaStreamImpl(StreamConnector connector,
MediaDevice device,
ZrtpControlImpl zrtpControl)
{
super(connector, device, zrtpControl);
if(logger.isTraceEnabled())
logger.trace("Created Audio Stream with hashCode " + hashCode());
}
/**
* Performs any optional configuration on the BufferControl of the
* specified RTPManager which is to be used as the
* RTPManager of this MediaStreamImpl.
*
* @param rtpManager the RTPManager which is to be used by this
* MediaStreamImpl
* @param bufferControl the BufferControl of rtpManager on
* which any optional configuration is to be performed
*/
@Override
protected void configureRTPManagerBufferControl(
RTPManager rtpManager,
BufferControl bufferControl)
{
/*
* It appears that if we don't do this managers don't play. You can try
* some other buffer size to see if you can get better smoothness.
*/
String bufferLengthStr
= NeomediaActivator.getConfigurationService()
.getString(PROPERTY_NAME_RECEIVE_BUFFER_LENGTH);
long bufferLength = 100;
try
{
if ((bufferLengthStr != null) && (bufferLengthStr.length() > 0))
bufferLength = Long.parseLong(bufferLengthStr);
}
catch (NumberFormatException nfe)
{
logger.warn(
bufferLengthStr
+ " is not a valid receive buffer length/long value",
nfe);
}
bufferLength = bufferControl.setBufferLength(bufferLength);
if (logger.isTraceEnabled())
logger.trace("Set receiver buffer length to " + bufferLength);
bufferControl.setEnabledThreshold(true);
bufferControl.setMinimumThreshold(100);
}
/**
* A stub that allows audio oriented streams to create and keep a reference
* to a DtmfTransformEngine.
*
* @return a DtmfTransformEngine if this is an audio oriented
* stream and null otherwise.
*/
@Override
protected DtmfTransformEngine createDtmfTransformEngine()
{
if(this.dtmfTransfrmEngine == null)
this.dtmfTransfrmEngine = new DtmfTransformEngine(this);
return this.dtmfTransfrmEngine;
}
/**
* Adds a DTMFListener to this AudioMediaStream which is
* to receive notifications when the remote party starts sending DTMF tones
* to us.
*
* @param listener the DTMFListener to register for notifications
* about the remote party starting sending of DTM tones to this
* AudioMediaStream
* @see AudioMediaStream#addDTMFListener(DTMFListener)
*/
public void addDTMFListener(DTMFListener listener)
{
if(!dtmfListeners.contains(listener))
{
dtmfListeners.add(listener);
}
}
/**
* Sets listener as the SimpleAudioLevelListener
* registered to receive notifications from our device session for changes
* in the levels of the party that's at the other end of this stream.
*
* @param listener the SimpleAudioLevelListener that we'd like to
* register or null if we want to stop stream audio level
* measurements.
*/
public void setStreamAudioLevelListener(SimpleAudioLevelListener listener)
{
getDeviceSession().setStreamAudioLevelListener(listener);
}
/**
* Registers listener as the CsrcAudioLevelListener that
* will receive notifications for changes in the levels of conference
* participants that the remote party could be mixing.
*
* @param listener the CsrcAudioLevelListener that we'd like to
* register or null if we'd like to stop receiving notifications.
*/
public void setCsrcAudioLevelListener(CsrcAudioLevelListener listener)
{
this.csrcAudioLevelListener = listener;
}
/**
* Registers {@link #CUSTOM_CODEC_FORMATS} with a specific
* RTPManager.
*
* @param rtpManager the RTPManager to register
* {@link #CUSTOM_CODEC_FORMATS} with
* @see MediaStreamImpl#registerCustomCodecFormats(RTPManager)
*/
@Override
protected void registerCustomCodecFormats(RTPManager rtpManager)
{
super.registerCustomCodecFormats(rtpManager);
for (AudioFormat format : CUSTOM_CODEC_FORMATS)
{
if (logger.isDebugEnabled())
logger.debug("registering format " + format + " with RTP manager");
/*
* NOTE (mkoch@rowa.de): com.sun.media.rtp.RtpSessionMgr.addFormat
* leaks memory, since it stores the Format in a static Vector.
* AFAIK there is no easy way around it, but the memory impact
* should not be too bad.
*/
rtpManager.addFormat( format,
MediaUtils.getRTPPayloadType(
format.getEncoding(), format.getSampleRate()));
}
}
/**
* Removes listener from the list of DTMFListeners
* registered with this AudioMediaStream to receive notifications
* about incoming DTMF tones.
*
* @param listener the DTMFListener to no longer be notified by
* this AudioMediaStream about incoming DTMF tones
* @see AudioMediaStream#removeDTMFListener(DTMFListener)
*/
public void removeDTMFListener(DTMFListener listener)
{
dtmfListeners.remove(listener);
}
/**
* Starts sending the specified DTMFTone until the
* stopSendingDTMF() method is called. Callers should keep in mind
* the fact that calling this method would most likely interrupt all audio
* transmission until the corresponding stop method is called. Also, calling
* this method successively without invoking the corresponding stop method
* between the calls will simply replace the DTMFTone from the
* first call with that from the second.
*
* @param tone the DTMFTone to start sending
* @see AudioMediaStream#startSendingDTMF(DTMFTone)
*/
public void startSendingDTMF(DTMFTone tone)
{
if(dtmfTransfrmEngine == null)
return;
dtmfTransfrmEngine.startSending(tone);
}
/**
* Interrupts transmission of a DTMFTone started with the
* startSendingDTMF() method. Has no effect if no tone is currently
* being sent.
*
* @see AudioMediaStream#stopSendingDTMF()
*/
public void stopSendingDTMF()
{
if(dtmfTransfrmEngine == null)
return;
dtmfTransfrmEngine.stopSendingDTMF();
}
/**
* In addition to calling
* {@link MediaStreamImpl#addRTPExtension(byte, RTPExtension)}
* this method enables sending of CSRC audio levels. The reason we are
* doing this here rather than in the super class is that CSRC levels only
* make sense for audio streams so we don't want them enabled in any other
* kind.
*
* @param extensionID the ID assigned to rtpExtension for the
* lifetime of this stream.
* @param rtpExtension the RTPExtension that is being added to this stream.
*/
@Override
public void addRTPExtension(byte extensionID, RTPExtension rtpExtension)
{
super.addRTPExtension(extensionID, rtpExtension);
if ( RTPExtension.CSRC_AUDIO_LEVEL_URN
.equals(rtpExtension.getURI().toString()))
{
getCsrcEngine().setCsrcAudioLevelAudioLevelExtensionID(
extensionID, rtpExtension.getDirection());
}
}
/**
* Sets listener as the SimpleAudioLevelListener
* registered to receive notifications from our device session for changes
* in the levels of the audio that this stream is sending out.
*
* @param listener the SimpleAudioLevelListener that we'd like to
* register or null if we want to stop local audio level
* measurements.
*/
public void setLocalUserAudioLevelListener(
SimpleAudioLevelListener listener)
{
getDeviceSession().setLocalUserAudioLevelListener(listener);
}
/**
* Returns the MediaDeviceSession associated with this stream
* after first casting it to AudioMediaDeviceSession since this is,
* after all, an AudioMediaStreamImpl.
*
* @return the AudioMediaDeviceSession associated with this stream.
*/
@Override
public AudioMediaDeviceSession getDeviceSession()
{
return (AudioMediaDeviceSession)super.getDeviceSession();
}
/**
* Returns the last audio level that was measured by the underlying device
* session for the specified ssrc (where ssrc could also
* correspond to our local sync source identifier).
*
* @param ssrc the SSRC ID whose last measured audio level we'd like to
* retrieve.
*
* @return the audio level that was last measured for the specified
* ssrc or -1 if no level has been cached for that ID.
*/
public int getLastMeasuredAudioLevel(long ssrc)
{
AudioMediaDeviceSession devSession = getDeviceSession();
if (devSession == null)
return -1;
if ( ssrc == getLocalSourceID() )
return devSession.getLastMeasuredLocalUserAudioLevel();
else
return devSession.getLastMeasuredAudioLevel(ssrc);
}
/**
* Delivers the audioLevels map to whoever's interested. This
* method is meant for use primarily by the transform engine handling
* incoming RTP packets (currently CsrcTransformEngine).
*
* @param audioLevels a bi-dimensional array mapping CSRC IDs to audio
* levels.
*/
public void fireConferenceAudioLevelEvent(final long[][] audioLevels)
{
CsrcAudioLevelListener csrcAudioLevelListener
= this.csrcAudioLevelListener;
if (csrcAudioLevelListener != null)
csrcAudioLevelListener.audioLevelsReceived(audioLevels);
}
/**
* Delivers the DTMF tones. This
* method is meant for use primarily by the transform engine handling
* incoming RTP packets (currently DtmfTransformEngine).
*
* @param tone the new tone
* @param end is end or start of tone.
*/
public void fireDTMFEvent(DTMFTone tone, boolean end)
{
Iterator iter = dtmfListeners.iterator();
DTMFToneEvent ev = new DTMFToneEvent(this, tone);
while (iter.hasNext())
{
DTMFListener listener = iter.next();
if(end)
listener.dtmfToneReceptionEnded(ev);
else
listener.dtmfToneReceptionStarted(ev);
}
}
/**
* Releases the resources allocated by this instance in the course of its
* execution and prepares it to be garbage collected.
*
* @see MediaStream#close()
*/
@Override
public void close()
{
super.close();
if(dtmfTransfrmEngine != null)
{
dtmfTransfrmEngine.stop();
dtmfTransfrmEngine = null;
}
}
/**
* The priority of the audio is 3, which is meant to be higher than
* other threads and higher than the video one.
* @return audio priority.
*/
@Override
protected int getPriority()
{
return 3;
}
}