Skip to main content

rtp_core/
codec.rs

1use std::fmt;
2use thiserror::Error;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum CodecType {
7    /// G.711 mu-law (PCMU), payload type 0
8    Pcmu,
9    /// G.711 A-law (PCMA), payload type 8
10    Pcma,
11    /// Opus codec, payload type 111 (dynamic)
12    Opus,
13}
14
15impl fmt::Display for CodecType {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            CodecType::Pcmu => write!(f, "PCMU (G.711 mu-law)"),
19            CodecType::Pcma => write!(f, "PCMA (G.711 A-law)"),
20            CodecType::Opus => write!(f, "Opus"),
21        }
22    }
23}
24
25impl CodecType {
26    pub fn payload_type(&self) -> u8 {
27        match self {
28            CodecType::Pcmu => 0,
29            CodecType::Pcma => 8,
30            CodecType::Opus => 111,
31        }
32    }
33
34    pub fn clock_rate(&self) -> u32 {
35        match self {
36            CodecType::Pcmu => 8000,
37            CodecType::Pcma => 8000,
38            CodecType::Opus => 48000,
39        }
40    }
41
42    pub fn name(&self) -> &'static str {
43        match self {
44            CodecType::Pcmu => "PCMU",
45            CodecType::Pcma => "PCMA",
46            CodecType::Opus => "opus",
47        }
48    }
49
50    pub fn from_payload_type(pt: u8) -> Option<Self> {
51        match pt {
52            0 => Some(CodecType::Pcmu),
53            8 => Some(CodecType::Pcma),
54            111 => Some(CodecType::Opus),
55            _ => None,
56        }
57    }
58
59    /// Samples per frame at 20ms ptime
60    pub fn samples_per_frame(&self) -> usize {
61        match self {
62            CodecType::Pcmu => 160,  // 8000 * 0.020
63            CodecType::Pcma => 160,
64            CodecType::Opus => 960,  // 48000 * 0.020
65        }
66    }
67}
68
69#[derive(Debug, Error)]
70pub enum CodecError {
71    #[error("encoding error: {0}")]
72    EncodingError(String),
73    #[error("decoding error: {0}")]
74    DecodingError(String),
75    #[error("unsupported codec")]
76    Unsupported,
77}
78
79/// Codec pipeline for encoding/decoding audio frames.
80///
81/// For PCMU/PCMA, we implement the G.711 codec directly.
82/// For Opus, uses the audiopus crate when the "opus" feature is enabled,
83/// otherwise falls back to a raw-bytes stub.
84pub struct CodecPipeline {
85    codec: CodecType,
86    #[cfg(feature = "opus")]
87    opus_encoder: Option<audiopus::coder::Encoder>,
88    #[cfg(feature = "opus")]
89    opus_decoder: Option<audiopus::coder::Decoder>,
90}
91
92impl CodecPipeline {
93    pub fn new(codec: CodecType) -> Self {
94        #[cfg(feature = "opus")]
95        let (opus_encoder, opus_decoder) = if codec == CodecType::Opus {
96            let enc = audiopus::coder::Encoder::new(
97                audiopus::SampleRate::Hz48000,
98                audiopus::Channels::Mono,
99                audiopus::Application::Voip,
100            ).ok();
101            let dec = audiopus::coder::Decoder::new(
102                audiopus::SampleRate::Hz48000,
103                audiopus::Channels::Mono,
104            ).ok();
105            (enc, dec)
106        } else {
107            (None, None)
108        };
109
110        Self {
111            codec,
112            #[cfg(feature = "opus")]
113            opus_encoder,
114            #[cfg(feature = "opus")]
115            opus_decoder,
116        }
117    }
118
119    pub fn codec_type(&self) -> CodecType {
120        self.codec
121    }
122
123    /// Encode PCM samples (16-bit linear, mono) to codec format
124    pub fn encode(&mut self, pcm_samples: &[i16]) -> Result<Vec<u8>, CodecError> {
125        match self.codec {
126            CodecType::Pcmu => Ok(pcm_samples.iter().map(|&s| linear_to_ulaw(s)).collect()),
127            CodecType::Pcma => Ok(pcm_samples.iter().map(|&s| linear_to_alaw(s)).collect()),
128            CodecType::Opus => {
129                #[cfg(feature = "opus")]
130                {
131                    if let Some(ref mut enc) = self.opus_encoder {
132                        let mut output = vec![0u8; 4000]; // max opus frame
133                        let len = enc.encode(pcm_samples, &mut output)
134                            .map_err(|e| CodecError::EncodingError(format!("opus encode: {}", e)))?;
135                        output.truncate(len);
136                        return Ok(output);
137                    }
138                }
139                // Stub fallback: encode as raw bytes
140                let mut bytes = Vec::with_capacity(pcm_samples.len() * 2);
141                for &sample in pcm_samples {
142                    bytes.extend_from_slice(&sample.to_le_bytes());
143                }
144                Ok(bytes)
145            }
146        }
147    }
148
149    /// Decode codec format to PCM samples (16-bit linear, mono)
150    pub fn decode(&mut self, data: &[u8]) -> Result<Vec<i16>, CodecError> {
151        match self.codec {
152            CodecType::Pcmu => Ok(data.iter().map(|&b| ulaw_to_linear(b)).collect()),
153            CodecType::Pcma => Ok(data.iter().map(|&b| alaw_to_linear(b)).collect()),
154            CodecType::Opus => {
155                #[cfg(feature = "opus")]
156                {
157                    if let Some(ref mut dec) = self.opus_decoder {
158                        let mut output = vec![0i16; 5760]; // max decode buffer
159                        let packet: audiopus::packet::Packet<'_> = data.try_into()
160                            .map_err(|e: audiopus::Error| CodecError::DecodingError(format!("opus packet: {}", e)))?;
161                        let signals: audiopus::MutSignals<'_, i16> = output.as_mut_slice().try_into()
162                            .map_err(|e: audiopus::Error| CodecError::DecodingError(format!("opus signals: {}", e)))?;
163                        let samples = dec.decode(Some(packet), signals, false)
164                            .map_err(|e| CodecError::DecodingError(format!("opus decode: {}", e)))?;
165                        output.truncate(samples);
166                        return Ok(output);
167                    }
168                }
169                // Stub fallback: decode raw bytes back to samples
170                if data.len() % 2 != 0 {
171                    return Err(CodecError::DecodingError(
172                        "invalid opus stub data length".to_string(),
173                    ));
174                }
175                let samples: Vec<i16> = data
176                    .chunks_exact(2)
177                    .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]]))
178                    .collect();
179                Ok(samples)
180            }
181        }
182    }
183
184    /// Generate silence for one frame
185    pub fn silence_frame(&self) -> Vec<u8> {
186        match self.codec {
187            CodecType::Pcmu => vec![0xFF; self.codec.samples_per_frame()], // mu-law silence
188            CodecType::Pcma => vec![0xD5; self.codec.samples_per_frame()], // A-law silence
189            CodecType::Opus => vec![0; self.codec.samples_per_frame() * 2], // stub silence
190        }
191    }
192}
193
194// G.711 mu-law encoding/decoding tables and functions
195
196const ULAW_BIAS: i32 = 0x84;
197const ULAW_CLIP: i32 = 32635;
198
199fn linear_to_ulaw(sample: i16) -> u8 {
200    let mut pcm_val = sample as i32;
201    let sign = if pcm_val < 0 {
202        pcm_val = -pcm_val;
203        0x80
204    } else {
205        0
206    };
207
208    if pcm_val > ULAW_CLIP {
209        pcm_val = ULAW_CLIP;
210    }
211    pcm_val += ULAW_BIAS;
212
213    let exponent = match pcm_val {
214        0..=0xFF => 0,
215        0x100..=0x1FF => 1,
216        0x200..=0x3FF => 2,
217        0x400..=0x7FF => 3,
218        0x800..=0xFFF => 4,
219        0x1000..=0x1FFF => 5,
220        0x2000..=0x3FFF => 6,
221        _ => 7,
222    };
223
224    let mantissa = (pcm_val >> (exponent + 3)) & 0x0F;
225    let ulaw_byte = !(sign | (exponent << 4) as i32 | mantissa) as u8;
226    ulaw_byte
227}
228
229fn ulaw_to_linear(ulaw_byte: u8) -> i16 {
230    let ulaw = !ulaw_byte;
231    let sign = ulaw & 0x80;
232    let exponent = ((ulaw >> 4) & 0x07) as i32;
233    let mantissa = (ulaw & 0x0F) as i32;
234
235    let mut sample = ((mantissa << 1) | 0x21) << (exponent + 2);
236    sample -= ULAW_BIAS as i32;
237
238    if sign != 0 {
239        -sample as i16
240    } else {
241        sample as i16
242    }
243}
244
245fn linear_to_alaw(sample: i16) -> u8 {
246    let mut pcm_val = sample as i32;
247    let sign = if pcm_val < 0 {
248        pcm_val = -pcm_val;
249        0x80i32
250    } else {
251        0
252    };
253
254    if pcm_val > 32767 {
255        pcm_val = 32767;
256    }
257
258    let (exponent, mantissa) = if pcm_val >= 256 {
259        let exp = match pcm_val {
260            256..=511 => 1,
261            512..=1023 => 2,
262            1024..=2047 => 3,
263            2048..=4095 => 4,
264            4096..=8191 => 5,
265            8192..=16383 => 6,
266            _ => 7,
267        };
268        let man = (pcm_val >> (exp + 3)) & 0x0F;
269        (exp, man)
270    } else {
271        (0, pcm_val >> 4)
272    };
273
274    let alaw_byte = (sign | (exponent << 4) | mantissa) as u8;
275    alaw_byte ^ 0x55
276}
277
278fn alaw_to_linear(alaw_byte: u8) -> i16 {
279    let alaw = alaw_byte ^ 0x55;
280    let sign = alaw & 0x80;
281    let exponent = ((alaw >> 4) & 0x07) as i32;
282    let mantissa = (alaw & 0x0F) as i32;
283
284    let sample = if exponent == 0 {
285        (mantissa << 4) | 0x08
286    } else {
287        ((mantissa << 1) | 0x21) << (exponent + 2)
288    };
289
290    if sign != 0 {
291        -(sample as i16)
292    } else {
293        sample as i16
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn test_codec_type_properties() {
303        assert_eq!(CodecType::Pcmu.payload_type(), 0);
304        assert_eq!(CodecType::Pcma.payload_type(), 8);
305        assert_eq!(CodecType::Opus.payload_type(), 111);
306
307        assert_eq!(CodecType::Pcmu.clock_rate(), 8000);
308        assert_eq!(CodecType::Opus.clock_rate(), 48000);
309
310        assert_eq!(CodecType::Pcmu.name(), "PCMU");
311        assert_eq!(CodecType::Pcma.name(), "PCMA");
312        assert_eq!(CodecType::Opus.name(), "opus");
313    }
314
315    #[test]
316    fn test_codec_from_payload_type() {
317        assert_eq!(CodecType::from_payload_type(0), Some(CodecType::Pcmu));
318        assert_eq!(CodecType::from_payload_type(8), Some(CodecType::Pcma));
319        assert_eq!(CodecType::from_payload_type(111), Some(CodecType::Opus));
320        assert_eq!(CodecType::from_payload_type(99), None);
321    }
322
323    #[test]
324    fn test_samples_per_frame() {
325        assert_eq!(CodecType::Pcmu.samples_per_frame(), 160);
326        assert_eq!(CodecType::Pcma.samples_per_frame(), 160);
327        assert_eq!(CodecType::Opus.samples_per_frame(), 960);
328    }
329
330    #[test]
331    fn test_pcmu_encode_decode_roundtrip() {
332        let mut codec = CodecPipeline::new(CodecType::Pcmu);
333
334        // Test with various sample values
335        let samples: Vec<i16> = vec![0, 100, -100, 1000, -1000, 8000, -8000, 32000, -32000];
336        let encoded = codec.encode(&samples).unwrap();
337        let decoded = codec.decode(&encoded).unwrap();
338
339        assert_eq!(samples.len(), decoded.len());
340        // G.711 is lossy, so values won't be exact but should be close
341        for (original, decoded) in samples.iter().zip(decoded.iter()) {
342            let diff = (*original as i32 - *decoded as i32).abs();
343            // Allow some quantization error
344            assert!(
345                diff < 500,
346                "Too much error: original={}, decoded={}, diff={}",
347                original,
348                decoded,
349                diff
350            );
351        }
352    }
353
354    #[test]
355    fn test_pcma_encode_decode_roundtrip() {
356        let mut codec = CodecPipeline::new(CodecType::Pcma);
357
358        let samples: Vec<i16> = vec![0, 100, -100, 1000, -1000, 8000, -8000, 32000, -32000];
359        let encoded = codec.encode(&samples).unwrap();
360        let decoded = codec.decode(&encoded).unwrap();
361
362        assert_eq!(samples.len(), decoded.len());
363        for (original, decoded) in samples.iter().zip(decoded.iter()) {
364            let diff = (*original as i32 - *decoded as i32).abs();
365            assert!(
366                diff < 500,
367                "Too much error: original={}, decoded={}, diff={}",
368                original,
369                decoded,
370                diff
371            );
372        }
373    }
374
375    #[test]
376    fn test_opus_roundtrip() {
377        let mut codec = CodecPipeline::new(CodecType::Opus);
378        // Generate a simple tone for testing
379        let samples: Vec<i16> = (0..960)
380            .map(|i| ((i as f64 / 960.0 * std::f64::consts::TAU).sin() * 16000.0) as i16)
381            .collect();
382        let encoded = codec.encode(&samples).unwrap();
383        let decoded = codec.decode(&encoded).unwrap();
384        // Opus is lossy but output should have correct number of samples
385        assert_eq!(decoded.len(), 960);
386        // Verify the decoded audio isn't silence (some energy preserved)
387        let max_sample = decoded.iter().map(|s| s.abs()).max().unwrap_or(0);
388        assert!(max_sample > 1000, "Expected audio energy, max sample was {}", max_sample);
389    }
390
391    #[cfg(not(feature = "opus"))]
392    #[test]
393    fn test_opus_stub_decode_odd_length() {
394        let mut codec = CodecPipeline::new(CodecType::Opus);
395        let result = codec.decode(&[0, 1, 2]); // Odd number of bytes
396        assert!(result.is_err());
397    }
398
399    #[test]
400    fn test_pcmu_silence() {
401        let codec = CodecPipeline::new(CodecType::Pcmu);
402        let silence = codec.silence_frame();
403        assert_eq!(silence.len(), 160);
404        assert!(silence.iter().all(|&b| b == 0xFF));
405    }
406
407    #[test]
408    fn test_pcma_silence() {
409        let codec = CodecPipeline::new(CodecType::Pcma);
410        let silence = codec.silence_frame();
411        assert_eq!(silence.len(), 160);
412        assert!(silence.iter().all(|&b| b == 0xD5));
413    }
414
415    #[test]
416    fn test_pcmu_encode_silence() {
417        let mut codec = CodecPipeline::new(CodecType::Pcmu);
418        let silence_pcm = vec![0i16; 160];
419        let encoded = codec.encode(&silence_pcm).unwrap();
420        assert_eq!(encoded.len(), 160);
421    }
422
423    #[test]
424    fn test_ulaw_known_values() {
425        // Silence (0) should encode to 0xFF
426        assert_eq!(linear_to_ulaw(0), 0xFF);
427        // Decode 0xFF should be close to 0
428        let decoded = ulaw_to_linear(0xFF);
429        assert!(decoded.abs() < 10, "Expected near-zero, got {}", decoded);
430    }
431
432    #[test]
433    fn test_codec_pipeline_type() {
434        let pipeline = CodecPipeline::new(CodecType::Pcmu);
435        assert_eq!(pipeline.codec_type(), CodecType::Pcmu);
436
437        let pipeline = CodecPipeline::new(CodecType::Pcma);
438        assert_eq!(pipeline.codec_type(), CodecType::Pcma);
439    }
440
441    #[test]
442    fn test_pcmu_full_frame() {
443        let mut codec = CodecPipeline::new(CodecType::Pcmu);
444        // Generate a sine-like wave
445        let samples: Vec<i16> = (0..160)
446            .map(|i| ((i as f64 / 160.0 * std::f64::consts::TAU).sin() * 16000.0) as i16)
447            .collect();
448
449        let encoded = codec.encode(&samples).unwrap();
450        assert_eq!(encoded.len(), 160); // 1 byte per sample for G.711
451
452        let decoded = codec.decode(&encoded).unwrap();
453        assert_eq!(decoded.len(), 160);
454    }
455}