Skip to main content

audio_codec/
telephone_event.rs

1//! RFC 4733 telephone-event (DTMF) payload helpers.
2//!
3//! The Encoder/Decoder move no audio samples — telephone-event is an event
4//! stream, not a waveform; they exist so `CodecType::TelephoneEvent` slots
5//! into codec tables.
6
7use super::{CodecError, Decoder, Encoder, Sample};
8
9pub const EVENT_CLOCK: u32 = 8000;
10pub const DEFAULT_EVENT_MS: u32 = 100;
11const INTERIM_STEP_MS: u64 = 50;
12const DEFAULT_VOLUME: u8 = 10;
13
14pub struct TelephoneEventDecoder {}
15
16impl TelephoneEventDecoder {
17    pub fn new() -> Self {
18        Self {}
19    }
20}
21
22impl Default for TelephoneEventDecoder {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl Decoder for TelephoneEventDecoder {
29    fn decode_into(&mut self, _data: &[u8], _out: &mut [Sample]) -> Result<usize, CodecError> {
30        Ok(0)
31    }
32
33    fn max_decode_samples(&self, _n_bytes: usize) -> usize {
34        0
35    }
36
37    fn sample_rate(&self) -> u32 {
38        8000
39    }
40
41    fn channels(&self) -> u16 {
42        1
43    }
44}
45
46pub struct TelephoneEventEncoder {}
47
48impl TelephoneEventEncoder {
49    pub fn new() -> Self {
50        Self {}
51    }
52}
53
54impl Default for TelephoneEventEncoder {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60impl Encoder for TelephoneEventEncoder {
61    fn encode_into(&mut self, _samples: &[Sample], _out: &mut [u8]) -> Result<usize, CodecError> {
62        Ok(0)
63    }
64
65    fn max_encode_bytes(&self, _n_samples: usize) -> usize {
66        0
67    }
68
69    fn sample_rate(&self) -> u32 {
70        8000
71    }
72
73    fn channels(&self) -> u16 {
74        1
75    }
76}
77
78/// Character → RFC 4733 event code (`0-9`, `*`=10, `#`=11, `A-D`=12-15).
79pub fn digit_to_event(c: char) -> Option<u8> {
80    match c {
81        '0'..='9' => Some(c as u8 - b'0'),
82        '*' => Some(10),
83        '#' => Some(11),
84        'A'..='D' | 'a'..='d' => Some(12 + (c.to_ascii_uppercase() as u8 - b'A')),
85        _ => None,
86    }
87}
88
89/// RFC 4733 event code → character.
90pub fn event_to_digit(event: u8) -> char {
91    match event {
92        0..=9 => (b'0' + event) as char,
93        10 => '*',
94        11 => '#',
95        12..=15 => (b'A' + event - 12) as char,
96        _ => '?',
97    }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub struct DtmfEvent {
102    pub event: u8,
103    pub end: bool,
104    pub volume: u8,
105    pub duration: u16,
106}
107
108/// Parse a telephone-event payload; with multiple 4-byte blocks the last wins.
109pub fn parse_telephone_event(data: &[u8]) -> Option<DtmfEvent> {
110    if data.len() < 4 || data.len() % 4 != 0 {
111        return None;
112    }
113    let off = (data.len() / 4 - 1) * 4;
114    Some(DtmfEvent {
115        event: data[off],
116        end: (data[off + 1] & 0x80) != 0,
117        volume: data[off + 1] & 0x3F,
118        duration: ((data[off + 2] as u16) << 8) | (data[off + 3] as u16),
119    })
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct DtmfPacket {
124    pub payload: [u8; 4],
125    pub marker: bool,
126}
127
128/// `event(8) | E(1) R(1) volume(6) | duration(16)`.
129pub fn encode_event_payload(event: u8, end: bool, volume: u8, duration: u16) -> [u8; 4] {
130    let flags = (if end { 0x80 } else { 0x00 }) | (volume & 0x3F);
131    [event, flags, (duration >> 8) as u8, (duration & 0xFF) as u8]
132}
133
134/// Packet sequence for one digit: interims + End ×3, marker on the first.
135/// `duration_ms`/`clock_rate` are untrusted — clamped to u16::MAX wire ticks.
136pub fn dtmf_sequence(digit: u8, duration_ms: u32, clock_rate: u32) -> alloc::vec::Vec<DtmfPacket> {
137    let total_samples = u64::from(duration_ms) * u64::from(clock_rate) / 1000;
138    let total_duration = total_samples.min(u64::from(u16::MAX)) as u16;
139    let step = ((INTERIM_STEP_MS * u64::from(clock_rate) / 1000).min(u64::from(u16::MAX))) as u16;
140
141    let mut packets = alloc::vec::Vec::new();
142    let mut first = true;
143    let mut d = step;
144    while d < total_duration {
145        packets.push(DtmfPacket {
146            payload: encode_event_payload(digit, false, DEFAULT_VOLUME, d),
147            marker: first,
148        });
149        first = false;
150        d = d.saturating_add(step);
151    }
152    for _ in 0..3 {
153        packets.push(DtmfPacket {
154            payload: encode_event_payload(digit, true, DEFAULT_VOLUME, total_duration),
155            marker: first,
156        });
157        first = false;
158    }
159    packets
160}
161
162pub fn dtmf_press(digit: u8) -> alloc::vec::Vec<DtmfPacket> {
163    dtmf_sequence(digit, DEFAULT_EVENT_MS, EVENT_CLOCK)
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn digit_mapping() {
172        assert_eq!(digit_to_event('0'), Some(0));
173        assert_eq!(digit_to_event('9'), Some(9));
174        assert_eq!(digit_to_event('*'), Some(10));
175        assert_eq!(digit_to_event('#'), Some(11));
176        assert_eq!(digit_to_event('A'), Some(12));
177        assert_eq!(digit_to_event('d'), Some(15));
178        assert_eq!(digit_to_event('x'), None);
179    }
180
181    #[test]
182    fn reverse_mapping() {
183        for c in "0123456789*#ABCD".chars() {
184            let e = digit_to_event(c).unwrap();
185            assert_eq!(event_to_digit(e), c);
186        }
187    }
188
189    #[test]
190    fn encode_payload_shape() {
191        assert_eq!(
192            encode_event_payload(5, true, 10, 1600),
193            [5, 0x8A, 0x06, 0x40]
194        );
195    }
196
197    #[test]
198    fn parse_event() {
199        let e = parse_telephone_event(&[5, 0x8A, 0x06, 0x40]).unwrap();
200        assert_eq!(e.event, 5);
201        assert!(e.end);
202        assert_eq!(e.volume, 10);
203        assert_eq!(e.duration, 1600);
204    }
205
206    #[test]
207    fn parse_takes_last_block() {
208        let e = parse_telephone_event(&[3, 0x0A, 0x01, 0x90, 5, 0x8A, 0x06, 0x40]).unwrap();
209        assert_eq!(e.event, 5);
210        assert!(e.end);
211    }
212
213    #[test]
214    fn parse_rejects_malformed() {
215        assert!(parse_telephone_event(&[1, 2, 3]).is_none());
216        assert!(parse_telephone_event(&[]).is_none());
217        assert!(parse_telephone_event(&[1, 2, 3, 4, 5]).is_none());
218    }
219
220    #[test]
221    fn sequence_shape() {
222        let packets = dtmf_sequence(5, 200, EVENT_CLOCK);
223        assert!(packets.len() >= 4);
224        assert!(packets[0].marker);
225        assert!(!packets[1..].iter().any(|p| p.marker));
226        assert!(packets.iter().all(|p| p.payload[0] == 5));
227        let last = packets.last().unwrap();
228        assert_eq!(last.payload[1] & 0x80, 0x80);
229        let dur = ((last.payload[2] as u16) << 8) | last.payload[3] as u16;
230        assert_eq!(dur, 1600);
231    }
232
233    #[test]
234    fn sequence_is_loss_resilient() {
235        let packets = dtmf_sequence(3, 100, EVENT_CLOCK);
236        assert_eq!(
237            packets.iter().filter(|p| p.payload[1] & 0x80 != 0).count(),
238            3
239        );
240    }
241
242    #[test]
243    fn roundtrip_encode_parse() {
244        for digit in 0u8..16 {
245            let press = dtmf_press(digit);
246            let e = parse_telephone_event(&press.last().unwrap().payload).unwrap();
247            assert_eq!(e.event, digit);
248            assert!(e.end);
249        }
250    }
251
252    #[test]
253    fn extreme_durations_do_not_overflow() {
254        let seq = dtmf_sequence(1, u32::MAX, EVENT_CLOCK);
255        let last = seq.last().unwrap();
256        let dur = ((last.payload[2] as u16) << 8) | last.payload[3] as u16;
257        assert_eq!(dur, u16::MAX);
258    }
259}