Skip to main content

rtp_engine/codec/
mod.rs

1//! Audio codec implementations for RTP media.
2//!
3//! This module provides encoding and decoding for common VoIP codecs:
4//!
5//! - **G.711 μ-law (PCMU)**: PT 0, 8kHz, widely supported
6//! - **G.711 A-law (PCMA)**: PT 8, 8kHz, common in Europe
7//! - **Opus**: PT 111, 48kHz, modern high-quality codec (feature-gated)
8//!
9//! # Example
10//!
11//! ```
12//! use rtp_engine::codec::{CodecType, create_encoder, create_decoder};
13//!
14//! let mut encoder = create_encoder(CodecType::Pcmu).unwrap();
15//! let mut decoder = create_decoder(CodecType::Pcmu).unwrap();
16//!
17//! // Encode 160 samples (20ms at 8kHz)
18//! let pcm: Vec<i16> = vec![0; 160];
19//! let mut encoded = Vec::new();
20//! encoder.encode(&pcm, &mut encoded);
21//!
22//! // Decode back to PCM
23//! let mut decoded = Vec::new();
24//! decoder.decode(&encoded, &mut decoded);
25//! ```
26
27mod g711;
28
29#[cfg(feature = "opus")]
30mod opus_codec;
31
32pub use g711::{PcmaDecoder, PcmaEncoder, PcmuDecoder, PcmuEncoder};
33
34#[cfg(feature = "opus")]
35pub use opus_codec::{OpusDecoder, OpusEncoder};
36
37use crate::error::Result;
38
39/// Supported audio codec types.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum CodecType {
42    /// G.711 μ-law (PCMU) - RTP payload type 0
43    Pcmu,
44    /// G.711 A-law (PCMA) - RTP payload type 8
45    Pcma,
46    /// Opus - RTP payload type 111 (dynamic)
47    #[cfg(feature = "opus")]
48    Opus,
49}
50
51impl CodecType {
52    /// Get the RTP payload type number for this codec.
53    pub fn payload_type(&self) -> u8 {
54        match self {
55            Self::Pcmu => 0,
56            Self::Pcma => 8,
57            #[cfg(feature = "opus")]
58            Self::Opus => 111,
59        }
60    }
61
62    /// Get the codec name as used in SDP.
63    pub fn name(&self) -> &'static str {
64        match self {
65            Self::Pcmu => "PCMU",
66            Self::Pcma => "PCMA",
67            #[cfg(feature = "opus")]
68            Self::Opus => "opus",
69        }
70    }
71
72    /// Get the clock rate (samples per second) for this codec.
73    pub fn clock_rate(&self) -> u32 {
74        match self {
75            Self::Pcmu | Self::Pcma => 8000,
76            #[cfg(feature = "opus")]
77            Self::Opus => 48000,
78        }
79    }
80
81    /// Get the number of channels for this codec.
82    pub fn channels(&self) -> u8 {
83        match self {
84            Self::Pcmu | Self::Pcma => 1,
85            #[cfg(feature = "opus")]
86            Self::Opus => 1, // We use mono for VoIP
87        }
88    }
89
90    /// Get the frame duration in milliseconds.
91    pub fn frame_duration_ms(&self) -> u32 {
92        20 // Standard 20ms frames for VoIP
93    }
94
95    /// Get the number of samples per frame.
96    pub fn samples_per_frame(&self) -> usize {
97        (self.clock_rate() * self.frame_duration_ms() / 1000) as usize
98    }
99
100    /// Parse a codec type from an RTP payload type number.
101    pub fn from_payload_type(pt: u8) -> Option<Self> {
102        match pt {
103            0 => Some(Self::Pcmu),
104            8 => Some(Self::Pcma),
105            #[cfg(feature = "opus")]
106            111 => Some(Self::Opus),
107            _ => None,
108        }
109    }
110
111    /// Parse a codec type from a codec name (case-insensitive).
112    pub fn from_name(name: &str) -> Option<Self> {
113        match name.to_uppercase().as_str() {
114            "PCMU" | "G711U" => Some(Self::Pcmu),
115            "PCMA" | "G711A" => Some(Self::Pcma),
116            #[cfg(feature = "opus")]
117            "OPUS" => Some(Self::Opus),
118            _ => None,
119        }
120    }
121}
122
123impl std::fmt::Display for CodecType {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        write!(f, "{}", self.name())
126    }
127}
128
129/// Trait for audio encoders.
130///
131/// Implementations encode PCM audio (signed 16-bit samples) to a compressed format.
132pub trait AudioEncoder: Send {
133    /// Encode PCM samples to the codec format.
134    ///
135    /// # Arguments
136    /// * `pcm` - Input PCM samples (signed 16-bit, mono)
137    /// * `output` - Output buffer for encoded data (appended to)
138    ///
139    /// # Returns
140    /// Number of samples consumed from input.
141    fn encode(&mut self, pcm: &[i16], output: &mut Vec<u8>) -> usize;
142
143    /// Get the RTP payload type for this encoder.
144    fn payload_type(&self) -> u8;
145
146    /// Get the codec type.
147    fn codec_type(&self) -> CodecType;
148}
149
150/// Trait for audio decoders.
151///
152/// Implementations decode compressed audio to PCM (signed 16-bit samples).
153pub trait AudioDecoder: Send {
154    /// Decode encoded data to PCM samples.
155    ///
156    /// # Arguments
157    /// * `encoded` - Input encoded data
158    /// * `output` - Output buffer for PCM samples (appended to)
159    fn decode(&mut self, encoded: &[u8], output: &mut Vec<i16>);
160
161    /// Get the codec type.
162    fn codec_type(&self) -> CodecType;
163}
164
165/// Create an encoder for the specified codec type.
166pub fn create_encoder(codec: CodecType) -> Result<Box<dyn AudioEncoder>> {
167    match codec {
168        CodecType::Pcmu => Ok(Box::new(PcmuEncoder::new())),
169        CodecType::Pcma => Ok(Box::new(PcmaEncoder::new())),
170        #[cfg(feature = "opus")]
171        CodecType::Opus => Ok(Box::new(OpusEncoder::new()?)),
172    }
173}
174
175/// Create a decoder for the specified codec type.
176pub fn create_decoder(codec: CodecType) -> Result<Box<dyn AudioDecoder>> {
177    match codec {
178        CodecType::Pcmu => Ok(Box::new(PcmuDecoder::new())),
179        CodecType::Pcma => Ok(Box::new(PcmaDecoder::new())),
180        #[cfg(feature = "opus")]
181        CodecType::Opus => Ok(Box::new(OpusDecoder::new()?)),
182    }
183}
184
185/// Negotiate a codec from SDP answer.
186///
187/// Parses the `m=audio` line and returns the first mutually supported codec.
188pub fn negotiate_codec(sdp: &str) -> CodecType {
189    for line in sdp.lines() {
190        let line = line.trim();
191        if line.starts_with("m=audio ") {
192            let parts: Vec<&str> = line.split_whitespace().collect();
193            // parts[3..] are payload types in priority order
194            for pt_str in parts.iter().skip(3) {
195                if let Ok(pt) = pt_str.parse::<u8>()
196                    && let Some(codec) = CodecType::from_payload_type(pt)
197                {
198                    return codec;
199                }
200            }
201        }
202    }
203    CodecType::Pcmu // fallback default
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn test_codec_type_properties() {
212        assert_eq!(CodecType::Pcmu.payload_type(), 0);
213        assert_eq!(CodecType::Pcma.payload_type(), 8);
214        assert_eq!(CodecType::Pcmu.clock_rate(), 8000);
215        assert_eq!(CodecType::Pcmu.samples_per_frame(), 160);
216    }
217
218    #[test]
219    fn test_codec_type_from_payload_type() {
220        assert_eq!(CodecType::from_payload_type(0), Some(CodecType::Pcmu));
221        assert_eq!(CodecType::from_payload_type(8), Some(CodecType::Pcma));
222        assert_eq!(CodecType::from_payload_type(99), None);
223    }
224
225    #[test]
226    fn test_codec_type_from_name() {
227        assert_eq!(CodecType::from_name("PCMU"), Some(CodecType::Pcmu));
228        assert_eq!(CodecType::from_name("pcma"), Some(CodecType::Pcma));
229        assert_eq!(CodecType::from_name("unknown"), None);
230    }
231
232    #[test]
233    fn test_negotiate_codec() {
234        let sdp = "v=0\r\n\
235                   m=audio 5004 RTP/AVP 0 8\r\n";
236        assert_eq!(negotiate_codec(sdp), CodecType::Pcmu);
237
238        let sdp2 = "v=0\r\n\
239                    m=audio 5004 RTP/AVP 8 0\r\n";
240        assert_eq!(negotiate_codec(sdp2), CodecType::Pcma);
241    }
242
243    #[test]
244    fn test_negotiate_codec_fallback() {
245        // No recognized codecs - should fall back to PCMU
246        let sdp = "v=0\r\nm=audio 5004 RTP/AVP 96 97 98\r\n";
247        assert_eq!(negotiate_codec(sdp), CodecType::Pcmu);
248    }
249
250    #[test]
251    fn test_negotiate_codec_no_audio() {
252        let sdp = "v=0\r\nm=video 5004 RTP/AVP 96\r\n";
253        assert_eq!(negotiate_codec(sdp), CodecType::Pcmu); // fallback
254    }
255
256    #[test]
257    fn test_codec_type_display() {
258        assert_eq!(format!("{}", CodecType::Pcmu), "PCMU");
259        assert_eq!(format!("{}", CodecType::Pcma), "PCMA");
260    }
261
262    #[test]
263    fn test_codec_type_name() {
264        assert_eq!(CodecType::Pcmu.name(), "PCMU");
265        assert_eq!(CodecType::Pcma.name(), "PCMA");
266    }
267
268    #[test]
269    fn test_codec_type_channels() {
270        assert_eq!(CodecType::Pcmu.channels(), 1);
271        assert_eq!(CodecType::Pcma.channels(), 1);
272    }
273
274    #[test]
275    fn test_codec_type_frame_duration() {
276        assert_eq!(CodecType::Pcmu.frame_duration_ms(), 20);
277        assert_eq!(CodecType::Pcma.frame_duration_ms(), 20);
278    }
279
280    #[test]
281    fn test_codec_from_name_aliases() {
282        assert_eq!(CodecType::from_name("G711U"), Some(CodecType::Pcmu));
283        assert_eq!(CodecType::from_name("G711A"), Some(CodecType::Pcma));
284        assert_eq!(CodecType::from_name("g711u"), Some(CodecType::Pcmu));
285    }
286
287    #[test]
288    fn test_create_encoder() {
289        let encoder = create_encoder(CodecType::Pcmu).unwrap();
290        assert_eq!(encoder.payload_type(), 0);
291        assert_eq!(encoder.codec_type(), CodecType::Pcmu);
292
293        let encoder = create_encoder(CodecType::Pcma).unwrap();
294        assert_eq!(encoder.payload_type(), 8);
295        assert_eq!(encoder.codec_type(), CodecType::Pcma);
296    }
297
298    #[test]
299    fn test_create_decoder() {
300        let decoder = create_decoder(CodecType::Pcmu).unwrap();
301        assert_eq!(decoder.codec_type(), CodecType::Pcmu);
302
303        let decoder = create_decoder(CodecType::Pcma).unwrap();
304        assert_eq!(decoder.codec_type(), CodecType::Pcma);
305    }
306
307    #[test]
308    fn test_encoder_empty_input() {
309        let mut encoder = create_encoder(CodecType::Pcmu).unwrap();
310        let mut output = Vec::new();
311        let consumed = encoder.encode(&[], &mut output);
312        assert_eq!(consumed, 0);
313        assert!(output.is_empty());
314    }
315
316    #[test]
317    fn test_decoder_empty_input() {
318        let mut decoder = create_decoder(CodecType::Pcmu).unwrap();
319        let mut output = Vec::new();
320        decoder.decode(&[], &mut output);
321        assert!(output.is_empty());
322    }
323
324    #[test]
325    fn test_codec_roundtrip_various_lengths() {
326        let mut encoder = create_encoder(CodecType::Pcmu).unwrap();
327        let mut decoder = create_decoder(CodecType::Pcmu).unwrap();
328
329        for len in [1, 10, 80, 160, 320, 480] {
330            let input: Vec<i16> = (0..len).map(|i| (i * 100) as i16).collect();
331            let mut encoded = Vec::new();
332            let consumed = encoder.encode(&input, &mut encoded);
333            assert_eq!(consumed, len);
334            assert_eq!(encoded.len(), len);
335
336            let mut decoded = Vec::new();
337            decoder.decode(&encoded, &mut decoded);
338            assert_eq!(decoded.len(), len);
339        }
340    }
341
342    #[test]
343    fn test_codec_type_hash_eq() {
344        use std::collections::HashSet;
345        let mut set = HashSet::new();
346        set.insert(CodecType::Pcmu);
347        set.insert(CodecType::Pcma);
348        set.insert(CodecType::Pcmu); // duplicate
349
350        assert_eq!(set.len(), 2);
351        assert!(set.contains(&CodecType::Pcmu));
352        assert!(set.contains(&CodecType::Pcma));
353    }
354
355    #[test]
356    fn test_codec_type_clone_copy() {
357        let codec = CodecType::Pcmu;
358        let copied = codec; // Copy trait
359        let cloned = copied; // Also copy
360
361        assert_eq!(codec, cloned);
362        assert_eq!(codec, copied);
363    }
364}