pub const OPUS_HEAD_MAGIC: &[u8; 8] = b"OpusHead";
pub const OPUS_HEAD_MIN_LEN: usize = 19;
pub const OPUS_HEAD_MAX_VERSION: u8 = 15;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusHeadError {
TooShort {
got: usize,
need: usize,
},
BadMagic,
IncompatibleVersion {
version: u8,
},
ZeroChannels,
ChannelCountForFamily {
family: u8,
channels: u8,
},
ZeroStreams,
CoupledExceedsStreams {
streams: u8,
coupled: u8,
},
TooManyDecodedChannels {
streams: u8,
coupled: u8,
},
MappingIndexOutOfRange {
output_channel: u8,
index: u8,
decoded_channels: u8,
},
}
impl core::fmt::Display for OpusHeadError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
OpusHeadError::TooShort { got, need } => {
write!(f, "OpusHead too short: {got} bytes, need {need}")
}
OpusHeadError::BadMagic => write!(f, "OpusHead missing 'OpusHead' magic signature"),
OpusHeadError::IncompatibleVersion { version } => {
write!(
f,
"OpusHead major version {} > {OPUS_HEAD_MAX_VERSION}",
version >> 4
)
}
OpusHeadError::ZeroChannels => write!(f, "OpusHead output channel count is zero"),
OpusHeadError::ChannelCountForFamily { family, channels } => write!(
f,
"OpusHead channel count {channels} invalid for mapping family {family}"
),
OpusHeadError::ZeroStreams => write!(f, "OpusHead stream count N is zero"),
OpusHeadError::CoupledExceedsStreams { streams, coupled } => {
write!(
f,
"OpusHead coupled count {coupled} exceeds stream count {streams}"
)
}
OpusHeadError::TooManyDecodedChannels { streams, coupled } => write!(
f,
"OpusHead decoded channels M+N = {}+{} exceeds 255",
coupled, streams
),
OpusHeadError::MappingIndexOutOfRange {
output_channel,
index,
decoded_channels,
} => write!(
f,
"OpusHead mapping index {index} for output channel {output_channel} \
is neither < {decoded_channels} nor 255"
),
}
}
}
impl std::error::Error for OpusHeadError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChannelMappingTable {
pub stream_count: u8,
pub coupled_count: u8,
pub mapping: Vec<u8>,
}
impl ChannelMappingTable {
pub fn decoded_channels(&self) -> u16 {
self.coupled_count as u16 + self.stream_count as u16
}
pub fn output_channels(&self) -> u8 {
self.mapping.len() as u8
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpusHead {
pub version: u8,
pub channel_count: u8,
pub pre_skip: u16,
pub input_sample_rate: u32,
pub output_gain_q7_8: i16,
pub mapping_family: u8,
pub mapping: ChannelMappingTable,
}
impl OpusHead {
pub fn parse(packet: &[u8]) -> Result<Self, OpusHeadError> {
if packet.len() < OPUS_HEAD_MIN_LEN {
return Err(OpusHeadError::TooShort {
got: packet.len(),
need: OPUS_HEAD_MIN_LEN,
});
}
if &packet[0..8] != OPUS_HEAD_MAGIC.as_slice() {
return Err(OpusHeadError::BadMagic);
}
let version = packet[8];
if version >> 4 > 0 {
return Err(OpusHeadError::IncompatibleVersion { version });
}
let channel_count = packet[9];
if channel_count == 0 {
return Err(OpusHeadError::ZeroChannels);
}
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_q7_8 = i16::from_le_bytes([packet[16], packet[17]]);
let mapping_family = packet[18];
let mapping = if mapping_family == 0 {
if channel_count > 2 {
return Err(OpusHeadError::ChannelCountForFamily {
family: mapping_family,
channels: channel_count,
});
}
ChannelMappingTable {
stream_count: 1,
coupled_count: channel_count - 1,
mapping: (0..channel_count).collect(),
}
} else {
if mapping_family == 1 && channel_count > 8 {
return Err(OpusHeadError::ChannelCountForFamily {
family: mapping_family,
channels: channel_count,
});
}
let table_len = 2 + channel_count as usize;
let need = OPUS_HEAD_MIN_LEN + table_len;
if packet.len() < need {
return Err(OpusHeadError::TooShort {
got: packet.len(),
need,
});
}
let stream_count = packet[19];
let coupled_count = packet[20];
if stream_count == 0 {
return Err(OpusHeadError::ZeroStreams);
}
if coupled_count > stream_count {
return Err(OpusHeadError::CoupledExceedsStreams {
streams: stream_count,
coupled: coupled_count,
});
}
if coupled_count as u16 + stream_count as u16 > 255 {
return Err(OpusHeadError::TooManyDecodedChannels {
streams: stream_count,
coupled: coupled_count,
});
}
let decoded_channels = coupled_count + stream_count; let map_start = 21;
let mut mapping = Vec::with_capacity(channel_count as usize);
for c in 0..channel_count as usize {
let index = packet[map_start + c];
if index != 255 && index >= decoded_channels {
return Err(OpusHeadError::MappingIndexOutOfRange {
output_channel: c as u8,
index,
decoded_channels,
});
}
mapping.push(index);
}
ChannelMappingTable {
stream_count,
coupled_count,
mapping,
}
};
Ok(OpusHead {
version,
channel_count,
pre_skip,
input_sample_rate,
output_gain_q7_8,
mapping_family,
mapping,
})
}
pub fn compose(&self) -> Result<Vec<u8>, OpusHeadError> {
if self.version >> 4 > 0 {
return Err(OpusHeadError::IncompatibleVersion {
version: self.version,
});
}
if self.channel_count == 0 {
return Err(OpusHeadError::ZeroChannels);
}
let mut out = Vec::with_capacity(OPUS_HEAD_MIN_LEN + 2 + self.channel_count as usize);
out.extend_from_slice(OPUS_HEAD_MAGIC);
out.push(self.version);
out.push(self.channel_count);
out.extend_from_slice(&self.pre_skip.to_le_bytes());
out.extend_from_slice(&self.input_sample_rate.to_le_bytes());
out.extend_from_slice(&self.output_gain_q7_8.to_le_bytes());
out.push(self.mapping_family);
if self.mapping_family == 0 {
if self.channel_count > 2 {
return Err(OpusHeadError::ChannelCountForFamily {
family: 0,
channels: self.channel_count,
});
}
let default = ChannelMappingTable {
stream_count: 1,
coupled_count: self.channel_count - 1,
mapping: (0..self.channel_count).collect(),
};
if self.mapping != default {
return Err(OpusHeadError::ChannelCountForFamily {
family: 0,
channels: self.channel_count,
});
}
} else {
if self.mapping_family == 1 && self.channel_count > 8 {
return Err(OpusHeadError::ChannelCountForFamily {
family: self.mapping_family,
channels: self.channel_count,
});
}
if self.mapping.mapping.len() != self.channel_count as usize {
return Err(OpusHeadError::TooShort {
got: OPUS_HEAD_MIN_LEN + 2 + self.mapping.mapping.len(),
need: OPUS_HEAD_MIN_LEN + 2 + self.channel_count as usize,
});
}
if self.mapping.stream_count == 0 {
return Err(OpusHeadError::ZeroStreams);
}
if self.mapping.coupled_count > self.mapping.stream_count {
return Err(OpusHeadError::CoupledExceedsStreams {
streams: self.mapping.stream_count,
coupled: self.mapping.coupled_count,
});
}
if self.mapping.decoded_channels() > 255 {
return Err(OpusHeadError::TooManyDecodedChannels {
streams: self.mapping.stream_count,
coupled: self.mapping.coupled_count,
});
}
let decoded_channels = self.mapping.coupled_count + self.mapping.stream_count;
out.push(self.mapping.stream_count);
out.push(self.mapping.coupled_count);
for (c, &index) in self.mapping.mapping.iter().enumerate() {
if index != 255 && index >= decoded_channels {
return Err(OpusHeadError::MappingIndexOutOfRange {
output_channel: c as u8,
index,
decoded_channels,
});
}
out.push(index);
}
}
Ok(out)
}
pub fn output_gain_linear(&self) -> f64 {
10f64.powf(self.output_gain_q7_8 as f64 / (20.0 * 256.0))
}
pub fn apply_gain(&self, pcm: &mut [i16]) {
apply_output_gain(pcm, self.output_gain_q7_8);
}
}
pub fn apply_output_gain(pcm: &mut [i16], gain_q7_8: i16) {
if gain_q7_8 == 0 {
return;
}
let scale = 10f64.powf(gain_q7_8 as f64 / (20.0 * 256.0));
for s in pcm.iter_mut() {
let scaled = (*s as f64 * scale).round();
*s = scaled.clamp(i16::MIN as f64, i16::MAX as f64) as i16;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PreSkip {
remaining: u32,
}
impl PreSkip {
pub fn new(pre_skip: u16) -> Self {
PreSkip {
remaining: pre_skip as u32,
}
}
pub fn from_head(head: &OpusHead) -> Self {
Self::new(head.pre_skip)
}
pub fn remaining(&self) -> u32 {
self.remaining
}
pub fn is_done(&self) -> bool {
self.remaining == 0
}
pub fn consume(&mut self, samples_per_channel: usize) -> usize {
let drop = (samples_per_channel as u32).min(self.remaining);
self.remaining -= drop;
drop as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
fn family0_header(channels: u8) -> Vec<u8> {
let mut h = Vec::new();
h.extend_from_slice(OPUS_HEAD_MAGIC);
h.push(1); h.push(channels);
h.extend_from_slice(&3840u16.to_le_bytes()); h.extend_from_slice(&48000u32.to_le_bytes()); h.extend_from_slice(&0i16.to_le_bytes()); h.push(0); h
}
#[test]
fn family0_mono_defaults() {
let head = OpusHead::parse(&family0_header(1)).unwrap();
assert_eq!(head.channel_count, 1);
assert_eq!(head.pre_skip, 3840);
assert_eq!(head.input_sample_rate, 48000);
assert_eq!(head.mapping.stream_count, 1);
assert_eq!(head.mapping.coupled_count, 0);
assert_eq!(head.mapping.mapping, vec![0]);
assert_eq!(head.mapping.decoded_channels(), 1);
}
#[test]
fn family0_stereo_defaults() {
let head = OpusHead::parse(&family0_header(2)).unwrap();
assert_eq!(head.mapping.stream_count, 1);
assert_eq!(head.mapping.coupled_count, 1); assert_eq!(head.mapping.mapping, vec![0, 1]);
assert_eq!(head.mapping.decoded_channels(), 2);
}
#[test]
fn family0_rejects_more_than_two_channels() {
assert_eq!(
OpusHead::parse(&family0_header(3)),
Err(OpusHeadError::ChannelCountForFamily {
family: 0,
channels: 3,
})
);
}
#[test]
fn bad_magic_rejected() {
let mut h = family0_header(1);
h[0] = b'X';
assert_eq!(OpusHead::parse(&h), Err(OpusHeadError::BadMagic));
}
#[test]
fn too_short_rejected() {
let h = vec![0u8; 10];
assert_eq!(
OpusHead::parse(&h),
Err(OpusHeadError::TooShort { got: 10, need: 19 })
);
}
#[test]
fn incompatible_major_version_rejected() {
let mut h = family0_header(1);
h[8] = 0x10; assert_eq!(
OpusHead::parse(&h),
Err(OpusHeadError::IncompatibleVersion { version: 0x10 })
);
let mut h = family0_header(1);
h[8] = 0x0F;
assert!(OpusHead::parse(&h).is_ok());
}
#[test]
fn zero_channels_rejected() {
let mut h = family0_header(1);
h[9] = 0;
assert_eq!(OpusHead::parse(&h), Err(OpusHeadError::ZeroChannels));
}
fn family1_5_1_header() -> Vec<u8> {
let mut h = Vec::new();
h.extend_from_slice(OPUS_HEAD_MAGIC);
h.push(1);
h.push(6); h.extend_from_slice(&312u16.to_le_bytes());
h.extend_from_slice(&0u32.to_le_bytes());
h.extend_from_slice(&0i16.to_le_bytes());
h.push(1); h.push(4); h.push(2); h.extend_from_slice(&[0, 1, 2, 3, 4, 5]); h
}
#[test]
fn family1_surround_table() {
let head = OpusHead::parse(&family1_5_1_header()).unwrap();
assert_eq!(head.mapping_family, 1);
assert_eq!(head.channel_count, 6);
assert_eq!(head.mapping.stream_count, 4);
assert_eq!(head.mapping.coupled_count, 2);
assert_eq!(head.mapping.decoded_channels(), 6); assert_eq!(head.mapping.mapping, vec![0, 1, 2, 3, 4, 5]);
assert_eq!(head.mapping.output_channels(), 6);
}
#[test]
fn family1_rejects_coupled_gt_streams() {
let mut h = family1_5_1_header();
h[20] = 5; assert_eq!(
OpusHead::parse(&h),
Err(OpusHeadError::CoupledExceedsStreams {
streams: 4,
coupled: 5,
})
);
}
#[test]
fn family1_rejects_zero_streams() {
let mut h = family1_5_1_header();
h[19] = 0;
assert_eq!(OpusHead::parse(&h), Err(OpusHeadError::ZeroStreams));
}
#[test]
fn family1_rejects_out_of_range_mapping_index() {
let mut h = family1_5_1_header();
h[21 + 5] = 6; assert_eq!(
OpusHead::parse(&h),
Err(OpusHeadError::MappingIndexOutOfRange {
output_channel: 5,
index: 6,
decoded_channels: 6,
})
);
let mut h = family1_5_1_header();
h[21 + 5] = 255;
assert!(OpusHead::parse(&h).is_ok());
}
#[test]
fn family1_rejects_truncated_table() {
let mut h = family1_5_1_header();
h.truncate(22); assert!(matches!(
OpusHead::parse(&h),
Err(OpusHeadError::TooShort { .. })
));
}
#[test]
fn family1_channel_count_bounds() {
let mut h = Vec::new();
h.extend_from_slice(OPUS_HEAD_MAGIC);
h.push(1);
h.push(9);
h.extend_from_slice(&0u16.to_le_bytes());
h.extend_from_slice(&0u32.to_le_bytes());
h.extend_from_slice(&0i16.to_le_bytes());
h.push(1);
h.push(6);
h.push(2);
h.extend_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 0]);
assert_eq!(
OpusHead::parse(&h),
Err(OpusHeadError::ChannelCountForFamily {
family: 1,
channels: 9,
})
);
}
#[test]
fn output_gain_linear_unity_at_zero() {
let head = OpusHead::parse(&family0_header(1)).unwrap();
assert!((head.output_gain_linear() - 1.0).abs() < 1e-12);
}
#[test]
fn output_gain_linear_positive_amplifies() {
let mut h = family0_header(1);
let g: i16 = 1536;
h[16..18].copy_from_slice(&g.to_le_bytes());
let head = OpusHead::parse(&h).unwrap();
assert!(head.output_gain_linear() > 1.9 && head.output_gain_linear() < 2.1);
}
#[test]
fn apply_output_gain_zero_is_noop() {
let mut pcm = vec![100i16, -200, 32767, -32768];
let before = pcm.clone();
apply_output_gain(&mut pcm, 0);
assert_eq!(pcm, before);
}
#[test]
fn apply_output_gain_doubles_at_plus_6db() {
let mut pcm = vec![100i16, -100, 50];
apply_output_gain(&mut pcm, 1536);
assert_eq!(pcm[0], 200);
assert_eq!(pcm[1], -200);
assert_eq!(pcm[2], 100);
}
#[test]
fn apply_output_gain_saturates() {
let mut pcm = vec![20000i16, -20000];
apply_output_gain(&mut pcm, 1536); assert_eq!(pcm[0], i16::MAX);
assert_eq!(pcm[1], i16::MIN);
}
#[test]
fn apply_gain_via_head() {
let mut h = family0_header(1);
h[16..18].copy_from_slice(&1536i16.to_le_bytes());
let head = OpusHead::parse(&h).unwrap();
let mut pcm = vec![10i16, -10];
head.apply_gain(&mut pcm);
assert_eq!(pcm, vec![20, -20]);
}
#[test]
fn pre_skip_consumes_across_packets() {
let mut ps = PreSkip::new(312);
assert!(!ps.is_done());
assert_eq!(ps.remaining(), 312);
assert_eq!(ps.consume(960), 312);
assert!(ps.is_done());
assert_eq!(ps.consume(960), 0);
}
#[test]
fn compose_roundtrips_parse() {
for bytes in [family0_header(1), family0_header(2), family1_5_1_header()] {
let head = OpusHead::parse(&bytes).unwrap();
let composed = head.compose().unwrap();
assert_eq!(composed, bytes);
assert_eq!(OpusHead::parse(&composed).unwrap(), head);
}
}
#[test]
fn compose_rejects_invalid_headers() {
let base = OpusHead::parse(&family1_5_1_header()).unwrap();
let mut h = base.clone();
h.version = 0x20;
assert!(matches!(
h.compose(),
Err(OpusHeadError::IncompatibleVersion { .. })
));
let mut h = base.clone();
h.channel_count = 0;
assert_eq!(h.compose(), Err(OpusHeadError::ZeroChannels));
let mut h = OpusHead::parse(&family0_header(2)).unwrap();
h.mapping.mapping = vec![1, 0];
assert!(matches!(
h.compose(),
Err(OpusHeadError::ChannelCountForFamily { family: 0, .. })
));
let mut h = OpusHead::parse(&family0_header(2)).unwrap();
h.channel_count = 3;
h.mapping.mapping = vec![0, 1, 2];
assert!(matches!(
h.compose(),
Err(OpusHeadError::ChannelCountForFamily { family: 0, .. })
));
let mut h = base.clone();
h.mapping.stream_count = 0;
h.mapping.coupled_count = 0;
assert_eq!(h.compose(), Err(OpusHeadError::ZeroStreams));
let mut h = base.clone();
h.mapping.coupled_count = h.mapping.stream_count + 1;
assert!(matches!(
h.compose(),
Err(OpusHeadError::CoupledExceedsStreams { .. })
));
let mut h = base.clone();
h.mapping.stream_count = 200;
h.mapping.coupled_count = 100;
assert!(matches!(
h.compose(),
Err(OpusHeadError::TooManyDecodedChannels { .. })
));
let mut h = base.clone();
h.mapping.mapping.pop();
assert!(matches!(h.compose(), Err(OpusHeadError::TooShort { .. })));
let mut h = base.clone();
h.mapping.mapping[3] = 6; assert!(matches!(
h.compose(),
Err(OpusHeadError::MappingIndexOutOfRange { .. })
));
let mut h = base.clone();
h.mapping.mapping[3] = 255;
let bytes = h.compose().unwrap();
assert_eq!(OpusHead::parse(&bytes).unwrap(), h);
}
#[test]
fn pre_skip_spanning_multiple_packets() {
let mut ps = PreSkip::new(1500);
assert_eq!(ps.consume(960), 960);
assert_eq!(ps.remaining(), 540);
assert_eq!(ps.consume(960), 540);
assert!(ps.is_done());
assert_eq!(ps.consume(960), 0);
}
#[test]
fn pre_skip_from_head() {
let head = OpusHead::parse(&family0_header(1)).unwrap();
let ps = PreSkip::from_head(&head);
assert_eq!(ps.remaining(), 3840);
}
}