@@ -160,27 +160,47 @@ impl DtmfSender {
160160
161161/// Receive-side de-duplicator. RFC 2833 sends each event multiple times; we
162162/// only report each distinct event once.
163+ ///
164+ /// Dedup uses two signals:
165+ /// 1. End-of-event marker — when the burst terminates, clear last_event
166+ /// so the next body packet (even of the same digit) reports.
167+ /// 2. Large RTP-timestamp gap — all packets of one RFC 2833 burst
168+ /// share (roughly) the same ts. A jump of several hundred ticks
169+ /// signals a new press even when every end-of-event packet was
170+ /// dropped by the jitter buffer. The threshold is loose enough to
171+ /// tolerate test fixtures that don't quite follow RFC 2833's
172+ /// "constant ts within a burst" rule.
163173pub struct DtmfReceiver {
164174 last_sn : Option < u16 > ,
165175 last_event : Option < u8 > ,
176+ last_event_ts : Option < u32 > ,
166177}
167178
179+ /// Timestamp delta (in RTP ticks @ 8 kHz = 1 ms) beyond which we treat
180+ /// two packets as belonging to different bursts. An in-burst ts is
181+ /// supposed to be constant; one whole RFC 2833 burst lasts ~220 ms, so
182+ /// a 3200-tick (400 ms) gap safely distinguishes bursts without
183+ /// false-positives on test fixtures that nudge ts within a press.
184+ const NEW_BURST_TS_DELTA : u32 = 3200 ;
185+
168186impl Default for DtmfReceiver {
169187 fn default ( ) -> Self { Self :: new ( ) }
170188}
171189
172190impl DtmfReceiver {
173191 pub fn new ( ) -> Self {
174- Self { last_sn : None , last_event : None }
192+ Self { last_sn : None , last_event : None , last_event_ts : None }
175193 }
176194
177- /// Feed an RFC 2833 payload with its RTP sequence number. Returns the
178- /// digit char on the first packet of a new distinct event; duplicate
179- /// packets within the same burst (body repeats + end-of-event) return
180- /// None. A repeated digit after an end-marker counts as new — three
181- /// "1" presses in a row produce three reports, matching the PCAP
182- /// replay test in test/interface/projectrtpdtmf.js.
183- pub fn feed ( & mut self , sn : u16 , payload : & [ u8 ] ) -> Option < char > {
195+ /// Feed an RFC 2833 payload with its RTP sequence number and
196+ /// timestamp. Returns the digit char on the first packet of a new
197+ /// distinct event; duplicate packets within the same burst (body
198+ /// repeats + end-of-event) return None. A repeated digit after an
199+ /// end-marker — or after a large timestamp gap even without the
200+ /// end-marker — counts as new, so three "1" presses in a row
201+ /// produce three reports whether or not the EOE packets survive
202+ /// the jitter buffer.
203+ pub fn feed ( & mut self , sn : u16 , ts : u32 , payload : & [ u8 ] ) -> Option < char > {
184204 let ev = decode_event ( payload) ?;
185205 let advanced = match self . last_sn {
186206 Some ( last) => sn. wrapping_sub ( last) < 0x8000 && sn != last,
@@ -194,13 +214,24 @@ impl DtmfReceiver {
194214 // of any code (including the same digit re-pressed) starts a
195215 // new burst and gets reported.
196216 self . last_event = None ;
217+ self . last_event_ts = None ;
197218 return None ;
198219 }
199220
200- if self . last_event == Some ( ev. event ) {
201- return None ;
202- }
221+ // New-burst detection:
222+ // - event code changed → different digit pressed
223+ // - ts jumped far beyond the normal in-burst spread → same
224+ // digit re-pressed, EOE was lost in jitter
225+ let same_event = self . last_event == Some ( ev. event ) ;
226+ let big_ts_gap = match self . last_event_ts {
227+ Some ( last_ts) => ts. wrapping_sub ( last_ts) >= NEW_BURST_TS_DELTA ,
228+ None => false ,
229+ } ;
230+ let same_burst = same_event && !big_ts_gap;
231+ if same_burst { return None ; }
232+
203233 self . last_event = Some ( ev. event ) ;
234+ self . last_event_ts = Some ( ts) ;
204235 event_to_char ( ev. event )
205236 }
206237}
@@ -258,14 +289,36 @@ mod tests {
258289 let mut r = DtmfReceiver :: new ( ) ;
259290 let p = encode_event ( 3 , false , 10 , 160 ) ;
260291 // Multiple duplicates of the same burst — only one report.
261- assert_eq ! ( r. feed( 100 , & p) , Some ( '3' ) ) ;
262- assert_eq ! ( r. feed( 101 , & p) , None ) ;
263- assert_eq ! ( r. feed( 102 , & p) , None ) ;
292+ assert_eq ! ( r. feed( 100 , 1000 , & p) , Some ( '3' ) ) ;
293+ assert_eq ! ( r. feed( 101 , 1000 , & p) , None ) ;
294+ assert_eq ! ( r. feed( 102 , 1000 , & p) , None ) ;
264295 // End marker observed — doesn't re-report same digit.
265296 let pe = encode_event ( 3 , true , 10 , 480 ) ;
266- assert_eq ! ( r. feed( 103 , & pe) , None ) ;
297+ assert_eq ! ( r. feed( 103 , 1000 , & pe) , None ) ;
267298 // New digit — reported.
268299 let p2 = encode_event ( 7 , false , 10 , 160 ) ;
269- assert_eq ! ( r. feed( 104 , & p2) , Some ( '7' ) ) ;
300+ assert_eq ! ( r. feed( 104 , 2000 , & p2) , Some ( '7' ) ) ;
301+ }
302+
303+ #[ test]
304+ fn receiver_handles_lost_end_of_event_via_timestamp ( ) {
305+ // Regression for the PCAP-replay test 2 failure: when all
306+ // end-of-event packets for one burst are dropped by the jitter
307+ // buffer, the next press of the same digit must still report
308+ // — detected via the RTP timestamp changing between bursts.
309+ let mut r = DtmfReceiver :: new ( ) ;
310+ let p = encode_event ( 1 , false , 10 , 160 ) ;
311+
312+ // First press — body packets at ts=5000.
313+ assert_eq ! ( r. feed( 100 , 5000 , & p) , Some ( '1' ) ) ;
314+ assert_eq ! ( r. feed( 101 , 5000 , & p) , None ) ;
315+ assert_eq ! ( r. feed( 102 , 5000 , & p) , None ) ;
316+ // EOE packets are lost (never fed).
317+
318+ // Second press — body at ts=10000. Must report despite EOE loss.
319+ assert_eq ! ( r. feed( 110 , 10000 , & p) , Some ( '1' ) ) ;
320+
321+ // Third press — body at ts=15000. Same digit again.
322+ assert_eq ! ( r. feed( 120 , 15000 , & p) , Some ( '1' ) ) ;
270323 }
271324}
0 commit comments