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