rusty_h264_encoder/
bitacct.rs1#[allow(unused_imports)]
23use alloc::vec::Vec;
24
25use rusty_h264_common::atomic::AtomicU64;
26
27use core::sync::atomic::{AtomicBool, Ordering};
28
29#[derive(Clone, Copy)]
31pub enum B {
32 SkipFlag = 0,
33 MbType = 1,
34 RefIdx = 2,
35 Mvd = 3,
36 IntraBody = 4,
37 Cbp = 5,
38 QpDelta = 6,
39 ResidLuma = 7,
40 ResidChroma = 8,
41 Terminate = 9,
42 MvdBypass = 10,
46 IntraResid = 11,
48 MvdSign = 12,
50}
51
52pub const N: usize = 13;
53const NAMES: [&str; N] = [
54 "mb_skip_flag",
55 "mb_type/sub_type",
56 "ref_idx",
57 "mvd (MOTION)",
58 "intra MB body (I+P)",
59 "cbp",
60 "mb_qp_delta",
61 "residual luma (TEX)",
62 "residual chroma (TEX)",
63 "end_of_slice",
64 " └ of which mvd bypass",
65 "intra residual (TEX)",
66 " └ of which mvd SIGNS",
67];
68
69static ON: AtomicBool = AtomicBool::new(false);
70static BITS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
71static COUNT: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
72static ACTUAL: AtomicU64 = AtomicU64::new(0);
74
75#[inline]
76pub fn enabled() -> bool {
77 ON.load(Ordering::Relaxed)
78}
79
80pub fn set_enabled(on: bool) {
82 ON.store(on, Ordering::Relaxed);
83}
84
85pub fn init_from_env() {
86 if rusty_h264_common::knob("RFF_BITACCT")
87 .map(|v| v != "0")
88 .unwrap_or(false)
89 {
90 set_enabled(true);
91 }
92}
93
94#[inline]
95pub fn add(b: B, bits: u64) {
96 BITS[b as usize].fetch_add(bits, Ordering::Relaxed);
97 COUNT[b as usize].fetch_add(1, Ordering::Relaxed);
98}
99
100pub fn add_actual_bytes(n: usize) {
102 ACTUAL.fetch_add(n as u64 * 8, Ordering::Relaxed);
103}
104
105pub fn reset() {
106 for i in 0..N {
107 BITS[i].store(0, Ordering::Relaxed);
108 COUNT[i].store(0, Ordering::Relaxed);
109 }
110 ACTUAL.store(0, Ordering::Relaxed);
111}
112
113pub fn dump(label: &str, mbs: u64) {
116 let vals: Vec<u64> = (0..N).map(|i| BITS[i].load(Ordering::Relaxed)).collect();
117 let cnts: Vec<u64> = (0..N).map(|i| COUNT[i].load(Ordering::Relaxed)).collect();
118 let accounted: u64 = vals.iter().sum();
119 let actual = ACTUAL.load(Ordering::Relaxed);
120 println!("\n=== BIT ACCOUNTANT — {label} ===");
121 println!(
122 "{:<24}{:>12}{:>9}{:>12}{:>12}",
123 "syntax element", "bits", "share", "bits/MB", "elements"
124 );
125 println!("{}", "-".repeat(69));
126 for i in 0..N {
127 if cnts[i] == 0 && vals[i] == 0 {
128 continue;
129 }
130 println!(
131 "{:<24}{:>12}{:>8.1}%{:>12.1}{:>12}",
132 NAMES[i],
133 vals[i],
134 100.0 * vals[i] as f64
135 / (accounted
136 - vals[B::MvdBypass as usize]
137 - vals[B::IntraResid as usize]
138 - vals[B::MvdSign as usize])
139 .max(1) as f64,
140 vals[i] as f64 / mbs.max(1) as f64,
141 cnts[i]
142 );
143 }
144 println!("{}", "-".repeat(69));
145 let sub =
149 vals[B::MvdBypass as usize] + vals[B::IntraResid as usize] + vals[B::MvdSign as usize];
150 let accounted = accounted - sub;
151 let mv = vals[B::Mvd as usize] + vals[B::RefIdx as usize];
152 let tex =
153 vals[B::ResidLuma as usize] + vals[B::ResidChroma as usize] + vals[B::IntraResid as usize];
154 let x264_syntax = accounted - tex - vals[B::Terminate as usize];
156 let misc = accounted - mv - tex;
157 let _ = misc;
158 let pc = |v: u64| 100.0 * v as f64 / accounted.max(1) as f64;
159 println!(
160 "x264-comparable: NON-RESIDUAL SYNTAX {:.1}% (of which mvd {:.1}%) TEXTURE {:.1}% hdr/term {:.1}%",
161 pc(x264_syntax),
162 pc(mv),
163 pc(tex),
164 pc(vals[B::Terminate as usize])
165 );
166 println!(
168 "reconciliation: accounted {accounted} / actual {actual} = {:.1}% (residue {} bits = slice headers + NAL + flush)",
169 100.0 * accounted as f64 / actual.max(1) as f64,
170 actual as i64 - accounted as i64
171 );
172}
173
174pub const MVD_K: usize = 65; static MVD_BITS: [AtomicU64; MVD_K] = [const { AtomicU64::new(0) }; MVD_K];
181static MVD_CNT: [AtomicU64; MVD_K] = [const { AtomicU64::new(0) }; MVD_K];
182
183#[inline]
184pub fn add_mvd_sample(abs_d: u32, bits: u64) {
185 let k = (abs_d as usize).min(MVD_K - 1);
186 MVD_BITS[k].fetch_add(bits, Ordering::Relaxed);
187 MVD_CNT[k].fetch_add(1, Ordering::Relaxed);
188}
189
190pub fn dump_mvd_table() {
191 println!("|d|,count,avg_bits");
192 for k in 0..MVD_K {
193 let (b, c) = (
194 MVD_BITS[k].load(Ordering::Relaxed),
195 MVD_CNT[k].load(Ordering::Relaxed),
196 );
197 if c > 0 {
198 println!("{k},{c},{:.3}", b as f64 / c as f64);
199 }
200 }
201}