voice_engine/media/codecs/
mod.rs

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