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
16use super::Decoded;
17#[cfg(feature = "aac")]
18use crate::aac;
19use crate::opus;
20use crate::pcm;
21use crate::{Activity, Error, Format};
22
23const MAX_FRAME_MS: usize = 120;
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum Start {
37 #[default]
43 Oldest,
44 Latest,
50}
51
52#[derive(Clone, Debug, Default)]
61#[non_exhaustive]
62pub struct Config {
63 pub format: Format,
65 pub sample_rate: Option<u32>,
68 pub channels: Option<u32>,
71 pub latency_max: Option<Duration>,
82 pub start: Start,
84}
85
86impl Config {
87 pub fn new() -> Self {
90 Self::default()
91 }
92}
93
94pub struct Decoder {
99 backend: Backend,
100 sample_rate: u32,
101 channel_count: u32,
102 delay: usize,
103}
104
105enum Backend {
106 Opus(Opus),
107 Pcm {
108 bytes_per_frame: usize,
109 },
110 #[cfg(feature = "aac")]
111 Aac(Box<Aac>),
112}
113
114struct Opus {
115 inner: *mut OpusDecoder,
116 pre_skip_remaining: usize,
117 max_frame_size: usize,
118 in_dtx: bool,
119}
120
121unsafe impl Send for Opus {}
123
124#[cfg(feature = "aac")]
127struct Aac {
128 inner: symphonia_codec_aac::AacDecoder,
129}
130
131impl Decoder {
132 pub fn new(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
138 match &catalog.codec {
139 hang::catalog::AudioCodec::Opus => Self::new_opus(catalog),
140 hang::catalog::AudioCodec::Pcm => Self::new_pcm(catalog),
141 #[cfg(feature = "aac")]
142 hang::catalog::AudioCodec::AAC(aac) => Self::new_aac(catalog, aac.profile),
143 codec => Err(Error::Unsupported(format!("unsupported audio codec: {codec}"))),
144 }
145 }
146
147 fn new_opus(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
148 let (sample_rate, channel_count, pre_skip) = if let Some(desc) = &catalog.description {
149 let mut buf = desc.as_ref();
150 match moq_mux::codec::opus::Config::parse(&mut buf) {
151 Ok(head) => (head.sample_rate, head.channel_count, head.pre_skip),
152 Err(_) => (catalog.sample_rate, catalog.channel_count, 0),
153 }
154 } else {
155 (catalog.sample_rate, catalog.channel_count, 0)
156 };
157
158 opus::validate_rate(sample_rate)?;
159 let channels = opus::validate_channels(channel_count)?;
160
161 let mut err = 0i32;
162 let inner = unsafe { opus_decoder_create(sample_rate as i32, channels, &mut err) };
164 if err != OPUS_OK || inner.is_null() {
165 return Err(opus::error(err, "opus_decoder_create"));
166 }
167
168 let max_frame_size = (sample_rate as usize * MAX_FRAME_MS) / 1000;
169 let pre_skip_remaining = (pre_skip as usize * sample_rate as usize) / 48_000;
170
171 Ok(Self {
172 backend: Backend::Opus(Opus {
173 inner,
174 pre_skip_remaining,
175 max_frame_size,
176 in_dtx: false,
177 }),
178 sample_rate,
179 channel_count,
180 delay: pre_skip_remaining,
181 })
182 }
183
184 #[cfg(feature = "aac")]
193 fn new_aac(catalog: &hang::catalog::AudioConfig, profile: u8) -> Result<Self, Error> {
194 use symphonia_core::codecs::audio::well_known::CODEC_ID_AAC;
195 use symphonia_core::codecs::audio::{AudioCodecParameters, AudioDecoderOptions};
196
197 let description = aac::description(catalog, profile)?;
198
199 let mut params = AudioCodecParameters::new();
200 params
201 .for_codec(CODEC_ID_AAC)
202 .with_extra_data(description.to_vec().into_boxed_slice());
203
204 let inner = symphonia_codec_aac::AacDecoder::try_new(¶ms, &AudioDecoderOptions::default())
205 .map_err(|err| Error::Unsupported(format!("aac decoder: {err}")))?;
206
207 let params = inner.codec_params();
210 let sample_rate = params
211 .sample_rate
212 .ok_or_else(|| Error::Unsupported("aac config declares no sample rate".into()))?;
213 let channel_count = params
214 .channels
215 .as_ref()
216 .map(|channels| channels.count())
217 .ok_or_else(|| Error::Unsupported("aac config declares no channels".into()))?;
218
219 Ok(Self {
220 backend: Backend::Aac(Box::new(Aac { inner })),
221 sample_rate,
222 channel_count: channel_count as u32,
223 delay: 0,
224 })
225 }
226
227 fn new_pcm(catalog: &hang::catalog::AudioConfig) -> Result<Self, Error> {
228 if catalog.sample_rate == 0 {
229 return Err(Error::Unsupported("pcm sample rate must be greater than zero".into()));
230 }
231 if catalog.channel_count == 0 {
232 return Err(Error::Unsupported("pcm channel count must be greater than zero".into()));
233 }
234 if catalog.description.is_some() {
235 return Err(Error::Unsupported("pcm catalog description must be absent".into()));
236 }
237 let bitrate = pcm::bitrate(catalog.sample_rate, catalog.channel_count)?;
238 if catalog.bitrate.is_some_and(|declared| declared != bitrate) {
239 return Err(Error::Unsupported(format!(
240 "pcm catalog bitrate must be {bitrate} bits per second"
241 )));
242 }
243 let bytes_per_frame = pcm::frame_bytes(1, catalog.channel_count)?;
244
245 Ok(Self {
246 backend: Backend::Pcm { bytes_per_frame },
247 sample_rate: catalog.sample_rate,
248 channel_count: catalog.channel_count,
249 delay: 0,
250 })
251 }
252
253 pub fn sample_rate(&self) -> u32 {
255 self.sample_rate
256 }
257
258 pub fn channel_count(&self) -> u32 {
260 self.channel_count
261 }
262
263 pub fn reset(&mut self) -> Result<(), Error> {
265 self.reset_prediction()?;
266 if let Backend::Opus(opus) = &mut self.backend {
267 opus.pre_skip_remaining = self.delay;
268 }
269 Ok(())
270 }
271
272 pub(super) fn reset_prediction(&mut self) -> Result<(), Error> {
274 match &mut self.backend {
275 Backend::Opus(opus) => {
276 let rc = unsafe { opus_decoder_ctl_impl(opus.inner, OPUS_RESET_STATE, varargs![]) };
278 if rc != OPUS_OK {
279 return Err(crate::opus::error(rc, "OPUS_RESET_STATE"));
280 }
281 opus.in_dtx = false;
282 }
283 Backend::Pcm { .. } => {}
284 #[cfg(feature = "aac")]
285 Backend::Aac(aac) => aac.inner.reset(),
286 }
287 Ok(())
288 }
289
290 pub(super) fn delay_remaining(&self) -> usize {
296 match &self.backend {
297 Backend::Opus(opus) => opus.pre_skip_remaining,
298 Backend::Pcm { .. } => 0,
299 #[cfg(feature = "aac")]
300 Backend::Aac(_) => 0,
301 }
302 }
303
304 pub fn decode(&mut self, packet: &[u8]) -> Result<Decoded, Error> {
309 match &mut self.backend {
310 Backend::Opus(opus) => {
311 let mut out = vec![0.0f32; opus.max_frame_size * self.channel_count as usize];
312 let samples = unsafe {
315 opus_decode_float(
316 &mut *opus.inner,
317 packet.as_ptr(),
318 packet.len() as i32,
319 out.as_mut_ptr(),
320 opus.max_frame_size as i32,
321 0,
322 )
323 };
324 if samples < 0 {
325 return Err(crate::opus::decode_error(samples));
326 }
327 out.truncate(samples as usize * self.channel_count as usize);
328 let trim_frames = opus.pre_skip_remaining.min(samples as usize);
329 if trim_frames > 0 {
330 let trim_samples = trim_frames * self.channel_count as usize;
331 out.copy_within(trim_samples.., 0);
332 out.truncate(out.len() - trim_samples);
333 opus.pre_skip_remaining -= trim_frames;
334 }
335 let activity = crate::opus::activity(packet, opus.in_dtx);
336 opus.in_dtx = activity.is_dtx();
337 Ok(Decoded { samples: out, activity })
338 }
339 Backend::Pcm { bytes_per_frame } => {
340 if packet.is_empty() || !packet.len().is_multiple_of(*bytes_per_frame) {
341 return Err(Error::Misaligned {
342 got: packet.len(),
343 expected: packet.len().max(1).next_multiple_of(*bytes_per_frame),
344 });
345 }
346
347 let out = packet
348 .chunks_exact(pcm::BYTES_PER_SAMPLE)
349 .map(|sample| f32::from_le_bytes([sample[0], sample[1], sample[2], sample[3]]))
350 .collect();
351 Ok(Decoded {
352 samples: out,
353 activity: Activity::Active,
354 })
355 }
356 #[cfg(feature = "aac")]
357 Backend::Aac(aac) => {
358 let packet = symphonia_core::packet::PacketRef::new(
362 0,
363 symphonia_core::units::Timestamp::ZERO,
364 symphonia_core::units::Duration::ZERO,
365 packet,
366 );
367
368 let decoded = aac
369 .inner
370 .decode_ref(&packet)
371 .map_err(|err| Error::Decode(format!("aac: {err}")))?;
372
373 let mut out = Vec::new();
374 decoded.copy_to_vec_interleaved(&mut out);
375 Ok(Decoded {
376 samples: out,
377 activity: Activity::Active,
378 })
379 }
380 }
381 }
382}
383
384impl Drop for Opus {
385 fn drop(&mut self) {
386 unsafe { opus_decoder_destroy(self.inner) };
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[cfg(feature = "aac")]
406 const AAC_DESCRIPTION: &[u8] = b"\x12\x08";
407
408 #[cfg(feature = "aac")]
409 const AAC_FRAMES: [&[u8]; 3] = [
410 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",
411 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",
412 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",
413 ];
414
415 #[cfg(feature = "aac")]
416 fn aac_catalog() -> hang::catalog::AudioConfig {
417 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AAC { profile: 2 }, 44_100, 1);
418 catalog.description = Some(bytes::Bytes::from_static(AAC_DESCRIPTION));
419 catalog
420 }
421
422 #[cfg(feature = "aac")]
423 #[test]
424 fn aac_decodes_a_sine() {
425 let mut decoder = Decoder::new(&aac_catalog()).unwrap();
426 assert_eq!(decoder.sample_rate(), 44_100);
427 assert_eq!(decoder.channel_count(), 1);
428
429 let decoded: Vec<Vec<f32>> = AAC_FRAMES
430 .iter()
431 .map(|frame| decoder.decode(frame).unwrap().samples)
432 .collect();
433
434 for pcm in &decoded {
436 assert_eq!(pcm.len(), 1024);
437 }
438
439 let last = decoded.last().unwrap();
443 let rms = (last.iter().map(|s| s * s).sum::<f32>() / last.len() as f32).sqrt();
444 assert!((0.65..0.8).contains(&rms), "expected a full-scale sine, got {rms} RMS");
445 }
446
447 #[cfg(feature = "aac")]
448 #[test]
449 fn aac_reports_a_truncated_packet_as_decode() {
450 let mut decoder = Decoder::new(&aac_catalog()).unwrap();
451
452 let truncated = &AAC_FRAMES[0][..16];
453 assert!(matches!(decoder.decode(truncated), Err(Error::Decode(_))));
454 }
455
456 #[cfg(feature = "aac")]
457 #[test]
458 fn aac_synthesizes_a_missing_description() {
459 let mut catalog = aac_catalog();
461 catalog.description = None;
462
463 let mut decoder = Decoder::new(&catalog).unwrap();
464 assert_eq!(decoder.sample_rate(), 44_100);
465 assert_eq!(decoder.decode(AAC_FRAMES[0]).unwrap().samples.len(), 1024);
466 }
467
468 #[test]
472 fn opus_reports_a_rejected_packet_as_decode() {
473 let head = moq_mux::codec::opus::Config::new(48_000, 2).encode().unwrap();
474 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2);
475 catalog.description = Some(head);
476
477 let mut decoder = Decoder::new(&catalog).unwrap();
478
479 assert!(matches!(decoder.decode(&[0xFF; 3]), Err(Error::Decode(_))));
481 }
482
483 #[test]
484 fn pcm_rejects_incomplete_channel_frame() {
485 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
486 let mut decoder = Decoder::new(&catalog).unwrap();
487
488 assert!(matches!(
489 decoder.decode(&[]),
490 Err(Error::Misaligned { got: 0, expected: 8 })
491 ));
492 assert!(matches!(
493 decoder.decode(&[0; 4]),
494 Err(Error::Misaligned { got: 4, expected: 8 })
495 ));
496 }
497
498 #[test]
499 fn decoder_rejects_unknown_codec() {
500 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Unknown("future".into()), 48_000, 2);
501
502 assert!(matches!(Decoder::new(&catalog), Err(Error::Unsupported(_))));
503 }
504
505 #[test]
506 fn pcm_rejects_incorrect_catalog_bitrate() {
507 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
508 catalog.bitrate = Some(1);
509
510 assert!(matches!(Decoder::new(&catalog), Err(Error::Unsupported(_))));
511 }
512}