oggopus_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 ogg parser for mono and stereo opus audio.
7 *
8 * While this tries to follow the RFCs to the maximum extent reasonable, this is not suitable as
9 * general purpose ogg opus parser and you should never use this for untrusted inputs. This was
10 * built for parsing opus data from internal flash as part of an embedded system. You will want to
11 * use something else for anything more powerful than that.
12 *
13 * See also [RFC3533](https://datatracker.ietf.org/doc/html/rfc3533)
14 * and [RFC7845](https://datatracker.ietf.org/doc/html/rfc7845).
15 *
16 * # Limitations
17 * - Supports only one logical stream at a time. Grouping is not supported.
18 * - Mixing (interleaving or otherwise) other types of streams than opus is not supported.
19 * - This parses ID header and ignores comment header.
20 * - This does not validate CRC or handle missing packets.
21 * - Seeking is not supported.
22 * - Parsing of [RFC8486](https://datatracker.ietf.org/doc/html/rfc8486) family channel mappings is not supported.
23 */
24
25#![cfg_attr(not(test), no_std)]
26#![deny(missing_docs)]
27
28mod container;
29pub mod opus;
30
31pub use container::{OggError, Packet, Packets};
32pub use opus::ChannelMapping;
33pub use states::Either;
34
35pub mod prelude {
36    /*!
37     * oggopus_embedded prelude.
38     *
39     * Includes the most commonly needed types.
40     *
41     * ```
42     * # #![allow(unused_imports)]
43     * use oggopus_embedded::prelude::*;
44     * ```
45     */
46
47    pub use super::{Bitstream, ChannelMapping, Either};
48}
49
50/// Error values for formatting.
51#[derive(Debug, PartialEq)]
52#[doc(hidden)]
53#[non_exhaustive]
54pub enum ErrorValues {
55    UnexpectedSequenceNumber(u32),
56    SequenceNumberMismatch(u32, u32),
57}
58
59impl core::fmt::Display for ErrorValues {
60    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
61        use ErrorValues::*;
62        match &self {
63            UnexpectedSequenceNumber(number) => f.write_fmt(format_args!(
64                "unexpected page sequence number in header: {}",
65                number
66            )),
67            SequenceNumberMismatch(previous, current) => f.write_fmt(format_args!(
68                "page sequence numbers are not sequential, previous: {}, current: {}",
69                previous, current,
70            )),
71        }
72    }
73}
74
75/// Error from parsing bitstream.
76#[derive(Debug, PartialEq)]
77pub enum BitstreamError {
78    /// Error from parsing ogg container.
79    OggError(OggError),
80    /// Error from parsing opus data within ogg container.
81    OpusError(opus::OpusError),
82    /// Invalid ogg stream encountered.
83    InvalidOggStream(ErrorValues),
84    /// Invalid opus stream encountered.
85    InvalidOpusStream(&'static str),
86    /// Unsupported opus version encountered. Indicates requested version.
87    UnsupportedOpusVersion(u8),
88    /**
89     * Unsupported ogg opus stream encountered. Enabled features may affect this.
90     *
91     * See also [`OpusError::UnsupportedStream`][`opus::OpusError::UnsupportedStream`]
92     * and [`OggError::UnsupportedStream`].
93     */
94    UnsupportedStream(&'static str),
95    /**
96     * Stream is not an opus stream but something else.
97     *
98     * See also [`OpusError::NotOpusStream`][`opus::OpusError::NotOpusStream`].
99     */
100    NotOpusStream,
101}
102
103impl core::fmt::Display for BitstreamError {
104    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105        use BitstreamError::*;
106        match self {
107            OggError(error) => error.fmt(f),
108            OpusError(error) => error.fmt(f),
109            InvalidOggStream(error) => {
110                f.write_str("invalid ogg stream: ")?;
111                error.fmt(f)
112            }
113            InvalidOpusStream(error) => f.write_str(error),
114            UnsupportedOpusVersion(version) => {
115                f.write_fmt(format_args!("unsupported Opus version: {}", version))
116            }
117            UnsupportedStream(error) => f.write_str(error),
118            NotOpusStream => f.write_str("this is not an Opus stream"),
119        }
120    }
121}
122
123impl core::error::Error for BitstreamError {
124    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
125        use BitstreamError::*;
126        match self {
127            OggError(error) => Some(error),
128            OpusError(error) => Some(error),
129            _ => None,
130        }
131    }
132}
133
134impl From<OggError> for BitstreamError {
135    fn from(error: OggError) -> BitstreamError {
136        match error {
137            OggError::UnsupportedStream(error) => Self::UnsupportedStream(error),
138            OggError::InvalidStream(error) => Self::InvalidOggStream(error),
139            _ => Self::OggError(error),
140        }
141    }
142}
143
144impl From<opus::OpusError> for BitstreamError {
145    fn from(error: opus::OpusError) -> BitstreamError {
146        match error {
147            opus::OpusError::UnsupportedStream(error) => Self::UnsupportedStream(error),
148            opus::OpusError::NotOpusStream => Self::NotOpusStream,
149            _ => Self::OpusError(error),
150        }
151    }
152}
153
154/// Result of parsing bitstream.
155pub type Result<'data, T> = core::result::Result<T, BitstreamError>;
156
157/// Ogg opus bitstream.
158#[derive(Debug)]
159pub struct Bitstream<'data> {
160    data: &'data [u8],
161}
162
163impl<'data> Bitstream<'data> {
164    /**
165     * Construct new [`Bitstream`] for constant data.
166     */
167    pub const fn new(data: &'data [u8]) -> Self {
168        Self { data }
169    }
170
171    /**
172     * Create [`BitstreamReader`] to parse [`Bitstream`].
173     *
174     * Returns [`BitstreamReader`] that is positioned at the beginning of a stream.
175     */
176    pub fn reader<'bs>(&'bs self) -> BitstreamReader<'bs, 'data, states::Beginning> {
177        BitstreamReader::<'bs, 'data, states::Beginning>::new(self)
178    }
179}
180
181pub mod states {
182    //! [`BitstreamReader`][`super::BitstreamReader`] states.
183
184    mod sealed {
185        pub trait Sealed {}
186
187        impl Sealed for super::Beginning {}
188        impl Sealed for super::InStream {}
189        impl Sealed for super::EndOfStream {}
190    }
191
192    /// [`BitstreamReader`][`super::BitstreamReader`] is at the beginning of parsing bitstream.
193    #[derive(Debug, PartialEq)]
194    pub struct Beginning;
195    /// [`BitstreamReader`][`super::BitstreamReader`] has parsed headers and is ready to return opus data.
196    #[derive(Debug, PartialEq)]
197    pub struct InStream {
198        // TODO: Allow selecting bitstream
199        // TODO: Allow having multiple bitstreams in the same file but reading only one
200        /// Serial number of the bitstream.
201        pub bitstream_serial_number: u32,
202        /// Page sequence number of the last read page.
203        pub page_sequence_number: u32,
204    }
205    /// [`BitstreamReader`][`super::BitstreamReader`] has completed stream parsing.
206    #[derive(Debug, PartialEq)]
207    pub struct EndOfStream;
208
209    /// State trait for [`BitstreamReader`][`super::BitstreamReader`]. Sealed.
210    pub trait ReaderState: sealed::Sealed {}
211
212    impl ReaderState for Beginning {}
213    impl ReaderState for InStream {}
214    impl ReaderState for EndOfStream {}
215
216    /// Either state may be returned.
217    #[derive(Debug, PartialEq)]
218    pub enum Either<A, B> {
219        /// Parsing can continue.
220        Continued(A),
221        /// Parsing has reached the end of the stream.
222        Ended(B),
223    }
224}
225
226use states::{Beginning, EndOfStream, InStream, ReaderState};
227
228/// Header with reader for the stream or stream ended.
229pub type EitherHeaderOrEnded<'bs, 'data> = (
230    Either<BitstreamReader<'bs, 'data, InStream>, BitstreamReader<'bs, 'data, EndOfStream>>,
231    opus::OpusHeader,
232);
233
234/// Packets with reader for the stream or stream ended.
235pub type EitherPacketsOrEnded<'bs, 'data, const BUFFER_SIZE: usize> = (
236    Either<BitstreamReader<'bs, 'data, InStream>, BitstreamReader<'bs, 'data, EndOfStream>>,
237    Packets<'data, BUFFER_SIZE>,
238);
239
240/// Reader for [`Bitstream`].
241#[derive(Debug, PartialEq)]
242pub struct BitstreamReader<'bs, 'data: 'bs, S: ReaderState> {
243    bitstream: core::marker::PhantomData<&'bs Bitstream<'data>>,
244    remaining: &'data [u8],
245    marker: S,
246}
247
248impl<S: ReaderState> BitstreamReader<'_, '_, S> {
249    /**
250     * Construct [`BitstreamReader`] for [`Bitstream`].
251     *
252     * ```ignore
253     * use oggopus_embedded::Bitstream;
254     * let stream = Bitstream::new(include_bytes!("audio.opus"));
255     * ```
256     */
257    pub fn new<'bs, 'data>(
258        bitstream: &'bs Bitstream<'data>,
259    ) -> BitstreamReader<'bs, 'data, Beginning> {
260        BitstreamReader {
261            bitstream: core::marker::PhantomData::<_>,
262            remaining: bitstream.data,
263            marker: Beginning,
264        }
265    }
266}
267
268impl<'bs, 'data> BitstreamReader<'bs, 'data, Beginning> {
269    /**
270     * Read a header packet from [`Bitstream`].
271     *
272     * Also skips the comments packet and returs [`BitstreamReader`] that can read the following opus packets.
273     *
274     * ```rust
275     * # use oggopus_embedded::{Bitstream, opus::ChannelMapping};
276     * # let data = include_bytes!("test/mono.opus");
277     * # let stream = Bitstream::new(data);
278     * let reader = stream.reader();
279     * let (reader, header) = reader.read_header().unwrap();
280     * if let ChannelMapping::Family0 { channels } = header.channels {
281     *     println!(
282     *         "{} channels at {} Hz with pre skip of {}",
283     *         channels, header.sample_rate, header.pre_skip
284     *     );
285     * }
286     * ```
287     */
288    pub fn read_header(self) -> Result<'data, EitherHeaderOrEnded<'bs, 'data>> {
289        use BitstreamError::*;
290        let BitstreamReader {
291            bitstream,
292            remaining,
293            ..
294        } = self;
295        let (remaining, mut packets) = Packets::<30>::parse(remaining)?;
296        let bitstream_serial_number = packets.bitstream_serial_number();
297        let page_sequence_number = packets.current_page_sequence_number();
298        if page_sequence_number != 0 {
299            return Err(InvalidOggStream(ErrorValues::UnexpectedSequenceNumber(
300                page_sequence_number,
301            )));
302        }
303        if let Some(packet) = packets.next() {
304            let header = opus::OpusHeader::parse(packet.data)?;
305            if header.version > 15 {
306                return Err(UnsupportedOpusVersion(header.version));
307            }
308            if packets.next().is_some() {
309                return Err(InvalidOpusStream("unexpected segment after header"));
310            }
311            let (remaining, last_page) = container::Page::skip(remaining)?;
312            if last_page.bitstream_serial_number() != bitstream_serial_number {
313                return Err(UnsupportedStream(
314                    "bitstream serial number changed unexpectedly",
315                ));
316            }
317            Ok((
318                Either::Continued(BitstreamReader {
319                    bitstream,
320                    remaining,
321                    marker: InStream {
322                        bitstream_serial_number,
323                        page_sequence_number: last_page.page_sequence_number(),
324                    },
325                }),
326                header,
327            ))
328        } else {
329            Err(InvalidOpusStream("missing header"))
330        }
331    }
332}
333
334impl<'bs, 'data> BitstreamReader<'bs, 'data, InStream> {
335    /**
336     * Read next packets from Bitstream.
337     *
338     * Returns also the next [`BitstreamReader`] to read further content.
339     *
340     * ```rust
341     * # use oggopus_embedded::{Bitstream, EitherHeaderOrEnded, EitherPacketsOrEnded, opus::ChannelMapping, states::Either};
342     * # let data = include_bytes!("test/mono.opus");
343     * # let stream = Bitstream::new(data);
344     * # let reader = stream.reader();
345     * # let (reader, _header) = reader.read_header().unwrap();
346     * # let channels = 1;
347     * # let sample_rate = 16_000;
348     * let mut reader = match reader {
349     *     Either::Continued(reader) => reader,
350     *     _ => panic!("No more data"),
351     * };
352     * loop {
353     *     let (new_reader, mut packets) = reader.next_packets::<1_024>().unwrap();
354     *     while let Some(packet) = packets.next() {
355     *         // Decode or whatever you need to do here
356     *         println!("Got {} bytes of opus data", packet.data.len());
357     *     }
358     *     match new_reader {
359     *         Either::Continued(new_reader) => {
360     *             // Prepare for the next loop
361     *             reader = new_reader;
362     *         }
363     *         Either::Ended(_reader) => {
364     *             break; // You can also expect the next stream to start here
365     *         }
366     *     }
367     * }
368     * ```
369     */
370    pub fn next_packets<const BUFFER_SIZE: usize>(
371        &self,
372    ) -> Result<'data, EitherPacketsOrEnded<'bs, 'data, BUFFER_SIZE>> {
373        use BitstreamError::*;
374        let (remaining, packets) = Packets::parse(self.remaining)?;
375        if self.marker.bitstream_serial_number != packets.bitstream_serial_number() {
376            return Err(UnsupportedStream(
377                "bitstream serial number changed unexpectedly",
378            ));
379        }
380        if packets.current_page_sequence_number() != self.marker.page_sequence_number + 1 {
381            return Err(InvalidOggStream(ErrorValues::SequenceNumberMismatch(
382                self.marker.page_sequence_number,
383                packets.current_page_sequence_number(),
384            )));
385        }
386        if !packets.end_of_stream() {
387            Ok((
388                Either::Continued(BitstreamReader {
389                    bitstream: self.bitstream,
390                    remaining,
391                    marker: InStream {
392                        bitstream_serial_number: self.marker.bitstream_serial_number,
393                        page_sequence_number: packets.last_page_sequence_number(),
394                    },
395                }),
396                packets,
397            ))
398        } else {
399            Ok((
400                Either::Ended(BitstreamReader {
401                    bitstream: self.bitstream,
402                    remaining,
403                    marker: EndOfStream,
404                }),
405                packets,
406            ))
407        }
408    }
409}
410
411impl<'bs, 'data> BitstreamReader<'bs, 'data, EndOfStream> {
412    /**
413     * Return whether there is more data to read.
414     */
415    pub fn has_more(&self) -> bool {
416        !self.remaining.is_empty()
417    }
418
419    /**
420     * Get next reader for more data if there is any.
421     *
422     * ```rust
423     * # use oggopus_embedded::{Bitstream, EitherHeaderOrEnded, EitherPacketsOrEnded, opus::ChannelMapping, states::Either};
424     * # let data = include_bytes!("test/mono.opus");
425     * # let stream = Bitstream::new(data);
426     * # let (reader, _header) = stream.reader().read_header().unwrap();
427     * # let Either::Continued(mut reader) = reader
428     * # else { panic!("Data endded abruptly"); };
429     * # let channels = 1;
430     * # let sample_rate = 16_000;
431     * loop {
432     *     let (new_reader, _packets) = reader.next_packets::<1_024>().unwrap();
433     *     // ...
434     *     match new_reader {
435     *         Either::Continued(new_reader) => {
436     *             reader = new_reader;
437     *         }
438     *         Either::Ended(old_reader) => {
439     *             if let Some(new_reader) = old_reader.next_reader() {
440     *                 // Reinitialize decoding and continue looping
441     *                 let (new_reader, header) = new_reader.read_header().unwrap();
442     *                 if let Either::Continued(reader) = new_reader {
443     *                 }
444     *             } else {
445     *                 break;
446     *             }
447     *         }
448     *     }
449     * }
450     */
451    pub fn next_reader(self) -> Option<BitstreamReader<'bs, 'data, Beginning>> {
452        if self.has_more() {
453            Some(BitstreamReader {
454                bitstream: core::marker::PhantomData::<_>,
455                remaining: self.remaining,
456                marker: Beginning,
457            })
458        } else {
459            None
460        }
461    }
462}
463
464#[cfg(test)]
465mod test {
466    use super::*;
467    use core::error::Error;
468
469    #[test]
470    fn parse_mono() {
471        const DATA: &[u8] = include_bytes!("test/mono.opus");
472        let bitstream = Bitstream::new(DATA);
473        let reader = bitstream.reader();
474        let (either, header) = reader.read_header().unwrap();
475        assert_eq!(
476            header.channels,
477            opus::ChannelMapping::Family0 { channels: 1 }
478        );
479        // Parsing a little bit to improve coverage as llvm-cov cannot run doctests without nightly
480        if let Either::Continued(reader) = either {
481            let (either, mut packets) = reader.next_packets::<512>().unwrap();
482            let result = packets.next();
483            assert!(result.is_some());
484            if let Either::Ended(reader) = either {
485                assert!(!reader.has_more());
486                assert!(reader.next_reader().is_none());
487            }
488        } else {
489            panic!("Unexpected end of stream in test");
490        }
491    }
492
493    #[test]
494    fn parse_stereo() {
495        const DATA: &[u8] = include_bytes!("test/stereo.opus");
496        let bitstream = Bitstream::new(DATA);
497        let reader = bitstream.reader();
498        let (_either, header) = reader.read_header().unwrap();
499        assert_eq!(
500            header.channels,
501            opus::ChannelMapping::Family0 { channels: 2 }
502        );
503    }
504
505    #[test]
506    fn parse_vorbis() {
507        const DATA: &[u8] = include_bytes!("test/vorbis.ogg");
508        let bitstream = Bitstream::new(DATA);
509        let reader = bitstream.reader();
510        let result = reader.read_header();
511        assert_eq!(result, Err(BitstreamError::NotOpusStream));
512        let error = result.unwrap_err();
513        assert!(error.source().is_none());
514        assert_eq!(error.to_string(), "this is not an Opus stream");
515    }
516
517    #[test]
518    fn parse_invalid_stream() {
519        const DATA: &[u8] = &[0, 0, 0, 0];
520        let bitstream = Bitstream::new(DATA);
521        let reader = bitstream.reader();
522        let result = reader.read_header();
523        assert_eq!(
524            result,
525            Err(BitstreamError::OggError(OggError::NotOggStream))
526        );
527        let error = result.unwrap_err();
528        assert!(error.source().is_some());
529        assert_eq!(error.to_string(), "this is not an ogg stream");
530    }
531
532    #[test]
533    fn parse_unexpected_sequence_number() {
534        let mut data = Vec::from(include_bytes!("test/mono.opus"));
535        data[0x12] = 1;
536        let bitstream = Bitstream::new(&data);
537        let reader = bitstream.reader();
538        let result = reader.read_header();
539        assert_eq!(
540            result,
541            Err(BitstreamError::InvalidOggStream(
542                ErrorValues::UnexpectedSequenceNumber(1)
543            ))
544        );
545        let error = result.unwrap_err();
546        assert_eq!(
547            error.to_string(),
548            "invalid ogg stream: unexpected page sequence number in header: 1"
549        );
550    }
551
552    #[test]
553    fn parse_unsupported_ogg_version() {
554        let mut data = Vec::from(include_bytes!("test/mono.opus"));
555        data[4] = 2;
556        let bitstream = Bitstream::new(&data);
557        let reader = bitstream.reader();
558        let result = reader.read_header();
559        assert_eq!(
560            result,
561            Err(BitstreamError::OggError(OggError::UnsupportedVersion(2)))
562        );
563        let error = result.unwrap_err();
564        assert_eq!(error.to_string(), "unsupported ogg version: 2");
565    }
566
567    #[test]
568    fn parse_unsupported_opus_version() {
569        let mut data = Vec::from(include_bytes!("test/mono.opus"));
570        data[0x24] = 0x10;
571        let bitstream = Bitstream::new(&data);
572        let reader = bitstream.reader();
573        let result = reader.read_header();
574        assert_eq!(result, Err(BitstreamError::UnsupportedOpusVersion(16)));
575        let error = result.unwrap_err();
576        assert_eq!(error.to_string(), "unsupported Opus version: 16");
577    }
578}