Skip to main content

audio_codec/
opus.rs

1use super::{CodecError, Decoder, Encoder, Sample};
2#[cfg(feature = "std")]
3use super::PcmBuf;
4// `Box` lives in the std prelude; under `no_std` it comes from `alloc`.
5#[cfg(not(feature = "std"))]
6use alloc::boxed::Box;
7pub use opus_rs::Application as OpusApplication;
8use opus_rs::{Application, OpusDecoder as OpusDecoderRaw, OpusEncoder as OpusEncoderRaw};
9
10// Heap-free scratch-buffer caps (worst case: 48 kHz, stereo, 20 ms frame).
11const OPUS_MAX_FRAME: usize = 960; // 20 ms @ 48 kHz per channel
12const OPUS_MAX_CHANNELS: usize = 2;
13const OPUS_MAX_SAMPLES: usize = OPUS_MAX_FRAME * OPUS_MAX_CHANNELS; // 1920
14// RFC 6716: a single Opus packet carries at most 1276 bytes. Used by the
15// std-only `encode` helper's scratch packet buffer.
16#[cfg(feature = "std")]
17const OPUS_MAX_PACKET: usize = 1276;
18
19pub struct OpusDecoder {
20    decoder: Box<OpusDecoderRaw>,
21    sample_rate: u32,
22    channels: u16,
23    w_output_f32: [f32; OPUS_MAX_SAMPLES],
24    w_pcm_i16: [i16; OPUS_MAX_SAMPLES],
25}
26
27impl OpusDecoder {
28    /// Create a new Opus decoder instance
29    pub fn new(sample_rate: u32, channels: u16) -> Self {
30        let decoder = Box::new(
31            OpusDecoderRaw::new(sample_rate as i32, channels as usize)
32                .expect("Failed to create Opus decoder"),
33        );
34
35        Self {
36            decoder,
37            sample_rate,
38            channels,
39            w_output_f32: [0.0; OPUS_MAX_SAMPLES],
40            w_pcm_i16: [0; OPUS_MAX_SAMPLES],
41        }
42    }
43
44    /// Create a default Opus decoder (48kHz, stereo)
45    pub fn new_default() -> Self {
46        Self::new(48000, 2)
47    }
48
49    /// Lower-level decode that does not perform the stereo→mono downmix.
50    /// Used internally by both the trait impl and the back-comat `decode`.
51    pub fn decode_into_raw(&mut self, data: &[u8], output: &mut [i16]) -> usize {
52        if data.is_empty() {
53            return 0;
54        }
55
56        // Detect the actual channel count from the Opus packet's TOC byte
57        // (bit 2 is the stereo flag per RFC 6716). opus-rs 0.1.19+ rejects
58        // decoding when the packet channel count doesn't match the decoder's,
59        // so we adapt the decoder here to avoid returning empty PCM.
60        let packet_channels = if data[0] & 0x04 != 0 { 2usize } else { 1 };
61        if self.channels as usize != packet_channels {
62            self.channels = packet_channels as u16;
63            // Reuse the existing heap allocation (see `clippy::replace_box`).
64            *self.decoder = OpusDecoderRaw::new(self.sample_rate as i32, packet_channels)
65                .expect("Failed to create Opus decoder");
66        }
67
68        let channels = usize::from(self.channels);
69        let frame_size = (self.sample_rate as usize * 20) / 1000;
70        let max_samples = frame_size * channels;
71
72        match self
73            .decoder
74            .decode(data, frame_size, &mut self.w_output_f32[..max_samples])
75        {
76            Ok(len) => {
77                let total_samples = len * channels;
78                if total_samples == 0 {
79                    return 0;
80                }
81
82                for i in 0..total_samples {
83                    self.w_pcm_i16[i] =
84                        (self.w_output_f32[i] * 32768.0).clamp(-32768.0, 32767.0) as i16;
85                }
86
87                let n = total_samples.min(output.len());
88                output[..n].copy_from_slice(&self.w_pcm_i16[..n]);
89                n
90            }
91            Err(_) => 0,
92        }
93    }
94}
95
96impl Decoder for OpusDecoder {
97    fn decode_into(&mut self, data: &[u8], out: &mut [Sample]) -> Result<usize, CodecError> {
98        if data.is_empty() {
99            return Ok(0);
100        }
101        let n = self.decode_into_raw(data, out);
102        if n == 0 {
103            Err(CodecError::DecodeFailed)
104        } else {
105            Ok(n)
106        }
107    }
108
109    fn max_decode_samples(&self, n_bytes: usize) -> usize {
110        // One 20ms frame per packet is the typical case; size for stereo
111        // to cover the worst case (caller may downmix afterwards).
112        let frame_size = (self.sample_rate as usize * 20) / 1000;
113        let _ = n_bytes;
114        frame_size * 2
115    }
116
117    fn sample_rate(&self) -> u32 {
118        self.sample_rate
119    }
120
121    fn channels(&self) -> u16 {
122        self.channels
123    }
124
125    /// Override the default to preserve back-comat stereo→mono downmix
126    /// when the decoder was configured with `channels == 2`.
127    #[cfg(feature = "std")]
128    fn decode(&mut self, data: &[u8]) -> PcmBuf {
129        if data.is_empty() {
130            return Vec::new();
131        }
132        let packet_channels = if data[0] & 0x04 != 0 { 2usize } else { 1 };
133
134        let frame_size = (self.sample_rate as usize * 20) / 1000;
135        let max_samples = frame_size * packet_channels;
136        let mut pcm = vec![0i16; max_samples];
137        let n = self.decode_into_raw(data, &mut pcm);
138        pcm.truncate(n);
139        if usize::from(self.channels) == 2 {
140            pcm = pcm
141                .chunks_exact(2)
142                .map(|chunk| ((chunk[0] as i32 + chunk[1] as i32) / 2) as i16)
143                .collect();
144        }
145        pcm
146    }
147}
148
149pub struct OpusEncoder {
150    encoder: Box<OpusEncoderRaw>,
151    sample_rate: u32,
152    channels: u16,
153    w_input_f32: [f32; OPUS_MAX_SAMPLES],
154}
155
156impl OpusEncoder {
157    pub fn new_with_application(sample_rate: u32, channels: u16, application: Application) -> Self {
158        let encoder = Box::new(
159            OpusEncoderRaw::new(sample_rate as i32, channels as usize, application)
160                .expect("Failed to create Opus encoder"),
161        );
162
163        Self {
164            encoder,
165            sample_rate,
166            channels,
167            w_input_f32: [0.0; OPUS_MAX_SAMPLES],
168        }
169    }
170
171    /// Create a new Opus encoder instance.
172    ///
173    /// Keep backward-compatible defaults with pre-0.3.31 behavior:
174    /// - VoIP application
175    /// - caller can provide mono PCM even when encoder is configured as stereo;
176    ///   `encode()` duplicates mono samples to stereo.
177    pub fn new(sample_rate: u32, channels: u16) -> Self {
178        let mut enc = Self::new_with_application(sample_rate, channels, Application::Voip);
179        enc.encoder.bitrate_bps = if channels == 2 { 64000 } else { 48000 };
180        enc.encoder.complexity = 5;
181        enc.encoder.use_cbr = true;
182        enc
183    }
184
185    /// Create a default Opus encoder (48kHz, stereo)
186    pub fn new_default() -> Self {
187        Self::new(48000, 2)
188    }
189
190    /// Set the encoder bitrate in bits per second.
191    pub fn set_bitrate(&mut self, bitrate_bps: i32) {
192        self.encoder.bitrate_bps = bitrate_bps;
193    }
194
195    /// Set the encoder complexity (0-10).
196    pub fn set_complexity(&mut self, complexity: i32) {
197        self.encoder.complexity = complexity;
198    }
199
200    /// Enable or disable constant bitrate (CBR) mode.
201    pub fn set_cbr(&mut self, cbr: bool) {
202        self.encoder.use_cbr = cbr;
203    }
204
205    /// Encode into a caller-provided packet buffer.
206    ///
207    /// Returns `Some(bytes_written)` on success. Expects `samples` to match
208    /// the encoder's channel count (interleaved stereo when `channels == 2`).
209    pub fn encode_into_raw(&mut self, samples: &[Sample], output: &mut [u8]) -> Option<usize> {
210        let channels = usize::from(self.channels);
211        if samples.is_empty() || channels == 0 || !samples.len().is_multiple_of(channels) {
212            return None;
213        }
214        // Fixed scratch cap (heap-free build).
215        if samples.len() > OPUS_MAX_SAMPLES {
216            return None;
217        }
218
219        let frame_size = samples.len() / channels;
220
221        for (dst, &s) in self.w_input_f32[..samples.len()]
222            .iter_mut()
223            .zip(samples.iter())
224        {
225            *dst = s as f32 / 32768.0;
226        }
227
228        self.encoder
229            .encode(&self.w_input_f32[..samples.len()], frame_size, output)
230            .ok()
231    }
232
233    #[cfg(feature = "std")]
234    fn encode_raw(&mut self, samples: &[Sample]) -> Vec<u8> {
235        let channels = usize::from(self.channels);
236        if samples.is_empty()
237            || channels == 0
238            || !samples.len().is_multiple_of(channels)
239            || samples.len() > OPUS_MAX_SAMPLES
240        {
241            return Vec::new();
242        }
243
244        let frame_size = samples.len() / channels;
245
246        for (dst, &s) in self.w_input_f32[..samples.len()]
247            .iter_mut()
248            .zip(samples.iter())
249        {
250            *dst = s as f32 / 32768.0;
251        }
252
253        let mut packet = [0u8; OPUS_MAX_PACKET];
254        match self.encoder.encode(
255            &self.w_input_f32[..samples.len()],
256            frame_size,
257            &mut packet,
258        ) {
259            Ok(len) => packet[..len].to_vec(),
260            Err(_) => Vec::new(),
261        }
262    }
263}
264
265impl Encoder for OpusEncoder {
266    fn encode_into(&mut self, samples: &[Sample], out: &mut [u8]) -> Result<usize, CodecError> {
267        if samples.is_empty() {
268            return Ok(0);
269        }
270        // Note: this expects `samples` to already match the encoder's channel
271        // count (interleaved stereo if `channels == 2`). For the legacy
272        // mono→stereo upmix behavior, use the std-only `encode` method.
273        match self.encode_into_raw(samples, out) {
274            Some(n) => Ok(n),
275            None => Err(CodecError::EncodeFailed),
276        }
277    }
278
279    fn max_encode_bytes(&self, _n_samples: usize) -> usize {
280        // Per RFC 6716 the maximum Opus packet size is 1275 bytes.
281        1275
282    }
283
284    fn sample_rate(&self) -> u32 {
285        self.sample_rate
286    }
287
288    fn channels(&self) -> u16 {
289        self.channels
290    }
291
292    /// Override the default to preserve back-comat mono→stereo upmix
293    /// when the encoder was configured with `channels == 2`.
294    #[cfg(feature = "std")]
295    fn encode(&mut self, samples: &[Sample]) -> Vec<u8> {
296        if self.channels == 2 {
297            // mono→stereo upmix into a local scratch buffer (no allocation).
298            if samples.len() > OPUS_MAX_FRAME {
299                return Vec::new();
300            }
301            let mut stereo = [0i16; OPUS_MAX_SAMPLES];
302            for (i, &sample) in samples.iter().enumerate() {
303                stereo[2 * i] = sample;
304                stereo[2 * i + 1] = sample;
305            }
306            return self.encode_raw(&stereo[..samples.len() * 2]);
307        }
308        self.encode_raw(samples)
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    fn make_mono_pcm_20ms() -> Vec<i16> {
317        (0..960).map(|i| ((i * 100) % 32767) as i16).collect()
318    }
319
320    #[test]
321    fn test_opus_encode_decode_20ms_produces_960_mono_samples() {
322        let mut enc = OpusEncoder::new_default();
323        let pcm = make_mono_pcm_20ms();
324        let opus_pkt = enc.encode(&pcm);
325        assert!(!opus_pkt.is_empty(), "encoder should produce output");
326
327        // TOC byte bit 2 should be set (stereo flag from mono→stereo upmix)
328        assert!(opus_pkt[0] & 0x04 != 0, "packet should be stereo");
329
330        let mut dec = OpusDecoder::new_default();
331        let decoded = dec.decode(&opus_pkt);
332
333        // 20ms at 48kHz mono = 960 samples
334        assert_eq!(
335            decoded.len(),
336            960,
337            "decoder should downmix stereo→mono and output 960 samples, got {}",
338            decoded.len()
339        );
340    }
341
342    #[test]
343    fn test_opus_consecutive_frames_all_produce_960() {
344        let mut enc = OpusEncoder::new_default();
345        let pcm = make_mono_pcm_20ms();
346        let opus_pkt = enc.encode(&pcm);
347
348        let mut dec = OpusDecoder::new_default();
349        for i in 0..5 {
350            let decoded = dec.decode(&opus_pkt);
351            assert_eq!(
352                decoded.len(),
353                960,
354                "frame {} should produce 960 mono samples, got {}",
355                i,
356                decoded.len()
357            );
358        }
359    }
360
361    #[test]
362    fn test_opus_decoder_output_has_reasonable_energy() {
363        let mut enc = OpusEncoder::new_default();
364        // 440Hz sine at 48kHz, 20ms
365        let pcm: Vec<i16> = (0..960)
366            .map(|i| {
367                let t = i as f64 / 48000.0;
368                (16384.0 * (2.0 * std::f64::consts::PI * 440.0 * t).sin()) as i16
369            })
370            .collect();
371        let opus_pkt = enc.encode(&pcm);
372
373        let mut dec = OpusDecoder::new_default();
374        let decoded = dec.decode(&opus_pkt);
375        assert_eq!(decoded.len(), 960);
376
377        let energy: f64 =
378            decoded.iter().map(|&s| (s as f64).powi(2)).sum::<f64>() / decoded.len() as f64;
379        let rms = energy.sqrt();
380        assert!(
381            rms > 100.0 && rms < 20000.0,
382            "decoded audio RMS {} should be in reasonable range",
383            rms
384        );
385    }
386
387    #[test]
388    fn test_opus_decoder_handles_mono_packet_gracefully() {
389        // Create a mono Opus encoder, encode PCM → mono Opus packet
390        let mut mono_enc = OpusEncoder::new(48000, 1);
391        let pcm = make_mono_pcm_20ms();
392        let mono_pkt = mono_enc.encode(&pcm);
393        assert!(!mono_pkt.is_empty());
394        // Mono packet TOC bit 2 should be 0
395        assert!(
396            mono_pkt[0] & 0x04 == 0,
397            "mono encoder should produce mono packet"
398        );
399
400        // Decode with default stereo decoder — should handle mono→stereo transition
401        let mut dec = OpusDecoder::new_default();
402        let decoded = dec.decode(&mono_pkt);
403        assert_eq!(
404            decoded.len(),
405            960,
406            "first mono packet should produce exactly 960 mono samples, got {}",
407            decoded.len()
408        );
409
410        // Second mono packet should also be 960
411        let decoded2 = dec.decode(&mono_pkt);
412        assert_eq!(
413            decoded2.len(),
414            960,
415            "second mono packet should also produce 960 mono samples, got {}",
416            decoded2.len()
417        );
418    }
419
420    #[test]
421    fn test_opus_stereo_packet_downmix_produces_960() {
422        let mut enc = OpusEncoder::new_default();
423        let pcm = make_mono_pcm_20ms();
424        let stereo_pkt = enc.encode(&pcm);
425        assert!(stereo_pkt[0] & 0x04 != 0);
426
427        let mut dec = OpusDecoder::new_default();
428        let decoded = dec.decode(&stereo_pkt);
429        assert_eq!(
430            decoded.len(),
431            960,
432            "stereo packet should downmix to 960 mono samples"
433        );
434    }
435}