Skip to main content

audio_codec/
opus.rs

1#[cfg(feature = "std")]
2use super::PcmBuf;
3use super::{CodecError, Decoder, Encoder, Sample};
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
255            .encoder
256            .encode(&self.w_input_f32[..samples.len()], frame_size, &mut packet)
257        {
258            Ok(len) => packet[..len].to_vec(),
259            Err(_) => Vec::new(),
260        }
261    }
262}
263
264impl Encoder for OpusEncoder {
265    fn encode_into(&mut self, samples: &[Sample], out: &mut [u8]) -> Result<usize, CodecError> {
266        if samples.is_empty() {
267            return Ok(0);
268        }
269        // Note: this expects `samples` to already match the encoder's channel
270        // count (interleaved stereo if `channels == 2`). For the legacy
271        // mono→stereo upmix behavior, use the std-only `encode` method.
272        match self.encode_into_raw(samples, out) {
273            Some(n) => Ok(n),
274            None => Err(CodecError::EncodeFailed),
275        }
276    }
277
278    fn max_encode_bytes(&self, _n_samples: usize) -> usize {
279        // Per RFC 6716 the maximum Opus packet size is 1275 bytes.
280        1275
281    }
282
283    fn sample_rate(&self) -> u32 {
284        self.sample_rate
285    }
286
287    fn channels(&self) -> u16 {
288        self.channels
289    }
290
291    /// Override the default to preserve back-comat mono→stereo upmix
292    /// when the encoder was configured with `channels == 2`.
293    #[cfg(feature = "std")]
294    fn encode(&mut self, samples: &[Sample]) -> Vec<u8> {
295        if self.channels == 2 {
296            // mono→stereo upmix into a local scratch buffer (no allocation).
297            if samples.len() > OPUS_MAX_FRAME {
298                return Vec::new();
299            }
300            let mut stereo = [0i16; OPUS_MAX_SAMPLES];
301            for (i, &sample) in samples.iter().enumerate() {
302                stereo[2 * i] = sample;
303                stereo[2 * i + 1] = sample;
304            }
305            return self.encode_raw(&stereo[..samples.len() * 2]);
306        }
307        self.encode_raw(samples)
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314
315    fn make_mono_pcm_20ms() -> Vec<i16> {
316        (0..960).map(|i| ((i * 100) % 32767) as i16).collect()
317    }
318
319    #[test]
320    fn test_opus_encode_decode_20ms_produces_960_mono_samples() {
321        let mut enc = OpusEncoder::new_default();
322        let pcm = make_mono_pcm_20ms();
323        let opus_pkt = enc.encode(&pcm);
324        assert!(!opus_pkt.is_empty(), "encoder should produce output");
325
326        // TOC byte bit 2 should be set (stereo flag from mono→stereo upmix)
327        assert!(opus_pkt[0] & 0x04 != 0, "packet should be stereo");
328
329        let mut dec = OpusDecoder::new_default();
330        let decoded = dec.decode(&opus_pkt);
331
332        // 20ms at 48kHz mono = 960 samples
333        assert_eq!(
334            decoded.len(),
335            960,
336            "decoder should downmix stereo→mono and output 960 samples, got {}",
337            decoded.len()
338        );
339    }
340
341    #[test]
342    fn test_opus_consecutive_frames_all_produce_960() {
343        let mut enc = OpusEncoder::new_default();
344        let pcm = make_mono_pcm_20ms();
345        let opus_pkt = enc.encode(&pcm);
346
347        let mut dec = OpusDecoder::new_default();
348        for i in 0..5 {
349            let decoded = dec.decode(&opus_pkt);
350            assert_eq!(
351                decoded.len(),
352                960,
353                "frame {} should produce 960 mono samples, got {}",
354                i,
355                decoded.len()
356            );
357        }
358    }
359
360    #[test]
361    fn test_opus_decoder_output_has_reasonable_energy() {
362        let mut enc = OpusEncoder::new_default();
363        // 440Hz sine at 48kHz, 20ms
364        let pcm: Vec<i16> = (0..960)
365            .map(|i| {
366                let t = i as f64 / 48000.0;
367                (16384.0 * (2.0 * std::f64::consts::PI * 440.0 * t).sin()) as i16
368            })
369            .collect();
370        let opus_pkt = enc.encode(&pcm);
371
372        let mut dec = OpusDecoder::new_default();
373        let decoded = dec.decode(&opus_pkt);
374        assert_eq!(decoded.len(), 960);
375
376        let energy: f64 =
377            decoded.iter().map(|&s| (s as f64).powi(2)).sum::<f64>() / decoded.len() as f64;
378        let rms = energy.sqrt();
379        assert!(
380            rms > 100.0 && rms < 20000.0,
381            "decoded audio RMS {} should be in reasonable range",
382            rms
383        );
384    }
385
386    #[test]
387    fn test_opus_decoder_handles_mono_packet_gracefully() {
388        // Create a mono Opus encoder, encode PCM → mono Opus packet
389        let mut mono_enc = OpusEncoder::new(48000, 1);
390        let pcm = make_mono_pcm_20ms();
391        let mono_pkt = mono_enc.encode(&pcm);
392        assert!(!mono_pkt.is_empty());
393        // Mono packet TOC bit 2 should be 0
394        assert!(
395            mono_pkt[0] & 0x04 == 0,
396            "mono encoder should produce mono packet"
397        );
398
399        // Decode with default stereo decoder — should handle mono→stereo transition
400        let mut dec = OpusDecoder::new_default();
401        let decoded = dec.decode(&mono_pkt);
402        assert_eq!(
403            decoded.len(),
404            960,
405            "first mono packet should produce exactly 960 mono samples, got {}",
406            decoded.len()
407        );
408
409        // Second mono packet should also be 960
410        let decoded2 = dec.decode(&mono_pkt);
411        assert_eq!(
412            decoded2.len(),
413            960,
414            "second mono packet should also produce 960 mono samples, got {}",
415            decoded2.len()
416        );
417    }
418
419    #[test]
420    fn test_opus_stereo_packet_downmix_produces_960() {
421        let mut enc = OpusEncoder::new_default();
422        let pcm = make_mono_pcm_20ms();
423        let stereo_pkt = enc.encode(&pcm);
424        assert!(stereo_pkt[0] & 0x04 != 0);
425
426        let mut dec = OpusDecoder::new_default();
427        let decoded = dec.decode(&stereo_pkt);
428        assert_eq!(
429            decoded.len(),
430            960,
431            "stereo packet should downmix to 960 mono samples"
432        );
433    }
434}