use std::collections::VecDeque;
use oxideav_core::{
AudioFrame, CodecCapabilities, CodecId, CodecInfo, CodecParameters, CodecRegistry, CodecTag,
Confidence, Decoder, Error, Frame, Packet, ProbeContext, Result, SampleFormat,
};
use crate::adts::{AdtsHeader, ADTS_HEADER_BYTES_NO_CRC};
use crate::decode::{DecodedFrame, StreamDecoder};
use crate::latm::{LoasDecoder, AUDIO_SYNC_STREAM_SYNCWORD};
pub const CODEC_ID_STR: &str = "aac";
pub const MP4_OBJECT_TYPE_AAC: u8 = 0x40;
pub const WAVE_FORMAT_RAW_AAC1: u16 = 0x00FF;
pub const WAVE_FORMAT_MPEG_ADTS_AAC: u16 = 0x1601;
pub fn make_decoder(params: &CodecParameters) -> Result<Box<dyn Decoder>> {
let sample_rate = params.sample_rate.unwrap_or(44_100);
let channels = params.channels.unwrap_or(2);
let mut out_params = CodecParameters::audio(CodecId::new(CODEC_ID_STR));
out_params.sample_rate = Some(sample_rate);
out_params.channels = Some(channels);
out_params.sample_format = Some(SampleFormat::S16);
let mut dec = AacDecoder::new(CodecId::new(CODEC_ID_STR), out_params);
if let Some(v) = params.options.get("sbr_downsampled") {
dec.set_sbr_downsampled(matches!(v, "true" | "1"));
}
if let Some(v) = params.options.get("sbr_low_power") {
dec.set_sbr_low_power(matches!(v, "true" | "1"));
}
Ok(Box::new(dec))
}
pub struct AacDecoder {
codec_id: CodecId,
output: CodecParameters,
stream: StreamDecoder,
loas: LoasDecoder,
transport: Option<Transport>,
pending: VecDeque<AudioFrame>,
eof: bool,
sbr_downsampled: bool,
sbr_low_power: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Transport {
Adts,
Loas,
}
impl std::fmt::Debug for AacDecoder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AacDecoder")
.field("codec_id", &self.codec_id)
.field("transport", &self.transport)
.field("pending", &self.pending.len())
.field("eof", &self.eof)
.finish()
}
}
impl AacDecoder {
fn new(codec_id: CodecId, output: CodecParameters) -> Self {
Self {
codec_id,
output,
stream: StreamDecoder::new(),
loas: LoasDecoder::new(),
transport: None,
pending: VecDeque::new(),
eof: false,
sbr_downsampled: false,
sbr_low_power: false,
}
}
pub fn set_sbr_downsampled(&mut self, downsampled: bool) {
self.sbr_downsampled = downsampled;
self.stream.set_sbr_downsampled(downsampled);
self.loas.set_sbr_downsampled(downsampled);
}
pub fn set_sbr_low_power(&mut self, low_power: bool) {
self.sbr_low_power = low_power;
self.stream.set_sbr_low_power(low_power);
self.loas.set_sbr_low_power(low_power);
}
pub fn output_params(&self) -> &CodecParameters {
&self.output
}
fn decoded_to_audio(decoded: &DecodedFrame, pts: Option<i64>) -> AudioFrame {
let mut bytes = Vec::with_capacity(decoded.pcm.len() * 2);
for &s in &decoded.pcm {
bytes.extend_from_slice(&s.to_le_bytes());
}
AudioFrame {
samples: decoded.pcm.len().checked_div(decoded.channels).unwrap_or(0) as u32,
pts,
data: vec![bytes],
}
}
fn queue_decoded(&mut self, decoded: &DecodedFrame, pts: Option<i64>) -> bool {
if decoded.channels > 0 {
self.output.sample_rate = Some(decoded.sample_rate);
self.output.channels = Some(decoded.channels as u16);
self.pending.push_back(Self::decoded_to_audio(decoded, pts));
true
} else {
false
}
}
fn send_adts(&mut self, data: &[u8], pts: Option<i64>) -> Result<()> {
let mut pos = 0usize;
let mut produced_any = false;
while pos + ADTS_HEADER_BYTES_NO_CRC <= data.len() {
let (header, payload_offset) = AdtsHeader::parse(&data[pos..])
.map_err(|e| Error::other(format!("oxideav-aac: adts header: {e}")))?;
let frame_len = header.aac_frame_length as usize;
if frame_len < payload_offset || pos + frame_len > data.len() {
return Err(Error::other(
"oxideav-aac: ADTS frame length overruns packet",
));
}
let decoded = self
.stream
.decode_adts_frame(&data[pos..pos + frame_len])
.map_err(|e| Error::other(format!("oxideav-aac: decode_adts_frame: {e}")))?;
produced_any |= self.queue_decoded(&decoded, pts);
pos += frame_len;
}
if !produced_any && pos == 0 {
return Err(Error::other(
"oxideav-aac: packet held no complete ADTS frame",
));
}
Ok(())
}
fn send_loas(&mut self, data: &[u8], pts: Option<i64>) -> Result<()> {
let decoded_frames = self
.loas
.decode_all(data)
.map_err(|e| Error::other(format!("oxideav-aac: loas decode: {e}")))?;
let mut produced_any = false;
for decoded in &decoded_frames {
produced_any |= self.queue_decoded(decoded, pts);
}
if !produced_any && decoded_frames.is_empty() {
return Err(Error::other(
"oxideav-aac: packet held no complete LOAS sync frame",
));
}
Ok(())
}
}
impl Decoder for AacDecoder {
fn codec_id(&self) -> &CodecId {
&self.codec_id
}
fn send_packet(&mut self, packet: &Packet) -> Result<()> {
if self.eof {
return Err(Error::other("oxideav-aac: cannot send_packet after flush"));
}
let data = skip_id3v2(&packet.data);
let transport = match self.transport {
Some(t) => t,
None => {
let Some(t) = detect_transport(data) else {
return Err(Error::other(
"oxideav-aac: packet has neither an ADTS nor a LOAS syncword",
));
};
self.transport = Some(t);
t
}
};
match transport {
Transport::Adts => self.send_adts(data, packet.pts),
Transport::Loas => self.send_loas(data, packet.pts),
}
}
fn receive_frame(&mut self) -> Result<Frame> {
if let Some(audio) = self.pending.pop_front() {
return Ok(Frame::Audio(audio));
}
if self.eof {
return Err(Error::Eof);
}
Err(Error::NeedMore)
}
fn flush(&mut self) -> Result<()> {
self.eof = true;
Ok(())
}
fn reset(&mut self) -> Result<()> {
self.stream = StreamDecoder::new();
self.loas = LoasDecoder::new();
self.stream.set_sbr_downsampled(self.sbr_downsampled);
self.loas.set_sbr_downsampled(self.sbr_downsampled);
self.stream.set_sbr_low_power(self.sbr_low_power);
self.loas.set_sbr_low_power(self.sbr_low_power);
self.transport = None;
self.pending.clear();
self.eof = false;
Ok(())
}
}
fn detect_transport(data: &[u8]) -> Option<Transport> {
if data.len() < 2 {
return None;
}
if data[0] == 0xFF && (data[1] & 0xF0) == 0xF0 {
return Some(Transport::Adts);
}
let first11 = (u32::from(data[0]) << 3) | (u32::from(data[1]) >> 5);
if first11 == AUDIO_SYNC_STREAM_SYNCWORD {
return Some(Transport::Loas);
}
None
}
fn skip_id3v2(data: &[u8]) -> &[u8] {
if data.len() < 10 || &data[..3] != b"ID3" {
return data;
}
let size = data[6..10]
.iter()
.fold(0usize, |acc, &b| (acc << 7) | usize::from(b & 0x7f));
let footer = if data[5] & 0x10 != 0 { 10 } else { 0 };
let total = 10 + size + footer;
if total >= data.len() {
data
} else {
&data[total..]
}
}
fn probe_aac(ctx: &ProbeContext) -> Confidence {
let Some(pkt) = ctx.packet else {
return 0.5;
};
let pkt = skip_id3v2(pkt);
if pkt.len() < 2 {
return 0.2;
}
if pkt[0] == 0xFF && (pkt[1] & 0xF0) == 0xF0 && AdtsHeader::parse(pkt).is_ok() {
return 1.0;
}
if detect_transport(pkt) == Some(Transport::Loas) {
return 0.9;
}
0.2
}
pub fn register_codecs(reg: &mut CodecRegistry) {
let info = CodecInfo::new(CodecId::new(CODEC_ID_STR))
.capabilities(
CodecCapabilities::audio("aac")
.with_decode()
.with_encode()
.with_lossy(true),
)
.decoder(make_decoder)
.encoder(crate::codec_encoder::make_encoder)
.probe(probe_aac)
.tags([
CodecTag::mp4_object_type(MP4_OBJECT_TYPE_AAC),
CodecTag::wave_format(WAVE_FORMAT_RAW_AAC1),
CodecTag::wave_format(WAVE_FORMAT_MPEG_ADTS_AAC),
CodecTag::fourcc(b"mp4a"),
CodecTag::fourcc(b"aac "),
CodecTag::matroska("A_AAC"),
]);
reg.register(info);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decode::FRAME_LEN;
use oxideav_core::TimeBase;
fn build_params(sample_rate: u32, channels: u16) -> CodecParameters {
let mut p = CodecParameters::audio(CodecId::new(CODEC_ID_STR));
p.sample_rate = Some(sample_rate);
p.channels = Some(channels);
p.sample_format = Some(SampleFormat::S16);
p
}
fn fixture_bytes(name: &str) -> Option<Vec<u8>> {
let path = format!(
"{}/../../docs/audio/aac/fixtures/{name}/input.aac",
env!("CARGO_MANIFEST_DIR")
);
if !std::path::Path::new(&path).exists() {
eprintln!("skip: staged ADTS fixture not present at {path}");
return None;
}
Some(std::fs::read(&path).expect("read staged ADTS fixture"))
}
fn split_into_packets(bytes: &[u8]) -> Vec<Packet> {
let bytes = skip_id3v2(bytes);
let tb = TimeBase::new(1, 44_100);
let mut packets = Vec::new();
let mut pos = 0usize;
let mut pts: i64 = 0;
while pos + ADTS_HEADER_BYTES_NO_CRC <= bytes.len() {
let Ok((header, _)) = AdtsHeader::parse(&bytes[pos..]) else {
break;
};
let fl = header.aac_frame_length as usize;
if fl == 0 || pos + fl > bytes.len() {
break;
}
let mut pkt = Packet::new(0, tb, bytes[pos..pos + fl].to_vec());
pkt.pts = Some(pts);
packets.push(pkt);
pts += FRAME_LEN as i64;
pos += fl;
}
packets
}
fn fixture_bytes_ext(name: &str, ext: &str) -> Option<Vec<u8>> {
let path = format!(
"{}/../../docs/audio/aac/fixtures/{name}/input.{ext}",
env!("CARGO_MANIFEST_DIR")
);
if !std::path::Path::new(&path).exists() {
eprintln!("skip: staged fixture not present at {path}");
return None;
}
Some(std::fs::read(&path).expect("read staged fixture"))
}
#[test]
fn detect_transport_recognises_adts_and_loas() {
assert_eq!(detect_transport(&[0xFF, 0xF1, 0x00]), Some(Transport::Adts));
assert_eq!(detect_transport(&[0x56, 0xE0, 0x00]), Some(Transport::Loas));
assert_eq!(detect_transport(&[0x00, 0x00]), None);
assert_eq!(detect_transport(&[0xFF]), None);
}
#[test]
fn loas_packet_decodes_through_trait() {
let Some(buf) = fixture_bytes_ext("aac-latm-stream", "latm") else {
return;
};
let mut pkt = Packet::new(0, TimeBase::new(1, 44_100), buf.clone());
pkt.pts = Some(0);
let mut dec = make_decoder(&build_params(44_100, 2)).expect("decoder");
dec.send_packet(&pkt).expect("send_packet (loas)");
let mut frames = 0usize;
let mut samples_total = 0usize;
while let Ok(Frame::Audio(a)) = dec.receive_frame() {
assert_eq!(a.samples as usize, FRAME_LEN);
assert_eq!(a.data[0].len(), FRAME_LEN * 2 * 2);
frames += 1;
samples_total += a.data[0].len() / 2;
}
assert!(frames > 0, "LOAS packet produced no frames");
assert_eq!(samples_total, 65_536);
}
#[test]
fn loas_trait_matches_loas_decoder_pcm() {
let Some(buf) = fixture_bytes_ext("aac-latm-stream", "latm") else {
return;
};
let mut reference = LoasDecoder::new();
let ref_frames = reference.decode_all(&buf).expect("LoasDecoder");
let mut ref_pcm: Vec<i16> = Vec::new();
for f in &ref_frames {
ref_pcm.extend_from_slice(&f.pcm);
}
let mut pkt = Packet::new(0, TimeBase::new(1, 44_100), buf);
pkt.pts = Some(0);
let mut dec = make_decoder(&build_params(44_100, 2)).expect("decoder");
dec.send_packet(&pkt).expect("send_packet");
let mut trait_pcm: Vec<i16> = Vec::new();
while let Ok(Frame::Audio(a)) = dec.receive_frame() {
for c in a.data[0].chunks_exact(2) {
trait_pcm.push(i16::from_le_bytes([c[0], c[1]]));
}
}
assert_eq!(trait_pcm, ref_pcm, "LOAS trait diverged from LoasDecoder");
}
#[test]
fn probe_scores_loas_sync() {
let pkt = [0x56u8, 0xE0, 0x00, 0x00];
let tag = CodecTag::mp4_object_type(MP4_OBJECT_TYPE_AAC);
let ctx = ProbeContext::new(&tag).packet(&pkt);
assert!((probe_aac(&ctx) - 0.9).abs() < f32::EPSILON);
}
#[test]
fn make_decoder_builds_and_reports_id() {
let dec = make_decoder(&build_params(44_100, 2)).expect("decoder builds");
assert_eq!(dec.codec_id().as_str(), CODEC_ID_STR);
}
#[test]
fn make_decoder_defaults_without_hints() {
let p = CodecParameters::audio(CodecId::new(CODEC_ID_STR));
let _ = make_decoder(&p).expect("default-params decoder builds");
}
#[test]
fn receive_without_packet_is_need_more() {
let mut dec = make_decoder(&build_params(44_100, 2)).unwrap();
match dec.receive_frame() {
Err(Error::NeedMore) => {}
other => panic!("expected NeedMore, got {other:?}"),
}
}
#[test]
fn mono_fixture_decodes_one_frame_per_packet() {
let Some(buf) = fixture_bytes("aac-lc-mono-8000-16kbps-adts") else {
return;
};
let packets = split_into_packets(&buf);
assert!(!packets.is_empty(), "fixture yielded zero packets");
let mut dec = make_decoder(&build_params(8_000, 1)).expect("decoder");
let mut frames = 0usize;
for pkt in &packets {
dec.send_packet(pkt).expect("send_packet");
loop {
match dec.receive_frame() {
Ok(Frame::Audio(a)) => {
assert_eq!(a.samples as usize, FRAME_LEN);
assert_eq!(a.data.len(), 1, "interleaved single plane");
assert_eq!(a.data[0].len(), FRAME_LEN * 2);
assert_eq!(a.pts, pkt.pts);
frames += 1;
}
Ok(other) => panic!("expected Audio, got {other:?}"),
Err(Error::NeedMore) => break,
Err(e) => panic!("receive_frame: {e}"),
}
}
}
assert_eq!(frames, packets.len(), "one frame per packet");
}
#[test]
fn stereo_fixture_decodes_two_channel_planes() {
let Some(buf) = fixture_bytes("aac-lc-intensity-stereo") else {
return;
};
let packets = split_into_packets(&buf);
let mut dec = make_decoder(&build_params(44_100, 2)).expect("decoder");
dec.send_packet(&packets[0]).expect("send_packet 0");
let Frame::Audio(a) = dec.receive_frame().expect("frame 0") else {
panic!("expected AudioFrame");
};
assert_eq!(a.samples as usize, FRAME_LEN);
assert_eq!(a.data[0].len(), FRAME_LEN * 2 * 2);
}
#[test]
fn trait_decode_matches_stream_decoder_pcm() {
let Some(buf) = fixture_bytes("aac-lc-mono-8000-16kbps-adts") else {
return;
};
let mut reference = StreamDecoder::new();
let ref_frames = reference.decode_all(&buf).expect("reference decode_all");
let mut ref_pcm: Vec<i16> = Vec::new();
for f in &ref_frames {
ref_pcm.extend_from_slice(&f.pcm);
}
let packets = split_into_packets(&buf);
let mut dec = make_decoder(&build_params(8_000, 1)).expect("decoder");
let mut trait_pcm: Vec<i16> = Vec::new();
for pkt in &packets {
dec.send_packet(pkt).expect("send_packet");
while let Ok(Frame::Audio(a)) = dec.receive_frame() {
for c in a.data[0].chunks_exact(2) {
trait_pcm.push(i16::from_le_bytes([c[0], c[1]]));
}
}
}
assert_eq!(
trait_pcm, ref_pcm,
"trait decode diverged from StreamDecoder"
);
}
#[test]
fn flush_then_receive_yields_eof_after_drain() {
let Some(buf) = fixture_bytes("aac-lc-mono-8000-16kbps-adts") else {
return;
};
let packets = split_into_packets(&buf);
let mut dec = make_decoder(&build_params(8_000, 1)).unwrap();
dec.send_packet(&packets[0]).unwrap();
dec.flush().unwrap();
let _ = dec.receive_frame().expect("pending frame drains");
match dec.receive_frame() {
Err(Error::Eof) => {}
other => panic!("expected Eof, got {other:?}"),
}
}
#[test]
fn send_after_flush_is_rejected() {
let Some(buf) = fixture_bytes("aac-lc-mono-8000-16kbps-adts") else {
return;
};
let packets = split_into_packets(&buf);
let mut dec = make_decoder(&build_params(8_000, 1)).unwrap();
dec.flush().unwrap();
assert!(dec.send_packet(&packets[0]).is_err());
}
#[test]
fn reset_re_enables_send_and_restores_clean_state() {
let Some(buf) = fixture_bytes("aac-lc-mono-8000-16kbps-adts") else {
return;
};
let packets = split_into_packets(&buf);
let mut dec = make_decoder(&build_params(8_000, 1)).unwrap();
dec.send_packet(&packets[0]).unwrap();
let Frame::Audio(first_clean) = dec.receive_frame().unwrap() else {
panic!("audio");
};
for pkt in packets.iter().take(4) {
dec.send_packet(pkt).unwrap();
while let Ok(Frame::Audio(_)) = dec.receive_frame() {}
}
dec.flush().unwrap();
dec.reset().unwrap();
dec.send_packet(&packets[0]).unwrap();
let Frame::Audio(first_again) = dec.receive_frame().unwrap() else {
panic!("audio");
};
assert_eq!(
first_again.data, first_clean.data,
"reset did not restore the initial decode state"
);
}
#[test]
fn multi_frame_packet_emits_one_audio_frame_each() {
let Some(buf) = fixture_bytes("aac-lc-mono-8000-16kbps-adts") else {
return;
};
let packets = split_into_packets(&buf);
assert!(packets.len() >= 2);
let mut joined = packets[0].data.clone();
joined.extend_from_slice(&packets[1].data);
let mut pkt = Packet::new(0, TimeBase::new(1, 8_000), joined);
pkt.pts = Some(0);
let mut dec = make_decoder(&build_params(8_000, 1)).unwrap();
dec.send_packet(&pkt).unwrap();
let mut n = 0usize;
while let Ok(Frame::Audio(_)) = dec.receive_frame() {
n += 1;
}
assert_eq!(n, 2, "two ADTS frames in one packet → two AudioFrames");
}
fn synth_adts_header() -> [u8; 7] {
let mut bits: Vec<u8> = Vec::new();
let mut push = |val: u32, n: u32| {
for i in (0..n).rev() {
bits.push(((val >> i) & 1) as u8);
}
};
push(0xFFF, 12); push(0, 1); push(0, 2); push(1, 1); push(1, 2); push(4, 4); push(0, 1); push(1, 3); push(0, 1); push(0, 1); push(0, 1); push(0, 1); push(7, 13); push(0x7FF, 11); push(0, 2); let mut out = [0u8; 7];
for (i, chunk) in bits.chunks(8).enumerate() {
let mut b = 0u8;
for (j, &bit) in chunk.iter().enumerate() {
b |= bit << (7 - j);
}
out[i] = b;
}
out
}
#[test]
fn probe_scores_adts_sync() {
let hdr = synth_adts_header();
let tag = CodecTag::mp4_object_type(MP4_OBJECT_TYPE_AAC);
let ctx = ProbeContext::new(&tag).packet(&hdr);
assert!(AdtsHeader::parse(&hdr).is_ok(), "test ADTS header invalid");
assert!((probe_aac(&ctx) - 1.0).abs() < f32::EPSILON);
}
#[test]
fn probe_scores_low_for_non_adts() {
let pkt = [0x00u8, 0x00, 0x00, 0x00];
let tag = CodecTag::mp4_object_type(MP4_OBJECT_TYPE_AAC);
let ctx = ProbeContext::new(&tag).packet(&pkt);
assert!(probe_aac(&ctx) < 0.5);
}
#[test]
fn probe_default_without_packet() {
let tag = CodecTag::mp4_object_type(MP4_OBJECT_TYPE_AAC);
let ctx = ProbeContext::new(&tag);
assert!((probe_aac(&ctx) - 0.5).abs() < f32::EPSILON);
}
#[test]
fn probe_uses_fixture_first_bytes() {
let Some(buf) = fixture_bytes("aac-lc-mono-8000-16kbps-adts") else {
return;
};
let tag = CodecTag::mp4_object_type(MP4_OBJECT_TYPE_AAC);
let ctx = ProbeContext::new(&tag).packet(&buf);
assert!((probe_aac(&ctx) - 1.0).abs() < f32::EPSILON);
}
#[test]
fn register_installs_decoder_factory() {
let mut reg = CodecRegistry::new();
register_codecs(&mut reg);
assert!(reg.has_decoder(&CodecId::new(CODEC_ID_STR)));
let _ = reg
.first_decoder(&build_params(44_100, 2))
.expect("registry-built decoder");
}
#[test]
fn register_claims_all_tags() {
let mut reg = CodecRegistry::new();
register_codecs(&mut reg);
for tag in [
CodecTag::mp4_object_type(MP4_OBJECT_TYPE_AAC),
CodecTag::wave_format(WAVE_FORMAT_RAW_AAC1),
CodecTag::wave_format(WAVE_FORMAT_MPEG_ADTS_AAC),
CodecTag::fourcc(b"mp4a"),
CodecTag::fourcc(b"aac "),
CodecTag::matroska("A_AAC"),
] {
let ctx = ProbeContext::new(&tag);
assert_eq!(
reg.resolve_tag_ref(&ctx).map(|c| c.as_str()),
Some(CODEC_ID_STR),
"tag {tag:?} did not resolve to aac",
);
}
}
#[test]
fn sbr_downsampled_option_emits_core_rate() {
let Some(buf) = fixture_bytes("he-aac-v1-stereo-44100-32kbps-adts") else {
return;
};
let packets = split_into_packets(&buf);
assert!(packets.len() > 2);
let mut params = build_params(22_050, 2);
params.options.insert("sbr_downsampled", "true");
let mut dec = make_decoder(¶ms).unwrap();
let run = |dec: &mut Box<dyn Decoder>, pkts: &[Packet]| -> Vec<AudioFrame> {
let mut frames = Vec::new();
for pkt in pkts {
dec.send_packet(pkt).unwrap();
while let Ok(Frame::Audio(f)) = dec.receive_frame() {
frames.push(f);
}
}
frames
};
let frames = run(&mut dec, &packets[..2]);
assert_eq!(frames.len(), 2);
for f in &frames {
assert_eq!(f.samples, 1024, "downsampled SBR frame length");
assert_eq!(f.data[0].len(), 1024 * 2 * 2);
}
dec.reset().unwrap();
let frames2 = run(&mut dec, &packets[..2]);
assert_eq!(frames2.len(), 2);
assert_eq!(frames2[0].samples, 1024);
assert_eq!(
frames2[0].data[0], frames[0].data[0],
"post-reset decode differs"
);
}
}