/* * 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; } }