Files
craft-libs/crates/video/examples/av1_av1dec.rs
Dylan M. Taylor 16564be34b craft-video: AV1 decoder from FilmCraft, with DeckCraft's hardening (av1-decode) (#836)
* craft-video: AV1 decoder from FilmCraft (`av1-decode` feature)

FilmCraft's crates/av1 at 4c0d2ad7 becomes craft_video::av1::decode. The encoder moves to
av1/encode and is still exported from craft_video::av1. Feature av1 now turns on av1-decode and
av1-encode, so either half can be built without the other.

The ignored speed tests no longer read the thread CPU clock (FilmCraft used rustix for it).
craft_video::budget (the picture memory budget from DeckCraft's bitstream crate) is added for the
next commit. craft-video is built at opt-level 3 in the dev profile, as FilmCraft built its
codecs.

* craft-video: AV1 decoder hardening from DeckCraft's copy

- The picture store (eight reference slots, the new frame, frames in flight) is checked against
  a memory budget before a frame is allocated; Decoder::set_memory_budget.
- Non-uniform tile spacing is refused past MAX_TILE_COLS columns or MAX_TILE_ROWS rows.

* craft-video: AV1 fuzz seeds are 64x64 so SVT-AV1 2.x can encode them

FilmCraft's seeds were 64x48; the SVT-AV1 2.3 on the Ubuntu runners refuses a source under 64
lines, and an encoder that exists but fails is a test failure, not a skip.
2026-11-11 17:43:10 -05:00

55 lines
2.3 KiB
Rust

//! Decode an IVF AV1 file: `cargo run --release -p craft-video --features av1-decode,threads --example av1_av1dec -- in.ivf [out.yuv]`.
//! Writes raw planar 16-bit little-endian output. `AV1_THREADS=n` sets the thread count,
//! `AV1_LOOPS=k` decodes the file k times (benchmarking), `AV1_STATS=1` prints stage statistics.
use std::io::Write;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("usage: av1dec in.ivf [out.yuv]");
std::process::exit(2);
}
let data = std::fs::read(&args[1]).expect("read input");
let mut out = args.get(2).map(|p| std::io::BufWriter::new(std::fs::File::create(p).expect("create output")));
let threads = std::env::var("AV1_THREADS").ok().and_then(|s| s.parse().ok()).unwrap_or(0usize);
let loops = std::env::var("AV1_LOOPS").ok().and_then(|s| s.parse().ok()).unwrap_or(1usize);
let hl = u16::from_le_bytes([data[6], data[7]]) as usize;
let t0 = std::time::Instant::now();
let mut n = 0;
let mut dec = if threads > 0 { craft_video::av1::decode::Decoder::with_threads(threads) } else { craft_video::av1::decode::Decoder::new() };
// AV1_DRAFT=1: draft mode (non-reference frames skip the in-loop filters).
dec.set_draft(std::env::var_os("AV1_DRAFT").is_some());
let mut write = |pics: Vec<craft_video::av1::decode::Picture>| {
for pic in pics {
n += 1;
if let Some(o) = out.as_mut() {
for pl in &pic.planes {
let bytes: Vec<u8> = pl.iter().flat_map(|s| s.to_le_bytes()).collect();
o.write_all(&bytes).unwrap();
}
}
}
};
for _ in 0..loops {
let mut p = hl;
let mut i = 0i64;
while p + 12 <= data.len() {
let sz = u32::from_le_bytes(data[p..p + 4].try_into().unwrap()) as usize;
p += 12;
let Some(tu) = data.get(p..p + sz) else { break };
p += sz;
match dec.decode_pts(tu, i) {
Ok(pics) => write(pics),
Err(e) => eprintln!("temporal unit {i}: {e}"),
}
i += 1;
}
write(dec.flush());
}
eprintln!("{n} pictures in {:.3}s", t0.elapsed().as_secs_f64());
if std::env::var_os("AV1_STATS").is_some() {
eprintln!("{:#?}", dec.stats());
}
}