Files
alehelandClaude Opus 5.5 7d27e2d0a6 craft-audio: AAC decoder
FilmCraft's `filmcraft-aac` decoder as `craft_audio::aac` behind
`aac-decode`, kept over symphonia's: the same quality (about 135 dB
SNR against ffmpeg), and it decodes 5.1, which symphonia refuses.
HE-AAC decodes as its AAC-LC core, upsampled 2x to the stream's rate.

- `TrackDecoder` decodes AAC, including ADTS-framed packets without
  extradata (MPEG-TS, raw `.aac`).
- The native read path takes AAC in MP4 and Matroska and raw ADTS
  streams (frame sync over the file, with resync after damage).
- AAC errors convert into `AudioError`.

The AAC module's shared parts (Huffman, MDCT, TNS, ICS) arrive whole
as ported; their encoder half is unused until the encoder PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:02:29 +01:00

35 lines
1.6 KiB
Rust

//! Demux an MP4 with craft-media and decode its audio packets with `TrackDecoder`: what an app
//! with its own container code (or a network stream) does instead of `decode`.
//!
//! `cargo run -p craft-audio --example packets --features decode -- clip.m4a`
use craft_audio::TrackDecoder;
use craft_media::isobmff;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).ok_or("usage: packets <file.mp4>")?;
let bytes = std::fs::read(&path)?;
let file = isobmff::open(bytes.as_slice())?;
let track = file.track_of_kind(isobmff::TrackKind::Audio).ok_or("no audio track")?;
let t = &file.tracks[track];
// The container-neutral codec description every craft-media demuxer produces.
let params = t.codec_params();
let packets: Vec<Vec<u8>> = (0..t.samples.len()).map(|i| file.read_sample(bytes.as_slice(), track, i)).collect::<Result<_, _>>()?;
// The first packets reveal HE-AAC that the configuration does not announce.
let lookahead: Vec<&[u8]> = packets.iter().take(craft_audio::LOOKAHEAD_PACKETS).map(Vec::as_slice).collect();
let mut dec = TrackDecoder::new(&params, &lookahead)?;
println!("{} {} Hz {}", dec.codec().name(), dec.sample_rate(), dec.layout());
let mut frames = 0;
for p in &packets {
// A damaged packet is replaced by concealment of its length, so the timeline holds.
let block = dec.decode(p).or_else(|_| dec.conceal(Some(p)))?;
frames += block.frames();
}
// Container timing (the edit list's priming) is the caller's to apply; `decode` does it.
println!("{} packets, {frames} decoded frames", packets.len());
Ok(())
}