Skip to main content

pito_codecs/opus/
header.rs

1use crate::media::{Error, Result};
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4pub struct OpusHead {
5    pub channels: usize,
6    pub pre_skip: u16,
7    pub input_sample_rate: u32,
8    pub output_gain: i16,
9    pub mapping_family: u8,
10    pub streams: usize,
11    pub coupled_streams: usize,
12    pub mapping: Vec<u8>,
13}
14
15impl OpusHead {
16    pub fn simple(channels: usize) -> Result<OpusHead> {
17        if !(1..=2).contains(&channels) {
18            return Err(Error::Invalid("family 0 supports 1 or 2 channels".into()));
19        }
20        Ok(OpusHead {
21            channels,
22            pre_skip: 0,
23            input_sample_rate: 48000,
24            output_gain: 0,
25            mapping_family: 0,
26            streams: 1,
27            coupled_streams: channels - 1,
28            mapping: (0..channels as u8).collect(),
29        })
30    }
31
32    pub fn parse(data: &[u8]) -> Result<OpusHead> {
33        if data.len() < 19 || &data[..8] != b"OpusHead" {
34            return Err(Error::Invalid("missing OpusHead magic".into()));
35        }
36        let version = data[8];
37        if version >> 4 != 0 {
38            return Err(Error::Unsupported("OpusHead major version".into()));
39        }
40        let channels = data[9] as usize;
41        let pre_skip = u16::from_le_bytes([data[10], data[11]]);
42        let input_sample_rate = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
43        let output_gain = i16::from_le_bytes([data[16], data[17]]);
44        let family = data[18];
45        Self::build(
46            channels,
47            pre_skip,
48            input_sample_rate,
49            output_gain,
50            family,
51            &data[19..],
52        )
53    }
54
55    pub fn from_dops(data: &[u8]) -> Result<OpusHead> {
56        if data.len() < 11 {
57            return Err(Error::Invalid("dOps too short".into()));
58        }
59        if data[0] != 0 {
60            return Err(Error::Unsupported("dOps version".into()));
61        }
62        let channels = data[1] as usize;
63        let pre_skip = u16::from_be_bytes([data[2], data[3]]);
64        let input_sample_rate = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
65        let output_gain = i16::from_be_bytes([data[8], data[9]]);
66        Self::build(
67            channels,
68            pre_skip,
69            input_sample_rate,
70            output_gain,
71            data[10],
72            &data[11..],
73        )
74    }
75
76    pub fn to_bytes(&self) -> Vec<u8> {
77        let mut v = b"OpusHead".to_vec();
78        v.push(1);
79        v.push(self.channels as u8);
80        v.extend(self.pre_skip.to_le_bytes());
81        v.extend(self.input_sample_rate.to_le_bytes());
82        v.extend(self.output_gain.to_le_bytes());
83        v.push(self.mapping_family);
84        if self.mapping_family != 0 {
85            v.push(self.streams as u8);
86            v.push(self.coupled_streams as u8);
87            v.extend(&self.mapping);
88        }
89        v
90    }
91
92    fn build(
93        channels: usize,
94        pre_skip: u16,
95        input_sample_rate: u32,
96        output_gain: i16,
97        family: u8,
98        rest: &[u8],
99    ) -> Result<OpusHead> {
100        if channels == 0 {
101            return Err(Error::Invalid("zero channels".into()));
102        }
103        let (streams, coupled, mapping) = if family == 0 {
104            if channels > 2 {
105                return Err(Error::Invalid("family 0 allows at most 2 channels".into()));
106            }
107            (1, channels - 1, (0..channels as u8).collect::<Vec<_>>())
108        } else {
109            if family == 3 {
110                return Err(Error::Unsupported(
111                    "channel mapping family 3 (projection)".into(),
112                ));
113            }
114            if rest.len() < 2 + channels {
115                return Err(Error::Invalid("truncated channel mapping table".into()));
116            }
117            let streams = rest[0] as usize;
118            let coupled = rest[1] as usize;
119            if streams == 0 || coupled > streams || streams + coupled > 255 {
120                return Err(Error::Invalid("bad stream counts".into()));
121            }
122            if family == 1 && !(1..=8).contains(&channels) {
123                return Err(Error::Invalid("family 1 allows 1 to 8 channels".into()));
124            }
125            let mapping = rest[2..2 + channels].to_vec();
126            if mapping
127                .iter()
128                .any(|&m| m != 255 && m as usize >= streams + coupled)
129            {
130                return Err(Error::Invalid("channel mapping out of range".into()));
131            }
132            (streams, coupled, mapping)
133        };
134        Ok(OpusHead {
135            channels,
136            pre_skip,
137            input_sample_rate,
138            output_gain,
139            mapping_family: family,
140            streams,
141            coupled_streams: coupled,
142            mapping,
143        })
144    }
145
146    pub fn gain_linear(&self) -> f32 {
147        10f32.powf(self.output_gain as f32 / (20.0 * 256.0))
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn parse_family0_and_roundtrip() {
157        let mut h = b"OpusHead".to_vec();
158        h.extend([1, 2, 0x38, 0x01, 0x80, 0xBB, 0, 0, 0x00, 0x01, 0]);
159        let head = OpusHead::parse(&h).unwrap();
160        assert_eq!(head.channels, 2);
161        assert_eq!(head.pre_skip, 312);
162        assert_eq!(head.input_sample_rate, 48000);
163        assert_eq!(head.output_gain, 256);
164        assert_eq!((head.streams, head.coupled_streams), (1, 1));
165        assert_eq!(head.to_bytes(), h);
166        assert!((head.gain_linear() - 1.122).abs() < 1e-3);
167    }
168
169    #[test]
170    fn parse_family1_51() {
171        let mut h = b"OpusHead".to_vec();
172        h.extend([
173            1, 6, 0x38, 0x01, 0x80, 0xBB, 0, 0, 0, 0, 1, 4, 2, 0, 4, 1, 2, 3, 5,
174        ]);
175        let head = OpusHead::parse(&h).unwrap();
176        assert_eq!(
177            (head.channels, head.streams, head.coupled_streams),
178            (6, 4, 2)
179        );
180        assert_eq!(head.mapping, vec![0, 4, 1, 2, 3, 5]);
181        assert_eq!(OpusHead::parse(&head.to_bytes()).unwrap(), head);
182    }
183
184    #[test]
185    fn rejects_garbage() {
186        assert!(OpusHead::parse(b"OpusHea").is_err());
187        assert!(OpusHead::parse(b"OpusTags...........").is_err());
188        let mut h = b"OpusHead".to_vec();
189        h.extend([1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
190        assert!(OpusHead::parse(&h).is_err());
191        let mut h = b"OpusHead".to_vec();
192        h.extend([1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 5]);
193        assert!(OpusHead::parse(&h).is_err());
194    }
195
196    #[test]
197    fn dops() {
198        let d = [0u8, 2, 0x01, 0x38, 0, 0, 0xBB, 0x80, 0, 0, 0];
199        let head = OpusHead::from_dops(&d).unwrap();
200        assert_eq!(head.pre_skip, 312);
201        assert_eq!(head.channels, 2);
202    }
203}