mod import;
pub use import::*;
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("channel mapping family 0 only supports mono/stereo (got {0} channels)")]
UnsupportedChannelCount(u32),
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Config {
pub sample_rate: u32,
pub channel_count: u32,
}
impl Config {
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);
}
buf.advance(1); let channel_count = buf.get_u8() as u32;
buf.advance(2); let sample_rate = buf.get_u32_le();
if buf.remaining() > 0 {
buf.advance(buf.remaining());
}
Ok(Self {
sample_rate,
channel_count,
})
}
pub fn encode(&self) -> Result<Bytes> {
if !(1..=2).contains(&self.channel_count) {
return Err(Error::UnsupportedChannelCount(self.channel_count));
}
let mut head = Vec::with_capacity(19);
head.extend_from_slice(b"OpusHead");
head.push(1); head.push(self.channel_count as u8);
head.extend_from_slice(&0u16.to_le_bytes()); head.extend_from_slice(&self.sample_rate.to_le_bytes());
head.extend_from_slice(&0i16.to_le_bytes()); head.push(0); 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 {
sample_rate: 48000,
channel_count: 2,
};
let encoded = cfg.encode().unwrap();
assert_eq!(encoded.len(), 19);
let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
assert_eq!(parsed.sample_rate, 48000);
assert_eq!(parsed.channel_count, 2);
}
#[test]
fn parse_rejects_invalid_signature() {
let mut bytes = Config {
sample_rate: 48000,
channel_count: 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 {
sample_rate: 48000,
channel_count: 6,
}
.encode()
.unwrap_err();
assert!(matches!(err, Error::UnsupportedChannelCount(6)));
}
}