#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(test), no_std)]
#![deny(missing_docs)]
mod container;
pub mod opus;
pub use container::{OggError, Packet, Packets};
pub use opus::ChannelMapping;
pub use states::Either;
pub mod prelude {
pub use super::{Bitstream, ChannelMapping, Either};
}
#[derive(Debug, PartialEq)]
#[doc(hidden)]
#[non_exhaustive]
pub enum ErrorValues {
UnexpectedSequenceNumber(u32),
SequenceNumberMismatch(u32, u32),
}
impl core::fmt::Display for ErrorValues {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use ErrorValues::*;
match &self {
UnexpectedSequenceNumber(number) => f.write_fmt(format_args!(
"unexpected page sequence number in header: {}",
number
)),
SequenceNumberMismatch(previous, current) => f.write_fmt(format_args!(
"page sequence numbers are not sequential, previous: {}, current: {}",
previous, current,
)),
}
}
}
#[derive(Debug, PartialEq)]
pub enum BitstreamError {
OggError(OggError),
OpusError(opus::OpusError),
InvalidOggStream(ErrorValues),
InvalidOpusStream(&'static str),
UnsupportedOpusVersion(u8),
UnsupportedStream(&'static str),
NotOpusStream,
}
impl core::fmt::Display for BitstreamError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use BitstreamError::*;
match self {
OggError(error) => error.fmt(f),
OpusError(error) => error.fmt(f),
InvalidOggStream(error) => {
f.write_str("invalid ogg stream: ")?;
error.fmt(f)
}
InvalidOpusStream(error) => f.write_str(error),
UnsupportedOpusVersion(version) => {
f.write_fmt(format_args!("unsupported Opus version: {}", version))
}
UnsupportedStream(error) => f.write_str(error),
NotOpusStream => f.write_str("this is not an Opus stream"),
}
}
}
impl core::error::Error for BitstreamError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
use BitstreamError::*;
match self {
OggError(error) => Some(error),
OpusError(error) => Some(error),
_ => None,
}
}
}
impl From<OggError> for BitstreamError {
fn from(error: OggError) -> BitstreamError {
match error {
OggError::UnsupportedStream(error) => Self::UnsupportedStream(error),
OggError::InvalidStream(error) => Self::InvalidOggStream(error),
_ => Self::OggError(error),
}
}
}
impl From<opus::OpusError> for BitstreamError {
fn from(error: opus::OpusError) -> BitstreamError {
match error {
opus::OpusError::UnsupportedStream(error) => Self::UnsupportedStream(error),
opus::OpusError::NotOpusStream => Self::NotOpusStream,
_ => Self::OpusError(error),
}
}
}
pub type Result<'data, T> = core::result::Result<T, BitstreamError>;
#[derive(Debug)]
pub struct Bitstream<'data> {
data: &'data [u8],
}
impl<'data> Bitstream<'data> {
pub const fn new(data: &'data [u8]) -> Self {
Self { data }
}
pub fn reader<'bs>(&'bs self) -> BitstreamReader<'bs, 'data, states::Beginning> {
BitstreamReader::<'bs, 'data, states::Beginning>::new(self)
}
}
pub mod states {
mod sealed {
pub trait Sealed {}
impl Sealed for super::Beginning {}
impl Sealed for super::InStream {}
impl Sealed for super::EndOfStream {}
}
#[derive(Debug, PartialEq)]
pub struct Beginning;
#[derive(Debug, PartialEq)]
pub struct InStream {
pub bitstream_serial_number: u32,
pub page_sequence_number: u32,
}
#[derive(Debug, PartialEq)]
pub struct EndOfStream;
pub trait ReaderState: sealed::Sealed {}
impl ReaderState for Beginning {}
impl ReaderState for InStream {}
impl ReaderState for EndOfStream {}
#[derive(Debug, PartialEq)]
pub enum Either<A, B> {
Continued(A),
Ended(B),
}
}
use states::{Beginning, EndOfStream, InStream, ReaderState};
pub type EitherHeaderOrEnded<'bs, 'data> = (
Either<BitstreamReader<'bs, 'data, InStream>, BitstreamReader<'bs, 'data, EndOfStream>>,
opus::OpusHeader,
);
pub type EitherPacketsOrEnded<'bs, 'data, const BUFFER_SIZE: usize> = (
Either<BitstreamReader<'bs, 'data, InStream>, BitstreamReader<'bs, 'data, EndOfStream>>,
Packets<'data, BUFFER_SIZE>,
);
#[derive(Debug, PartialEq)]
pub struct BitstreamReader<'bs, 'data: 'bs, S: ReaderState> {
bitstream: core::marker::PhantomData<&'bs Bitstream<'data>>,
remaining: &'data [u8],
marker: S,
}
impl<S: ReaderState> BitstreamReader<'_, '_, S> {
pub fn new<'bs, 'data>(
bitstream: &'bs Bitstream<'data>,
) -> BitstreamReader<'bs, 'data, Beginning> {
BitstreamReader {
bitstream: core::marker::PhantomData::<_>,
remaining: bitstream.data,
marker: Beginning,
}
}
}
impl<'bs, 'data> BitstreamReader<'bs, 'data, Beginning> {
pub fn read_header(self) -> Result<'data, EitherHeaderOrEnded<'bs, 'data>> {
use BitstreamError::*;
let BitstreamReader {
bitstream,
remaining,
..
} = self;
let (remaining, mut packets) = Packets::<30>::parse(remaining)?;
let bitstream_serial_number = packets.bitstream_serial_number();
let page_sequence_number = packets.current_page_sequence_number();
if page_sequence_number != 0 {
return Err(InvalidOggStream(ErrorValues::UnexpectedSequenceNumber(
page_sequence_number,
)));
}
if let Some(packet) = packets.next() {
let header = opus::OpusHeader::parse(packet.data)?;
if header.version > 15 {
return Err(UnsupportedOpusVersion(header.version));
}
if packets.next().is_some() {
return Err(InvalidOpusStream("unexpected segment after header"));
}
let (remaining, last_page) = container::Page::skip(remaining)?;
if last_page.bitstream_serial_number() != bitstream_serial_number {
return Err(UnsupportedStream(
"bitstream serial number changed unexpectedly",
));
}
Ok((
Either::Continued(BitstreamReader {
bitstream,
remaining,
marker: InStream {
bitstream_serial_number,
page_sequence_number: last_page.page_sequence_number(),
},
}),
header,
))
} else {
Err(InvalidOpusStream("missing header"))
}
}
}
impl<'bs, 'data> BitstreamReader<'bs, 'data, InStream> {
pub fn next_packets<const BUFFER_SIZE: usize>(
&self,
) -> Result<'data, EitherPacketsOrEnded<'bs, 'data, BUFFER_SIZE>> {
use BitstreamError::*;
let (remaining, packets) = Packets::parse(self.remaining)?;
if self.marker.bitstream_serial_number != packets.bitstream_serial_number() {
return Err(UnsupportedStream(
"bitstream serial number changed unexpectedly",
));
}
if packets.current_page_sequence_number() != self.marker.page_sequence_number + 1 {
return Err(InvalidOggStream(ErrorValues::SequenceNumberMismatch(
self.marker.page_sequence_number,
packets.current_page_sequence_number(),
)));
}
if !packets.end_of_stream() {
Ok((
Either::Continued(BitstreamReader {
bitstream: self.bitstream,
remaining,
marker: InStream {
bitstream_serial_number: self.marker.bitstream_serial_number,
page_sequence_number: packets.last_page_sequence_number(),
},
}),
packets,
))
} else {
Ok((
Either::Ended(BitstreamReader {
bitstream: self.bitstream,
remaining,
marker: EndOfStream,
}),
packets,
))
}
}
}
impl<'bs, 'data> BitstreamReader<'bs, 'data, EndOfStream> {
pub fn has_more(&self) -> bool {
!self.remaining.is_empty()
}
pub fn next_reader(self) -> Option<BitstreamReader<'bs, 'data, Beginning>> {
if self.has_more() {
Some(BitstreamReader {
bitstream: core::marker::PhantomData::<_>,
remaining: self.remaining,
marker: Beginning,
})
} else {
None
}
}
}
#[cfg(test)]
mod test {
use super::*;
use core::error::Error;
#[test]
fn parse_mono() {
const DATA: &[u8] = include_bytes!("test/mono.opus");
let bitstream = Bitstream::new(DATA);
let reader = bitstream.reader();
let (either, header) = reader.read_header().unwrap();
assert_eq!(
header.channels,
opus::ChannelMapping::Family0 { channels: 1 }
);
if let Either::Continued(reader) = either {
let (either, mut packets) = reader.next_packets::<512>().unwrap();
let result = packets.next();
assert!(result.is_some());
if let Either::Ended(reader) = either {
assert!(!reader.has_more());
assert!(reader.next_reader().is_none());
}
} else {
panic!("Unexpected end of stream in test");
}
}
#[test]
fn parse_stereo() {
const DATA: &[u8] = include_bytes!("test/stereo.opus");
let bitstream = Bitstream::new(DATA);
let reader = bitstream.reader();
let (_either, header) = reader.read_header().unwrap();
assert_eq!(
header.channels,
opus::ChannelMapping::Family0 { channels: 2 }
);
}
#[test]
fn parse_vorbis() {
const DATA: &[u8] = include_bytes!("test/vorbis.ogg");
let bitstream = Bitstream::new(DATA);
let reader = bitstream.reader();
let result = reader.read_header();
assert_eq!(result, Err(BitstreamError::NotOpusStream));
let error = result.unwrap_err();
assert!(error.source().is_none());
assert_eq!(error.to_string(), "this is not an Opus stream");
}
#[test]
fn parse_invalid_stream() {
const DATA: &[u8] = &[0, 0, 0, 0];
let bitstream = Bitstream::new(DATA);
let reader = bitstream.reader();
let result = reader.read_header();
assert_eq!(
result,
Err(BitstreamError::OggError(OggError::NotOggStream))
);
let error = result.unwrap_err();
assert!(error.source().is_some());
assert_eq!(error.to_string(), "this is not an ogg stream");
}
#[test]
fn parse_unexpected_sequence_number() {
let mut data = Vec::from(include_bytes!("test/mono.opus"));
data[0x12] = 1;
let bitstream = Bitstream::new(&data);
let reader = bitstream.reader();
let result = reader.read_header();
assert_eq!(
result,
Err(BitstreamError::InvalidOggStream(
ErrorValues::UnexpectedSequenceNumber(1)
))
);
let error = result.unwrap_err();
assert_eq!(
error.to_string(),
"invalid ogg stream: unexpected page sequence number in header: 1"
);
}
#[test]
fn parse_unsupported_ogg_version() {
let mut data = Vec::from(include_bytes!("test/mono.opus"));
data[4] = 2;
let bitstream = Bitstream::new(&data);
let reader = bitstream.reader();
let result = reader.read_header();
assert_eq!(
result,
Err(BitstreamError::OggError(OggError::UnsupportedVersion(2)))
);
let error = result.unwrap_err();
assert_eq!(error.to_string(), "unsupported ogg version: 2");
}
#[test]
fn parse_unsupported_opus_version() {
let mut data = Vec::from(include_bytes!("test/mono.opus"));
data[0x24] = 0x10;
let bitstream = Bitstream::new(&data);
let reader = bitstream.reader();
let result = reader.read_header();
assert_eq!(result, Err(BitstreamError::UnsupportedOpusVersion(16)));
let error = result.unwrap_err();
assert_eq!(error.to_string(), "unsupported Opus version: 16");
}
}