Skip to content

Commit 9452bda

Browse files
committed
Improved interpretation of DTMF tones in DtmfTransformEngine
This change addresses a number of issues that led to incorrect interpretations of DTMF tones, especially tone sequences. It introduces a queue to mitigate previous thread-safety issues and handle more than one RTP packet at a time (a single tone is associated with at least three RTP packets). It also adds support to recognize shorter tones (<120 ms) that are typically represented by one self-contained RTP packet containing both start and end indications.
1 parent 5d74f78 commit 9452bda

File tree

1 file changed

+90
-56
lines changed

1 file changed

+90
-56
lines changed

src/org/jitsi/impl/neomedia/transform/dtmf/DtmfTransformEngine.java

Lines changed: 90 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.jitsi.impl.neomedia.transform.dtmf;
1717

1818
import java.util.*;
19+
import java.util.concurrent.*;
1920

2021
import javax.media.*;
2122

@@ -25,6 +26,7 @@
2526
import org.jitsi.service.neomedia.*;
2627
import org.jitsi.service.neomedia.codec.*;
2728
import org.jitsi.service.neomedia.format.*;
29+
import org.jitsi.util.*;
2830

2931
/**
3032
* The class is responsible for sending DTMF tones in an RTP audio stream as
@@ -38,6 +40,13 @@ public class DtmfTransformEngine
3840
extends SinglePacketTransformer
3941
implements TransformEngine
4042
{
43+
/**
44+
* The <tt>Logger</tt> used by the <tt>DtmfTransformEngine</tt> class and
45+
* its instances for logging output.
46+
*/
47+
private static final Logger logger
48+
= Logger.getLogger(DtmfTransformEngine.class);
49+
4150
/**
4251
* The <tt>AudioMediaStreamImpl</tt> that this transform engine was created
4352
* by and that it's going to deliver DTMF packets for.
@@ -500,72 +509,108 @@ private void checkIfCurrentToneMustBeStopped()
500509
}
501510

502511
/**
503-
* A simple thread that waits for new tones to be reported from incoming
504-
* RTP packets and then delivers them to the <tt>AudioMediaStream</tt>
505-
* associated with this engine. The reason we need to do this in a separate
506-
* thread is of course the time sensitive nature of incoming RTP packets.
512+
* A simple thread that waits for new tone events to be reported from
513+
* incoming RTP packets and then delivers them as start / end events to the
514+
* <tt>AudioMediaStream</tt> associated with this engine.
515+
* The reason we need to do this in a separate thread is of course the
516+
* time sensitive nature of incoming RTP packets.
507517
*/
508518
private class DTMFDispatcher
509519
implements Runnable
510520
{
511521
/** Indicates whether this thread is supposed to be running */
512522
private boolean isRunning = false;
513523

514-
/** The tone that we last received from the reverseTransform thread*/
515-
private DTMFRtpTone lastReceivedTone = null;
516-
517-
/** The tone that we last received from the reverseTransform thread*/
524+
/** The tone that we last reported to the listener */
518525
private DTMFRtpTone lastReportedTone = null;
519526

520527
/**
521-
* Have we received end of the currently started tone.
528+
* Timestamp the last reported tone start; used to identify starts
529+
* when the marker bit is lost
530+
*/
531+
private long lastReportedStart = 0;
532+
533+
/** Timestamp the last reported tone end; used to prevent duplicates */
534+
private long lastReportedEnd = 0;
535+
536+
/**
537+
* The maximum number of DTMF events that are queued for processing
522538
*/
523-
private boolean toEnd = false;
539+
private int QUEUE_SIZE = 100;
524540

525541
/**
526-
* Waits for new tone to be reported via the <tt>addTonePacket()</tt>
527-
* method and then delivers them to the <tt>AudioMediaStream</tt> that
528-
* we are associated with.
542+
* The queue of <tt>DtmfRawPacket</tt>s pending processing
543+
*/
544+
private final LinkedBlockingQueue<DtmfRawPacket> queue
545+
= new LinkedBlockingQueue<>(QUEUE_SIZE);
546+
547+
/**
548+
* Waits for new tone events to be reported via the
549+
* <tt>addTonePacket()</tt> method and delivers them as start / end
550+
* events to the <tt>AudioMediaStream</tt> that we are associated with.
529551
*/
530552
public void run()
531553
{
532554
isRunning = true;
533555

534-
DTMFRtpTone temp = null;
535-
536556
while(isRunning)
537557
{
538-
synchronized(this)
558+
DtmfRawPacket pkt;
559+
DTMFRtpTone tone;
560+
561+
try
562+
{
563+
pkt = queue.poll(500, TimeUnit.MILLISECONDS);
564+
}
565+
catch (InterruptedException iex)
539566
{
540-
if(lastReceivedTone == null)
541-
{
542-
try
543-
{
544-
wait();
545-
}
546-
catch (InterruptedException ie) {}
547-
}
548-
549-
temp = lastReceivedTone;
550-
// make lastReportedLevels to null
551-
// so we will wait for the next tone on next iteration
552-
lastReceivedTone = null;
567+
continue;
553568
}
554569

555-
if(temp != null
556-
&& ((lastReportedTone == null && !toEnd)
557-
|| (lastReportedTone != null && toEnd)))
570+
// The current thread has potentially waited.
571+
if (!isRunning)
558572
{
559-
//now notify our listener
560-
if (mediaStream != null)
561-
{
562-
mediaStream.fireDTMFEvent(temp, toEnd);
563-
if(toEnd)
564-
lastReportedTone = null;
565-
else
566-
lastReportedTone = temp;
567-
toEnd = false;
568-
}
573+
break;
574+
}
575+
576+
if (pkt == null)
577+
{
578+
continue;
579+
}
580+
581+
tone = getToneFromPacket(pkt);
582+
583+
/*
584+
* Detect DTMF tone start by looking for new tones
585+
* It doesn't make sense to look at the 'marked' flag as those
586+
* packets may be re-sent multiple times if they also contain
587+
* the 'end' bit.
588+
*/
589+
if (lastReportedTone == null
590+
&& pkt.getTimestamp() != lastReportedStart)
591+
{
592+
logger.info("Delivering DTMF tone start: " +
593+
tone.getValue());
594+
// now notify our listener
595+
mediaStream.fireDTMFEvent(tone, false);
596+
lastReportedStart = pkt.getTimestamp();
597+
lastReportedTone = tone;
598+
}
599+
600+
/*
601+
* Detect DTMF tone end via the explicit 'end' flag.
602+
* End packets are repeated for redundancy. To filter out
603+
* duplicates, we track them by their timestamp.
604+
* Start and end may be present in the same packet, typically
605+
* for durations below 120 ms.
606+
*/
607+
if (pkt.isEnd() && pkt.getTimestamp() != lastReportedEnd
608+
&& tone == lastReportedTone) {
609+
logger.info("Delivering DTMF tone end: " + tone.getValue());
610+
// now notify our listener
611+
mediaStream.fireDTMFEvent(tone, true);
612+
lastReportedEnd = pkt.getTimestamp();
613+
lastReportedTone = null;
569614
}
570615
}
571616
}
@@ -578,13 +623,7 @@ public void run()
578623
*/
579624
public void addTonePacket(DtmfRawPacket p)
580625
{
581-
synchronized(this)
582-
{
583-
this.lastReceivedTone = getToneFromPacket(p);
584-
this.toEnd = p.isEnd();
585-
586-
notifyAll();
587-
}
626+
queue.offer(p);
588627
}
589628

590629
/**
@@ -593,13 +632,8 @@ public void addTonePacket(DtmfRawPacket p)
593632
*/
594633
public void stop()
595634
{
596-
synchronized(this)
597-
{
598-
this.lastReceivedTone = null;
599-
isRunning = false;
600-
601-
notifyAll();
602-
}
635+
isRunning = false;
636+
queue.clear();
603637
}
604638

605639
/**

0 commit comments

Comments
 (0)