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))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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)));
}
}