1use std::time::Duration;
7
8use unsafe_libopus::{
9 OPUS_OK, OPUS_RESET_STATE, OpusDecoder, opus_decode_float, opus_decoder_create, opus_decoder_ctl_impl,
10 opus_decoder_destroy, varargs,
11};
12
13#[cfg(feature = "aac")]
14use symphonia_core::codecs::audio::AudioDecoder;
15
16#[cfg(feature = "aac")]
17use crate::aac;
18use crate::opus;
19use crate::pcm;
20use crate::{Error, Format};
21
22const MAX_FRAME_MS: usize = 120;
24
25#[derive(Clone, Debug, Default)]
34#[non_exhaustive]
35pub struct Config {
36 pub format: Format,
38 pub sample_rate: Option<u32>,
41 pub channels: Option<u32>,
44 pub latency_max: Option<Duration>,
55}
56
57impl Config {
58 pub fn new() -> Self {
61 Self::default()
62 }
63}
64
65pub struct Decoder {
70 backend: Backend,
71 sample_rate: u32,
72 channel_count: u32,
73 delay: usize,
74}
75
76enum Backend {
77 Opus(Opus),
78 Pcm {
79 bytes_per_frame: usize,
80 },
81 #[cfg(feature = "aac")]
82 Aac(Box<Aac>),
83}
84
85struct Opus {
86 inner: *mut OpusDecoder,
87 pre_skip_remaining: usize,
88 max_frame_size: usize,
89}
90
91unsafe impl Send for Opus {}
93
94#[cfg(feature = "aac")]
97struct Aac {
98 inner: symphonia_codec_aac::AacDecoder,
99}
100
101impl Decoder {
102 pub fn new(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
108 match &catalog.codec {
109 hang::catalog::AudioCodec::Opus => Self::new_opus(catalog),
110 hang::catalog::AudioCodec::Pcm => Self::new_pcm(catalog),
111 #[cfg(feature = "aac")]
112 hang::catalog::AudioCodec::AAC(aac) => Self::new_aac(catalog, aac.profile),
113 codec => Err(Error::Unsupported(format!("unsupported audio codec: {codec}"))),
114 }
115 }
116
117 fn new_opus(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
118 let (sample_rate, channel_count, pre_skip) = if let Some(desc) = &catalog.description {
119 let mut buf = desc.as_ref();
120 match moq_mux::codec::opus::Config::parse(&mut buf) {
121 Ok(head) => (head.sample_rate, head.channel_count, head.pre_skip),
122 Err(_) => (catalog.sample_rate, catalog.channel_count, 0),
123 }
124 } else {
125 (catalog.sample_rate, catalog.channel_count, 0)
126 };
127
128 opus::validate_rate(sample_rate)?;
129 let channels = opus::validate_channels(channel_count)?;
130
131 let mut err = 0i32;
132 let inner = unsafe { opus_decoder_create(sample_rate as i32, channels, &mut err) };
134 if err != OPUS_OK || inner.is_null() {
135 return Err(opus::error(err, "opus_decoder_create"));
136 }
137
138 let max_frame_size = (sample_rate as usize * MAX_FRAME_MS) / 1000;
139 let pre_skip_remaining = (pre_skip as usize * sample_rate as usize) / 48_000;
140
141 Ok(Self {
142 backend: Backend::Opus(Opus {
143 inner,
144 pre_skip_remaining,
145 max_frame_size,
146 }),
147 sample_rate,
148 channel_count,
149 delay: pre_skip_remaining,
150 })
151 }
152
153 #[cfg(feature = "aac")]
162 fn new_aac(catalog: &hang::catalog::AudioConfig, profile: u8) -> Result<Self, Error> {
163 use symphonia_core::codecs::audio::well_known::CODEC_ID_AAC;
164 use symphonia_core::codecs::audio::{AudioCodecParameters, AudioDecoderOptions};
165
166 let description = aac::description(catalog, profile)?;
167
168 let mut params = AudioCodecParameters::new();
169 params
170 .for_codec(CODEC_ID_AAC)
171 .with_extra_data(description.to_vec().into_boxed_slice());
172
173 let inner = symphonia_codec_aac::AacDecoder::try_new(¶ms, &AudioDecoderOptions::default())
174 .map_err(|err| Error::Unsupported(format!("aac decoder: {err}")))?;
175
176 let params = inner.codec_params();
179 let sample_rate = params
180 .sample_rate
181 .ok_or_else(|| Error::Unsupported("aac config declares no sample rate".into()))?;
182 let channel_count = params
183 .channels
184 .as_ref()
185 .map(|channels| channels.count())
186 .ok_or_else(|| Error::Unsupported("aac config declares no channels".into()))?;
187
188 Ok(Self {
189 backend: Backend::Aac(Box::new(Aac { inner })),
190 sample_rate,
191 channel_count: channel_count as u32,
192 delay: 0,
193 })
194 }
195
196 fn new_pcm(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
197 if catalog.sample_rate == 0 {
198 return Err(Error::Unsupported("pcm sample rate must be greater than zero".into()));
199 }
200 if catalog.channel_count == 0 {
201 return Err(Error::Unsupported("pcm channel count must be greater than zero".into()));
202 }
203 if catalog.description.is_some() {
204 return Err(Error::Unsupported("pcm catalog description must be absent".into()));
205 }
206 let bitrate = pcm::bitrate(catalog.sample_rate, catalog.channel_count)?;
207 if catalog.bitrate.is_some_and(|declared| declared != bitrate) {
208 return Err(Error::Unsupported(format!(
209 "pcm catalog bitrate must be {bitrate} bits per second"
210 )));
211 }
212 let bytes_per_frame = pcm::frame_bytes(1, catalog.channel_count)?;
213
214 Ok(Self {
215 backend: Backend::Pcm { bytes_per_frame },
216 sample_rate: catalog.sample_rate,
217 channel_count: catalog.channel_count,
218 delay: 0,
219 })
220 }
221
222 pub fn sample_rate(&self) -> u32 {
224 self.sample_rate
225 }
226
227 pub fn channel_count(&self) -> u32 {
229 self.channel_count
230 }
231
232 pub fn reset(&mut self) -> Result<(), Error> {
234 match &mut self.backend {
235 Backend::Opus(opus) => {
236 let rc = unsafe { opus_decoder_ctl_impl(opus.inner, OPUS_RESET_STATE, varargs![]) };
238 if rc != OPUS_OK {
239 return Err(crate::opus::error(rc, "OPUS_RESET_STATE"));
240 }
241 opus.pre_skip_remaining = self.delay;
242 }
243 Backend::Pcm { .. } => {}
244 #[cfg(feature = "aac")]
245 Backend::Aac(aac) => aac.inner.reset(),
246 }
247 Ok(())
248 }
249
250 pub(super) fn delay(&self) -> usize {
252 self.delay
253 }
254
255 pub fn decode(&mut self, packet: &[u8]) -> Result<Vec<f32>, Error> {
257 match &mut self.backend {
258 Backend::Opus(opus) => {
259 let mut out = vec![0.0f32; opus.max_frame_size * self.channel_count as usize];
260 let samples = unsafe {
263 opus_decode_float(
264 &mut *opus.inner,
265 packet.as_ptr(),
266 packet.len() as i32,
267 out.as_mut_ptr(),
268 opus.max_frame_size as i32,
269 0,
270 )
271 };
272 if samples < 0 {
273 return Err(crate::opus::decode_error(samples));
274 }
275 out.truncate(samples as usize * self.channel_count as usize);
276 let trim_frames = opus.pre_skip_remaining.min(samples as usize);
277 if trim_frames > 0 {
278 let trim_samples = trim_frames * self.channel_count as usize;
279 out.copy_within(trim_samples.., 0);
280 out.truncate(out.len() - trim_samples);
281 opus.pre_skip_remaining -= trim_frames;
282 }
283 Ok(out)
284 }
285 Backend::Pcm { bytes_per_frame } => {
286 if packet.is_empty() || !packet.len().is_multiple_of(*bytes_per_frame) {
287 return Err(Error::Misaligned {
288 got: packet.len(),
289 expected: packet.len().max(1).next_multiple_of(*bytes_per_frame),
290 });
291 }
292
293 Ok(packet
294 .chunks_exact(pcm::BYTES_PER_SAMPLE)
295 .map(|sample| f32::from_le_bytes([sample[0], sample[1], sample[2], sample[3]]))
296 .collect())
297 }
298 #[cfg(feature = "aac")]
299 Backend::Aac(aac) => {
300 let packet = symphonia_core::packet::PacketRef::new(
304 0,
305 symphonia_core::units::Timestamp::ZERO,
306 symphonia_core::units::Duration::ZERO,
307 packet,
308 );
309
310 let decoded = aac
311 .inner
312 .decode_ref(&packet)
313 .map_err(|err| Error::Decode(format!("aac: {err}")))?;
314
315 let mut out = Vec::new();
316 decoded.copy_to_vec_interleaved(&mut out);
317 Ok(out)
318 }
319 }
320 }
321}
322
323impl Drop for Opus {
324 fn drop(&mut self) {
325 unsafe { opus_decoder_destroy(self.inner) };
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[cfg(feature = "aac")]
345 const AAC_DESCRIPTION: &[u8] = b"\x12\x08";
346
347 #[cfg(feature = "aac")]
348 const AAC_FRAMES: [&[u8]; 3] = [
349 b"\x01\x52\xf2\x8b\x1a\xd7\x8e\x7b\xfd\xa7\xef\xe7\xe3\x55\xd3\x4d\x2f\x55\x2e\x47\x1c\x92\x49\x11\x20\x77\x3f\xbe\x74\xdd\x99\xb3\x7b\xfb\x90\xc9\xf0\x61\x9f\xdc\x0c\x9f\x06\x19\xfd\xe1\x1f\x1f\x00\x67\xf7\x03\x87\xc0\x19\xfd\xc0\xc9\xf0\x07",
350 b"\x01\x1e\x32\x89\xe2\x9d\x6b\x33\xe7\xff\xe2\xfe\xbf\xfa\xff\xe7\x2f\x8b\xd5\xd5\xe7\x5f\x3f\x59\xeb\xf1\xcb\xba\xa5\x5e\x52\x4a\xbd\x8d\x74\x50\x8c\x08\xa8\xa0\xd4\x51\x40\xa1\x86\x5d\x06\xb4\x6c\x32\xe6\x25\x9a\x66\x75\xcd\xf9\xbf\x6f\x83\xb7\x53\x80",
351 b"\x01\x1e\x32\x8a\x22\x7d\x40\x87\x48\xdb\xdf\xff\xf9\x4f\xff\x87\xde\xef\x8b\xeb\x1e\x77\x5d\xfc\x67\x8f\x8c\x77\x8a\xd6\x29\x96\x1f\x29\xe7\x39\xd4\x53\xcf\x3c\xf3\xce\x79\xd4\x27\x9c\xf5\x65\x2a\x9b\xe9\x80\xb7\xba\xa9\xf9\x58\xc7\x3c\x58\x27\x8a\x60\xa1\x57",
352 ];
353
354 #[cfg(feature = "aac")]
355 fn aac_catalog() -> hang::catalog::AudioConfig {
356 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AAC { profile: 2 }, 44_100, 1);
357 catalog.description = Some(bytes::Bytes::from_static(AAC_DESCRIPTION));
358 catalog
359 }
360
361 #[cfg(feature = "aac")]
362 #[test]
363 fn aac_decodes_a_sine() {
364 let mut decoder = Decoder::new(&aac_catalog()).unwrap();
365 assert_eq!(decoder.sample_rate(), 44_100);
366 assert_eq!(decoder.channel_count(), 1);
367
368 let decoded: Vec<Vec<f32>> = AAC_FRAMES.iter().map(|frame| decoder.decode(frame).unwrap()).collect();
369
370 for pcm in &decoded {
372 assert_eq!(pcm.len(), 1024);
373 }
374
375 let last = decoded.last().unwrap();
379 let rms = (last.iter().map(|s| s * s).sum::<f32>() / last.len() as f32).sqrt();
380 assert!((0.65..0.8).contains(&rms), "expected a full-scale sine, got {rms} RMS");
381 }
382
383 #[cfg(feature = "aac")]
384 #[test]
385 fn aac_reports_a_truncated_packet_as_decode() {
386 let mut decoder = Decoder::new(&aac_catalog()).unwrap();
387
388 let truncated = &AAC_FRAMES[0][..16];
389 assert!(matches!(decoder.decode(truncated), Err(Error::Decode(_))));
390 }
391
392 #[cfg(feature = "aac")]
393 #[test]
394 fn aac_synthesizes_a_missing_description() {
395 let mut catalog = aac_catalog();
397 catalog.description = None;
398
399 let mut decoder = Decoder::new(&catalog).unwrap();
400 assert_eq!(decoder.sample_rate(), 44_100);
401 assert_eq!(decoder.decode(AAC_FRAMES[0]).unwrap().len(), 1024);
402 }
403
404 #[test]
408 fn opus_reports_a_rejected_packet_as_decode() {
409 let head = moq_mux::codec::opus::Config::new(48_000, 2).encode().unwrap();
410 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2);
411 catalog.description = Some(head);
412
413 let mut decoder = Decoder::new(&catalog).unwrap();
414
415 assert!(matches!(decoder.decode(&[0xFF; 3]), Err(Error::Decode(_))));
417 }
418
419 #[test]
420 fn pcm_rejects_incomplete_channel_frame() {
421 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
422 let mut decoder = Decoder::new(&catalog).unwrap();
423
424 assert!(matches!(
425 decoder.decode(&[]),
426 Err(Error::Misaligned { got: 0, expected: 8 })
427 ));
428 assert!(matches!(
429 decoder.decode(&[0; 4]),
430 Err(Error::Misaligned { got: 4, expected: 8 })
431 ));
432 }
433
434 #[test]
435 fn decoder_rejects_unknown_codec() {
436 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Unknown("future".into()), 48_000, 2);
437
438 assert!(matches!(Decoder::new(&catalog), Err(Error::Unsupported(_))));
439 }
440
441 #[test]
442 fn pcm_rejects_incorrect_catalog_bitrate() {
443 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
444 catalog.bitrate = Some(1);
445
446 assert!(matches!(Decoder::new(&catalog), Err(Error::Unsupported(_))));
447 }
448}