Skip to main content

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