1use opus_decoder::OpusDecoder;
9use symphonia::core::codecs::CodecParameters;
10
11#[derive(Debug, thiserror::Error)]
13pub enum OpusError {
14 #[error("opus decoder init failed: {0}")]
15 Init(String),
16 #[error("opus decode error: {0}")]
17 Decode(String),
18 #[error("invalid opus header")]
19 InvalidHeader,
20}
21
22pub struct OpusBridge {
24 decoder: OpusDecoder,
25 channels: usize,
26 pcm_buf: Vec<f32>,
28 pre_skip: u32,
30 skipped: u32,
32 packet_index: u64,
34}
35
36const OPUS_HEAD_MAGIC: &[u8] = b"OpusHead";
45const OPUS_HEAD_MIN_LEN: usize = 19;
46
47fn parse_opus_head(data: &[u8]) -> Result<(usize, u32), OpusError> {
49 if data.len() < OPUS_HEAD_MIN_LEN || &data[..8] != OPUS_HEAD_MAGIC {
50 return Err(OpusError::InvalidHeader);
51 }
52 let channels = data[9] as usize;
53 let pre_skip = u16::from_le_bytes([data[10], data[11]]) as u32;
54 Ok((channels, pre_skip))
55}
56
57impl OpusBridge {
58 pub fn new(params: &CodecParameters) -> Result<Self, OpusError> {
64 let (channels, pre_skip) = if let Some(extra) = ¶ms.extra_data {
66 parse_opus_head(extra)?
67 } else {
68 let ch = params.channels.map(|c| c.count()).unwrap_or(2);
70 (ch, 0)
71 };
72
73 if channels == 0 || channels > 2 {
74 return Err(OpusError::Init(format!(
77 "unsupported channel count: {channels} (only mono/stereo supported)"
78 )));
79 }
80
81 let decoder =
82 OpusDecoder::new(48000, channels).map_err(|e| OpusError::Init(format!("{e:?}")))?;
83
84 let max_samples = 5760 * channels;
86 let pcm_buf = vec![0.0f32; max_samples];
87
88 Ok(Self {
89 decoder,
90 channels,
91 pcm_buf,
92 pre_skip,
93 skipped: 0,
94 packet_index: 0,
95 })
96 }
97
98 pub fn channels(&self) -> usize {
100 self.channels
101 }
102
103 pub fn decode_packet(&mut self, data: &[u8]) -> Result<&[f32], OpusError> {
109 let idx = self.packet_index;
110 self.packet_index += 1;
111
112 if idx < 2 {
114 return Ok(&[]);
115 }
116
117 let frames_per_channel = self
118 .decoder
119 .decode_float(data, &mut self.pcm_buf, false)
120 .map_err(|e| OpusError::Decode(format!("{e:?}")))?;
121
122 let total_samples = frames_per_channel * self.channels;
123
124 let start = if self.skipped < self.pre_skip {
126 let remaining = (self.pre_skip - self.skipped) as usize;
127 let skip_frames = remaining.min(frames_per_channel);
128 self.skipped += skip_frames as u32;
129 skip_frames * self.channels
130 } else {
131 0
132 };
133
134 Ok(&self.pcm_buf[start..total_samples])
135 }
136
137 pub fn reset(&mut self) {
139 self.decoder.reset();
140 self.skipped = self.pre_skip; }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_parse_opus_head_valid() {
152 let mut header = vec![0u8; 19];
154 header[..8].copy_from_slice(b"OpusHead");
155 header[8] = 1; header[9] = 2; header[10] = 0x38; header[11] = 0x01;
159 let (channels, pre_skip) = parse_opus_head(&header).unwrap();
162 assert_eq!(channels, 2);
163 assert_eq!(pre_skip, 312);
164 }
165
166 #[test]
167 fn test_parse_opus_head_mono() {
168 let mut header = vec![0u8; 19];
169 header[..8].copy_from_slice(b"OpusHead");
170 header[8] = 1;
171 header[9] = 1; header[10] = 0x00;
173 header[11] = 0x00;
174
175 let (channels, pre_skip) = parse_opus_head(&header).unwrap();
176 assert_eq!(channels, 1);
177 assert_eq!(pre_skip, 0);
178 }
179
180 #[test]
181 fn test_parse_opus_head_invalid_magic() {
182 let header = b"NotOpusHead_padding";
183 assert!(parse_opus_head(header).is_err());
184 }
185
186 #[test]
187 fn test_parse_opus_head_too_short() {
188 let header = b"OpusHea"; assert!(parse_opus_head(header).is_err());
190 }
191
192 #[test]
193 fn test_opus_bridge_new_stereo() {
194 let mut header = vec![0u8; 19];
196 header[..8].copy_from_slice(b"OpusHead");
197 header[8] = 1;
198 header[9] = 2; header[10] = 0x38;
200 header[11] = 0x01; let mut params = CodecParameters::new();
203 params.with_extra_data(header.into_boxed_slice());
204
205 let bridge = OpusBridge::new(¶ms).unwrap();
206 assert_eq!(bridge.channels(), 2);
207 }
208
209 #[test]
210 fn test_opus_bridge_rejects_multichannel() {
211 let mut header = vec![0u8; 19];
212 header[..8].copy_from_slice(b"OpusHead");
213 header[8] = 1;
214 header[9] = 6; header[10] = 0x00;
216 header[11] = 0x00;
217
218 let mut params = CodecParameters::new();
219 params.with_extra_data(header.into_boxed_slice());
220
221 assert!(OpusBridge::new(¶ms).is_err());
222 }
223}