opus_mux/
lib.rs

1//! Implementation of RFC 7845 demultiplexing of an Opus stream from an Ogg container
2#![no_std]
3
4extern crate alloc;
5#[cfg(feature = "std")]
6extern crate std;
7
8mod ogg;
9
10use alloc::boxed::Box;
11use core::{convert::TryInto, fmt};
12
13pub struct Demuxer {
14    // Page decoding state
15    stream: ogg::Stream,
16
17    // Packet decoding state
18    header: Option<InternalHeader>,
19    tags: Option<Box<[u8]>>,
20}
21
22impl Demuxer {
23    pub fn new() -> Self {
24        Self {
25            stream: ogg::Stream::new(),
26
27            header: None,
28            tags: None,
29        }
30    }
31
32    /// Consume a block of Ogg container data
33    ///
34    /// After a call to `push`, other methods may begin returning `Some`.
35    pub fn push(&mut self, data: &[u8]) -> Result<()> {
36        self.stream.push(data);
37        'next_page: loop {
38            let mut page = match self.stream.next() {
39                None => {
40                    break;
41                }
42                Some(x) => x,
43            };
44            let page_header = page.header();
45
46            if self.header.is_none() {
47                loop {
48                    let packet = match page.next() {
49                        None => continue 'next_page,
50                        Some(x) => x,
51                    };
52
53                    // Read packets until we get the header or run out
54                    const ID_MAGIC: &[u8; 8] = b"OpusHead";
55                    if page_header.bos {
56                        // Is this an Opus ogg encapsulation v1-compatible stream?
57                        if !packet.starts_with(ID_MAGIC)
58                            || packet.get(8).ok_or(Error::Malformed)? & 0xF0 != 0
59                        {
60                            continue;
61                        }
62                        self.header = Some(InternalHeader {
63                            serial: page_header.stream_serial,
64                            header: Header {
65                                channels: packet[9],
66                                pre_skip: u16::from_le_bytes(
67                                    packet
68                                        .get(10..12)
69                                        .ok_or(Error::Malformed)?
70                                        .try_into()
71                                        .unwrap(),
72                                ),
73                                output_gain: i16::from_le_bytes(
74                                    packet
75                                        .get(16..18)
76                                        .ok_or(Error::Malformed)?
77                                        .try_into()
78                                        .unwrap(),
79                                ),
80                            },
81                        });
82                        break;
83                    }
84                }
85            }
86
87            if self.tags.is_none() {
88                loop {
89                    let packet = match page.next() {
90                        None => continue 'next_page,
91                        Some(x) => x,
92                    };
93
94                    // Read packets until we get the tags or run out
95                    const COMMENT_MAGIC: &[u8; 8] = b"OpusTags";
96                    if Some(page_header.stream_serial) != self.header.as_ref().map(|x| x.serial) {
97                        continue;
98                    }
99                    if packet.starts_with(COMMENT_MAGIC) {
100                        self.tags = Some(packet.into());
101                        break;
102                    }
103                }
104            }
105
106            break;
107        }
108
109        Ok(())
110    }
111
112    /// Access the decoded `Header`, if available
113    #[inline]
114    pub fn header(&self) -> Option<&Header> {
115        self.header.as_ref().map(|x| &x.header)
116    }
117
118    /// Extract the Opus tags, if available. Will not return `Some` before `header` does.
119    #[inline]
120    pub fn tags(&self) -> Option<&[u8]> {
121        self.tags.as_deref()
122    }
123
124    /// Extract a block of Opus stream data, if available. Will not return `Some` before `header` or
125    /// `tags` do.
126    pub fn next(&mut self) -> Option<&[u8]> {
127        let header = match (&self.header, &self.tags) {
128            (&Some(ref h), &Some(_)) => h,
129            _ => return None,
130        };
131        loop {
132            let page = self.stream.next()?;
133            if page.header().stream_serial != header.serial {
134                continue;
135            }
136            if let Some(packet) = page.into_next() {
137                // Hack around bad lifetime check: https://github.com/rust-lang/rust/issues/54663
138                unsafe {
139                    return Some(core::mem::transmute::<&[u8], &[u8]>(packet));
140                }
141            }
142        }
143    }
144}
145
146impl Default for Demuxer {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152struct InternalHeader {
153    serial: u32,
154    header: Header,
155}
156
157#[derive(Debug, Copy, Clone)]
158pub struct Header {
159    /// Number of channels
160    pub channels: u8,
161    /// Number of samples to discard from the decoder output when starting playback
162    pub pre_skip: u16,
163    /// Encoded gain to be applied when decoding. To decode into an amplitude scaling factor,
164    /// compute `10.0.powf(f32::from(output_gain)/(20.0*256.0))`.
165    pub output_gain: i16,
166}
167
168pub type Result<T> = core::result::Result<T, Error>;
169
170#[derive(Debug, Clone)]
171pub enum Error {
172    Malformed,
173}
174
175impl fmt::Display for Error {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        f.pad("malformed container")
178    }
179}
180
181#[cfg(feature = "std")]
182impl std::error::Error for Error {}