Skip to content

Commit 4ab8d48

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 d1f44f8 commit 4ab8d48

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

@@ -26,6 +27,7 @@
2627
import org.jitsi.service.neomedia.codec.*;
2728
import org.jitsi.service.neomedia.format.*;
2829
import org.jitsi.utils.*;
30+
import org.jitsi.utils.logging.*;
2931

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

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

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

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

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

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

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

591630
/**
@@ -594,13 +633,8 @@ public void addTonePacket(DtmfRawPacket p)
594633
*/
595634
public void stop()
596635
{
597-
synchronized(this)
598-
{
599-
this.lastReceivedTone = null;
600-
isRunning = false;
601-
602-
notifyAll();
603-
}
636+
isRunning = false;
637+
queue.clear();
604638
}
605639

606640
/**

0 commit comments

Comments
 (0)