Skip to main content

audio_codec/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3// Opus wraps the (large, ~260 KB) opus-rs encoder/decoder on the heap, so it
4// needs `alloc` even in `no_std`.
5#[cfg(all(not(feature = "std"), feature = "opus"))]
6extern crate alloc;
7
8pub use error::CodecError;
9
10pub mod error;
11pub mod g722;
12pub mod g729;
13#[cfg(feature = "opus")]
14pub mod opus;
15pub mod pcma;
16pub mod pcmu;
17pub mod resampler;
18pub mod telephone_event;
19
20#[cfg(feature = "std")]
21pub use resampler::Resampler;
22#[cfg(feature = "std")]
23pub use resampler::{BoxedResampler, resample};
24
25pub type Sample = i16;
26
27#[cfg(feature = "std")]
28pub type PcmBuf = Vec<Sample>;
29
30#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
31pub enum CodecType {
32    PCMU,
33    PCMA,
34    G722,
35    G729,
36    #[cfg(feature = "opus")]
37    Opus,
38    TelephoneEvent,
39}
40
41/// Decoder trait: converts codec-specific bytes into PCM samples.
42///
43/// The slice-based `decode_into` is the primary, always-available method
44/// (works in `no_std`). The convenience `decode` returning a `Vec` is only
45/// available with the `std` feature and has a default implementation that
46/// delegates to `decode_into`.
47pub trait Decoder: Send + Sync {
48    /// Decode `data` into `out`, returning the number of samples written.
49    ///
50    /// Returns [`CodecError::BufferTooSmall`] if `out` cannot hold the
51    /// decoded samples (use `max_decode_samples` to size it).
52    fn decode_into(&mut self, data: &[u8], out: &mut [Sample]) -> Result<usize, CodecError>;
53
54    /// Upper bound on the number of samples that `decode_into` will write
55    /// for an input of `n_bytes` bytes.
56    fn max_decode_samples(&self, n_bytes: usize) -> usize;
57
58    /// Get the sample rate of the decoded audio.
59    fn sample_rate(&self) -> u32;
60
61    /// Get the number of channels.
62    fn channels(&self) -> u16;
63
64    /// Convenience wrapper that allocates a `Vec` and calls `decode_into`.
65    #[cfg(feature = "std")]
66    fn decode(&mut self, data: &[u8]) -> PcmBuf {
67        let max = self.max_decode_samples(data.len());
68        let mut buf = vec![0i16; max];
69        match self.decode_into(data, &mut buf) {
70            Ok(n) => {
71                buf.truncate(n);
72                buf
73            }
74            Err(_) => Vec::new(),
75        }
76    }
77}
78
79/// Encoder trait: converts PCM samples into codec-specific bytes.
80///
81/// The slice-based `encode_into` is the primary, always-available method
82/// (works in `no_std`). The convenience `encode` returning a `Vec` is only
83/// available with the `std` feature and has a default implementation that
84/// delegates to `encode_into`.
85pub trait Encoder: Send + Sync {
86    /// Encode `samples` into `out`, returning the number of bytes written.
87    ///
88    /// Returns [`CodecError::BufferTooSmall`] if `out` cannot hold the
89    /// encoded bytes (use `max_encode_bytes` to size it).
90    fn encode_into(&mut self, samples: &[Sample], out: &mut [u8]) -> Result<usize, CodecError>;
91
92    /// Upper bound on the number of bytes that `encode_into` will write
93    /// for an input of `n_samples` samples.
94    fn max_encode_bytes(&self, n_samples: usize) -> usize;
95
96    /// Get the sample rate expected for input samples.
97    fn sample_rate(&self) -> u32;
98
99    /// Get the number of channels expected for input.
100    fn channels(&self) -> u16;
101
102    /// Convenience wrapper that allocates a `Vec` and calls `encode_into`.
103    #[cfg(feature = "std")]
104    fn encode(&mut self, samples: &[Sample]) -> Vec<u8> {
105        let max = self.max_encode_bytes(samples.len());
106        let mut buf = vec![0u8; max];
107        match self.encode_into(samples, &mut buf) {
108            Ok(n) => {
109                buf.truncate(n);
110                buf
111            }
112            Err(_) => Vec::new(),
113        }
114    }
115}
116
117#[cfg(feature = "std")]
118pub fn create_decoder(codec: CodecType) -> Box<dyn Decoder> {
119    match codec {
120        CodecType::PCMU => Box::new(pcmu::PcmuDecoder::new()),
121        CodecType::PCMA => Box::new(pcma::PcmaDecoder::new()),
122        CodecType::G722 => Box::new(g722::G722Decoder::new()),
123        CodecType::G729 => Box::new(g729::G729Decoder::new()),
124        #[cfg(feature = "opus")]
125        CodecType::Opus => Box::new(opus::OpusDecoder::new_default()),
126        CodecType::TelephoneEvent => Box::new(telephone_event::TelephoneEventDecoder::new()),
127    }
128}
129
130#[cfg(feature = "std")]
131pub fn create_encoder(codec: CodecType) -> Box<dyn Encoder> {
132    match codec {
133        CodecType::PCMU => Box::new(pcmu::PcmuEncoder::new()),
134        CodecType::PCMA => Box::new(pcma::PcmaEncoder::new()),
135        CodecType::G722 => Box::new(g722::G722Encoder::new()),
136        CodecType::G729 => Box::new(g729::G729Encoder::new()),
137        #[cfg(feature = "opus")]
138        CodecType::Opus => Box::new(opus::OpusEncoder::new_default()),
139        CodecType::TelephoneEvent => Box::new(telephone_event::TelephoneEventEncoder::new()),
140    }
141}
142
143#[cfg(all(feature = "std", feature = "opus"))]
144pub fn create_opus_encoder(
145    sample_rate: u32,
146    channels: u16,
147    application: opus::OpusApplication,
148) -> Box<dyn Encoder> {
149    Box::new(opus::OpusEncoder::new_with_application(
150        sample_rate,
151        channels,
152        application,
153    ))
154}
155
156#[cfg(all(feature = "std", feature = "opus"))]
157pub fn create_opus_decoder(sample_rate: u32, channels: u16) -> Box<dyn Decoder> {
158    Box::new(opus::OpusDecoder::new(sample_rate, channels))
159}
160
161impl CodecType {
162    pub fn mime_type(&self) -> &str {
163        match self {
164            CodecType::PCMU => "audio/PCMU",
165            CodecType::PCMA => "audio/PCMA",
166            CodecType::G722 => "audio/G722",
167            CodecType::G729 => "audio/G729",
168            #[cfg(feature = "opus")]
169            CodecType::Opus => "audio/opus",
170            CodecType::TelephoneEvent => "audio/telephone-event",
171        }
172    }
173    pub fn rtpmap(&self) -> &str {
174        match self {
175            CodecType::PCMU => "PCMU/8000",
176            CodecType::PCMA => "PCMA/8000",
177            CodecType::G722 => "G722/8000",
178            CodecType::G729 => "G729/8000",
179            #[cfg(feature = "opus")]
180            CodecType::Opus => "opus/48000/2",
181            CodecType::TelephoneEvent => "telephone-event/8000",
182        }
183    }
184    pub fn fmtp(&self) -> Option<&str> {
185        match self {
186            CodecType::PCMU => None,
187            CodecType::PCMA => None,
188            CodecType::G722 => None,
189            CodecType::G729 => None,
190            #[cfg(feature = "opus")]
191            CodecType::Opus => Some("minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1"),
192            CodecType::TelephoneEvent => Some("0-16"),
193        }
194    }
195
196    pub fn clock_rate(&self) -> u32 {
197        match self {
198            CodecType::PCMU => 8000,
199            CodecType::PCMA => 8000,
200            CodecType::G722 => 8000,
201            CodecType::G729 => 8000,
202            #[cfg(feature = "opus")]
203            CodecType::Opus => 48000,
204            CodecType::TelephoneEvent => 8000,
205        }
206    }
207
208    pub fn channels(&self) -> u16 {
209        match self {
210            #[cfg(feature = "opus")]
211            CodecType::Opus => 2,
212            _ => 1,
213        }
214    }
215
216    pub fn payload_type(&self) -> u8 {
217        match self {
218            CodecType::PCMU => 0,
219            CodecType::PCMA => 8,
220            CodecType::G722 => 9,
221            CodecType::G729 => 18,
222            #[cfg(feature = "opus")]
223            CodecType::Opus => 111,
224            CodecType::TelephoneEvent => 101,
225        }
226    }
227    pub fn samplerate(&self) -> u32 {
228        match self {
229            CodecType::PCMU => 8000,
230            CodecType::PCMA => 8000,
231            CodecType::G722 => 16000,
232            CodecType::G729 => 8000,
233            #[cfg(feature = "opus")]
234            CodecType::Opus => 48000,
235            CodecType::TelephoneEvent => 8000,
236        }
237    }
238    pub fn is_audio(&self) -> bool {
239        match self {
240            CodecType::PCMU | CodecType::PCMA | CodecType::G722 => true,
241            CodecType::G729 => true,
242            #[cfg(feature = "opus")]
243            CodecType::Opus => true,
244            _ => false,
245        }
246    }
247
248    pub fn is_dynamic(&self) -> bool {
249        match self {
250            #[cfg(feature = "opus")]
251            CodecType::Opus => true,
252            CodecType::TelephoneEvent => true,
253            _ => false,
254        }
255    }
256}
257
258impl TryFrom<u8> for CodecType {
259    type Error = CodecError;
260
261    fn try_from(value: u8) -> Result<Self, Self::Error> {
262        match value {
263            0 => Ok(CodecType::PCMU),
264            8 => Ok(CodecType::PCMA),
265            9 => Ok(CodecType::G722),
266            18 => Ok(CodecType::G729), // Static payload type
267            // Dynamic payload type should get from the rtpmap in sdp offer, leave this for backward compatibility
268            101 => Ok(CodecType::TelephoneEvent),
269            #[cfg(feature = "opus")]
270            111 => Ok(CodecType::Opus), // Dynamic payload type
271            _ => Err(CodecError::InvalidCodecType),
272        }
273    }
274}
275
276impl TryFrom<&str> for CodecType {
277    type Error = CodecError;
278
279    fn try_from(name: &str) -> Result<Self, Self::Error> {
280        let b = name.as_bytes();
281        if b.eq_ignore_ascii_case(b"pcmu") || b.eq_ignore_ascii_case(b"ulaw") {
282            Ok(CodecType::PCMU)
283        } else if b.eq_ignore_ascii_case(b"pcma") || b.eq_ignore_ascii_case(b"alaw") {
284            Ok(CodecType::PCMA)
285        } else if b.eq_ignore_ascii_case(b"g722") {
286            Ok(CodecType::G722)
287        } else if b.eq_ignore_ascii_case(b"g729") {
288            Ok(CodecType::G729)
289        } else if cfg!(feature = "opus") && b.eq_ignore_ascii_case(b"opus") {
290            #[cfg(feature = "opus")]
291            {
292                Ok(CodecType::Opus)
293            }
294            #[cfg(not(feature = "opus"))]
295            {
296                Err(CodecError::InvalidCodecName)
297            }
298        } else if b.eq_ignore_ascii_case(b"telephone-event") {
299            Ok(CodecType::TelephoneEvent)
300        } else {
301            Err(CodecError::InvalidCodecName)
302        }
303    }
304}
305
306// ----------------------------------------------------------------------------
307// Byte <-> sample slice helpers
308// ----------------------------------------------------------------------------
309
310/// Write the little-endian byte representation of `samples` into `out`.
311///
312/// Returns the number of bytes written (`samples.len() * 2`). Returns
313/// [`CodecError::BufferTooSmall`] if `out` is too small.
314pub fn samples_to_bytes_into(samples: &[Sample], out: &mut [u8]) -> Result<usize, CodecError> {
315    let needed = core::mem::size_of_val(samples);
316    if out.len() < needed {
317        return Err(CodecError::BufferTooSmall);
318    }
319    if samples.is_empty() {
320        return Ok(0);
321    }
322    // A `&mut [u8]` is only guaranteed 1-byte aligned (and an empty buffer's
323    // dangling pointer is 1-aligned), so the i16 view is only sound when the
324    // pointer happens to be properly aligned; otherwise fall back to a
325    // bytewise copy.
326    let dst_aligned = out
327        .as_mut_ptr()
328        .addr()
329        .is_multiple_of(core::mem::align_of::<Sample>());
330    #[cfg(target_endian = "little")]
331    if dst_aligned {
332        // SAFETY: `out` is large enough (checked above) and its pointer is
333        // aligned to `Sample`; `[u8; N*2]` and `[i16; N]` then have the same
334        // layout.
335        let dst = unsafe {
336            core::slice::from_raw_parts_mut(out.as_mut_ptr() as *mut Sample, samples.len())
337        };
338        dst.copy_from_slice(samples);
339        return Ok(needed);
340    }
341    for (i, s) in samples.iter().enumerate() {
342        let b = s.to_le_bytes();
343        out[2 * i] = b[0];
344        out[2 * i + 1] = b[1];
345    }
346    Ok(needed)
347}
348
349/// Decode the little-endian bytes into `out` as `Sample` (i16) values.
350///
351/// Returns the number of samples written (`u8_data.len() / 2`). Returns
352/// [`CodecError::BufferTooSmall`] if `out` cannot hold them all.
353pub fn bytes_to_samples_into(u8_data: &[u8], out: &mut [Sample]) -> Result<usize, CodecError> {
354    let n = u8_data.len() / core::mem::size_of::<Sample>();
355    if out.len() < n {
356        return Err(CodecError::BufferTooSmall);
357    }
358    if n == 0 {
359        return Ok(0);
360    }
361    // See `samples_to_bytes_into`: a `&[u8]` is only guaranteed 1-byte
362    // aligned, so only build the i16 view when the pointer is aligned.
363    let src_aligned = u8_data
364        .as_ptr()
365        .addr()
366        .is_multiple_of(core::mem::align_of::<Sample>());
367    #[cfg(target_endian = "little")]
368    if src_aligned {
369        // SAFETY: see `samples_to_bytes_into`.
370        let src = unsafe { core::slice::from_raw_parts(u8_data.as_ptr() as *const Sample, n) };
371        out[..n].copy_from_slice(src);
372        return Ok(n);
373    }
374    for (i, chunk) in u8_data.chunks_exact(2).enumerate() {
375        out[i] = (chunk[0] as i16) | ((chunk[1] as i16) << 8);
376    }
377    Ok(n)
378}
379
380#[cfg(feature = "std")]
381pub fn samples_to_bytes(samples: &[Sample]) -> Vec<u8> {
382    let mut out = vec![0u8; core::mem::size_of_val(samples)];
383    let _ = samples_to_bytes_into(samples, &mut out);
384    out
385}
386
387#[cfg(feature = "std")]
388pub fn bytes_to_samples(u8_data: &[u8]) -> PcmBuf {
389    let n = u8_data.len() / core::mem::size_of::<Sample>();
390    let mut out = vec![0i16; n];
391    let _ = bytes_to_samples_into(u8_data, &mut out);
392    out
393}