Skip to main content

bit_accountant/
bit_accountant.rs

1// Dev/test target, not shipped: a panic here IS the diagnostic (that is what an
2// assertion is). The workspace's unwrap/expect/panic denials exist to keep them
3// off the decoder's untrusted-input path, so they are relaxed for this file.
4#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
5// Dev tools also accumulate fields and helpers kept for the NEXT investigation;
6// dead code here is a scratchpad, not a defect.
7#![allow(dead_code, unused)]
8#![allow(clippy::unnecessary_unwrap, clippy::zombie_processes)]
9//! BIT ACCOUNTANT (codec-analyzer instrument #6) — where do our bits go, and how
10//! does that compare with x264's own MOTION/TEXTURE/MISC split at a matched
11//! operating point?
12//!
13//! The remaining ~4% BD-rate gap vs x264 veryfast is a RATE question, so it needs
14//! the rate instrument, not the stage profiler. Buckets are exact CABAC bit
15//! deltas, so they reconcile against the real payload — the line that separates
16//! an instrument from a model.
17//!
18//!   cargo run --release -p rusty_h264-encoder --features asm --example bit_accountant \
19//!     -- video-tests/clips/foreman_cif.y4m
20use rusty_h264_common::types::YuvFrame;
21use rusty_h264_encoder::{Encoder, EncoderConfig, Preset};
22
23fn read_y4m(path: &str, max: usize) -> (usize, usize, Vec<YuvFrame>) {
24    let raw = std::fs::read(path).unwrap();
25    let e = raw.iter().position(|&b| b == b'\n').unwrap();
26    let hdr = std::str::from_utf8(&raw[..e]).unwrap();
27    let (mut w, mut h) = (0usize, 0usize);
28    for t in hdr.split_whitespace() {
29        match t.as_bytes().first() {
30            Some(b'W') => w = t[1..].parse().unwrap(),
31            Some(b'H') => h = t[1..].parse().unwrap(),
32            _ => {}
33        }
34    }
35    let (ys, cs) = (w * h, (w / 2) * (h / 2));
36    let (mut f, mut p) = (Vec::new(), e + 1);
37    while f.len() < max {
38        let Some(r) = raw[p..].iter().position(|&b| b == b'\n') else { break };
39        p += r + 1;
40        if p + ys + 2 * cs > raw.len() { break }
41        f.push(YuvFrame {
42            width: w, height: h,
43            y: raw[p..p + ys].to_vec(),
44            u: raw[p + ys..p + ys + cs].to_vec(),
45            v: raw[p + ys + cs..p + ys + 2 * cs].to_vec(),
46        });
47        p += ys + 2 * cs;
48    }
49    (w, h, f)
50}
51
52fn main() {
53    let path = std::env::args().nth(1).unwrap_or_else(|| "video-tests/clips/foreman_cif.y4m".into());
54    let n: usize = std::env::var("BA_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(24);
55    let qp: u8 = std::env::var("BA_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
56    let (w, h, frames) = read_y4m(&path, n);
57    let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
58    for (pname, preset) in [("quality", Preset::Quality)] {
59        let mut cfg = EncoderConfig::new(w, h);
60        cfg.qp = qp;
61        cfg.gop_size = 60;
62        cfg.preset = preset;
63        // The accountant must be runnable in the SAME configuration whose gap is
64        // under investigation, or its split describes a different encoder than the
65        // one being measured. BA_BFRAMES / BA_REFS mirror the all-tools arm.
66        if let Ok(v) = std::env::var("BA_BFRAMES") {
67            if let Ok(b) = v.parse::<u32>() {
68                cfg.bframes = b;
69                cfg.bframes_adaptive = false;
70            }
71        }
72        if let Ok(v) = std::env::var("BA_REFS") {
73            if let Ok(r) = v.parse::<u32>() {
74                cfg.num_ref_frames = r;
75            }
76        }
77        let cfg_bframes = cfg.bframes;
78        rusty_h264_encoder::bitacct::reset();
79        rusty_h264_encoder::bitacct::set_enabled(true);
80        let mut enc = Encoder::new(cfg).unwrap();
81        // B-frames require the lookahead path (`encode_all`); the per-frame
82        // `encode` loop cannot reorder. Use whichever the config demands so the
83        // accountant can profile the B-carrying arm at all.
84        let total: usize = if cfg_bframes > 0 {
85            enc.encode_all(&frames).unwrap().iter().map(|n| n.len()).sum()
86        } else {
87            let mut t = 0usize;
88            for f in &frames {
89                t += enc.encode(f).len();
90            }
91            t
92        };
93        rusty_h264_encoder::bitacct::add_actual_bytes(total);
94        rusty_h264_encoder::bitacct::set_enabled(false);
95        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
96        rusty_h264_encoder::bitacct::dump(
97            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
98            mbs,
99        );
100        if std::env::var_os("RFF_BSTATS").is_some() {
101            rusty_h264_encoder::mb16::bstats::dump();
102        }
103        if std::env::var_os("BA_MVDTAB").is_some() {
104            rusty_h264_encoder::bitacct::dump_mvd_table();
105        }
106    }
107}