moq_mux/codec/opus/
mod.rs1mod import;
7
8pub use import::*;
9
10use bytes::{Buf, Bytes};
11
12const OPUS_HEAD: u64 = u64::from_be_bytes(*b"OpusHead");
13
14#[derive(Debug, Clone, thiserror::Error)]
16#[non_exhaustive]
17pub enum Error {
18 #[error("OpusHead must be at least 19 bytes")]
20 HeadTooShort,
21
22 #[error("invalid OpusHead signature")]
24 InvalidSignature,
25
26 #[error("channel mapping family 0 only supports mono/stereo (got {0} channels)")]
29 UnsupportedChannelCount(u32),
30}
31
32pub type Result<T> = std::result::Result<T, Error>;
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36#[non_exhaustive]
37pub struct Config {
38 pub sample_rate: u32,
40 pub channel_count: u32,
42 pub pre_skip: u16,
44}
45
46impl Config {
47 pub fn new(sample_rate: u32, channel_count: u32) -> Self {
49 Self {
50 sample_rate,
51 channel_count,
52 pre_skip: 0,
53 }
54 }
55
56 pub fn with_pre_skip(mut self, pre_skip: u16) -> Self {
58 self.pre_skip = pre_skip;
59 self
60 }
61
62 pub fn parse<T: Buf>(buf: &mut T) -> Result<Self> {
67 if buf.remaining() < 19 {
68 return Err(Error::HeadTooShort);
69 }
70 let signature = buf.get_u64();
71 if signature != OPUS_HEAD {
72 return Err(Error::InvalidSignature);
73 }
74
75 buf.advance(1); let channel_count = buf.get_u8() as u32;
77 let pre_skip = buf.get_u16_le();
78 let sample_rate = buf.get_u32_le();
79
80 if buf.remaining() > 0 {
82 buf.advance(buf.remaining());
83 }
84
85 Ok(Self {
86 sample_rate,
87 channel_count,
88 pre_skip,
89 })
90 }
91
92 pub fn encode(&self) -> Result<Bytes> {
100 if !(1..=2).contains(&self.channel_count) {
101 return Err(Error::UnsupportedChannelCount(self.channel_count));
102 }
103 let mut head = Vec::with_capacity(19);
104 head.extend_from_slice(b"OpusHead");
105 head.push(1); head.push(self.channel_count as u8);
107 head.extend_from_slice(&self.pre_skip.to_le_bytes());
108 head.extend_from_slice(&self.sample_rate.to_le_bytes());
109 head.extend_from_slice(&0i16.to_le_bytes()); head.push(0); Ok(Bytes::from(head))
112 }
113}
114
115pub(crate) fn packet_samples(packet: &[u8]) -> Option<u32> {
122 let toc = *packet.first()?;
123 let frames = match toc & 0b11 {
124 0 => 1,
125 1 | 2 => 2,
126 _ => (packet.get(1)? & 0b0011_1111) as u32,
128 };
129 Some(config_samples(toc >> 3) * frames)
130}
131
132fn config_samples(config: u8) -> u32 {
134 match config {
135 0 | 4 | 8 => 480,
137 1 | 5 | 9 => 960,
138 2 | 6 | 10 => 1920,
139 3 | 7 | 11 => 2880,
140 12 | 14 => 480,
142 13 | 15 => 960,
143 16 | 20 | 24 | 28 => 120,
145 17 | 21 | 25 | 29 => 240,
146 18 | 22 | 26 | 30 => 480,
147 _ => 960,
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn packet_samples_reads_toc() {
158 assert_eq!(packet_samples(&[16 << 3]), Some(120));
160 assert_eq!(packet_samples(&[3 << 3]), Some(2880));
162 assert_eq!(packet_samples(&[(1 << 3) | 1]), Some(1920));
164 assert_eq!(packet_samples(&[(1 << 3) | 3, 4]), Some(3840));
166 assert_eq!(packet_samples(&[]), None);
167 }
168
169 #[test]
170 fn parses_valid_opus_head() {
171 let cfg = Config::new(48_000, 2).with_pre_skip(312);
172 let encoded = cfg.encode().unwrap();
173 assert_eq!(encoded.len(), 19);
174 let parsed = Config::parse(&mut encoded.as_ref()).unwrap();
175 assert_eq!(parsed.sample_rate, 48_000);
176 assert_eq!(parsed.channel_count, 2);
177 assert_eq!(parsed.pre_skip, 312);
178 assert_eq!(parsed, cfg);
179 }
180
181 #[test]
182 fn parse_rejects_invalid_signature() {
183 let mut bytes = Config::new(48_000, 1).encode().unwrap().to_vec();
184 bytes[0] = b'X';
185 assert!(Config::parse(&mut bytes.as_slice()).is_err());
186 }
187
188 #[test]
189 fn encode_rejects_multichannel() {
190 let err = Config::new(48_000, 6).encode().unwrap_err();
191 assert!(matches!(err, Error::UnsupportedChannelCount(6)));
192 }
193}