Skip to main content

bit_accountant/
bit_accountant.rs

1//! BIT ACCOUNTANT (codec-analyzer instrument #6) — where do our bits go, and how
2//! does that compare with x264's own MOTION/TEXTURE/MISC split at a matched
3//! operating point?
4//!
5//! The remaining ~4% BD-rate gap vs x264 veryfast is a RATE question, so it needs
6//! the rate instrument, not the stage profiler. Buckets are exact CABAC bit
7//! deltas, so they reconcile against the real payload — the line that separates
8//! an instrument from a model.
9//!
10//!   cargo run --release -p rusty_h264-encoder --features asm --example bit_accountant \
11//!     -- video-tests/clips/foreman_cif.y4m
12use rusty_h264_common::types::YuvFrame;
13use rusty_h264_encoder::{Encoder, EncoderConfig, Preset};
14
15fn read_y4m(path: &str, max: usize) -> (usize, usize, Vec<YuvFrame>) {
16    let raw = std::fs::read(path).unwrap();
17    let e = raw.iter().position(|&b| b == b'\n').unwrap();
18    let hdr = std::str::from_utf8(&raw[..e]).unwrap();
19    let (mut w, mut h) = (0usize, 0usize);
20    for t in hdr.split_whitespace() {
21        match t.as_bytes().first() {
22            Some(b'W') => w = t[1..].parse().unwrap(),
23            Some(b'H') => h = t[1..].parse().unwrap(),
24            _ => {}
25        }
26    }
27    let (ys, cs) = (w * h, (w / 2) * (h / 2));
28    let (mut f, mut p) = (Vec::new(), e + 1);
29    while f.len() < max {
30        let Some(r) = raw[p..].iter().position(|&b| b == b'\n') else { break };
31        p += r + 1;
32        if p + ys + 2 * cs > raw.len() { break }
33        f.push(YuvFrame {
34            width: w, height: h,
35            y: raw[p..p + ys].to_vec(),
36            u: raw[p + ys..p + ys + cs].to_vec(),
37            v: raw[p + ys + cs..p + ys + 2 * cs].to_vec(),
38        });
39        p += ys + 2 * cs;
40    }
41    (w, h, f)
42}
43
44fn main() {
45    let path = std::env::args().nth(1).unwrap_or_else(|| "video-tests/clips/foreman_cif.y4m".into());
46    let n: usize = std::env::var("BA_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(24);
47    let qp: u8 = std::env::var("BA_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
48    let (w, h, frames) = read_y4m(&path, n);
49    let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
50    for (pname, preset) in [("quality", Preset::Quality)] {
51        let mut cfg = EncoderConfig::new(w, h);
52        cfg.qp = qp;
53        cfg.gop_size = 60;
54        cfg.preset = preset;
55        rusty_h264_encoder::bitacct::reset();
56        rusty_h264_encoder::bitacct::set_enabled(true);
57        let mut enc = Encoder::new(cfg).unwrap();
58        let mut total = 0usize;
59        for f in &frames {
60            total += enc.encode(f).len();
61        }
62        rusty_h264_encoder::bitacct::add_actual_bytes(total);
63        rusty_h264_encoder::bitacct::set_enabled(false);
64        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
65        rusty_h264_encoder::bitacct::dump(
66            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
67            mbs,
68        );
69        if std::env::var_os("BA_MVDTAB").is_some() {
70            rusty_h264_encoder::bitacct::dump_mvd_table();
71        }
72    }
73}