1use opus_decoder::OpusDecoder;
10use std::panic::{AssertUnwindSafe, catch_unwind};
11use symphonia::core::codecs::audio::AudioCodecParameters;
12
13#[derive(Debug, thiserror::Error)]
15pub enum OpusError {
16 #[error("opus decoder init failed: {0}")]
17 Init(String),
18 #[error("opus decode error: {0}")]
19 Decode(String),
20 #[error("invalid opus header")]
21 InvalidHeader,
22 #[error("opus decoder panicked")]
23 Panicked,
24}
25
26pub struct OpusBridge {
28 decoder: OpusDecoder,
29 channels: usize,
30 pcm_buf: Vec<f32>,
32 pre_skip: u32,
34 skipped: u32,
36}
37
38const OPUS_HEAD_MAGIC: &[u8] = b"OpusHead";
47const OPUS_TAGS_MAGIC: &[u8] = b"OpusTags";
48const OPUS_HEAD_MIN_LEN: usize = 19;
49
50fn is_header_packet(data: &[u8]) -> bool {
58 data.len() >= 8 && (&data[..8] == OPUS_HEAD_MAGIC || &data[..8] == OPUS_TAGS_MAGIC)
59}
60
61fn parse_opus_head(data: &[u8]) -> Result<(usize, u32), OpusError> {
63 if data.len() < OPUS_HEAD_MIN_LEN || &data[..8] != OPUS_HEAD_MAGIC {
64 return Err(OpusError::InvalidHeader);
65 }
66 let channels = data[9] as usize;
67 let pre_skip = u16::from_le_bytes([data[10], data[11]]) as u32;
68 Ok((channels, pre_skip))
69}
70
71impl OpusBridge {
72 pub fn new(params: &AudioCodecParameters) -> Result<Self, OpusError> {
78 let (channels, pre_skip) = if let Some(extra) = ¶ms.extra_data {
80 parse_opus_head(extra)?
81 } else {
82 let ch = params.channels.as_ref().map(|c| c.count()).unwrap_or(2);
84 (ch, 0)
85 };
86
87 if channels == 0 || channels > 2 {
88 return Err(OpusError::Init(format!(
91 "unsupported channel count: {channels} (only mono/stereo supported)"
92 )));
93 }
94
95 let decoder =
96 OpusDecoder::new(48000, channels).map_err(|e| OpusError::Init(format!("{e:?}")))?;
97
98 let max_samples = 5760 * channels;
100 let pcm_buf = vec![0.0f32; max_samples];
101
102 Ok(Self {
103 decoder,
104 channels,
105 pcm_buf,
106 pre_skip,
107 skipped: 0,
108 })
109 }
110
111 pub fn channels(&self) -> usize {
113 self.channels
114 }
115
116 pub fn decode_packet(&mut self, data: &[u8]) -> Result<&[f32], OpusError> {
122 if is_header_packet(data) {
123 return Ok(&[]);
124 }
125
126 let decoder = &mut self.decoder;
132 let pcm_buf = &mut self.pcm_buf;
133 let frames_per_channel = match catch_unwind(AssertUnwindSafe(|| {
134 decoder.decode_float(data, pcm_buf, false)
135 })) {
136 Ok(Ok(frames)) => frames,
137 Ok(Err(e)) => return Err(OpusError::Decode(format!("{e:?}"))),
138 Err(_) => return Err(OpusError::Panicked),
139 };
140
141 let total_samples = frames_per_channel * self.channels;
142
143 let start = if self.skipped < self.pre_skip {
145 let remaining = (self.pre_skip - self.skipped) as usize;
146 let skip_frames = remaining.min(frames_per_channel);
147 self.skipped += skip_frames as u32;
148 skip_frames * self.channels
149 } else {
150 0
151 };
152
153 Ok(&self.pcm_buf[start..total_samples])
154 }
155
156 pub fn reset(&mut self) {
158 self.decoder.reset();
159 self.skipped = self.pre_skip; }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_parse_opus_head_valid() {
169 let mut header = vec![0u8; 19];
171 header[..8].copy_from_slice(b"OpusHead");
172 header[8] = 1; header[9] = 2; header[10] = 0x38; header[11] = 0x01;
176 let (channels, pre_skip) = parse_opus_head(&header).unwrap();
179 assert_eq!(channels, 2);
180 assert_eq!(pre_skip, 312);
181 }
182
183 #[test]
184 fn test_parse_opus_head_mono() {
185 let mut header = vec![0u8; 19];
186 header[..8].copy_from_slice(b"OpusHead");
187 header[8] = 1;
188 header[9] = 1; header[10] = 0x00;
190 header[11] = 0x00;
191
192 let (channels, pre_skip) = parse_opus_head(&header).unwrap();
193 assert_eq!(channels, 1);
194 assert_eq!(pre_skip, 0);
195 }
196
197 #[test]
198 fn test_parse_opus_head_invalid_magic() {
199 let header = b"NotOpusHead_padding";
200 assert!(parse_opus_head(header).is_err());
201 }
202
203 #[test]
204 fn test_parse_opus_head_too_short() {
205 let header = b"OpusHea"; assert!(parse_opus_head(header).is_err());
207 }
208
209 #[test]
210 fn test_opus_bridge_new_stereo() {
211 let mut header = vec![0u8; 19];
213 header[..8].copy_from_slice(b"OpusHead");
214 header[8] = 1;
215 header[9] = 2; header[10] = 0x38;
217 header[11] = 0x01; let mut params = AudioCodecParameters::new();
220 params.with_extra_data(header.into_boxed_slice());
221
222 let bridge = OpusBridge::new(¶ms).unwrap();
223 assert_eq!(bridge.channels(), 2);
224 }
225
226 #[test]
227 fn test_header_packets_recognised_by_magic() {
228 let mut head = vec![0u8; 19];
229 head[..8].copy_from_slice(b"OpusHead");
230 assert!(is_header_packet(&head));
231
232 let mut tags = vec![0u8; 32];
233 tags[..8].copy_from_slice(b"OpusTags");
234 assert!(is_header_packet(&tags));
235 }
236
237 #[test]
238 fn test_audio_packets_are_not_treated_as_headers() {
239 assert!(!is_header_packet(&[
242 0x4F, 0x70, 0x75, 0x11, 0x22, 0x33, 0x44, 0x55
243 ]));
244 assert!(!is_header_packet(&[0xFC, 0x01, 0x02, 0x03]));
245 assert!(!is_header_packet(&[]));
246 }
247
248 #[test]
249 fn test_opus_bridge_rejects_multichannel() {
250 let mut header = vec![0u8; 19];
251 header[..8].copy_from_slice(b"OpusHead");
252 header[8] = 1;
253 header[9] = 6; header[10] = 0x00;
255 header[11] = 0x00;
256
257 let mut params = AudioCodecParameters::new();
258 params.with_extra_data(header.into_boxed_slice());
259
260 assert!(OpusBridge::new(¶ms).is_err());
261 }
262}