use super::Decoded;
use super::backend::{self, Backend};
use crate::{Error, Layout};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum Kind {
#[default]
Auto,
Software,
Named(String),
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct Config {
pub kind: Kind,
}
impl Config {
pub fn new() -> Self {
Self::default()
}
}
pub struct Decoder {
backend: Box<dyn Backend>,
delay: usize,
delay_remaining: usize,
}
impl Decoder {
pub fn new(catalog: &hang::catalog::AudioConfig, config: &Config) -> Result<Self, Error> {
let backend = backend::open(catalog, config)?;
let delay = backend.delay();
Ok(Self {
backend,
delay,
delay_remaining: delay,
})
}
pub fn name(&self) -> &str {
self.backend.name()
}
pub fn sample_rate(&self) -> u32 {
self.backend.sample_rate()
}
pub fn layout(&self) -> Layout {
self.backend.layout()
}
pub fn reset(&mut self) -> Result<(), Error> {
self.reset_prediction()?;
self.reapply_delay();
Ok(())
}
pub(super) fn reapply_delay(&mut self) {
self.delay_remaining = self.delay;
}
pub(super) fn reset_prediction(&mut self) -> Result<(), Error> {
self.backend.reset()
}
pub(super) fn delay_remaining(&self) -> usize {
self.delay_remaining
}
pub fn decode(&mut self, packet: &[u8]) -> Result<Decoded, Error> {
let mut decoded = self.backend.decode(packet)?;
let channels = self.backend.layout().channels() as usize;
let trim = self.delay_remaining.min(decoded.samples.len() / channels);
if trim > 0 {
decoded.samples.drain(..trim * channels);
self.delay_remaining -= trim;
}
Ok(decoded)
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
#[cfg(feature = "aac")]
const AAC_DESCRIPTION: &[u8] = b"\x12\x08";
#[cfg(feature = "aac")]
const AAC_FRAMES: [&[u8]; 3] = [
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",
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",
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",
];
#[cfg(feature = "aac")]
fn aac_catalog() -> hang::catalog::AudioConfig {
let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AAC { profile: 2 }, 44_100, 1);
catalog.description = Some(bytes::Bytes::from_static(AAC_DESCRIPTION));
catalog
}
#[test]
fn decoder_is_unwind_safe() {
fn assert_unwind_safe<T: std::panic::UnwindSafe + std::panic::RefUnwindSafe>() {}
assert_unwind_safe::<Decoder>();
}
#[cfg(feature = "aac")]
#[test]
fn aac_decodes_a_sine() {
let mut decoder = Decoder::new(&aac_catalog(), &Config::default()).unwrap();
assert_eq!(decoder.name(), "symphonia");
assert_eq!(decoder.sample_rate(), 44_100);
assert_eq!(decoder.layout(), Layout::Mono);
let decoded: Vec<Vec<f32>> = AAC_FRAMES
.iter()
.map(|frame| decoder.decode(frame).unwrap().samples)
.collect();
for pcm in &decoded {
assert_eq!(pcm.len(), 1024);
}
let last = decoded.last().unwrap();
let rms = (last.iter().map(|s| s * s).sum::<f32>() / last.len() as f32).sqrt();
assert!((0.65..0.8).contains(&rms), "expected a full-scale sine, got {rms} RMS");
}
#[cfg(feature = "aac")]
#[test]
fn aac_reports_a_truncated_packet_as_decode() {
let mut decoder = Decoder::new(&aac_catalog(), &Config::default()).unwrap();
let truncated = &AAC_FRAMES[0][..16];
assert!(matches!(decoder.decode(truncated), Err(Error::Decode(_))));
}
#[cfg(feature = "aac")]
#[test]
fn aac_synthesizes_a_missing_description() {
let mut catalog = aac_catalog();
catalog.description = None;
let mut decoder = Decoder::new(&catalog, &Config::default()).unwrap();
assert_eq!(decoder.sample_rate(), 44_100);
assert_eq!(decoder.decode(AAC_FRAMES[0]).unwrap().samples.len(), 1024);
}
#[test]
fn opus_reports_a_rejected_packet_as_decode() {
let head = moq_mux::codec::opus::Config::new(48_000, 2).encode().unwrap();
let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2);
catalog.description = Some(head);
let mut decoder = Decoder::new(&catalog, &Config::default()).unwrap();
assert!(matches!(decoder.decode(&[0xFF; 3]), Err(Error::Decode(_))));
}
pub(crate) fn opus_packets(count: usize) -> Vec<bytes::Bytes> {
let mut encoder = crate::encode::Encoder::new(&crate::encode::Settings::new(48_000, Layout::Mono)).unwrap();
let frames = encoder.frame_size();
(0..count)
.map(|packet| {
let pcm: Vec<f32> = (packet * frames..(packet + 1) * frames)
.map(|i| (std::f32::consts::TAU * 440.0 * i as f32 / 48_000.0).sin() * 0.5)
.collect();
encoder.encode(&pcm).unwrap().payload
})
.collect()
}
pub(crate) fn opus_catalog(head: moq_mux::codec::opus::Config) -> hang::catalog::AudioConfig {
let mut catalog =
hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, head.sample_rate, head.channel_count);
catalog.description = Some(head.encode().unwrap());
catalog
}
fn rms(samples: &[f32]) -> f32 {
(samples.iter().map(|s| s * s).sum::<f32>() / samples.len() as f32).sqrt()
}
#[test]
fn opus_decodes_at_48k_whatever_the_input_rate() {
let packets = opus_packets(2);
for input_rate in [0, 8_000, 24_000, 44_100, 48_000, 96_000] {
let head = moq_mux::codec::opus::Config::new(input_rate, 1).with_pre_skip(312);
let mut decoder = Decoder::new(&opus_catalog(head), &Config::default()).unwrap();
assert_eq!(decoder.sample_rate(), 48_000, "input rate {input_rate}");
assert_eq!(decoder.decode(&packets[0]).unwrap().samples.len(), 960 - 312);
assert_eq!(decoder.decode(&packets[1]).unwrap().samples.len(), 960);
}
}
#[test]
fn opus_conceals_the_length_of_the_last_packet() {
let packets = opus_packets(3);
let mut decoder = Decoder::new(
&opus_catalog(moq_mux::codec::opus::Config::new(48_000, 1)),
&Config::default(),
)
.unwrap();
assert!(matches!(decoder.decode(&[]), Err(Error::Decode(_))));
for packet in &packets {
decoder.decode(packet).unwrap();
}
assert_eq!(decoder.decode(&[]).unwrap().samples.len(), 960);
assert_eq!(decoder.decode(&[]).unwrap().samples.len(), 960);
}
#[test]
fn opus_applies_the_declared_gain() {
let packets = opus_packets(5);
let decode = |output_gain: i16| {
let mut head = moq_mux::codec::opus::Config::new(48_000, 1);
head.output_gain = output_gain;
let mut decoder = Decoder::new(&opus_catalog(head), &Config::default()).unwrap();
let mut last = Vec::new();
for packet in &packets {
last = decoder.decode(packet).unwrap().samples;
}
decoder.reset().unwrap();
let reset = decoder.decode(&packets[4]).unwrap().samples;
(rms(&last), rms(&reset))
};
let (plain, plain_reset) = decode(0);
let (quiet, quiet_reset) = decode(-1541);
assert!((quiet / plain - 0.5).abs() < 0.001, "gain ratio {}", quiet / plain);
assert!(
(quiet_reset / plain_reset - 0.5).abs() < 0.001,
"gain ratio after reset {}",
quiet_reset / plain_reset
);
}
#[test]
fn opus_refuses_a_malformed_description() {
let valid = moq_mux::codec::opus::Config::new(48_000, 2).encode().unwrap().to_vec();
let mut version = valid.clone();
version[8] = 16;
let mut channels = valid.clone();
channels[9] = 3;
let mut signature = valid.clone();
signature[0] = b'X';
let mut table = valid.clone();
table[18] = 1;
for (name, description) in [
("truncated", valid[..18].to_vec()),
("empty", Vec::new()),
("signature", signature),
("version", version),
("channels", channels),
("table", table),
] {
let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 2);
catalog.description = Some(description.into());
assert!(
matches!(Decoder::new(&catalog, &Config::default()), Err(Error::Unsupported(_))),
"{name}"
);
}
}
#[test]
fn opus_decodes_without_a_description() {
let packets = opus_packets(1);
let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 24_000, 1);
let mut decoder = Decoder::new(&catalog, &Config::default()).unwrap();
assert_eq!(decoder.sample_rate(), 48_000);
assert_eq!(decoder.decode(&packets[0]).unwrap().samples.len(), 960);
let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Opus, 48_000, 6);
assert!(matches!(
Decoder::new(&catalog, &Config::default()),
Err(Error::Unsupported(_))
));
}
#[test]
fn pcm_rejects_incomplete_channel_frame() {
let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
let mut decoder = Decoder::new(&catalog, &Config::default()).unwrap();
assert!(matches!(
decoder.decode(&[]),
Err(Error::Misaligned { got: 0, expected: 8 })
));
assert!(matches!(
decoder.decode(&[0; 4]),
Err(Error::Misaligned { got: 4, expected: 8 })
));
}
#[test]
fn decoder_rejects_unknown_codec() {
let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Unknown("future".into()), 48_000, 2);
assert!(matches!(
Decoder::new(&catalog, &Config::default()),
Err(Error::Unsupported(_))
));
}
#[test]
fn pcm_rejects_incorrect_catalog_bitrate() {
let mut catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
catalog.bitrate = Some(1);
assert!(matches!(
Decoder::new(&catalog, &Config::default()),
Err(Error::Unsupported(_))
));
}
#[cfg(feature = "aac")]
#[test]
fn named_aac_decodes_aac() {
let config = Config {
kind: Kind::Named("aac".into()),
};
assert!(Decoder::new(&aac_catalog(), &config).is_ok());
let config = Config {
kind: Kind::Named("opus".into()),
};
assert!(matches!(
Decoder::new(&aac_catalog(), &config),
Err(Error::Unsupported(_))
));
}
#[test]
fn refuses_unavailable_backend() {
let catalog = hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Pcm, 48_000, 2);
let config = Config {
kind: Kind::Named("missing".into()),
};
assert!(matches!(Decoder::new(&catalog, &config), Err(Error::Unsupported(_))));
}
}