mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 08:52:32 +00:00
* 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.
55 lines
2.3 KiB
Rust
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());
|
|
}
|
|
}
|