mod import;
pub mod mapping;
pub use import::*;
pub use mapping::Mapping;
use bytes::{Buf, Bytes};
const OPUS_HEAD: u64 = u64::from_be_bytes(*b"OpusHead");
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("OpusHead must be at least 19 bytes")]
HeadTooShort,
#[error("invalid OpusHead signature")]
InvalidSignature,
#[error("unsupported OpusHead version {0}")]
UnsupportedVersion(u8),
#[error("channel mapping family does not allow {0} channels")]
UnsupportedChannelCount(u32),
#[error("OpusHead channel mapping table is truncated")]
MappingTableTooShort,
#[error("invalid OpusHead channel mapping table")]
InvalidMappingTable,
#[error("unsupported channel mapping family {0}")]
UnsupportedMappingFamily(u8),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Config {
pub sample_rate: u32,
pub channel_count: u32,
pub pre_skip: u16,
pub output_gain: i16,
pub mapping_family: u8,
pub mapping: Option<Mapping>,
}
impl Config {
pub fn new(sample_rate: u32, channel_count: u32) -> Self {
Self {
sample_rate,
channel_count,
pre_skip: 0,
output_gain: 0,
mapping_family: 0,
mapping: None,
}
}
pub fn with_pre_skip(mut self, pre_skip: u16) -> Self {
self.pre_skip = pre_skip;
self
}
pub fn parse<T: Buf>(buf: &mut T) -> Result<Self> {
if buf.remaining() < 19 {
return Err(Error::HeadTooShort);
}
let signature = buf.get_u64();
if signature != OPUS_HEAD {
return Err(Error::InvalidSignature);
}
let version = buf.get_u8();
if version > 15 {
return Err(Error::UnsupportedVersion(version));
}
let channel_count = buf.get_u8() as u32;
let pre_skip = buf.get_u16_le();
let sample_rate = buf.get_u32_le();
let output_gain = buf.get_i16_le();
let mapping_family = buf.get_u8();
let mapping = match mapping_family {
0 if (1..=2).contains(&channel_count) => None,
0 => return Err(Error::UnsupportedChannelCount(channel_count)),
family => Some(Mapping::parse(buf, family, channel_count as u8)?),
};
Ok(Self {
sample_rate,
channel_count,
pre_skip,
output_gain,
mapping_family,
mapping,
})
}
pub fn encode(&self) -> Result<Bytes> {
if self.mapping_family != self.mapping.map_or(0, |mapping| mapping.family()) {
return Err(Error::UnsupportedMappingFamily(self.mapping_family));
}
let valid = match &self.mapping {
None => (1..=2).contains(&self.channel_count),
Some(mapping) => self.channel_count == mapping.table().len() as u32,
};
if !valid {
return Err(Error::UnsupportedChannelCount(self.channel_count));
}
let mut head = Vec::with_capacity(21 + self.channel_count as usize);
head.extend_from_slice(b"OpusHead");
head.push(1); head.push(self.channel_count as u8);
head.extend_from_slice(&self.pre_skip.to_le_bytes());
head.extend_from_slice(&self.sample_rate.to_le_bytes());
head.extend_from_slice(&self.output_gain.to_le_bytes());
match &self.mapping {
None => head.push(0),
Some(mapping) => {
head.extend_from_slice(&[mapping.family(), mapping.streams(), mapping.coupled()]);
head.extend_from_slice(mapping.table());
}
}
Ok(Bytes::from(head))
}
}
pub(crate) fn packet_samples(packet: &[u8]) -> Option<u32> {
let toc = *packet.first()?;
let frames = match toc & 0b11 {
0 => 1,
1 | 2 => 2,
_ => (packet.get(1)? & 0b0011_1111) as u32,
};
Some(config_samples(toc >> 3) * frames)
}
fn config_samples(config: u8) -> u32 {
match config {
0 | 4 | 8 => 480,
1 | 5 | 9 => 960,
2 | 6 | 10 => 1920,
3 | 7 | 11 => 2880,
12 | 14 => 480,
13 | 15 => 960,
16 | 20 | 24 | 28 => 120,
17 | 21 | 25 | 29 => 240,
18 | 22 | 26 | 30 => 480,
_ => 960,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packet_samples_reads_toc() {
assert_eq!(packet_samples(&[16 << 3]), Some(120));
assert_eq!(packet_samples(&[3 << 3]), Some(2880));
assert_eq!(packet_samples(&[(1 << 3) | 1]), Some(1920));
assert_eq!(packet_samples(&[(1 << 3) | 3, 4]), Some(3840));
assert_eq!(packet_samples(&[]), None);
}
#[test]
fn parses_valid_opus_head() {
let cfg = Config::new(48_000, 2).with_pre_skip(312);
let encoded = cfg.encode().unwrap();
assert_eq!(encoded.len(), 19);
let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
assert_eq!(parsed.sample_rate, 48_000);
assert_eq!(parsed.channel_count, 2);
assert_eq!(parsed.pre_skip, 312);
assert_eq!(parsed, cfg);
}
#[test]
fn parse_rejects_invalid_signature() {
let mut bytes = Config::new(48_000, 1).encode().unwrap().to_vec();
bytes[0] = b'X';
assert!(Config::parse(&mut bytes.as_slice()).is_err());
}
#[test]
fn encode_rejects_multichannel() {
let err = Config::new(48_000, 6).encode().unwrap_err();
assert!(matches!(err, Error::UnsupportedChannelCount(6)));
}
fn head(version: u8, channels: u8, gain: i16, family: u8) -> Vec<u8> {
let mut head = b"OpusHead".to_vec();
head.push(version);
head.push(channels);
head.extend_from_slice(&312u16.to_le_bytes());
head.extend_from_slice(&44_100u32.to_le_bytes());
head.extend_from_slice(&gain.to_le_bytes());
head.push(family);
head
}
#[test]
fn parses_gain_and_input_rate() {
let bytes = head(1, 2, -1536, 0);
let parsed = Config::parse(&mut bytes.as_slice()).unwrap();
assert_eq!(parsed.sample_rate, 44_100);
assert_eq!(parsed.pre_skip, 312);
assert_eq!(parsed.output_gain, -1536);
assert_eq!(parsed.mapping_family, 0);
let encoded = parsed.encode().unwrap();
assert_eq!(encoded.as_ref(), bytes.as_slice());
}
#[test]
fn parse_accepts_minor_versions_only() {
for version in [0, 1, 15] {
assert!(Config::parse(&mut head(version, 2, 0, 0).as_slice()).is_ok());
}
assert!(matches!(
Config::parse(&mut head(16, 2, 0, 0).as_slice()),
Err(Error::UnsupportedVersion(16))
));
}
#[test]
fn parses_a_mapping_table() {
let mut bytes = head(1, 6, 0, 1);
bytes.extend_from_slice(&[4, 2, 0, 4, 1, 2, 3, 5]);
let trailing = [bytes.as_slice(), b"trailing"].concat();
let mut buf = trailing.as_slice();
let parsed = Config::parse(&mut buf).unwrap();
assert_eq!(buf, b"trailing");
assert_eq!(parsed.channel_count, 6);
assert_eq!(parsed.mapping_family, 1);
let mapping = parsed.mapping.unwrap();
assert_eq!(mapping.family(), 1);
assert_eq!(mapping.streams(), 4);
assert_eq!(mapping.coupled(), 2);
assert_eq!(mapping.table(), &[0, 4, 1, 2, 3, 5]);
assert_eq!(parsed.encode().unwrap(), bytes);
}
#[test]
fn encodes_the_vorbis_mappings() {
for channels in 1..=8u8 {
let mut config = Config::new(48_000, channels as u32);
config.mapping_family = 1;
config.mapping = Some(Mapping::vorbis(channels).unwrap());
let head = config.encode().unwrap();
assert_eq!(head.len(), 21 + channels as usize, "{channels} channels");
assert_eq!(
Config::parse(&mut head.as_ref()).unwrap(),
config,
"{channels} channels"
);
}
let five_one = Mapping::vorbis(6).unwrap();
assert_eq!((five_one.streams(), five_one.coupled()), (4, 2));
assert_eq!(five_one.table(), &[0, 4, 1, 2, 3, 5]);
assert!(matches!(Mapping::vorbis(0), Err(Error::UnsupportedChannelCount(0))));
assert!(matches!(Mapping::vorbis(9), Err(Error::UnsupportedChannelCount(9))));
let mut config = Config::new(48_000, 5);
config.mapping_family = 1;
config.mapping = Some(five_one);
assert!(matches!(config.encode(), Err(Error::UnsupportedChannelCount(5))));
let mut config = Config::new(48_000, 6);
config.mapping = Some(five_one);
assert!(matches!(config.encode(), Err(Error::UnsupportedMappingFamily(0))));
let mut config = Config::new(48_000, 2);
config.mapping_family = 1;
assert!(matches!(config.encode(), Err(Error::UnsupportedMappingFamily(1))));
}
#[test]
fn parse_rejects_channel_counts_the_family_does_not_allow() {
for (channels, family) in [(0, 0), (3, 0), (0, 1), (9, 1), (0, 255)] {
let mut bytes = head(1, channels, 0, family);
bytes.extend_from_slice(&[1, 0]);
bytes.extend(std::iter::repeat_n(0, channels as usize));
assert!(
matches!(
Config::parse(&mut bytes.as_slice()),
Err(Error::UnsupportedChannelCount(_))
),
"{channels} channels in family {family}"
);
}
}
#[test]
fn parse_rejects_a_bad_mapping_table() {
assert!(matches!(
Config::parse(&mut head(1, 2, 0, 1).as_slice()),
Err(Error::MappingTableTooShort)
));
let mut short = head(1, 2, 0, 1);
short.extend_from_slice(&[1, 1, 0]);
assert!(matches!(
Config::parse(&mut short.as_slice()),
Err(Error::MappingTableTooShort)
));
for table in [
[0, 0, 0, 1],
[1, 2, 0, 1],
[1, 1, 0, 2],
] {
let mut bytes = head(1, 2, 0, 1);
bytes.extend_from_slice(&table);
assert!(
matches!(Config::parse(&mut bytes.as_slice()), Err(Error::InvalidMappingTable)),
"{table:?}"
);
}
let mut silent = head(1, 2, 0, 1);
silent.extend_from_slice(&[1, 0, 0, 255]);
assert_eq!(
Config::parse(&mut silent.as_slice()).unwrap().mapping.unwrap().table(),
&[0, 255]
);
}
}