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