//! Opus identification header (`OpusHead`, RFC 7845 §5.1) and the ISO-BMFF `dOps` box payload. use crate::{Error, Result}; /// Decoder configuration: channel layout, multistream mapping, pre-skip and output gain. #[derive(Clone, Debug, PartialEq, Eq)] pub struct OpusHead { /// Output channel count (1..=255). pub channels: usize, /// Samples (at 48 kHz) to discard from the start of the decoded stream. pub pre_skip: u16, /// Sample rate of the original input (informational only). pub input_sample_rate: u32, /// Output gain in Q7.8 dB. pub output_gain: i16, /// Channel mapping family (0: mono/stereo, 1: Vorbis order surround, 255: unspecified order). pub mapping_family: u8, /// Number of elementary streams. pub streams: usize, /// Number of coupled (stereo) streams; these come first. pub coupled_streams: usize, /// Output channel → decoded channel index (255 = silence). pub mapping: Vec, } impl OpusHead { /// A family-0 configuration for plain mono/stereo streams (no header present). pub fn simple(channels: usize) -> Result { if !(1..=2).contains(&channels) { return Err(Error::InvalidArgument("family 0 supports 1 or 2 channels")); } Ok(OpusHead { channels, pre_skip: 0, input_sample_rate: 48000, output_gain: 0, mapping_family: 0, streams: 1, coupled_streams: channels - 1, mapping: (0..channels as u8).collect(), }) } /// Parses an `OpusHead` packet (Ogg first packet, Matroska `CodecPrivate`). pub fn parse(data: &[u8]) -> Result { if data.len() < 19 || &data[..8] != b"OpusHead" { return Err(Error::InvalidHeader("missing OpusHead magic")); } let version = data[8]; if version >> 4 != 0 { return Err(Error::Unsupported("OpusHead major version")); } let channels = data[9] as usize; let pre_skip = u16::from_le_bytes([data[10], data[11]]); let input_sample_rate = u32::from_le_bytes([data[12], data[13], data[14], data[15]]); let output_gain = i16::from_le_bytes([data[16], data[17]]); let family = data[18]; Self::build(channels, pre_skip, input_sample_rate, output_gain, family, &data[19..]) } /// Parses the payload of an ISO-BMFF `dOps` box (Opus in MP4, big-endian fields). pub fn from_dops(data: &[u8]) -> Result { if data.len() < 11 { return Err(Error::InvalidHeader("dOps too short")); } if data[0] != 0 { return Err(Error::Unsupported("dOps version")); } let channels = data[1] as usize; let pre_skip = u16::from_be_bytes([data[2], data[3]]); let input_sample_rate = u32::from_be_bytes([data[4], data[5], data[6], data[7]]); let output_gain = i16::from_be_bytes([data[8], data[9]]); Self::build(channels, pre_skip, input_sample_rate, output_gain, data[10], &data[11..]) } /// Serialises as an `OpusHead` packet. pub fn to_bytes(&self) -> Vec { let mut v = b"OpusHead".to_vec(); v.push(1); v.push(self.channels as u8); v.extend(self.pre_skip.to_le_bytes()); v.extend(self.input_sample_rate.to_le_bytes()); v.extend(self.output_gain.to_le_bytes()); v.push(self.mapping_family); if self.mapping_family != 0 { v.push(self.streams as u8); v.push(self.coupled_streams as u8); v.extend(&self.mapping); } v } fn build(channels: usize, pre_skip: u16, input_sample_rate: u32, output_gain: i16, family: u8, rest: &[u8]) -> Result { if channels == 0 { return Err(Error::InvalidHeader("zero channels")); } let (streams, coupled, mapping) = if family == 0 { if channels > 2 { return Err(Error::InvalidHeader("family 0 allows at most 2 channels")); } (1, channels - 1, (0..channels as u8).collect::>()) } else { if family == 3 { return Err(Error::Unsupported("channel mapping family 3 (projection)")); } if rest.len() < 2 + channels { return Err(Error::InvalidHeader("truncated channel mapping table")); } let streams = rest[0] as usize; let coupled = rest[1] as usize; if streams == 0 || coupled > streams || streams + coupled > 255 { return Err(Error::InvalidHeader("bad stream counts")); } if family == 1 && !(1..=8).contains(&channels) { return Err(Error::InvalidHeader("family 1 allows 1 to 8 channels")); } let mapping = rest[2..2 + channels].to_vec(); if mapping.iter().any(|&m| m != 255 && m as usize >= streams + coupled) { return Err(Error::InvalidHeader("channel mapping out of range")); } (streams, coupled, mapping) }; Ok(OpusHead { channels, pre_skip, input_sample_rate, output_gain, mapping_family: family, streams, coupled_streams: coupled, mapping }) } /// Checks the invariants the decoder indexes by, for headers built by hand rather than parsed. pub fn validate(&self) -> Result<()> { if self.channels == 0 { return Err(Error::InvalidHeader("zero channels")); } if self.streams == 0 || self.coupled_streams > self.streams || self.streams + self.coupled_streams > 255 { return Err(Error::InvalidHeader("bad stream counts")); } if self.mapping.len() != self.channels { return Err(Error::InvalidHeader("channel mapping length differs from channel count")); } if self.mapping.iter().any(|&m| m != 255 && m as usize >= self.streams + self.coupled_streams) { return Err(Error::InvalidHeader("channel mapping out of range")); } Ok(()) } /// Linear output gain factor. pub fn gain_linear(&self) -> f32 { 10f32.powf(self.output_gain as f32 / (20.0 * 256.0)) } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_family0_and_roundtrip() { let mut h = b"OpusHead".to_vec(); h.extend([1, 2, 0x38, 0x01, 0x80, 0xBB, 0, 0, 0x00, 0x01, 0]); let head = OpusHead::parse(&h).unwrap(); assert_eq!(head.channels, 2); assert_eq!(head.pre_skip, 312); assert_eq!(head.input_sample_rate, 48000); assert_eq!(head.output_gain, 256); assert_eq!((head.streams, head.coupled_streams), (1, 1)); assert_eq!(head.to_bytes(), h); assert!((head.gain_linear() - 1.122).abs() < 1e-3); } #[test] fn parse_family1_51() { let mut h = b"OpusHead".to_vec(); h.extend([1, 6, 0x38, 0x01, 0x80, 0xBB, 0, 0, 0, 0, 1, 4, 2, 0, 4, 1, 2, 3, 5]); let head = OpusHead::parse(&h).unwrap(); assert_eq!((head.channels, head.streams, head.coupled_streams), (6, 4, 2)); assert_eq!(head.mapping, vec![0, 4, 1, 2, 3, 5]); assert_eq!(OpusHead::parse(&head.to_bytes()).unwrap(), head); } #[test] fn rejects_garbage() { assert!(OpusHead::parse(b"OpusHea").is_err()); assert!(OpusHead::parse(b"OpusTags...........").is_err()); let mut h = b"OpusHead".to_vec(); h.extend([1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0]); assert!(OpusHead::parse(&h).is_err()); let mut h = b"OpusHead".to_vec(); h.extend([1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 5]); assert!(OpusHead::parse(&h).is_err()); } #[test] fn dops() { let d = [0u8, 2, 0x01, 0x38, 0, 0, 0xBB, 0x80, 0, 0, 0]; let head = OpusHead::from_dops(&d).unwrap(); assert_eq!(head.pre_skip, 312); assert_eq!(head.channels, 2); } }