Skip to main content

active_call/media/
dtmf.rs

1use std::sync::atomic::{AtomicU8, AtomicU16};
2// DTMF events as per RFC 4733
3const DTMF_EVENT_0: u8 = 0;
4const DTMF_EVENT_1: u8 = 1;
5const DTMF_EVENT_2: u8 = 2;
6const DTMF_EVENT_3: u8 = 3;
7const DTMF_EVENT_4: u8 = 4;
8const DTMF_EVENT_5: u8 = 5;
9const DTMF_EVENT_6: u8 = 6;
10const DTMF_EVENT_7: u8 = 7;
11const DTMF_EVENT_8: u8 = 8;
12const DTMF_EVENT_9: u8 = 9;
13const DTMF_EVENT_STAR: u8 = 10;
14const DTMF_EVENT_POUND: u8 = 11;
15const DTMF_EVENT_A: u8 = 12;
16const DTMF_EVENT_B: u8 = 13;
17const DTMF_EVENT_C: u8 = 14;
18const DTMF_EVENT_D: u8 = 15;
19
20pub struct DtmfDetector {
21    // Track the last seen event to avoid repeated events
22    last_event: AtomicU8,
23    last_duration: AtomicU16,
24    pub refer: Option<bool>,
25    pub suppress_dtmf_forward: bool,
26}
27
28#[derive(Debug)]
29struct DtmfPayload {
30    event: u8, // 8bits
31    #[allow(dead_code)]
32    is_end: bool, // 1bit
33    _reserved: u8, // 1bits
34    _volume: u8, // 6bits
35    duration: u16, // 16bits
36}
37
38impl DtmfPayload {
39    fn parse(payload: &[u8]) -> Option<Self> {
40        if payload.len() < 4 {
41            return None;
42        }
43
44        let event = payload[0];
45        if event > DTMF_EVENT_D {
46            return None;
47        }
48
49        //     0                   1                   2                   3
50        //     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
51        //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
52        //    |     event     |E|R| volume    |          duration             |
53        //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
54        //                  Figure 1: Payload Format for Named Events
55
56        // Second byte: End bit (E) is the most significant bit (bit 7)
57        let is_end = (payload[1] & 0b1000_0000) != 0;
58        // Reserved bit (R) is the second most significant bit (bit 6)
59        let reserved = payload[1] & 0b0100_0000;
60        // Volume is the 6 least significant bits (0-5)
61        let volume = payload[1] & 0b0011_1111;
62
63        // Duration is a 16-bit value spanning bytes 2 and 3
64        let duration_high = payload[2] as u16;
65        let duration_low = payload[3] as u16;
66        let duration = (duration_high << 8) | duration_low;
67
68        Some(Self {
69            event,
70            is_end,
71            _reserved: reserved,
72            _volume: volume,
73            duration,
74        })
75    }
76}
77
78impl DtmfDetector {
79    pub fn new() -> Self {
80        Self {
81            last_event: AtomicU8::new(0xFF),
82            last_duration: AtomicU16::new(0),
83            refer: None,
84            suppress_dtmf_forward: false,
85        }
86    }
87
88    // Detect DTMF events from RTP payload as specified in RFC 4733
89    pub fn detect_rtp(&self, payload_type: u8, payload: &[u8]) -> Option<String> {
90        // RFC 4733 defines DTMF events with payload types 96-127 (dynamic)
91        // However, we'll be more lenient and just check if the payload has the right format
92        if payload.len() < 4 {
93            return None;
94        }
95
96        // Generally, telephone-event payload type is in dynamic range 96-127
97        if payload_type < 96 || payload_type > 127 {
98            return None;
99        }
100
101        // Parse the DTMF payload
102        let dtmf_payload = DtmfPayload::parse(payload)?;
103
104        // Get current duration
105        let current_event = dtmf_payload.event;
106        let current_duration = dtmf_payload.duration;
107        let last_event = self
108            .last_event
109            .swap(current_event, std::sync::atomic::Ordering::Relaxed);
110        let last_duration = self
111            .last_duration
112            .swap(current_duration, std::sync::atomic::Ordering::Relaxed);
113
114        if current_event == last_event && current_duration >= last_duration {
115            return None;
116        }
117
118        Some(
119            match dtmf_payload.event {
120                DTMF_EVENT_0 => "0",
121                DTMF_EVENT_1 => "1",
122                DTMF_EVENT_2 => "2",
123                DTMF_EVENT_3 => "3",
124                DTMF_EVENT_4 => "4",
125                DTMF_EVENT_5 => "5",
126                DTMF_EVENT_6 => "6",
127                DTMF_EVENT_7 => "7",
128                DTMF_EVENT_8 => "8",
129                DTMF_EVENT_9 => "9",
130                DTMF_EVENT_STAR => "*",
131                DTMF_EVENT_POUND => "#",
132                DTMF_EVENT_A => "A",
133                DTMF_EVENT_B => "B",
134                DTMF_EVENT_C => "C",
135                DTMF_EVENT_D => "D",
136                _ => return None, // Invalid event
137            }
138            .to_string(),
139        )
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_dtmf_payload_parse() {
149        // Valid DTMF payload for digit "1" with end bit set
150        // Event: 1, End bit: 1, Reserved: 0, Volume: 10, Duration: 160
151        let payload = [1, 0x8A, 0, 160]; // 0x8A = 10001010 (end=1, reserved=0, volume=10)
152
153        let dtmf = DtmfPayload::parse(&payload).unwrap();
154        assert_eq!(dtmf.event, 1);
155        assert_eq!(dtmf.is_end, true);
156        assert_eq!(dtmf._reserved, 0);
157        assert_eq!(dtmf._volume, 10); // 10 = 001010 binary
158        assert_eq!(dtmf.duration, 160);
159
160        // Test payload with end bit not set
161        let payload = [2, 0x00, 0, 160]; // 0x00 = 00000000 (end=0, reserved=0, volume=0)
162
163        let dtmf = DtmfPayload::parse(&payload).unwrap();
164        assert_eq!(dtmf.event, 2);
165        assert_eq!(dtmf.is_end, false);
166        assert_eq!(dtmf._volume, 0);
167
168        // Invalid event code
169        let payload = [20, 0x80, 10, 100]; // 20 > DTMF_EVENT_D
170        assert!(DtmfPayload::parse(&payload).is_none());
171
172        // Too short payload
173        let payload = [1, 0x80, 10]; // Missing duration byte
174        assert!(DtmfPayload::parse(&payload).is_none());
175
176        // Test the specific case [2, 138, 3, 32]
177        let payload = [2, 138, 3, 32];
178        // 138 decimal = 10001010 binary
179        // End bit (bit 7) = 1
180        // Reserved bit (bit 6) = 0
181        // Volume (bits 0-5) = 001010 = 10
182
183        let dtmf = DtmfPayload::parse(&payload).unwrap();
184        assert_eq!(dtmf.event, 2); // DTMF digit "2"
185        assert_eq!(dtmf.is_end, true); // End bit is set
186        assert_eq!(dtmf._reserved, 0); // Reserved bit is 0
187        assert_eq!(dtmf._volume, 10); // Volume is 10
188        assert_eq!(dtmf.duration, 800); // Duration is 3 * 256 + 32 = 800
189    }
190
191    #[test]
192    fn test_dtmf_detection() {
193        let detector = DtmfDetector::new();
194
195        // Test basic detection
196        {
197            // Valid DTMF payload for digit "5" with end bit set
198            let payload = [DTMF_EVENT_5, 0x80, 10, 100];
199
200            // Use payload_type 101 (typical for telephone-event)
201            let digit = detector.detect_rtp(101, &payload);
202            assert_eq!(digit, Some("5".to_string()));
203
204            // Should reject payloads with invalid payload type
205            let digit = detector.detect_rtp(0, &payload);
206            assert_eq!(digit, None);
207
208            // Should reject payloads with end bit not set
209            let payload = [DTMF_EVENT_5, 0x00, 10, 100];
210            let digit = detector.detect_rtp(101, &payload);
211            assert_eq!(digit, None);
212        }
213
214        // Test duplicate detection
215        {
216            let detector = DtmfDetector::new(); // Use a fresh detector
217
218            // First event
219            let payload1 = [DTMF_EVENT_5, 0x80, 0, 100]; // Duration 100
220            let digit1 = detector.detect_rtp(101, &payload1);
221            assert_eq!(digit1, Some("5".to_string()));
222
223            // Similar duration - should be rejected as duplicate
224            let payload2 = [DTMF_EVENT_5, 0x80, 0, 100]; // Duration 150 (similar)
225            let digit2 = detector.detect_rtp(101, &payload2);
226            assert_eq!(digit2, None);
227
228            // Different event - should be detected
229            let payload4 = [DTMF_EVENT_6, 0x80, 1, 8]; // Event 6 ("6" key)
230            let digit4 = detector.detect_rtp(101, &payload4);
231            assert_eq!(digit4, Some("6".to_string()));
232        }
233    }
234
235    #[test]
236    fn test_dtmf_digit_0_first_press() {
237        let detector = DtmfDetector::new();
238
239        let payload = [DTMF_EVENT_0, 0x80, 0, 160]; // Event 0, end bit set
240        let digit = detector.detect_rtp(101, &payload);
241        assert_eq!(
242            digit,
243            Some("0".to_string()),
244            "First press of digit '0' should be recognized"
245        );
246
247        // Duplicate should be ignored
248        let payload2 = [DTMF_EVENT_0, 0x80, 0, 160]; // Same event, same duration
249        let digit2 = detector.detect_rtp(101, &payload2);
250        assert_eq!(digit2, None, "Duplicate DTMF should be filtered out");
251
252        // New press with smaller duration (new key press started) should be detected
253        let payload3 = [DTMF_EVENT_0, 0x80, 0, 80]; // Smaller duration = new press
254        let digit3 = detector.detect_rtp(101, &payload3);
255        assert_eq!(
256            digit3,
257            Some("0".to_string()),
258            "Second press with smaller duration should be recognized as new press"
259        );
260    }
261}