use oxideav_core::{Error, Result};
#[derive(Clone, Debug)]
pub struct OpusHead {
pub version: u8,
pub output_channel_count: u8,
pub pre_skip: u16,
pub input_sample_rate: u32,
pub output_gain: i16,
pub channel_mapping_family: u8,
pub mapping_table: Vec<u8>,
}
pub fn parse_opus_head(packet: &[u8]) -> Result<OpusHead> {
if packet.len() < 19 {
return Err(Error::invalid("OpusHead too short"));
}
if &packet[0..8] != b"OpusHead" {
return Err(Error::invalid("not an OpusHead packet"));
}
let version = packet[8];
if version & 0xF0 != 0 && version != 1 {
return Err(Error::unsupported(format!(
"unsupported OpusHead version {version}"
)));
}
let output_channel_count = packet[9];
if output_channel_count == 0 {
return Err(Error::invalid("OpusHead channel count is zero"));
}
let pre_skip = u16::from_le_bytes([packet[10], packet[11]]);
let input_sample_rate = u32::from_le_bytes([packet[12], packet[13], packet[14], packet[15]]);
let output_gain = i16::from_le_bytes([packet[16], packet[17]]);
let channel_mapping_family = packet[18];
let mapping_table = if channel_mapping_family == 0 {
Vec::new()
} else {
let expected = 2 + output_channel_count as usize;
if packet.len() < 19 + expected {
return Err(Error::invalid("OpusHead channel-mapping table truncated"));
}
packet[19..19 + expected].to_vec()
};
Ok(OpusHead {
version,
output_channel_count,
pre_skip,
input_sample_rate,
output_gain,
channel_mapping_family,
mapping_table,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_minimal_stereo_head() {
let mut p = Vec::new();
p.extend_from_slice(b"OpusHead");
p.push(1); p.push(2); p.extend_from_slice(&312u16.to_le_bytes());
p.extend_from_slice(&48_000u32.to_le_bytes());
p.extend_from_slice(&0i16.to_le_bytes());
p.push(0); let h = parse_opus_head(&p).unwrap();
assert_eq!(h.output_channel_count, 2);
assert_eq!(h.pre_skip, 312);
assert_eq!(h.input_sample_rate, 48_000);
assert_eq!(h.channel_mapping_family, 0);
assert!(h.mapping_table.is_empty());
}
#[test]
fn rejects_bad_signature() {
let p = vec![0u8; 20];
assert!(parse_opus_head(&p).is_err());
}
#[test]
fn parses_family_1_mapping() {
let mut p = Vec::new();
p.extend_from_slice(b"OpusHead");
p.push(1);
p.push(2);
p.extend_from_slice(&312u16.to_le_bytes());
p.extend_from_slice(&48_000u32.to_le_bytes());
p.extend_from_slice(&0i16.to_le_bytes());
p.push(1); p.push(1); p.push(1); p.push(0); p.push(1); let h = parse_opus_head(&p).unwrap();
assert_eq!(h.channel_mapping_family, 1);
assert_eq!(h.mapping_table, vec![1, 1, 0, 1]);
}
}