voice_engine/media/codecs/
mod.rs

1use crate::media::{PcmBuf, Sample};
2pub mod g722;
3#[cfg(feature = "g729")]
4pub mod g729;
5#[cfg(feature = "opus")]
6pub mod opus;
7pub mod pcma;
8pub mod pcmu;
9pub mod resample;
10pub mod telephone_event;
11#[cfg(test)]
12mod tests;
13#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
14pub enum CodecType {
15    PCMU,
16    PCMA,
17    G722,
18    #[cfg(feature = "g729")]
19    G729,
20    #[cfg(feature = "opus")]
21    Opus,
22    TelephoneEvent,
23}
24
25pub trait Decoder: Send + Sync {
26    /// Decode encoded audio data into PCM samples
27    fn decode(&mut self, data: &[u8]) -> PcmBuf;
28
29    /// Get the sample rate of the decoded audio
30    fn sample_rate(&self) -> u32;
31
32    /// Get the number of channels
33    fn channels(&self) -> u16;
34}
35
36pub trait Encoder: Send + Sync {
37    /// Encode PCM samples into codec-specific format
38    fn encode(&mut self, samples: &[Sample]) -> Vec<u8>;
39
40    /// Get the sample rate expected for input samples
41    fn sample_rate(&self) -> u32;
42
43    /// Get the number of channels expected for input
44    fn channels(&self) -> u16;
45}
46
47pub fn create_decoder(codec: CodecType) -> Box<dyn Decoder> {
48    match codec {
49        CodecType::PCMU => Box::new(pcmu::PcmuDecoder::new()),
50        CodecType::PCMA => Box::new(pcma::PcmaDecoder::new()),
51        CodecType::G722 => Box::new(g722::G722Decoder::new()),
52        #[cfg(feature = "g729")]
53        CodecType::G729 => Box::new(g729::G729Decoder::new()),
54        #[cfg(feature = "opus")]
55        CodecType::Opus => Box::new(opus::OpusDecoder::new_default()),
56        CodecType::TelephoneEvent => Box::new(telephone_event::TelephoneEventDecoder::new()),
57    }
58}
59
60pub fn create_encoder(codec: CodecType) -> Box<dyn Encoder> {
61    match codec {
62        CodecType::PCMU => Box::new(pcmu::PcmuEncoder::new()),
63        CodecType::PCMA => Box::new(pcma::PcmaEncoder::new()),
64        CodecType::G722 => Box::new(g722::G722Encoder::new()),
65        #[cfg(feature = "g729")]
66        CodecType::G729 => Box::new(g729::G729Encoder::new()),
67        #[cfg(feature = "opus")]
68        CodecType::Opus => Box::new(opus::OpusEncoder::new_default()),
69        CodecType::TelephoneEvent => Box::new(telephone_event::TelephoneEventEncoder::new()),
70    }
71}
72
73impl CodecType {
74    pub fn mime_type(&self) -> &str {
75        match self {
76            CodecType::PCMU => "audio/PCMU",
77            CodecType::PCMA => "audio/PCMA",
78            CodecType::G722 => "audio/G722",
79            #[cfg(feature = "g729")]
80            CodecType::G729 => "audio/G729",
81            #[cfg(feature = "opus")]
82            CodecType::Opus => "audio/opus",
83            CodecType::TelephoneEvent => "audio/telephone-event",
84        }
85    }
86    pub fn rtpmap(&self) -> &str {
87        match self {
88            CodecType::PCMU => "PCMU/8000",
89            CodecType::PCMA => "PCMA/8000",
90            CodecType::G722 => "G722/8000",
91            #[cfg(feature = "g729")]
92            CodecType::G729 => "G729/8000",
93            #[cfg(feature = "opus")]
94            CodecType::Opus => "opus/48000/2",
95            CodecType::TelephoneEvent => "telephone-event/8000",
96        }
97    }
98    pub fn fmtp(&self) -> Option<&str> {
99        match self {
100            CodecType::PCMU => None,
101            CodecType::PCMA => None,
102            CodecType::G722 => None,
103            #[cfg(feature = "g729")]
104            CodecType::G729 => None,
105            #[cfg(feature = "opus")]
106            CodecType::Opus => Some("minptime=10;useinbandfec=1"),
107            CodecType::TelephoneEvent => Some("0-16"),
108        }
109    }
110
111    pub fn clock_rate(&self) -> u32 {
112        match self {
113            CodecType::PCMU => 8000,
114            CodecType::PCMA => 8000,
115            CodecType::G722 => 8000,
116            #[cfg(feature = "g729")]
117            CodecType::G729 => 8000,
118            #[cfg(feature = "opus")]
119            CodecType::Opus => 48000,
120            CodecType::TelephoneEvent => 8000,
121        }
122    }
123
124    pub fn channels(&self) -> u16 {
125        match self {
126            #[cfg(feature = "opus")]
127            CodecType::Opus => 2,
128            _ => 1,
129        }
130    }
131
132    pub fn payload_type(&self) -> u8 {
133        match self {
134            CodecType::PCMU => 0,
135            CodecType::PCMA => 8,
136            CodecType::G722 => 9,
137            #[cfg(feature = "g729")]
138            CodecType::G729 => 18,
139            #[cfg(feature = "opus")]
140            CodecType::Opus => 111,
141            CodecType::TelephoneEvent => 101,
142        }
143    }
144    pub fn samplerate(&self) -> u32 {
145        match self {
146            CodecType::PCMU => 8000,
147            CodecType::PCMA => 8000,
148            CodecType::G722 => 16000,
149            #[cfg(feature = "g729")]
150            CodecType::G729 => 8000,
151            #[cfg(feature = "opus")]
152            CodecType::Opus => 48000,
153            CodecType::TelephoneEvent => 8000,
154        }
155    }
156    pub fn is_audio(&self) -> bool {
157        match self {
158            CodecType::PCMU | CodecType::PCMA | CodecType::G722 => true,
159            #[cfg(feature = "g729")]
160            CodecType::G729 => true,
161            #[cfg(feature = "opus")]
162            CodecType::Opus => true,
163            _ => false,
164        }
165    }
166
167    pub fn is_dynamic(&self) -> bool {
168        match self {
169            #[cfg(feature = "opus")]
170            CodecType::Opus => true,
171            CodecType::TelephoneEvent => true,
172            _ => false,
173        }
174    }
175}
176
177impl TryFrom<u8> for CodecType {
178    type Error = anyhow::Error;
179
180    fn try_from(value: u8) -> Result<Self, Self::Error> {
181        match value {
182            0 => Ok(CodecType::PCMU),
183            8 => Ok(CodecType::PCMA),
184            9 => Ok(CodecType::G722),
185            #[cfg(feature = "g729")]
186            18 => Ok(CodecType::G729), // Static payload type
187            #[cfg(feature = "opus")]
188            // Dynamic payload type shoulw get from the rtpmap in sdp offer, leave this for backward compatibility
189            101 => Ok(CodecType::TelephoneEvent),
190            111 => Ok(CodecType::Opus), // Dynamic payload type
191            _ => Err(anyhow::anyhow!("Invalid codec type: {}", value)),
192        }
193    }
194}
195
196/// Parse an rtpmap string like "96 opus/48000/2" into its components
197///
198/// Assumes RFC 4566/8866 compliant format:
199/// `<payload type> <encoding name>/<clock rate>[/<encoding parameters>]`
200///
201/// Returns: (payload_type, codec_type, clock_rate, channel_count)
202///
203/// # Examples
204/// ```
205/// use voice_engine::media::codecs::{parse_rtpmap, CodecType};
206///
207/// let (pt, codec, rate, channels) = parse_rtpmap("96 opus/48000/2").unwrap();
208/// assert_eq!(pt, 96);
209/// assert_eq!(codec, CodecType::Opus);
210/// assert_eq!(rate, 48000);
211/// assert_eq!(channels, 2);
212/// ```
213pub fn parse_rtpmap(rtpmap: &str) -> Result<(u8, CodecType, u32, u16), anyhow::Error> {
214    if let [payload_type_str, codec_spec] = rtpmap.split(' ').collect::<Vec<&str>>().as_slice() {
215        // Parse payload type
216        let payload_type = payload_type_str
217            .parse::<u8>()
218            .map_err(|e| anyhow::anyhow!("Failed to parse payload type: {}", e))?;
219        let codec_parts: Vec<&str> = codec_spec.split('/').collect();
220
221        if let [codec_name, clock_rate_str, channel_count @ ..] = codec_parts.as_slice() {
222            let codec_type = match codec_name.to_lowercase().as_str() {
223                "pcmu" => CodecType::PCMU,
224                "pcma" => CodecType::PCMA,
225                "g722" => CodecType::G722,
226                #[cfg(feature = "g729")]
227                "g729" => CodecType::G729,
228                #[cfg(feature = "opus")]
229                "opus" => CodecType::Opus,
230                "telephone-event" => CodecType::TelephoneEvent,
231                _ => return Err(anyhow::anyhow!("Unsupported codec name: {}", codec_name)),
232            };
233
234            let clock_rate = clock_rate_str
235                .parse::<u32>()
236                .map_err(|e| anyhow::anyhow!("Failed to parse clock rate: {}", e))?;
237
238            let channel_count = match channel_count {
239                ["2"] => 2,
240                _ => 1,
241            };
242            Ok((payload_type, codec_type, clock_rate, channel_count))
243        } else {
244            return Err(anyhow::anyhow!("Invalid codec specification in rtpmap"));
245        }
246    } else {
247        Err(anyhow::anyhow!(
248            "Invalid rtpmap format: missing space between payload type and encoding name"
249        ))
250    }
251}
252
253#[cfg(target_endian = "little")]
254pub fn samples_to_bytes(samples: &[Sample]) -> Vec<u8> {
255    unsafe {
256        std::slice::from_raw_parts(
257            samples.as_ptr() as *const u8,
258            samples.len() * std::mem::size_of::<Sample>(),
259        )
260        .to_vec()
261    }
262}
263
264#[cfg(target_endian = "big")]
265pub fn samples_to_bytes(samples: &[Sample]) -> Vec<u8> {
266    samples.iter().flat_map(|s| s.to_le_bytes()).collect()
267}
268
269#[cfg(target_endian = "little")]
270pub fn bytes_to_samples(u8_data: &[u8]) -> PcmBuf {
271    unsafe {
272        std::slice::from_raw_parts(
273            u8_data.as_ptr() as *const Sample,
274            u8_data.len() / std::mem::size_of::<Sample>(),
275        )
276        .to_vec()
277    }
278}
279#[cfg(target_endian = "big")]
280pub fn bytes_to_samples(u8_data: &[u8]) -> PcmBuf {
281    u8_data
282        .chunks(2)
283        .map(|chunk| (chunk[0] as i16) | ((chunk[1] as i16) << 8))
284        .collect()
285}