opus_embedded/
lib.rs

1/*
2 * Copyright (c) 2025 Tomi Leppänen
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5/*!
6 * Small no_std and no_alloc opus decoder for opus audio.
7 *
8 * Uses libopus.
9 */
10
11#![no_std]
12#![deny(missing_docs)]
13
14use az::SaturatingAs;
15use core::ffi::{c_int, CStr};
16use num_enum::{IntoPrimitive, TryFromPrimitive};
17use opus_embedded_sys::*;
18
19pub mod prelude {
20    /*!
21     * opus_embedded prelude.
22     *
23     * Includes the most commonly needed types.
24     *
25     * ```
26     * # #![allow(unused_imports)]
27     * use opus_embedded::prelude::*;
28     * ```
29     */
30
31    pub use super::{Channels, Decoder, SamplingRate};
32}
33
34/**
35 * # Safety
36 *
37 * The implementation of numeric must return a valid error code defined by libopus.
38 */
39unsafe trait RawOpusError {
40    /**
41     * Returns valid numeric error code defined by libopus.
42     */
43    fn numeric(&self) -> c_int;
44}
45
46/// Error from parsing opus data.
47pub trait OpusError {
48    /// Returns the error message as it is defined by libopus.
49    fn message(&self) -> &'static str;
50}
51
52impl<E: RawOpusError> OpusError for E {
53    fn message(&self) -> &'static str {
54        // SAFETY: OpusError::numeric() returns valid error code and null value is handled
55        let error = unsafe {
56            let error = opus_strerror(self.numeric());
57            if error.is_null() {
58                return "Unknown error";
59            }
60            CStr::from_ptr(error)
61        };
62        error.to_str().unwrap()
63    }
64}
65
66/// Error from decoding opus data.
67#[derive(Debug, PartialEq)]
68pub struct DecoderError {
69    error_code: c_int,
70}
71
72unsafe impl RawOpusError for DecoderError {
73    fn numeric(&self) -> c_int {
74        // SAFETY: This error code was given by libopus and we trust that it is correct
75        self.error_code
76    }
77}
78
79impl core::fmt::Display for DecoderError {
80    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
81        f.write_str(self.message())
82    }
83}
84
85impl core::error::Error for DecoderError {
86    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
87        None
88    }
89}
90
91/// Invalid opus data packet encountered.
92#[derive(Debug, PartialEq)]
93pub struct InvalidPacket {}
94
95unsafe impl RawOpusError for InvalidPacket {
96    fn numeric(&self) -> c_int {
97        OPUS_INVALID_PACKET
98    }
99}
100
101impl core::fmt::Display for InvalidPacket {
102    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103        f.write_str(self.message())
104    }
105}
106
107impl core::error::Error for InvalidPacket {
108    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
109        None
110    }
111}
112
113/**
114 * Number of channels for opus decoder.
115 *
116 * Note that stereo decoders cannot be created if stereo feature has not been enabled.
117 */
118#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
119#[repr(u8)]
120pub enum Channels {
121    /// Select mono audio.
122    Mono = 1,
123    /// Select stereo audio. Samples are interleaved.
124    Stereo = 2,
125}
126
127impl Channels {
128    /// Return the number of channels.
129    pub fn channels(&self) -> u8 {
130        (*self).into()
131    }
132}
133
134/// Opus decoder.
135#[derive(Debug)]
136pub struct Decoder {
137    decoder: OpusDecoder,
138    channels: Channels,
139}
140
141/**
142 * Sampling rate.
143 *
144 * Only valid sampling rates can be presented.
145 */
146#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
147#[repr(i32)]
148pub enum SamplingRate {
149    /// 8 kHz sampling rate.
150    F8k = 8000,
151    /// 12 kHz sampling rate.
152    F12k = 12000,
153    /// 16 kHz sampling rate.
154    F16k = 16000,
155    /// 24 kHz sampling rate.
156    F24k = 24000,
157    /// 48 kHz sampling rate.
158    F48k = 48000,
159}
160
161impl SamplingRate {
162    /// Creates sampling rate that is the same or higher than the requested value up to 48 kHz.
163    pub fn closest(value: i32) -> Self {
164        use SamplingRate::*;
165        if value <= F8k.into() {
166            F8k
167        } else if value <= F12k.into() {
168            F12k
169        } else if value <= F16k.into() {
170            F16k
171        } else if value <= F24k.into() {
172            F24k
173        } else {
174            F48k
175        }
176    }
177}
178
179impl Decoder {
180    /**
181     * Construct decoder from requested sampling rate and number of channels.
182     *
183     * See also [`opus_decoder_get_size`] and [`opus_decoder_init`].
184     */
185    pub fn new(freq: SamplingRate, channels: Channels) -> Result<Self, DecoderError> {
186        if !cfg!(feature = "stereo") && channels == Channels::Stereo {
187            let error_code = OPUS_ALLOC_FAIL;
188            return Err(DecoderError { error_code });
189        }
190        let mut decoder = Decoder {
191            decoder: OpusDecoder::default(),
192            channels,
193        };
194        let channels = channels.channels().into();
195        // SAFETY: The number of channels can be only one or two as required
196        let size = unsafe { opus_decoder_get_size(channels) };
197        assert!(
198            core::mem::size_of::<OpusDecoder>() >= size.try_into().unwrap(),
199            "OpusDecoder struct is too small!"
200        );
201        // SAFETY: decoder.decoder points to a correct sized chunk of memory
202        let error_code = unsafe { opus_decoder_init(&mut decoder.decoder, freq.into(), channels) };
203        // PANIC: All error codes are small integers
204        if error_code != OPUS_OK.try_into().unwrap() {
205            Err(DecoderError { error_code })
206        } else {
207            Ok(decoder)
208        }
209    }
210
211    /**
212     * Return the number of samples in the opus data multiplied by the number of channels.
213     *
214     * This value can be used for output buffer size for decoding when total number of samples in
215     * frame is expected.
216     *
217     * See also [`Decoder::get_nb_samples`].
218     */
219    pub fn get_nb_samples_total(&self, data: &[u8]) -> Result<usize, DecoderError> {
220        match self.channels {
221            Channels::Mono => self.get_nb_samples(data),
222            Channels::Stereo => Ok(self.get_nb_samples(data)? * 2),
223        }
224    }
225
226    /**
227     * Return the number of samples in the opus data.
228     *
229     * This value can be used for audio output when frame size is expected, i.e. the number of
230     * samples per channel.
231     *
232     * See also [`opus_decoder_get_nb_samples`].
233     */
234    pub fn get_nb_samples(&self, data: &[u8]) -> Result<usize, DecoderError> {
235        // SAFETY: The pointer points to a valid slice of data or null if the slice was empty.
236        // Length is derived from the input slice
237        let samples = unsafe {
238            let len = data.len().saturating_as();
239            let data = if !data.is_empty() {
240                data.as_ptr()
241            } else {
242                core::ptr::null()
243            };
244            opus_decoder_get_nb_samples(&self.decoder, data, len)
245        };
246        if samples < 0 {
247            Err(DecoderError {
248                error_code: samples,
249            })
250        } else {
251            Ok(samples.saturating_as())
252        }
253    }
254
255    /**
256     * Decode opus packet from data into output buffer.
257     *
258     * Returns decoded frame stored on output buffer. Its length is total number of samples in a
259     * frame.
260     *
261     * ```
262     * # use opus_embedded::{Decoder, SamplingRate, Channels};
263     * # let data = [0, 0, 0, 0, 0, 0, 0];
264     * # let data = data.as_slice();
265     * let mut decoder = Decoder::new(SamplingRate::F24k, Channels::Mono).unwrap();
266     * let mut output = Vec::new();
267     * output.resize(decoder.get_nb_samples_total(data).unwrap(), 0);
268     * let output = decoder.decode(data, &mut output).unwrap();
269     * println!("Got {} samples of data in output", output.len());
270     * ```
271     *
272     * See also [`opus_decode`].
273     */
274    pub fn decode<'output>(
275        &mut self,
276        data: &[u8],
277        output: &'output mut [i16],
278    ) -> Result<&'output [i16], DecoderError> {
279        // SAFETY: The pointers point to valid slices of data or null if their respective slice was
280        // empty. Lengths are derived from the respective slices
281        let samples = unsafe {
282            let len: i32 = data.len().saturating_as();
283            let data = if !data.is_empty() {
284                data.as_ptr()
285            } else {
286                core::ptr::null()
287            };
288            // Let's calculate frame_size that will fit in the output buffer
289            let frame_size: i32 = match self.channels {
290                Channels::Mono => output.len(),
291                Channels::Stereo => output.len() / 2,
292            }
293            .saturating_as();
294            let output = if !output.is_empty() {
295                output.as_mut_ptr()
296            } else {
297                core::ptr::null_mut()
298            };
299            opus_decode(&mut self.decoder, data, len, output, frame_size, 0)
300        };
301        if samples < 0 {
302            Err(DecoderError {
303                error_code: samples,
304            })
305        } else {
306            let frame_size = match self.channels {
307                Channels::Mono => samples as usize,
308                Channels::Stereo => samples as usize * 2,
309            };
310            Ok(&output[..frame_size])
311        }
312    }
313}
314
315/// Bandwidth in the opus data.
316#[derive(Copy, Clone, Debug, PartialEq)]
317pub enum Bandwidth {
318    /// Narrowband data (4 kHz bandpass).
319    Narrowband,
320    /// Mediumband data (6 kHz bandpass).
321    Mediumband,
322    /// Wideband data (8 kHz bandpass).
323    Wideband,
324    /// Superwideband data (12 kHz bandpass).
325    Superwideband,
326    /// Fullband data (20 kHz bandpass).
327    Fullband,
328}
329
330/// Wraps opus data into a packet type.
331#[derive(Debug)]
332pub struct OpusPacket<'data> {
333    data: &'data [u8],
334}
335
336impl<'data> OpusPacket<'data> {
337    /**
338     * Construct packet from data.
339     *
340     * Does not check for validity.
341     *
342     * See also [`opus_packet_get_nb_channels`].
343     *
344     * # Panics
345     * Panics if data is an empty slice.
346     */
347    pub fn new(data: &'data [u8]) -> Self {
348        assert!(!data.is_empty());
349        Self { data }
350    }
351
352    /// Return the number of channels for the packet.
353    pub fn get_nb_channels(&self) -> Result<u8, InvalidPacket> {
354        // SAFETY: The pointer points to a valid slice of data, and the size is not zero
355        let channels = unsafe {
356            let data = self.data.as_ptr();
357            opus_packet_get_nb_channels(data)
358        };
359        if channels < 0 {
360            debug_assert_eq!(channels, OPUS_INVALID_PACKET);
361            Err(InvalidPacket {})
362        } else {
363            Ok(channels.saturating_as())
364        }
365    }
366
367    /**
368     * Return the number of frames for the packet.
369     *
370     * See also [`opus_packet_get_nb_frames`].
371     */
372    pub fn get_nb_frames(&self) -> Result<u32, InvalidPacket> {
373        // SAFETY: The pointer points to a valid slice of data, the length is derived from the
374        // slice and the slice is not empty
375        let frames = unsafe {
376            let len = self.data.len().saturating_as();
377            let data = self.data.as_ptr();
378            opus_packet_get_nb_frames(data, len)
379        };
380        if frames < 0 {
381            debug_assert_eq!(frames, OPUS_INVALID_PACKET);
382            Err(InvalidPacket {})
383        } else {
384            Ok(frames.saturating_as())
385        }
386    }
387
388    /**
389     * Return the bandwidth of the packet.
390     *
391     * See also [`opus_packet_get_bandwidth`].
392     */
393    pub fn get_bandwidth(&self) -> Result<Bandwidth, InvalidPacket> {
394        // SAFETY: The pointer points to a valid slice of data, and the size is not zero
395        let bandwidth = unsafe {
396            let data = self.data.as_ptr();
397            opus_packet_get_bandwidth(data)
398        };
399        if bandwidth < 0 {
400            debug_assert_eq!(bandwidth, OPUS_INVALID_PACKET);
401            Err(InvalidPacket {})
402        } else {
403            use Bandwidth::*;
404            // PANIC: All bandwidth values are small positive integers
405            #[allow(non_snake_case)]
406            Ok(match bandwidth.try_into().unwrap() {
407                OPUS_BANDWIDTH_NARROWBAND => Narrowband,
408                OPUS_BANDWIDTH_MEDIUMBAND => Mediumband,
409                OPUS_BANDWIDTH_WIDEBAND => Wideband,
410                OPUS_BANDWIDTH_SUPERWIDEBAND => Superwideband,
411                OPUS_BANDWIDTH_FULLBAND => Fullband,
412                _ => panic!("Invalid bandwidth value returned by libopus"),
413            })
414        }
415    }
416
417    /**
418     * Return the number of sampels per frame in the packet.
419     *
420     * See also [`opus_packet_get_samples_per_frame`].
421     */
422    pub fn get_samples_per_frame(&self) -> Result<u32, InvalidPacket> {
423        // SAFETY: The pointer points to a valid slice of data, the length is derived from the
424        // slice and the slice is not empty
425        let samples = unsafe {
426            let len = self.data.len().saturating_as();
427            let data = self.data.as_ptr();
428            opus_packet_get_samples_per_frame(data, len)
429        };
430        if samples < 0 {
431            debug_assert_eq!(samples, OPUS_INVALID_PACKET);
432            Err(InvalidPacket {})
433        } else {
434            Ok(samples.saturating_as())
435        }
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    extern crate alloc;
442    use super::*;
443    use alloc::string::ToString;
444    use core::error::Error;
445
446    #[test]
447    fn create_decoder() {
448        let decoder = Decoder::new(SamplingRate::F8k, Channels::Mono);
449        assert!(decoder.is_ok());
450    }
451
452    #[test]
453    fn create_decoder_stereo() {
454        let decoder = Decoder::new(SamplingRate::F16k, Channels::Stereo);
455        if cfg!(feature = "stereo") {
456            assert!(decoder.is_ok());
457        } else {
458            assert!(decoder.is_err());
459            assert_eq!(decoder.unwrap_err().numeric(), OPUS_ALLOC_FAIL);
460        }
461    }
462
463    #[test]
464    fn sampling_rate() {
465        assert_eq!(SamplingRate::closest(8_000), SamplingRate::F8k);
466        assert_eq!(SamplingRate::closest(12_000), SamplingRate::F12k);
467        assert_eq!(SamplingRate::closest(16_000), SamplingRate::F16k);
468        assert_eq!(SamplingRate::closest(24_000), SamplingRate::F24k);
469        assert_eq!(SamplingRate::closest(48_000), SamplingRate::F48k);
470    }
471
472    #[test]
473    fn sampling_rate_from_primitive() {
474        assert!(SamplingRate::try_from(1_000).is_err());
475        assert!(SamplingRate::try_from(8_000).is_ok());
476        assert!(SamplingRate::try_from(12_000).is_ok());
477        assert!(SamplingRate::try_from(16_000).is_ok());
478        assert!(SamplingRate::try_from(24_000).is_ok());
479        assert!(SamplingRate::try_from(48_000).is_ok());
480        assert!(SamplingRate::try_from(64_000).is_err());
481    }
482
483    #[test]
484    fn channels_from_primitive() {
485        assert!(Channels::try_from(0).is_err());
486        assert!(Channels::try_from(1).is_ok());
487        assert!(Channels::try_from(2).is_ok());
488        for channels in 3..=255 {
489            assert!(Channels::try_from(channels).is_err());
490        }
491    }
492
493    #[test]
494    fn test_decoder_with_zero_length_packet() {
495        // NB: Error strings depend on libopus internal error messages.
496        const DATA: [u8; 0] = [0u8; 0];
497        let mut decoder = Decoder::new(SamplingRate::F8k, Channels::Mono).unwrap();
498        let result = decoder.get_nb_samples(&DATA);
499        assert_eq!(
500            result,
501            Err(DecoderError {
502                error_code: OPUS_BAD_ARG
503            })
504        );
505        let error = result.unwrap_err();
506        assert_eq!(error.numeric(), OPUS_BAD_ARG);
507        assert!(error.source().is_none());
508        assert_eq!(error.to_string(), "invalid argument");
509        // Passing empty slice (-> null) is a valid input for decoding
510        let mut output = [0i16; 100];
511        let result = decoder.decode(&DATA, &mut output);
512        assert_eq!(result.unwrap().len(), output.len());
513        for v in output {
514            assert_eq!(v, 0);
515        }
516        // However empty slice for output is not
517        let mut output = [0i16; 0];
518        let result = decoder.decode(&[0, 0, 0, 0, 0], &mut output);
519        assert_eq!(
520            result,
521            Err(DecoderError {
522                error_code: OPUS_BAD_ARG
523            })
524        );
525        let error = result.unwrap_err();
526        assert_eq!(error.numeric(), OPUS_BAD_ARG);
527        assert!(error.source().is_none());
528        assert_eq!(error.to_string(), "invalid argument");
529    }
530
531    #[test]
532    fn test_decoder_with_zero_packet() {
533        const DATA: [u8; 8] = [0x00u8; 8];
534        let mut decoder = Decoder::new(SamplingRate::F8k, Channels::Mono).unwrap();
535        assert_eq!(decoder.get_nb_samples(&DATA), Ok(80));
536        let mut output = [0i16; 80];
537        assert_eq!(decoder.decode(&DATA, &mut output).unwrap().len(), 80);
538    }
539
540    #[test]
541    fn test_decoder_with_0xff_packet() {
542        const DATA: [u8; 8] = [0xffu8; 8];
543        let mut decoder = Decoder::new(SamplingRate::F8k, Channels::Mono).unwrap();
544        let result = decoder.get_nb_samples(&DATA);
545        assert_eq!(
546            result,
547            Err(DecoderError {
548                error_code: OPUS_INVALID_PACKET
549            })
550        );
551        let error = result.unwrap_err();
552        assert_eq!(error.numeric(), OPUS_INVALID_PACKET);
553        assert!(error.source().is_none());
554        assert_eq!(error.to_string(), "corrupted stream");
555        let mut output = [0i16; 80];
556        let result = decoder.decode(&DATA, &mut output);
557        assert_eq!(
558            result,
559            Err(DecoderError {
560                error_code: OPUS_INVALID_PACKET
561            })
562        );
563        let error = result.unwrap_err();
564        assert_eq!(error.numeric(), OPUS_INVALID_PACKET);
565        assert!(error.source().is_none());
566        assert_eq!(error.to_string(), "corrupted stream");
567    }
568
569    #[test]
570    #[should_panic]
571    fn test_zero_length_packet() {
572        const DATA: [u8; 0] = [0u8; 0];
573        let _packet = OpusPacket::new(&DATA);
574    }
575
576    #[test]
577    fn test_zero_packet() {
578        let data = [0x00u8; 8];
579        let packet = OpusPacket::new(&data);
580        assert_eq!(packet.get_nb_channels(), Ok(1));
581        assert_eq!(packet.get_nb_frames(), Ok(1));
582        assert_eq!(packet.get_bandwidth(), Ok(Bandwidth::Narrowband));
583        assert_eq!(packet.get_samples_per_frame(), Ok(0));
584    }
585
586    #[test]
587    fn test_0xff_packet() {
588        let data = [0xFFu8; 8];
589        let packet = OpusPacket::new(&data);
590        assert_eq!(packet.get_nb_channels(), Ok(2));
591        assert_eq!(packet.get_nb_frames(), Ok(63));
592        assert_eq!(packet.get_bandwidth(), Ok(Bandwidth::Fullband));
593        assert_eq!(packet.get_samples_per_frame(), Ok(0));
594    }
595
596    #[test]
597    fn test_one_length_packet() {
598        // Something that returns OPUS_INVALID_PACKET
599        let packet = OpusPacket::new(&[0xff]);
600        assert_eq!(packet.get_nb_channels(), Ok(2));
601        let result = packet.get_nb_frames();
602        assert_eq!(result, Err(InvalidPacket {}));
603        let error = result.unwrap_err();
604        assert_eq!(error.numeric(), OPUS_INVALID_PACKET);
605        assert!(error.source().is_none());
606        assert_eq!(error.to_string(), "corrupted stream");
607        assert_eq!(packet.get_bandwidth(), Ok(Bandwidth::Fullband));
608        assert_eq!(packet.get_samples_per_frame(), Ok(0));
609    }
610
611    #[test]
612    fn test_packet_bandwidths() {
613        // Just tests that all values can appear
614        let packet = OpusPacket::new(&[0x00]);
615        assert_eq!(packet.get_bandwidth(), Ok(Bandwidth::Narrowband));
616        let packet = OpusPacket::new(&[0x20]);
617        assert_eq!(packet.get_bandwidth(), Ok(Bandwidth::Mediumband));
618        let packet = OpusPacket::new(&[0xB0]);
619        assert_eq!(packet.get_bandwidth(), Ok(Bandwidth::Wideband));
620        let packet = OpusPacket::new(&[0xC0]);
621        assert_eq!(packet.get_bandwidth(), Ok(Bandwidth::Superwideband));
622        let packet = OpusPacket::new(&[0xF0]);
623        assert_eq!(packet.get_bandwidth(), Ok(Bandwidth::Fullband));
624    }
625}