diff --git a/src/org/jitsi/impl/neomedia/transform/dtmf/DtmfTransformEngine.java b/src/org/jitsi/impl/neomedia/transform/dtmf/DtmfTransformEngine.java
index 6c053d335..a6be989b8 100644
--- a/src/org/jitsi/impl/neomedia/transform/dtmf/DtmfTransformEngine.java
+++ b/src/org/jitsi/impl/neomedia/transform/dtmf/DtmfTransformEngine.java
@@ -16,6 +16,7 @@
package org.jitsi.impl.neomedia.transform.dtmf;
import java.util.*;
+import java.util.concurrent.*;
import javax.media.*;
@@ -25,6 +26,7 @@
import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.codec.*;
import org.jitsi.service.neomedia.format.*;
+import org.jitsi.util.*;
/**
* The class is responsible for sending DTMF tones in an RTP audio stream as
@@ -38,6 +40,13 @@ public class DtmfTransformEngine
extends SinglePacketTransformer
implements TransformEngine
{
+ /**
+ * The Logger used by the DtmfTransformEngine class and
+ * its instances for logging output.
+ */
+ private static final Logger logger
+ = Logger.getLogger(DtmfTransformEngine.class);
+
/**
* The AudioMediaStreamImpl that this transform engine was created
* by and that it's going to deliver DTMF packets for.
@@ -500,10 +509,11 @@ private void checkIfCurrentToneMustBeStopped()
}
/**
- * A simple thread that waits for new tones 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.
+ * A simple thread that waits for new tone events to be reported from
+ * incoming RTP packets and then delivers them as start / end events 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 DTMFDispatcher
implements Runnable
@@ -511,61 +521,96 @@ private class DTMFDispatcher
/** Indicates whether this thread is supposed to be running */
private boolean isRunning = false;
- /** The tone that we last received from the reverseTransform thread*/
- private DTMFRtpTone lastReceivedTone = null;
-
- /** The tone that we last received from the reverseTransform thread*/
+ /** The tone that we last reported to the listener */
private DTMFRtpTone lastReportedTone = null;
/**
- * Have we received end of the currently started tone.
+ * Timestamp the last reported tone start; used to identify starts
+ * when the marker bit is lost
+ */
+ private long lastReportedStart = 0;
+
+ /** Timestamp the last reported tone end; used to prevent duplicates */
+ private long lastReportedEnd = 0;
+
+ /**
+ * The maximum number of DTMF events that are queued for processing
*/
- private boolean toEnd = false;
+ private int QUEUE_SIZE = 100;
/**
- * Waits for new tone to be reported via the addTonePacket()
- * method and then delivers them to the AudioMediaStream that
- * we are associated with.
+ * The queue of DtmfRawPackets pending processing
+ */
+ private final LinkedBlockingQueue queue
+ = new LinkedBlockingQueue<>(QUEUE_SIZE);
+
+ /**
+ * Waits for new tone events to be reported via the
+ * addTonePacket() method and delivers them as start / end
+ * events to the AudioMediaStream that we are associated with.
*/
public void run()
{
isRunning = true;
- DTMFRtpTone temp = null;
-
while(isRunning)
{
- synchronized(this)
+ DtmfRawPacket pkt;
+ DTMFRtpTone tone;
+
+ try
+ {
+ pkt = queue.poll(500, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException iex)
{
- if(lastReceivedTone == null)
- {
- try
- {
- wait();
- }
- catch (InterruptedException ie) {}
- }
-
- temp = lastReceivedTone;
- // make lastReportedLevels to null
- // so we will wait for the next tone on next iteration
- lastReceivedTone = null;
+ continue;
}
- if(temp != null
- && ((lastReportedTone == null && !toEnd)
- || (lastReportedTone != null && toEnd)))
+ // The current thread has potentially waited.
+ if (!isRunning)
{
- //now notify our listener
- if (mediaStream != null)
- {
- mediaStream.fireDTMFEvent(temp, toEnd);
- if(toEnd)
- lastReportedTone = null;
- else
- lastReportedTone = temp;
- toEnd = false;
- }
+ break;
+ }
+
+ if (pkt == null)
+ {
+ continue;
+ }
+
+ tone = getToneFromPacket(pkt);
+
+ /*
+ * Detect DTMF tone start by looking for new tones
+ * It doesn't make sense to look at the 'marked' flag as those
+ * packets may be re-sent multiple times if they also contain
+ * the 'end' bit.
+ */
+ if (lastReportedTone == null
+ && pkt.getTimestamp() != lastReportedStart)
+ {
+ logger.info("Delivering DTMF tone start: " +
+ tone.getValue());
+ // now notify our listener
+ mediaStream.fireDTMFEvent(tone, false);
+ lastReportedStart = pkt.getTimestamp();
+ lastReportedTone = tone;
+ }
+
+ /*
+ * Detect DTMF tone end via the explicit 'end' flag.
+ * End packets are repeated for redundancy. To filter out
+ * duplicates, we track them by their timestamp.
+ * Start and end may be present in the same packet, typically
+ * for durations below 120 ms.
+ */
+ if (pkt.isEnd() && pkt.getTimestamp() != lastReportedEnd
+ && tone == lastReportedTone) {
+ logger.info("Delivering DTMF tone end: " + tone.getValue());
+ // now notify our listener
+ mediaStream.fireDTMFEvent(tone, true);
+ lastReportedEnd = pkt.getTimestamp();
+ lastReportedTone = null;
}
}
}
@@ -578,13 +623,7 @@ public void run()
*/
public void addTonePacket(DtmfRawPacket p)
{
- synchronized(this)
- {
- this.lastReceivedTone = getToneFromPacket(p);
- this.toEnd = p.isEnd();
-
- notifyAll();
- }
+ queue.offer(p);
}
/**
@@ -593,13 +632,8 @@ public void addTonePacket(DtmfRawPacket p)
*/
public void stop()
{
- synchronized(this)
- {
- this.lastReceivedTone = null;
- isRunning = false;
-
- notifyAll();
- }
+ isRunning = false;
+ queue.clear();
}
/**