From 43c58e4b7bd5c2da5a99b5bc1d7ecec17d593da2 Mon Sep 17 00:00:00 2001 From: Emil Ivov Date: Sat, 12 Dec 2009 12:21:22 +0000 Subject: [PATCH] Adds final touches to CSRC level decoding. Implements event dispatching for CSRC audio levels. --- .../impl/neomedia/AudioMediaStreamImpl.java | 8 +- .../impl/neomedia/MediaStreamImpl.java | 3 + .../transform/csrc/CsrcTransformEngine.java | 118 ++++++++++++++++++ .../impl/protocol/sip/CallPeerSipImpl.java | 53 +++++++- 4 files changed, 176 insertions(+), 6 deletions(-) diff --git a/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java index 0eb8e2bc4..9ae84ffa8 100644 --- a/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java @@ -286,12 +286,14 @@ public int getLastMeasuredAudioLevel(long ssrc) } /** - * Delivers the audioLevels map to whoever's interested. + * 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 bidimensional array mapping CSRC IDs to audio + * @param audioLevels a bi-dimensional array mapping CSRC IDs to audio * levels. */ - private void fireConferenceAudioLevelEvent(final long[][] audioLevels) + public void fireConferenceAudioLevelEvent(final long[][] audioLevels) { if (this.csrcAudioLevelListener != null) this.csrcAudioLevelListener.audioLevelsReceived(audioLevels); diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java index 6732fd685..5b4c18312 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java @@ -345,6 +345,9 @@ public void close() engine.cleanup(); } + if(csrcEngine != null) + csrcEngine.stop(); + rtpConnector.removeTargets(); rtpConnectorTarget = null; diff --git a/src/net/java/sip/communicator/impl/neomedia/transform/csrc/CsrcTransformEngine.java b/src/net/java/sip/communicator/impl/neomedia/transform/csrc/CsrcTransformEngine.java index fb40d3278..ffe1b196b 100644 --- a/src/net/java/sip/communicator/impl/neomedia/transform/csrc/CsrcTransformEngine.java +++ b/src/net/java/sip/communicator/impl/neomedia/transform/csrc/CsrcTransformEngine.java @@ -6,6 +6,8 @@ */ package net.java.sip.communicator.impl.neomedia.transform.csrc; +import javax.media.*; + import net.java.sip.communicator.impl.neomedia.*; import net.java.sip.communicator.impl.neomedia.transform.*; @@ -43,6 +45,11 @@ public class CsrcTransformEngine */ private int extensionBuffLen = 0; + /** + * The dispatcher that is delivering audio levels to the media steam. + */ + private CsrcAudioLevelDispatcher csrcLevelDispatcher = null; + /** * Creates an engine instance that will be adding CSRC lists to the * specified stream. @@ -94,6 +101,22 @@ public PacketTransformer getRTPTransformer() */ public RawPacket reverseTransform(RawPacket pkt) { + if (csrcAudioLevelExtID > 0) + { + //extract the audio levels and send them to the dispatcher. + long[][] levels = pkt.extractCsrcLevels(csrcAudioLevelExtID); + + if(levels != null) + { + if (csrcLevelDispatcher == null) + csrcLevelDispatcher = new CsrcAudioLevelDispatcher(); + + if( ! csrcLevelDispatcher.isRunning ) + new Thread(csrcLevelDispatcher).start(); + + csrcLevelDispatcher.addLevels(levels); + } + } return pkt; } @@ -133,6 +156,15 @@ public synchronized RawPacket transform(RawPacket pkt) return pkt; } + /** + * Stops threads that this transform engine is using for even delivery. + */ + public void stop() + { + if(csrcLevelDispatcher != null) + csrcLevelDispatcher.stop(); + } + /** * Sets the ID that this transformer should be using for audio level * extensions or disables audio level extensions if extID is @@ -199,4 +231,90 @@ private byte[] getExtensionBuff(int ensureCapacity) return extensionBuff; } + /** + * A simple thread that waits for new levels to be reported from incoming + * RTP packets and then delivers them to the AudioMediaStream + * associated with this engine. The reason we need to do this in a separate + * thread is of course the time sensitive nature of incoming RTP packets. + */ + private class CsrcAudioLevelDispatcher + implements Runnable + { + /** Indicates whether this thread is supposed to be running */ + private boolean isRunning = true; + + /** The levels that we last received from the reverseTransform thread*/ + private long[][] lastReportedLevels = null; + + /** + * Waits for new levels to be reported via the addLevels() + * method and then delivers them to the AudioMediaStream that + * we are associated with. + */ + public void run() + { + isRunning = true; + + //no point in listening if our stream is not an audio one. + if(!(mediaStream instanceof AudioMediaStreamImpl)) + return; + + while(isRunning) + { + synchronized(this) + { + if(lastReportedLevels == null) + { + try + { + wait(); + } + catch (InterruptedException ie) {} + } + } + + if(lastReportedLevels != null) + { + //now notify our listener + if (mediaStream != null) + { + ((AudioMediaStreamImpl)mediaStream) + .fireConferenceAudioLevelEvent(lastReportedLevels); + } + } + } + } + + /** + * A level matrix that we should deliver to our media stream and + * its listeners in a separate thread. + * + * @param levels the levels that we'd like to queue for processing. + */ + public void addLevels(long[][] levels) + { + synchronized(this) + { + this.lastReportedLevels = levels; + + notifyAll(); + } + } + + /** + * Causes our run method to exit so that this thread would stop + * handling levels. + */ + public void stop() + { + synchronized(this) + { + this.lastReportedLevels = null; + isRunning = false; + + notifyAll(); + } + } + } + } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java index ec0662841..fd544f617 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java @@ -16,8 +16,7 @@ import javax.sip.message.*; import net.java.sip.communicator.impl.protocol.sip.sdp.*; -import net.java.sip.communicator.service.neomedia.event.ZrtpListener; -import net.java.sip.communicator.service.neomedia.event.SimpleAudioLevelListener; +import net.java.sip.communicator.service.neomedia.event.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; @@ -32,6 +31,7 @@ public class CallPeerSipImpl extends AbstractCallPeer implements SimpleAudioLevelListener, CallPeerConferenceListener, + CsrcAudioLevelListener, ZrtpListener { /** @@ -129,6 +129,15 @@ public class CallPeerSipImpl private List streamAudioLevelListeners = new ArrayList(); + /** + * Holds listeners registered for level changes in the audio of participants + * that this peer might be mixing and that we are not directly communicating + * with. + */ + private List + conferenceMemberAudioLevelListeners + = new ArrayList(); + /** * Creates a new call peer with address peerAddress. * @@ -1742,8 +1751,21 @@ public void removeStreamSoundLevelListener(SoundLevelListener listener) * @param listener the ConferenceMembersSoundLevelListener to add */ public void addConferenceMembersSoundLevelListener( - ConferenceMembersSoundLevelListener listener) + ConferenceMembersSoundLevelListener listener) { + synchronized (conferenceMemberAudioLevelListeners) + { + + if (conferenceMemberAudioLevelListeners.size() == 0) + { + // if this is the first listener that's being registered with + // us, we also need to register ourselves as a CSRC audio level + // listener with the media handler. + getMediaHandler().setCsrcAudioLevelListener(this); + } + + conferenceMemberAudioLevelListeners.add(listener); + } } /** @@ -1757,6 +1779,31 @@ public void addConferenceMembersSoundLevelListener( public void removeConferenceMembersSoundLevelListener( ConferenceMembersSoundLevelListener listener) { + synchronized (conferenceMemberAudioLevelListeners) + { + conferenceMemberAudioLevelListeners.remove(listener); + + if (conferenceMemberAudioLevelListeners.size() == 0) + { + // if this was the last listener then we also remove ourselves + // as a CSRC audio level listener from the handler so that we + // don't have to create new events and maps for something no one + // is interested in. + getMediaHandler().setCsrcAudioLevelListener(null); + } + } + } + + /** + * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[][])} + * so that we could deliver to {@link ConferenceMembersSoundLevelListener}s + * the events corresponding to the audio level changes that are being + * reported here. + */ + public void audioLevelsReceived(long[][] audioLevels) + { + // TODO Auto-generated method stub + } /**