Skip to main content

rtp_engine/codec/
g711.rs

1//! G.711 codec implementations (μ-law and A-law).
2//!
3//! G.711 is the most widely supported VoIP codec, providing 64 kbps audio
4//! at 8kHz sample rate with 8-bit samples.
5
6use super::{AudioDecoder, AudioEncoder, CodecType};
7
8/// G.711 μ-law (PCMU) encoder.
9///
10/// Compresses 16-bit PCM to 8-bit μ-law samples (1:2 compression).
11#[derive(Debug, Default)]
12pub struct PcmuEncoder;
13
14impl PcmuEncoder {
15    /// Create a new PCMU encoder.
16    pub fn new() -> Self {
17        Self
18    }
19}
20
21impl AudioEncoder for PcmuEncoder {
22    fn encode(&mut self, pcm: &[i16], output: &mut Vec<u8>) -> usize {
23        output.reserve(pcm.len());
24        for &sample in pcm {
25            output.push(linear_to_ulaw(sample));
26        }
27        pcm.len()
28    }
29
30    fn payload_type(&self) -> u8 {
31        0
32    }
33
34    fn codec_type(&self) -> CodecType {
35        CodecType::Pcmu
36    }
37}
38
39/// G.711 μ-law (PCMU) decoder.
40///
41/// Expands 8-bit μ-law samples to 16-bit PCM.
42#[derive(Debug, Default)]
43pub struct PcmuDecoder;
44
45impl PcmuDecoder {
46    /// Create a new PCMU decoder.
47    pub fn new() -> Self {
48        Self
49    }
50}
51
52impl AudioDecoder for PcmuDecoder {
53    fn decode(&mut self, encoded: &[u8], output: &mut Vec<i16>) {
54        output.reserve(encoded.len());
55        for &b in encoded {
56            output.push(ulaw_to_linear(b));
57        }
58    }
59
60    fn codec_type(&self) -> CodecType {
61        CodecType::Pcmu
62    }
63}
64
65/// G.711 A-law (PCMA) encoder.
66///
67/// Compresses 16-bit PCM to 8-bit A-law samples (1:2 compression).
68#[derive(Debug, Default)]
69pub struct PcmaEncoder;
70
71impl PcmaEncoder {
72    /// Create a new PCMA encoder.
73    pub fn new() -> Self {
74        Self
75    }
76}
77
78impl AudioEncoder for PcmaEncoder {
79    fn encode(&mut self, pcm: &[i16], output: &mut Vec<u8>) -> usize {
80        output.reserve(pcm.len());
81        for &sample in pcm {
82            output.push(linear_to_alaw(sample));
83        }
84        pcm.len()
85    }
86
87    fn payload_type(&self) -> u8 {
88        8
89    }
90
91    fn codec_type(&self) -> CodecType {
92        CodecType::Pcma
93    }
94}
95
96/// G.711 A-law (PCMA) decoder.
97///
98/// Expands 8-bit A-law samples to 16-bit PCM.
99#[derive(Debug, Default)]
100pub struct PcmaDecoder;
101
102impl PcmaDecoder {
103    /// Create a new PCMA decoder.
104    pub fn new() -> Self {
105        Self
106    }
107}
108
109impl AudioDecoder for PcmaDecoder {
110    fn decode(&mut self, encoded: &[u8], output: &mut Vec<i16>) {
111        output.reserve(encoded.len());
112        for &b in encoded {
113            output.push(alaw_to_linear(b));
114        }
115    }
116
117    fn codec_type(&self) -> CodecType {
118        CodecType::Pcma
119    }
120}
121
122// --- G.711 μ-law implementation (ITU-T G.711) ---
123
124const ULAW_BIAS: i16 = 0x84;
125const ULAW_MAX: i16 = 0x7FFF;
126
127/// Convert a 16-bit linear PCM sample to 8-bit μ-law.
128fn linear_to_ulaw(sample: i16) -> u8 {
129    let sign: u8;
130    let mut pcm = sample;
131
132    if pcm < 0 {
133        sign = 0x80;
134        pcm = if pcm == i16::MIN { ULAW_MAX } else { -pcm };
135    } else {
136        sign = 0;
137    }
138
139    pcm = pcm.saturating_add(ULAW_BIAS);
140
141    let mut exponent = 7u8;
142    let mut mask = 0x4000i16;
143    while exponent > 0 && (pcm & mask) == 0 {
144        exponent -= 1;
145        mask >>= 1;
146    }
147
148    let mantissa = ((pcm >> (exponent + 3)) & 0x0F) as u8;
149    !(sign | (exponent << 4) | mantissa)
150}
151
152/// Convert an 8-bit μ-law sample to 16-bit linear PCM.
153fn ulaw_to_linear(ulaw: u8) -> i16 {
154    let ulaw = !ulaw;
155    let sign = ulaw & 0x80;
156    let exponent = ((ulaw >> 4) & 0x07) as i32;
157    let mantissa = (ulaw & 0x0F) as i32;
158    let mut sample = ((mantissa << 3) + ULAW_BIAS as i32) << exponent;
159    sample -= ULAW_BIAS as i32;
160    if sign != 0 {
161        -sample as i16
162    } else {
163        sample as i16
164    }
165}
166
167// --- G.711 A-law implementation (ITU-T G.711) ---
168
169const ALAW_SEG_END: [i16; 8] = [0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF];
170
171/// Convert a 16-bit linear PCM sample to 8-bit A-law.
172fn linear_to_alaw(sample: i16) -> u8 {
173    let sign: u8;
174    let abs_sample: i16;
175
176    if sample >= 0 {
177        sign = 0xD5;
178        abs_sample = sample;
179    } else {
180        sign = 0x55;
181        abs_sample = if sample == i16::MIN {
182            i16::MAX
183        } else {
184            -sample
185        };
186    }
187
188    let sample = abs_sample >> 3;
189
190    let mut seg = 0usize;
191    while seg < 8 && sample > ALAW_SEG_END[seg] {
192        seg += 1;
193    }
194
195    let aval = if seg >= 8 {
196        0x7F
197    } else if seg < 2 {
198        (sample >> 1) as u8
199    } else {
200        ((seg as u8) << 4) | ((sample >> seg) & 0x0F) as u8
201    };
202
203    aval ^ sign
204}
205
206/// Convert an 8-bit A-law sample to 16-bit linear PCM.
207fn alaw_to_linear(alaw: u8) -> i16 {
208    let alaw = alaw ^ 0x55;
209    let seg = ((alaw & 0x70) >> 4) as i32;
210    let value = ((alaw & 0x0F) << 1) | 1;
211
212    let sample = if seg == 0 {
213        (value as i32) << 3
214    } else {
215        ((value as i32) | 0x20) << (seg + 2)
216    };
217
218    if (alaw & 0x80) != 0 {
219        sample as i16
220    } else {
221        -(sample as i16)
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_ulaw_roundtrip() {
231        // Test roundtrip for a range of values
232        for sample in [
233            -32768i16, -16384, -8192, -1024, -128, 0, 128, 1024, 8192, 16384, 32767,
234        ] {
235            let encoded = linear_to_ulaw(sample);
236            let decoded = ulaw_to_linear(encoded);
237            // G.711 is lossy, but should be reasonably close
238            let diff = (sample as i32 - decoded as i32).abs();
239            assert!(
240                diff < 2048,
241                "μ-law roundtrip too lossy: {} -> {} (diff {})",
242                sample,
243                decoded,
244                diff
245            );
246        }
247    }
248
249    #[test]
250    fn test_alaw_roundtrip() {
251        for sample in [
252            -32768i16, -16384, -8192, -1024, -128, 0, 128, 1024, 8192, 16384, 32767,
253        ] {
254            let encoded = linear_to_alaw(sample);
255            let decoded = alaw_to_linear(encoded);
256            let diff = (sample as i32 - decoded as i32).abs();
257            assert!(
258                diff < 2048,
259                "A-law roundtrip too lossy: {} -> {} (diff {})",
260                sample,
261                decoded,
262                diff
263            );
264        }
265    }
266
267    #[test]
268    fn test_ulaw_encoder_decoder() {
269        let mut encoder = PcmuEncoder::new();
270        let mut decoder = PcmuDecoder::new();
271
272        let input: Vec<i16> = (0..160)
273            .map(|i| ((i as f32 / 160.0) * 16000.0) as i16)
274            .collect();
275        let mut encoded = Vec::new();
276        let consumed = encoder.encode(&input, &mut encoded);
277
278        assert_eq!(consumed, 160);
279        assert_eq!(encoded.len(), 160);
280
281        let mut decoded = Vec::new();
282        decoder.decode(&encoded, &mut decoded);
283        assert_eq!(decoded.len(), 160);
284    }
285
286    #[test]
287    fn test_alaw_encoder_decoder() {
288        let mut encoder = PcmaEncoder::new();
289        let mut decoder = PcmaDecoder::new();
290
291        let input: Vec<i16> = (0..160)
292            .map(|i| ((i as f32 / 160.0) * 16000.0) as i16)
293            .collect();
294        let mut encoded = Vec::new();
295        let consumed = encoder.encode(&input, &mut encoded);
296
297        assert_eq!(consumed, 160);
298        assert_eq!(encoded.len(), 160);
299
300        let mut decoded = Vec::new();
301        decoder.decode(&encoded, &mut decoded);
302        assert_eq!(decoded.len(), 160);
303    }
304
305    #[test]
306    fn test_silence_encoding() {
307        let mut encoder = PcmuEncoder::new();
308        let silence: Vec<i16> = vec![0; 160];
309        let mut encoded = Vec::new();
310        encoder.encode(&silence, &mut encoded);
311
312        // μ-law silence is 0xFF (127 biased and inverted)
313        assert!(encoded.iter().all(|&b| b == 0xFF || b == 0x7F));
314    }
315
316    #[test]
317    fn test_ulaw_extreme_values() {
318        // Test the most extreme values
319        let max_encoded = linear_to_ulaw(i16::MAX);
320        let min_encoded = linear_to_ulaw(i16::MIN);
321
322        // Should encode to different values
323        assert_ne!(max_encoded, min_encoded);
324
325        // Decode back should preserve polarity
326        let max_decoded = ulaw_to_linear(max_encoded);
327        let min_decoded = ulaw_to_linear(min_encoded);
328
329        assert!(max_decoded > 0);
330        assert!(min_decoded < 0);
331    }
332
333    #[test]
334    fn test_alaw_extreme_values() {
335        let max_encoded = linear_to_alaw(i16::MAX);
336        let min_encoded = linear_to_alaw(i16::MIN);
337
338        assert_ne!(max_encoded, min_encoded);
339
340        let max_decoded = alaw_to_linear(max_encoded);
341        let min_decoded = alaw_to_linear(min_encoded);
342
343        assert!(max_decoded > 0);
344        assert!(min_decoded < 0);
345    }
346
347    #[test]
348    fn test_ulaw_monotonicity() {
349        // μ-law should be monotonic for positive values
350        let mut last_encoded = linear_to_ulaw(0);
351        for sample in (0..32768i32).step_by(256) {
352            let encoded = linear_to_ulaw(sample as i16);
353            // Encoded values for increasing input should not increase
354            // (due to complementing in μ-law encoding)
355            assert!(
356                encoded <= last_encoded || sample < 256,
357                "μ-law not monotonic at {}: {} > {}",
358                sample,
359                encoded,
360                last_encoded
361            );
362            last_encoded = encoded;
363        }
364    }
365
366    #[test]
367    fn test_pcmu_encoder_properties() {
368        let encoder = PcmuEncoder::new();
369
370        assert_eq!(encoder.codec_type(), CodecType::Pcmu);
371        assert_eq!(encoder.payload_type(), 0);
372    }
373
374    #[test]
375    fn test_pcma_encoder_properties() {
376        let encoder = PcmaEncoder::new();
377
378        assert_eq!(encoder.codec_type(), CodecType::Pcma);
379        assert_eq!(encoder.payload_type(), 8);
380    }
381
382    #[test]
383    fn test_pcmu_decoder_properties() {
384        let decoder = PcmuDecoder::new();
385
386        assert_eq!(decoder.codec_type(), CodecType::Pcmu);
387    }
388
389    #[test]
390    fn test_pcma_decoder_properties() {
391        let decoder = PcmaDecoder::new();
392
393        assert_eq!(decoder.codec_type(), CodecType::Pcma);
394    }
395
396    #[test]
397    fn test_encoder_partial_frame() {
398        let mut encoder = PcmuEncoder::new();
399
400        // Less than one frame
401        let input: Vec<i16> = vec![1000; 80];
402        let mut encoded = Vec::new();
403        let consumed = encoder.encode(&input, &mut encoded);
404
405        assert_eq!(consumed, 80);
406        assert_eq!(encoded.len(), 80);
407    }
408
409    #[test]
410    fn test_encoder_multiple_frames() {
411        let mut encoder = PcmuEncoder::new();
412
413        // Multiple frames
414        let input: Vec<i16> = vec![1000; 480];
415        let mut encoded = Vec::new();
416        let consumed = encoder.encode(&input, &mut encoded);
417
418        assert_eq!(consumed, 480);
419        assert_eq!(encoded.len(), 480);
420    }
421
422    #[test]
423    fn test_ulaw_all_codewords() {
424        // Test that all 256 codewords decode without panic
425        for codeword in 0u8..=255 {
426            let _decoded = ulaw_to_linear(codeword);
427            // If we got here without panic, the codeword is valid
428        }
429    }
430
431    #[test]
432    fn test_alaw_all_codewords() {
433        // Test that all 256 codewords decode without panic
434        for codeword in 0u8..=255 {
435            let _decoded = alaw_to_linear(codeword);
436            // If we got here without panic, the codeword is valid
437        }
438    }
439
440    #[test]
441    fn test_sine_wave_encoding() {
442        let mut encoder = PcmuEncoder::new();
443        let mut decoder = PcmuDecoder::new();
444
445        // Generate a 1kHz sine wave at 8kHz sample rate
446        let samples: Vec<i16> = (0..160)
447            .map(|i| {
448                let t = i as f32 / 8000.0;
449                (1000.0 * (2.0 * std::f32::consts::PI * t).sin() * 16000.0) as i16
450            })
451            .collect();
452
453        let mut encoded = Vec::new();
454        encoder.encode(&samples, &mut encoded);
455
456        let mut decoded = Vec::new();
457        decoder.decode(&encoded, &mut decoded);
458
459        // Check that decoded signal has similar characteristics
460        assert_eq!(decoded.len(), samples.len());
461
462        // Calculate correlation between original and decoded
463        let correlation: f64 = samples
464            .iter()
465            .zip(decoded.iter())
466            .map(|(&a, &b)| (a as f64) * (b as f64))
467            .sum();
468
469        // Should have positive correlation (same polarity/phase)
470        assert!(
471            correlation > 0.0,
472            "Decoded signal not correlated with original"
473        );
474    }
475}