1use opus_rs::OpusDecoder;
10use std::panic::{AssertUnwindSafe, catch_unwind};
11use symphonia::core::codecs::audio::AudioCodecParameters;
12
13const MAX_FRAME: usize = 5760;
15
16#[derive(Debug, thiserror::Error)]
18pub enum OpusError {
19 #[error("opus decoder init failed: {0}")]
20 Init(String),
21 #[error("opus decode error: {0}")]
22 Decode(String),
23 #[error("invalid opus header")]
24 InvalidHeader,
25 #[error("opus decoder panicked")]
26 Panicked,
27}
28
29pub struct OpusBridge {
31 decoder: OpusDecoder,
32 channels: usize,
33 pcm_buf: Vec<f32>,
35 pre_skip: u32,
37 skipped: u32,
39}
40
41const OPUS_HEAD_MAGIC: &[u8] = b"OpusHead";
50const OPUS_TAGS_MAGIC: &[u8] = b"OpusTags";
51const OPUS_HEAD_MIN_LEN: usize = 19;
52
53fn is_header_packet(data: &[u8]) -> bool {
61 data.len() >= 8 && (&data[..8] == OPUS_HEAD_MAGIC || &data[..8] == OPUS_TAGS_MAGIC)
62}
63
64fn parse_opus_head(data: &[u8]) -> Result<(usize, u32), OpusError> {
66 if data.len() < OPUS_HEAD_MIN_LEN || &data[..8] != OPUS_HEAD_MAGIC {
67 return Err(OpusError::InvalidHeader);
68 }
69 let channels = data[9] as usize;
70 let pre_skip = u16::from_le_bytes([data[10], data[11]]) as u32;
71 Ok((channels, pre_skip))
72}
73
74impl OpusBridge {
75 pub fn new(params: &AudioCodecParameters) -> Result<Self, OpusError> {
81 let (channels, pre_skip) = if let Some(extra) = ¶ms.extra_data {
83 parse_opus_head(extra)?
84 } else {
85 let ch = params.channels.as_ref().map(|c| c.count()).unwrap_or(2);
87 (ch, 0)
88 };
89
90 if channels == 0 || channels > 2 {
91 return Err(OpusError::Init(format!(
94 "unsupported channel count: {channels} (only mono/stereo supported)"
95 )));
96 }
97
98 let decoder = OpusDecoder::new(48000, channels).map_err(|e| OpusError::Init(e.into()))?;
99
100 let max_samples = MAX_FRAME * channels;
101 let pcm_buf = vec![0.0f32; max_samples];
102
103 Ok(Self {
104 decoder,
105 channels,
106 pcm_buf,
107 pre_skip,
108 skipped: 0,
109 })
110 }
111
112 pub fn channels(&self) -> usize {
114 self.channels
115 }
116
117 pub fn decode_packet(&mut self, data: &[u8]) -> Result<&[f32], OpusError> {
123 if is_header_packet(data) {
124 return Ok(&[]);
125 }
126
127 let decoder = &mut self.decoder;
130 let pcm_buf = &mut self.pcm_buf;
131 let frames_per_channel = match catch_unwind(AssertUnwindSafe(|| {
132 decoder.decode(data, MAX_FRAME, pcm_buf)
133 })) {
134 Ok(Ok(frames)) => frames,
135 Ok(Err(e)) => return Err(OpusError::Decode(e.into())),
136 Err(_) => return Err(OpusError::Panicked),
137 };
138
139 let total_samples = frames_per_channel * self.channels;
140
141 let start = if self.skipped < self.pre_skip {
143 let remaining = (self.pre_skip - self.skipped) as usize;
144 let skip_frames = remaining.min(frames_per_channel);
145 self.skipped += skip_frames as u32;
146 skip_frames * self.channels
147 } else {
148 0
149 };
150
151 Ok(&self.pcm_buf[start..total_samples])
152 }
153
154 pub fn reset(&mut self, at_start: bool) {
157 if let Ok(decoder) = OpusDecoder::new(48000, self.channels) {
159 self.decoder = decoder;
160 }
161 self.skipped = if at_start { 0 } else { self.pre_skip };
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_parse_opus_head_valid() {
171 let mut header = vec![0u8; 19];
173 header[..8].copy_from_slice(b"OpusHead");
174 header[8] = 1; header[9] = 2; header[10] = 0x38; header[11] = 0x01;
178 let (channels, pre_skip) = parse_opus_head(&header).unwrap();
181 assert_eq!(channels, 2);
182 assert_eq!(pre_skip, 312);
183 }
184
185 #[test]
186 fn test_parse_opus_head_mono() {
187 let mut header = vec![0u8; 19];
188 header[..8].copy_from_slice(b"OpusHead");
189 header[8] = 1;
190 header[9] = 1; header[10] = 0x00;
192 header[11] = 0x00;
193
194 let (channels, pre_skip) = parse_opus_head(&header).unwrap();
195 assert_eq!(channels, 1);
196 assert_eq!(pre_skip, 0);
197 }
198
199 #[test]
200 fn test_parse_opus_head_invalid_magic() {
201 let header = b"NotOpusHead_padding";
202 assert!(parse_opus_head(header).is_err());
203 }
204
205 #[test]
206 fn test_parse_opus_head_too_short() {
207 let header = b"OpusHea"; assert!(parse_opus_head(header).is_err());
209 }
210
211 #[test]
212 fn test_opus_bridge_new_stereo() {
213 let mut header = vec![0u8; 19];
215 header[..8].copy_from_slice(b"OpusHead");
216 header[8] = 1;
217 header[9] = 2; header[10] = 0x38;
219 header[11] = 0x01; let mut params = AudioCodecParameters::new();
222 params.with_extra_data(header.into_boxed_slice());
223
224 let bridge = OpusBridge::new(¶ms).unwrap();
225 assert_eq!(bridge.channels(), 2);
226 }
227
228 #[test]
229 fn pre_skip_is_dropped_at_the_start_and_only_there() {
230 let mut header = vec![0u8; 19];
231 header[..8].copy_from_slice(b"OpusHead");
232 header[8] = 1;
233 header[9] = 2;
234 header[10..12].copy_from_slice(&312u16.to_le_bytes());
235 let mut params = AudioCodecParameters::new();
236 params.with_extra_data(header.into_boxed_slice());
237 let mut bridge = OpusBridge::new(¶ms).unwrap();
238
239 let pcm: Vec<f32> = (0..960 * 2)
241 .map(|i| ((i / 2) as f32 * 0.05).sin() * 0.5)
242 .collect();
243 let mut encoder = opus_rs::OpusEncoder::new(48000, 2, opus_rs::Application::Audio).unwrap();
244 let mut packet = vec![0u8; 1500];
245 let len = encoder.encode(&pcm, 960, &mut packet).unwrap();
246 let packet = &packet[..len];
247
248 let frames = |bridge: &mut OpusBridge| bridge.decode_packet(packet).unwrap().len() / 2;
249 assert_eq!(frames(&mut bridge), 960 - 312);
250 assert_eq!(frames(&mut bridge), 960);
251 bridge.reset(false);
252 assert_eq!(frames(&mut bridge), 960);
253 bridge.reset(true);
254 assert_eq!(frames(&mut bridge), 960 - 312);
255 }
256
257 #[test]
258 fn test_header_packets_recognised_by_magic() {
259 let mut head = vec![0u8; 19];
260 head[..8].copy_from_slice(b"OpusHead");
261 assert!(is_header_packet(&head));
262
263 let mut tags = vec![0u8; 32];
264 tags[..8].copy_from_slice(b"OpusTags");
265 assert!(is_header_packet(&tags));
266 }
267
268 #[test]
269 fn test_audio_packets_are_not_treated_as_headers() {
270 assert!(!is_header_packet(&[
273 0x4F, 0x70, 0x75, 0x11, 0x22, 0x33, 0x44, 0x55
274 ]));
275 assert!(!is_header_packet(&[0xFC, 0x01, 0x02, 0x03]));
276 assert!(!is_header_packet(&[]));
277 }
278
279 #[test]
280 fn test_opus_bridge_rejects_multichannel() {
281 let mut header = vec![0u8; 19];
282 header[..8].copy_from_slice(b"OpusHead");
283 header[8] = 1;
284 header[9] = 6; header[10] = 0x00;
286 header[11] = 0x00;
287
288 let mut params = AudioCodecParameters::new();
289 params.with_extra_data(header.into_boxed_slice());
290
291 assert!(OpusBridge::new(¶ms).is_err());
292 }
293}