diff options
author | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2011-06-28 17:29:13 +0000 |
---|---|---|
committer | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2011-06-28 17:29:13 +0000 |
commit | 4c433ed736ec55a61c5677ab4de583e24dfb1154 (patch) | |
tree | 123abb75b363b3218bddc7321780a3e51f774e48 | |
parent | 9bcbb52cdc44a76dfddafd8bd0216e29588fdaac (diff) | |
download | jitsi-4c433ed736ec55a61c5677ab4de583e24dfb1154.zip jitsi-4c433ed736ec55a61c5677ab4de583e24dfb1154.tar.gz jitsi-4c433ed736ec55a61c5677ab4de583e24dfb1154.tar.bz2 |
Fixes a deadlock in audio level functionality which used to freeze the user interface while leaving the rest of the application operating as expected (e.g. audio used to continue to be captured locally and sent to the remote peer and to be received from the remote peer and played back locally.)
11 files changed, 236 insertions, 111 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java index 2b867ea..1489881 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/OneToOneCallPeerPanel.java @@ -435,22 +435,23 @@ public class OneToOneCallPeerPanel add(remoteLevelPanel, constraints); - this.callPeer.addStreamSoundLevelListener(new SoundLevelListener() - { - public void soundLevelChanged(SoundLevelChangeEvent event) - { - remoteLevelIndicator.updateSoundLevel(event.getLevel()); - } - }); + this.callPeer.addStreamSoundLevelListener( + new SoundLevelListener() + { + public void soundLevelChanged(Object source, int level) + { + remoteLevelIndicator.updateSoundLevel(level); + } + }); this.callPeer.getCall().addLocalUserSoundLevelListener( - new SoundLevelListener() - { - public void soundLevelChanged(SoundLevelChangeEvent event) + new SoundLevelListener() { - localLevelIndicator.updateSoundLevel(event.getLevel()); - } - }); + public void soundLevelChanged(Object source, int level) + { + localLevelIndicator.updateSoundLevel(level); + } + }); } /** diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java index fe89b98..35a52d4 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferenceCallPanel.java @@ -138,9 +138,9 @@ public class ConferenceCallPanel call.addLocalUserSoundLevelListener(new SoundLevelListener() { - public void soundLevelChanged(SoundLevelChangeEvent evt) + public void soundLevelChanged(Object source, int level) { - localPeerPanel.fireLocalUserSoundLevelChanged(evt.getLevel()); + localPeerPanel.fireLocalUserSoundLevelChanged(level); } }); diff --git a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java index cb8d67a..358976f 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/conference/ConferencePeerPanel.java @@ -761,18 +761,14 @@ public class ConferencePeerPanel implements SoundLevelListener { /** - * Delivers <tt>SoundLevelChangeEvent</tt>s on stream sound level change. + * Updates the sound level bar upon stream sound level changes. * - * @param evt the notification event containing the list of changes. + * {@inheritDoc} */ - public void soundLevelChanged(SoundLevelChangeEvent evt) + public void soundLevelChanged(Object source, int level) { - Object evtSource = evt.getSource(); - - if (evtSource.equals(callPeer)) - { - updateSoundBar(evt.getLevel()); - } + if (source.equals(callPeer)) + updateSoundBar(level); } } diff --git a/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java index d9bf2b8..b5ce238 100644 --- a/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java @@ -373,10 +373,10 @@ public class AudioMediaStreamImpl * method is meant for use primarily by the transform engine handling * incoming RTP packets (currently <tt>CsrcTransformEngine</tt>). * - * @param audioLevels a bi-dimensional array mapping CSRC IDs to audio - * levels. + * @param audioLevels a array mapping CSRC IDs to audio levels in + * consecutive elements. */ - public void fireConferenceAudioLevelEvent(final long[][] audioLevels) + public void fireConferenceAudioLevelEvent(final long[] audioLevels) { CsrcAudioLevelListener csrcAudioLevelListener = this.csrcAudioLevelListener; diff --git a/src/net/java/sip/communicator/impl/neomedia/RawPacket.java b/src/net/java/sip/communicator/impl/neomedia/RawPacket.java index 0106f94..55e01fd 100644 --- a/src/net/java/sip/communicator/impl/neomedia/RawPacket.java +++ b/src/net/java/sip/communicator/impl/neomedia/RawPacket.java @@ -696,31 +696,43 @@ public class RawPacket }
/**
- * Returns a bi-dimensional byte array containing a map binding CSRC IDs to
- * audio levels as reported by the remote party that sent this packet.
+ * Returns a map binding CSRC IDs to audio levels as reported by the remote
+ * party that sent this packet.
*
* @param csrcExtID the ID of the extension that's transporting csrc audio
* levels in the session that this <tt>RawPacket</tt> belongs to.
*
- * @return a bi-dimensional byte array containing a map binding CSRC IDs to
- * audio levels as reported by the remote party that sent this packet.
+ * @return an array representing a map binding CSRC IDs to audio levels as
+ * reported by the remote party that sent this packet. The entries of the
+ * map are contained in consecutive elements of the returned array where
+ * elements at even indices stand for CSRC IDs and elements at odd indices
+ * stand for the associated audio levels
*/
- public long[][] extractCsrcLevels(byte csrcExtID)
+ public long[] extractCsrcLevels(byte csrcExtID)
{
- if( !getExtensionBit() || getExtensionLength() == 0
- || getCsrcCount() == 0)
+ if (!getExtensionBit()
+ || (getExtensionLength() == 0)
+ || (getCsrcCount() == 0))
return null;
int csrcCount = getCsrcCount();
- long[][] csrcLevels = new long[csrcCount][2];
+ /*
+ * XXX The guideline which is also supported by Google and recommended
+ * for Android is that single-dimensional arrays should be preferred to
+ * multi-dimensional arrays in Java because the former take less space
+ * than the latter and are thus more efficient in terms of memory and
+ * garbage collection.
+ */
+ long[] csrcLevels = new long[csrcCount * 2];
//first extract the csrc IDs
int csrcStartIndex = offset + FIXED_HEADER_SIZE;
for (int i = 0; i < csrcCount; i++)
{
- csrcLevels[i][0] = readInt(csrcStartIndex);
+ int csrcLevelsIndex = 2 * i;
- csrcLevels[i][1] = getCsrcLevel(i, csrcExtID);
+ csrcLevels[csrcLevelsIndex] = readInt(csrcStartIndex);
+ csrcLevels[csrcLevelsIndex + 1] = getCsrcLevel(i, csrcExtID);
csrcStartIndex += 4;
}
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 80ef4e7..e17a976 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 @@ -108,7 +108,7 @@ public class CsrcTransformEngine if (csrcAudioLevelExtID > 0 && audioLevelDirection.allowsReceiving()) { //extract the audio levels and send them to the dispatcher. - long[][] levels = pkt.extractCsrcLevels(csrcAudioLevelExtID); + long[] levels = pkt.extractCsrcLevels(csrcAudioLevelExtID); if(levels != null) { @@ -269,7 +269,7 @@ public class CsrcTransformEngine private boolean isRunning = false; /** The levels that we last received from the reverseTransform thread*/ - private long[][] lastReportedLevels = null; + private long[] lastReportedLevels = null; /** * Waits for new levels to be reported via the <tt>addLevels()</tt> @@ -284,7 +284,7 @@ public class CsrcTransformEngine if(!(mediaStream instanceof AudioMediaStreamImpl)) return; - long[][] temp = null; + long[] temp = null; while(isRunning) { @@ -323,7 +323,7 @@ public class CsrcTransformEngine * * @param levels the levels that we'd like to queue for processing. */ - public void addLevels(long[][] levels) + public void addLevels(long[] levels) { synchronized(this) { diff --git a/src/net/java/sip/communicator/impl/protocol/gibberish/CallPeerGibberishImpl.java b/src/net/java/sip/communicator/impl/protocol/gibberish/CallPeerGibberishImpl.java index 00e34e4..097e84a 100644 --- a/src/net/java/sip/communicator/impl/protocol/gibberish/CallPeerGibberishImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/gibberish/CallPeerGibberishImpl.java @@ -87,13 +87,13 @@ public class CallPeerGibberishImpl public void run() { fireConferenceMembersSoundLevelEvent( - new HashMap<ConferenceMember, Integer>() + new HashMap<ConferenceMember, Integer>() { { put(member1, new Integer(random.nextInt(255))); put(member2, new Integer(random.nextInt(255))); } - }); + }); } }, 500, 100); } @@ -250,21 +250,17 @@ public class CallPeerGibberishImpl */ void fireStreamSoundLevelEvent(int level) { - SoundLevelChangeEvent event - = new SoundLevelChangeEvent(this, level); - - SoundLevelListener[] ls; + SoundLevelListener[] listeners; synchronized(soundLevelListeners) { - ls = soundLevelListeners.toArray( - new SoundLevelListener[soundLevelListeners.size()]); + listeners + = soundLevelListeners.toArray( + new SoundLevelListener[soundLevelListeners.size()]); } - for (SoundLevelListener listener : ls) - { - listener.soundLevelChanged(event); - } + for (SoundLevelListener listener : listeners) + listener.soundLevelChanged(this, level); } /** diff --git a/src/net/java/sip/communicator/service/neomedia/event/CsrcAudioLevelListener.java b/src/net/java/sip/communicator/service/neomedia/event/CsrcAudioLevelListener.java index 084ed26..8a3926a 100644 --- a/src/net/java/sip/communicator/service/neomedia/event/CsrcAudioLevelListener.java +++ b/src/net/java/sip/communicator/service/neomedia/event/CsrcAudioLevelListener.java @@ -23,8 +23,8 @@ public interface CsrcAudioLevelListener * taking part in a conference call. * * @param audioLevels the new set of levels for the various contributing - * sources in the conference call. + * sources in the conference call */ - public void audioLevelsReceived(final long[][] audioLevels); + public void audioLevelsReceived(final long[] audioLevels); } diff --git a/src/net/java/sip/communicator/service/protocol/event/SoundLevelListener.java b/src/net/java/sip/communicator/service/protocol/event/SoundLevelListener.java index c48c069..3fde4a2 100644 --- a/src/net/java/sip/communicator/service/protocol/event/SoundLevelListener.java +++ b/src/net/java/sip/communicator/service/protocol/event/SoundLevelListener.java @@ -26,6 +26,7 @@ import java.util.*; * <tt>ConferenceMember</tt>s. * * @author Yana Stamcheva + * @author Lyubomir Marinov */ public interface SoundLevelListener extends EventListener @@ -36,8 +37,18 @@ public interface SoundLevelListener * from a given <tt>CallPeer</tt>. In the case of conference focus the audio * stream level would be the total level including all * <tt>ConferenceMember</tt>s levels. + * <p> + * In contrast to the conventions of Java and Jitsi, + * <tt>SoundLevelListener</tt> does not fire an <tt>EventObject</tt> (i.e. + * <tt>SoundLevelChangeEvent</tt>) in order to try to reduce the number of + * allocations related to sound level changes since their number is expected + * to be very large. + * </p> * - * @param event the <tt>StreamSoundLevelEvent</tt> containing the new level + * @param source the <tt>Object</tt> which is the source of the sound level + * change event being fired to this <tt>SoundLevelListener</tt> + * @param level the sound level to notify this <tt>SoundLevelListener</tt> + * about */ - public void soundLevelChanged(SoundLevelChangeEvent event); + public void soundLevelChanged(Object source, int level); } diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java index 2d04471..326c7e9 100644 --- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java +++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java @@ -67,10 +67,22 @@ public abstract class MediaAwareCall< protected final U parentOpSet; /** - * Holds listeners registered for level changes in local audio. + * The list of <tt>SoundLevelListener</tt>s interested in level changes of + * local audio. + * <p> + * It is implemented as a copy-on-write storage because the number of + * additions and removals of <tt>SoundLevelListener</tt>s is expected to be + * far smaller than the number of audio level changes. The access to it is + * to be synchronized using {@link #localUserAudioLevelListenersSyncRoot}. + * </p> + */ + private List<SoundLevelListener> localUserAudioLevelListeners; + + /** + * The <tt>Object</tt> to synchronize the access to + * {@link #localUserAudioLevelListeners}. */ - private final List<SoundLevelListener> localUserAudioLevelListeners - = new ArrayList<SoundLevelListener>(); + private final Object localUserAudioLevelListenersSyncRoot = new Object(); /** * The indicator which determines whether this <tt>Call</tt> is set @@ -137,7 +149,7 @@ public abstract class MediaAwareCall< callPeer.addCallPeerListener(this); - synchronized(localUserAudioLevelListeners) + synchronized(localUserAudioLevelListenersSyncRoot) { // if there's someone listening for audio level events then they'd // also like to know about the new peer. @@ -172,7 +184,7 @@ public abstract class MediaAwareCall< getCallPeersVector().remove(callPeer); callPeer.removeCallPeerListener(this); - synchronized(localUserAudioLevelListeners) + synchronized (localUserAudioLevelListenersSyncRoot) { // remove sound level listeners from the peer callPeer.getMediaHandler().setLocalUserAudioLevelListener(null); @@ -387,27 +399,37 @@ public abstract class MediaAwareCall< */ public void addLocalUserSoundLevelListener(SoundLevelListener l) { - synchronized(localUserAudioLevelListeners) + synchronized (localUserAudioLevelListenersSyncRoot) { - if (localUserAudioLevelListeners.isEmpty()) + if ((localUserAudioLevelListeners == null) + || localUserAudioLevelListeners.isEmpty()) { //if this is the first listener that's being registered with //us, we also need to register ourselves as an audio level //listener with the media handler. we do this so that audio //level would only be calculated if anyone is interested in //receiving them. - Iterator<T> cps = getCallPeers(); + Iterator<T> callPeerIter = getCallPeers(); - while (cps.hasNext()) + while (callPeerIter.hasNext()) { - T callPeer = cps.next(); - - callPeer.getMediaHandler() + callPeerIter.next() + .getMediaHandler() .setLocalUserAudioLevelListener( - localAudioLevelDelegator); + localAudioLevelDelegator); } } + /* + * Implement localUserAudioLevelListeners as a copy-on-write storage + * so that iterators over it can iterate without + * ConcurrentModificationExceptions. + */ + localUserAudioLevelListeners + = (localUserAudioLevelListeners == null) + ? new ArrayList<SoundLevelListener>() + : new ArrayList<SoundLevelListener>( + localUserAudioLevelListeners); localUserAudioLevelListeners.add(l); } } @@ -423,23 +445,36 @@ public abstract class MediaAwareCall< */ public void removeLocalUserSoundLevelListener(SoundLevelListener l) { - synchronized(localUserAudioLevelListeners) + synchronized (localUserAudioLevelListenersSyncRoot) { - localUserAudioLevelListeners.remove(l); + /* + * Implement localUserAudioLevelListeners as a copy-on-write storage + * so that iterators over it can iterate over it without + * ConcurrentModificationExceptions. + */ + if (localUserAudioLevelListeners != null) + { + localUserAudioLevelListeners + = new ArrayList<SoundLevelListener>( + localUserAudioLevelListeners); + if (localUserAudioLevelListeners.remove(l) + && localUserAudioLevelListeners.isEmpty()) + localUserAudioLevelListeners = null; + } - if (localUserAudioLevelListeners.isEmpty()) + if ((localUserAudioLevelListeners == null) + || localUserAudioLevelListeners.isEmpty()) { //if this was the last listener that was registered with us then //no long need to have a delegator registered with the call //peer media handlers. We therefore remove it so that audio //level calculations would be ceased. - Iterator<T> cps = getCallPeers(); + Iterator<T> callPeerIter = getCallPeers(); - while (cps.hasNext()) + while (callPeerIter.hasNext()) { - T callPeer = cps.next(); - - callPeer.getMediaHandler() + callPeerIter.next() + .getMediaHandler() .setLocalUserAudioLevelListener(null); } } @@ -455,13 +490,34 @@ public abstract class MediaAwareCall< */ private void fireLocalUserAudioLevelChangeEvent(int newLevel) { - SoundLevelChangeEvent evt - = new SoundLevelChangeEvent(this, newLevel); + List<SoundLevelListener> localUserAudioLevelListeners; + + synchronized (localUserAudioLevelListenersSyncRoot) + { + /* + * Since the localUserAudioLevelListeners field of this + * MediaAwareCall is implemented as a copy-on-write storage, just + * get a reference to it and it should be safe to iterate over it + * without ConcurrentModificationExceptions. + */ + localUserAudioLevelListeners = this.localUserAudioLevelListeners; + } - synchronized( localUserAudioLevelListeners ) + if (localUserAudioLevelListeners != null) { - for(SoundLevelListener listener : localUserAudioLevelListeners) - listener.soundLevelChanged(evt); + /* + * Iterate over localUserAudioLevelListeners using an index rather + * than an Iterator in order to try to reduce the number of + * allocations (as the number of audio level changes is expected to + * be very large). + */ + int localUserAudioLevelListenerCount + = localUserAudioLevelListeners.size(); + + for(int i = 0; i < localUserAudioLevelListenerCount; i++) + localUserAudioLevelListeners.get(i).soundLevelChanged( + this, + newLevel); } } diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java index a43fab4..5dcfd29 100644 --- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java +++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallPeer.java @@ -73,11 +73,22 @@ public abstract class MediaAwareCallPeer = new LinkedList<PropertyChangeListener>(); /** - * Holds listeners registered for level changes in the audio we are getting - * from the remote participant. + * The list of <tt>SoundLevelListener</tt>s interested in level changes in + * the audio we are getting from the remote peer. + * <p> + * It is implemented as a copy-on-write storage because the number of + * additions and removals of <tt>SoundLevelListener</tt>s is expected to be + * far smaller than the number of audio level changes. The access to it is + * to be synchronized using {@link #streamAudioLevelListenersSyncRoot}. + * </p> */ - private final List<SoundLevelListener> streamAudioLevelListeners - = new ArrayList<SoundLevelListener>(); + private List<SoundLevelListener> streamAudioLevelListeners; + + /** + * The <tt>Object</tt> to synchronize the access to + * {@link #streamAudioLevelListeners}. + */ + private final Object streamAudioLevelListenersSyncRoot = new Object(); /** * Holds listeners registered for level changes in the audio of participants @@ -522,9 +533,10 @@ public abstract class MediaAwareCallPeer */ public void addStreamSoundLevelListener(SoundLevelListener listener) { - synchronized (streamAudioLevelListeners) + synchronized (streamAudioLevelListenersSyncRoot) { - if (streamAudioLevelListeners.size() == 0) + if ((streamAudioLevelListeners == null) + || streamAudioLevelListeners.isEmpty()) { // if this is the first listener that's being registered with // us, we also need to register ourselves as an audio level @@ -534,6 +546,16 @@ public abstract class MediaAwareCallPeer getMediaHandler().setStreamAudioLevelListener(this); } + /* + * Implement streamAudioLevelListeners as a copy-on-write storage so + * that iterators over it can iterate without + * ConcurrentModificationExceptions. + */ + streamAudioLevelListeners + = (streamAudioLevelListeners == null) + ? new ArrayList<SoundLevelListener>() + : new ArrayList<SoundLevelListener>( + streamAudioLevelListeners); streamAudioLevelListeners.add(listener); } } @@ -547,11 +569,25 @@ public abstract class MediaAwareCallPeer */ public void removeStreamSoundLevelListener(SoundLevelListener listener) { - synchronized (streamAudioLevelListeners) + synchronized (streamAudioLevelListenersSyncRoot) { - streamAudioLevelListeners.remove(listener); + /* + * Implement streamAudioLevelListeners as a copy-on-write storage so + * that iterators over it can iterate over it without + * ConcurrentModificationExceptions. + */ + if (streamAudioLevelListeners != null) + { + streamAudioLevelListeners + = new ArrayList<SoundLevelListener>( + streamAudioLevelListeners); + if (streamAudioLevelListeners.remove(listener) + && streamAudioLevelListeners.isEmpty()) + streamAudioLevelListeners = null; + } - if (streamAudioLevelListeners.size() == 0) + if ((streamAudioLevelListeners == null) + || streamAudioLevelListeners.isEmpty()) { // if this was the last listener then we also need to remove // ourselves as an audio level so that audio levels would only @@ -621,7 +657,7 @@ public abstract class MediaAwareCallPeer * @param audioLevels the levels that we need to dispatch to all registered * <tt>ConferenceMemberSoundLevelListeners</tt>. */ - public void audioLevelsReceived(long[][] audioLevels) + public void audioLevelsReceived(long[] audioLevels) { if (getConferenceMemberCount() == 0) return; @@ -629,14 +665,14 @@ public abstract class MediaAwareCallPeer Map<ConferenceMember, Integer> levelsMap = new HashMap<ConferenceMember, Integer>(); - for (int i = 0; i < audioLevels.length; i++) + for (int i = 0; i < audioLevels.length; i += 2) { - ConferenceMember mmbr = findConferenceMember(audioLevels[i][0]); + ConferenceMember mmbr = findConferenceMember(audioLevels[i]); if (mmbr == null) continue; else - levelsMap.put(mmbr, (int)audioLevels[i][1]); + levelsMap.put(mmbr, (int)audioLevels[i + 1]); } ConferenceMembersSoundLevelEvent evt @@ -804,32 +840,49 @@ public abstract class MediaAwareCallPeer * pass the sound levels measured on the stream so we can see * the stream activity of the call. */ - if (isConferenceFocus() && (getConferenceMemberCount() > 0) - && (getConferenceMemberCount() < 3)) + int conferenceMemberCount; + + if (isConferenceFocus() + && ((conferenceMemberCount = getConferenceMemberCount()) > 0) + && (conferenceMemberCount < 3)) { long audioRemoteSSRC = getMediaHandler().getAudioRemoteSSRC(); if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN) { - long[][] audioLevels = new long[1][2]; - audioLevels[0][0] = audioRemoteSSRC; - audioLevels[0][1] = newLevel; - - audioLevelsReceived(audioLevels); + audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel }); return; } } - synchronized( streamAudioLevelListeners ) + List<SoundLevelListener> streamAudioLevelListeners; + + synchronized (streamAudioLevelListenersSyncRoot) { - if (streamAudioLevelListeners.size() > 0) - { - SoundLevelChangeEvent evt - = new SoundLevelChangeEvent(this, newLevel); + /* + * Since the streamAudioLevelListeners field of this + * MediaAwareCallPeer is implemented as a copy-on-write storage, + * just get a reference to it and it should be safe to iterate over it + * without ConcurrentModificationExceptions. + */ + streamAudioLevelListeners = this.streamAudioLevelListeners; + } - for(SoundLevelListener listener : streamAudioLevelListeners) - listener.soundLevelChanged(evt); - } + if (streamAudioLevelListeners != null) + { + /* + * Iterate over streamAudioLevelListeners using an index rather than + * an Iterator in order to try to reduce the number of allocations + * (as the number of audio level changes is expected to be very + * large). + */ + int streamAudioLevelListenerCount + = streamAudioLevelListeners.size(); + + for(int i = 0; i < streamAudioLevelListenerCount; i++) + streamAudioLevelListeners.get(i).soundLevelChanged( + this, + newLevel); } } |