1use super::Decoded;
8use super::backend::{self, Backend};
9use crate::{Error, Layout};
10
11#[derive(Clone, Debug, Default, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum Kind {
15 #[default]
17 Auto,
18 Software,
20 Named(String),
24}
25
26#[derive(Clone, Debug, Default)]
28#[non_exhaustive]
29pub struct Config {
30 pub kind: Kind,
32}
33
34impl Config {
35 pub fn new() -> Self {
37 Self::default()
38 }
39}
40
41pub struct Decoder {
46 backend: Box<dyn Backend>,
47 delay: usize,
49 delay_remaining: usize,
50}
51
52impl Decoder {
53 pub fn new(catalog: &hang::catalog::AudioConfig, config: &Config) -> Result<Self, Error> {
61 let backend = backend::open(catalog, config)?;
62 let delay = backend.delay();
63 Ok(Self {
64 backend,
65 delay,
66 delay_remaining: delay,
67 })
68 }
69
70 pub fn name(&self) -> &str {
72 self.backend.name()
73 }
74
75 pub fn sample_rate(&self) -> u32 {
77 self.backend.sample_rate()
78 }
79
80 pub fn layout(&self) -> Layout {
82 self.backend.layout()
83 }
84
85 pub fn reset(&mut self) -> Result<(), Error> {
87 self.reset_prediction()?;
88 self.reapply_delay();
89 Ok(())
90 }
91
92 pub(super) fn reapply_delay(&mut self) {
94 self.delay_remaining = self.delay;
95 }
96
97 pub(super) fn reset_prediction(&mut self) -> Result<(), Error> {
99 self.backend.reset()
100 }
101
102 pub(super) fn delay_remaining(&self) -> usize {
108 self.delay_remaining
109 }
110
111 pub fn decode(&mut self, packet: &[u8]) -> Result<Decoded, Error> {
119 let mut decoded = self.backend.decode(packet)?;
120 let channels = self.backend.layout().channels() as usize;
121 let trim = self.delay_remaining.min(decoded.samples.len() / channels);
122 if trim > 0 {
123 decoded.samples.drain(..trim * channels);
124 self.delay_remaining -= trim;
125 }
126 Ok(decoded)
127 }
128}
129
130#[cfg(test)]
131pub(crate) mod tests {
132 use super::*;
133
134 #[cfg(feature = "aac")]
145 const AAC_DESCRIPTION: &[u8] = b"\x12\x08";
146
147 #[cfg(feature = "aac")]
148 const AAC_FRAMES: [&[u8]; 3] = [
149 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",
150 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",
151 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",
152 ];
153
154 #[cfg(feature = "aac")]
155 fn aac_catalog() -> hang::catalog::AudioConfig {
156 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AAC { profile: 2 }, 44_100, 1);
157 catalog.description = Some(bytes::Bytes::from_static(AAC_DESCRIPTION));
158 catalog
159 }
160
161 #[test]
163 fn decoder_is_unwind_safe() {
164 fn assert_unwind_safe<T: std::panic::UnwindSafe + std::panic::RefUnwindSafe>() {}
165 assert_unwind_safe::<Decoder>();
166 }
167
168 #[cfg(feature = "aac")]
169 #[test]
170 fn aac_decodes_a_sine() {
171 let mut decoder = Decoder::new(&aac_catalog(), &Config::default()).unwrap();
172 assert_eq!(decoder.name(), "symphonia");
173 assert_eq!(decoder.sample_rate(), 44_100);
174 assert_eq!(decoder.layout(), Layout::Mono);
175
176 let decoded: Vec<Vec<f32>> = AAC_FRAMES
177 .iter()
178 .map(|frame| decoder.decode(frame).unwrap().samples)
179 .collect();
180
181 for pcm in &decoded {
183 assert_eq!(pcm.len(), 1024);
184 }
185
186 let last = decoded.last().unwrap();
190 let rms = (last.iter().map(|s| s * s).sum::<f32>() / last.len() as f32).sqrt();
191 assert!((0.65..0.8).contains(&rms), "expected a full-scale sine, got {rms} RMS");
192 }
193
194 #[cfg(feature = "aac")]
195 #[test]
196 fn aac_reports_a_truncated_packet_as_decode() {
197 let mut decoder = Decoder::new(&aac_catalog(), &Config::default()).unwrap();
198
199 let truncated = &AAC_FRAMES[0][..16];
200 assert!(matches!(decoder.decode(truncated), Err(Error::Decode(_))));
201 }
202
203 #[cfg(feature = "aac")]
204 #[test]
205 fn aac_synthesizes_a_missing_description() {
206 let mut catalog = aac_catalog();
208 catalog.description = None;
209
210 let mut decoder = Decoder::new(&catalog, &Config::default()).unwrap();
211 assert_eq!(decoder.sample_rate(), 44_100);
212 assert_eq!(decoder.decode(AAC_FRAMES[0]).unwrap().samples.len(), 1024);
213 }
214
215 #[test]
219 fn opus_reports_a_rejected_packet_as_decode() {
220 let head = moq_mux::codec::opus::Config::new(48_000, 2).encode().unwrap();
221 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2);
222 catalog.description = Some(head);
223
224 let mut decoder = Decoder::new(&catalog, &Config::default()).unwrap();
225
226 assert!(matches!(decoder.decode(&[0xFF; 3]), Err(Error::Decode(_))));
228 }
229
230 pub(crate) fn opus_packets(count: usize) -> Vec<bytes::Bytes> {
233 let mut encoder = crate::encode::Encoder::new(&crate::encode::Settings::new(48_000, Layout::Mono)).unwrap();
234 let frames = encoder.frame_size();
235 (0..count)
236 .map(|packet| {
237 let pcm: Vec<f32> = (packet * frames..(packet + 1) * frames)
238 .map(|i| (std::f32::consts::TAU * 440.0 * i as f32 / 48_000.0).sin() * 0.5)
239 .collect();
240 encoder.encode(&pcm).unwrap().payload
241 })
242 .collect()
243 }
244
245 pub(crate) fn opus_catalog(head: moq_mux::codec::opus::Config) -> hang::catalog::AudioConfig {
247 let mut catalog =
248 hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, head.sample_rate, head.channel_count);
249 catalog.description = Some(head.encode().unwrap());
250 catalog
251 }
252
253 fn rms(samples: &[f32]) -> f32 {
254 (samples.iter().map(|s| s * s).sum::<f32>() / samples.len() as f32).sqrt()
255 }
256
257 #[test]
260 fn opus_decodes_at_48k_whatever_the_input_rate() {
261 let packets = opus_packets(2);
262 for input_rate in [0, 8_000, 24_000, 44_100, 48_000, 96_000] {
263 let head = moq_mux::codec::opus::Config::new(input_rate, 1).with_pre_skip(312);
264 let mut decoder = Decoder::new(&opus_catalog(head), &Config::default()).unwrap();
265 assert_eq!(decoder.sample_rate(), 48_000, "input rate {input_rate}");
266
267 assert_eq!(decoder.decode(&packets[0]).unwrap().samples.len(), 960 - 312);
269 assert_eq!(decoder.decode(&packets[1]).unwrap().samples.len(), 960);
270 }
271 }
272
273 #[test]
276 fn opus_conceals_the_length_of_the_last_packet() {
277 let packets = opus_packets(3);
278 let mut decoder = Decoder::new(
279 &opus_catalog(moq_mux::codec::opus::Config::new(48_000, 1)),
280 &Config::default(),
281 )
282 .unwrap();
283
284 assert!(matches!(decoder.decode(&[]), Err(Error::Decode(_))));
286
287 for packet in &packets {
288 decoder.decode(packet).unwrap();
289 }
290 assert_eq!(decoder.decode(&[]).unwrap().samples.len(), 960);
291
292 assert_eq!(decoder.decode(&[]).unwrap().samples.len(), 960);
294 }
295
296 #[test]
297 fn opus_applies_the_declared_gain() {
298 let packets = opus_packets(5);
299 let decode = |output_gain: i16| {
300 let mut head = moq_mux::codec::opus::Config::new(48_000, 1);
301 head.output_gain = output_gain;
302 let mut decoder = Decoder::new(&opus_catalog(head), &Config::default()).unwrap();
303 let mut last = Vec::new();
304 for packet in &packets {
305 last = decoder.decode(packet).unwrap().samples;
306 }
307 decoder.reset().unwrap();
309 let reset = decoder.decode(&packets[4]).unwrap().samples;
310 (rms(&last), rms(&reset))
311 };
312
313 let (plain, plain_reset) = decode(0);
315 let (quiet, quiet_reset) = decode(-1541);
316 assert!((quiet / plain - 0.5).abs() < 0.001, "gain ratio {}", quiet / plain);
317 assert!(
318 (quiet_reset / plain_reset - 0.5).abs() < 0.001,
319 "gain ratio after reset {}",
320 quiet_reset / plain_reset
321 );
322 }
323
324 #[test]
327 fn opus_refuses_a_malformed_description() {
328 let valid = moq_mux::codec::opus::Config::new(48_000, 2).encode().unwrap().to_vec();
329 let mut version = valid.clone();
330 version[8] = 16;
331 let mut channels = valid.clone();
332 channels[9] = 3;
333 let mut signature = valid.clone();
334 signature[0] = b'X';
335 let mut table = valid.clone();
337 table[18] = 1;
338
339 for (name, description) in [
340 ("truncated", valid[..18].to_vec()),
341 ("empty", Vec::new()),
342 ("signature", signature),
343 ("version", version),
344 ("channels", channels),
345 ("table", table),
346 ] {
347 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2);
348 catalog.description = Some(description.into());
349 assert!(
350 matches!(Decoder::new(&catalog, &Config::default()), Err(Error::Unsupported(_))),
351 "{name}"
352 );
353 }
354 }
355
356 #[test]
358 fn opus_decodes_without_a_description() {
359 let packets = opus_packets(1);
360 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 24_000, 1);
361 let mut decoder = Decoder::new(&catalog, &Config::default()).unwrap();
362 assert_eq!(decoder.sample_rate(), 48_000);
363 assert_eq!(decoder.decode(&packets[0]).unwrap().samples.len(), 960);
364
365 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 6);
366 assert!(matches!(
367 Decoder::new(&catalog, &Config::default()),
368 Err(Error::Unsupported(_))
369 ));
370 }
371
372 #[test]
373 fn pcm_rejects_incomplete_channel_frame() {
374 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
375 let mut decoder = Decoder::new(&catalog, &Config::default()).unwrap();
376
377 assert!(matches!(
378 decoder.decode(&[]),
379 Err(Error::Misaligned { got: 0, expected: 8 })
380 ));
381 assert!(matches!(
382 decoder.decode(&[0; 4]),
383 Err(Error::Misaligned { got: 4, expected: 8 })
384 ));
385 }
386
387 #[test]
388 fn decoder_rejects_unknown_codec() {
389 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Unknown("future".into()), 48_000, 2);
390
391 assert!(matches!(
392 Decoder::new(&catalog, &Config::default()),
393 Err(Error::Unsupported(_))
394 ));
395 }
396
397 #[test]
398 fn pcm_rejects_incorrect_catalog_bitrate() {
399 let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
400 catalog.bitrate = Some(1);
401
402 assert!(matches!(
403 Decoder::new(&catalog, &Config::default()),
404 Err(Error::Unsupported(_))
405 ));
406 }
407
408 #[cfg(feature = "aac")]
410 #[test]
411 fn named_aac_decodes_aac() {
412 let config = Config {
413 kind: Kind::Named("aac".into()),
414 };
415 assert!(Decoder::new(&aac_catalog(), &config).is_ok());
416
417 let config = Config {
418 kind: Kind::Named("opus".into()),
419 };
420 assert!(matches!(
421 Decoder::new(&aac_catalog(), &config),
422 Err(Error::Unsupported(_))
423 ));
424 }
425
426 #[test]
427 fn refuses_unavailable_backend() {
428 let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
429 let config = Config {
430 kind: Kind::Named("missing".into()),
431 };
432 assert!(matches!(Decoder::new(&catalog, &config), Err(Error::Unsupported(_))));
433 }
434}