mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-10 08:51:41 +00:00
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>
35 lines
1.6 KiB
Rust
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(¶ms, &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(())
|
|
}
|