rusty_h264_decoder/cabac.rs
1//! CABAC arithmetic decoding engine (spec §9.3.3.2) + context initialization
2//! (§9.3.1.1). The literal-spec engine (codIRange/codIOffset, RenormD), which is
3//! bit-exact to openh264's optimized variant. Tables in [`crate::cabac_tables`].
4
5use rusty_h264_common::cabac_tables::{CTX_INIT, RANGE_LPS, STATE_TRANS};
6
7/// A context model is ONE byte: `state * 2 + mps` (0..=127) — ffmpeg/openh264's
8/// packing (H-35). The literal two-field form cost two loads and two stores per
9/// bin plus `1 - mps` arithmetic; packed, a bin is one byte load, one table
10/// lookup, one byte store, and `s & 1` for the value. The three tables below
11/// fold the state transition AND the state-0 MPS flip into the lookup, so the
12/// decoded bins are identical by construction.
13///
14/// Built at compile time from the spec tables, so there is no init cost and no
15/// `OnceLock` check on the hot path.
16const fn build_lps_range() -> [u8; 4 * 128] {
17 let mut t = [0u8; 4 * 128];
18 let mut q = 0;
19 while q < 4 {
20 let mut s = 0;
21 while s < 128 {
22 t[q * 128 + s] = RANGE_LPS[s >> 1][q];
23 s += 1;
24 }
25 q += 1;
26 }
27 t
28}
29/// ONE transition table covering both paths: `[0..128)` is the MPS path (state
30/// advances, MPS unchanged) and `[128..256)` the LPS path (state falls back, and
31/// at state 0 the MPS FLIPS per spec §9.3.3.2.1.1 — baked in, never branched).
32/// Indexed `s | (lps_mask & 128)`, which is what makes the bin loop branchless.
33const fn build_trans() -> [u8; 256] {
34 let mut t = [0u8; 256];
35 let mut s = 0;
36 while s < 128 {
37 let mps = s as u8 & 1;
38 t[s] = (STATE_TRANS[s >> 1][1] << 1) | mps;
39 let new_mps = if s >> 1 == 0 { 1 - mps } else { mps };
40 t[128 + s] = (STATE_TRANS[s >> 1][0] << 1) | new_mps;
41 s += 1;
42 }
43 t
44}
45static LPS_RANGE: [u8; 4 * 128] = build_lps_range();
46static TRANS: [u8; 256] = build_trans();
47
48/// The CABAC decoder: arithmetic engine reading MSB-first from the RBSP plus the
49/// 460 adaptive context models.
50pub struct Cabac<'a> {
51 data: &'a [u8],
52 /// Next byte to load into the bit window.
53 byte_pos: usize,
54 /// MSB-aligned unread bits (H-34): the old engine extracted ONE bit per
55 /// `read_bit` — a bounds check, byte index and shift per renorm shift. The
56 /// window refills up to 8 bytes at once and serves multi-bit takes; it
57 /// consumes the same bits in the same order, so every bin (and therefore
58 /// the bitstream interpretation) is identical by construction. Zero-fills
59 /// past the end of the buffer exactly like the old reader (the fuzzer's
60 /// slice-loop bound relies on that).
61 window: u64,
62 /// Number of valid bits at the top of `window`.
63 wbits: u32,
64 range: u32,
65 offset: u32,
66 /// 460 context models, each packed as `state * 2 + mps`.
67 ctx: [u8; 460],
68 /// Bring-up symbol trace (Brick 0.3): when `RH_CABAC_TRACE=1`, print the
69 /// spec-canonical entering `(codIRange, codIOffset)` before each bin, in the
70 /// SAME `"<n> <D|B|T> r=<range> o=<offset>"` format as the instrumented openh264
71 /// oracle — so the two traces diff line-for-line to localise the first divergence.
72 trace: bool,
73 sym: u64,
74}
75
76impl Cabac<'_> {
77 #[inline]
78 fn tr(&mut self, kind: &str) {
79 if self.trace {
80 eprintln!("{} {} r={} o={}", self.sym, kind, self.range, self.offset);
81 self.sym += 1;
82 }
83 }
84
85}
86
87impl<'a> Cabac<'a> {
88 /// Initializes from the RBSP `data` at byte offset `start_byte` (the slice
89 /// data, byte-aligned past the header), the slice's `qp` (clamped 0..51),
90 /// `cabac_init_idc`, and whether the slice is I/SI (spec §9.3.1).
91 pub fn new(data: &'a [u8], start_byte: usize, qp: i32, init_idc: u32, is_i: bool) -> Self {
92 let model = if is_i { 0 } else { ((init_idc + 1) as usize).min(3) };
93 let q = qp.clamp(0, 51);
94 let mut ctx = [0u8; 460];
95 for (i, c) in ctx.iter_mut().enumerate() {
96 let (m, n) = CTX_INIT[i][model];
97 let pre = (((m as i32 * q) >> 4) + n as i32).clamp(1, 126);
98 // Packed as state*2 + mps; same (state, mps) pair as the spec form.
99 *c = if pre <= 63 {
100 ((63 - pre) as u8) << 1
101 } else {
102 (((pre - 64) as u8) << 1) | 1
103 };
104 }
105 let trace = std::env::var_os("RH_CABAC_TRACE").is_some();
106 let mut e = Cabac { data, byte_pos: start_byte, window: 0, wbits: 0, range: 510, offset: 0, ctx, trace, sym: 0 };
107 e.offset = e.take(9);
108 e
109 }
110
111 /// Engine state `(codIRange, codIOffset)` — for bring-up verification against the
112 /// oracle's symbol 0 (Brick 1.1). At slice start this is `(510, first-9-bits)`.
113 pub fn dbg_state(&self) -> (u32, u32) {
114 (self.range, self.offset)
115 }
116
117 /// Tops the window up to ≥ 57 valid bits (fast path: one 8-byte load when
118 /// the remaining data allows, else per-byte with zero-fill past the end).
119 #[inline]
120 fn refill(&mut self) {
121 if let Some(chunk) = self.data.get(self.byte_pos..self.byte_pos + 8) {
122 // Load 8 bytes big-endian, keep as many WHOLE bytes as fit below the
123 // current valid bits, masked so no stale bits land past `wbits`.
124 let take_bytes = ((64 - self.wbits) / 8) as usize; // ≥ 4 when called from take()
125 let keep = (take_bytes * 8) as u32;
126 let v = u64::from_be_bytes(chunk.try_into().unwrap());
127 let v = if keep == 64 { v } else { v & (!0u64 << (64 - keep)) };
128 self.window |= v >> self.wbits;
129 self.byte_pos += take_bytes;
130 self.wbits += keep;
131 return;
132 }
133 while self.wbits <= 56 {
134 let b = self.data.get(self.byte_pos).copied().unwrap_or(0);
135 self.window |= (b as u64) << (56 - self.wbits);
136 self.byte_pos += 1;
137 self.wbits += 8;
138 }
139 }
140
141 /// Takes the next `n` (≤ 32) bits MSB-first; zero-fills past the buffer end.
142 /// `n == 0` is legal and yields 0 — the branchless renorm calls it with the
143 /// shift count straight out of `leading_zeros`, which is 0 whenever no
144 /// renormalization is due. `(w >> (63-n)) >> 1` equals `w >> (64-n)` for
145 /// n ≥ 1 and 0 for n = 0, so no shift ever reaches the illegal width 64.
146 #[inline(always)]
147 fn take(&mut self, n: u32) -> u32 {
148 if self.wbits < n {
149 self.refill();
150 }
151 let v = ((self.window >> (63 - n)) >> 1) as u32;
152 self.window <<= n;
153 self.wbits -= n;
154 v
155 }
156
157 /// Renormalization (spec §9.3.3.2.2): keep `range` ≥ 256, refilling `offset`.
158 /// BRANCHLESS: `range ≤ 510` always, so `leading_zeros() - 23` is exactly the
159 /// spec loop's iteration count and is 0 when no renormalization is due (the
160 /// shifts and the zero-width `take` are then no-ops). Same bits, same order.
161 #[inline(always)]
162 fn renorm(&mut self) {
163 let n = self.range.leading_zeros() - 23;
164 self.range <<= n;
165 self.offset = (self.offset << n) | self.take(n);
166 }
167
168 /// Decodes a context-coded bin (spec §9.3.3.2.1), updating the context model.
169 pub fn decode_decision(&mut self, ctx_idx: usize) -> u32 {
170 self.tr("D");
171 // BRANCHLESS bin decode (H-35, ffmpeg's `get_cabac_inline` shape). The
172 // LPS/MPS test is inherently ~coin-flip on a well-adapted context, so a
173 // branch here mispredicts constantly; instead derive an all-ones/zero
174 // MASK and select with arithmetic. `& 127` is free insurance that also
175 // proves every table index in range, dropping the bounds checks.
176 let s = (self.ctx[ctx_idx] & 127) as usize;
177 let q = ((self.range >> 6) & 3) as usize;
178 let lps = LPS_RANGE[q * 128 + s] as u32;
179 // PRECONDITION of the mask arithmetic below: `range >= 256` on entry, so
180 // `range - lps` (lps <= 240) stays positive and the i32 sign test is a
181 // true "offset >= range" test. Renormalization guarantees it after every
182 // bin, and `new()` starts at 510 — the literal `if` form did not need
183 // this, so it is asserted rather than assumed.
184 debug_assert!(self.range >= 256, "renorm invariant broken: range={}", self.range);
185 self.range -= lps;
186 // mask = !0 when `offset >= range` (the LPS path), else 0. `range` and
187 // `offset` are both < 2^16 here, so the i32 arithmetic cannot overflow.
188 let mask = ((self.range as i32 - self.offset as i32 - 1) >> 31) as u32;
189 // LPS: offset -= range; range = lps. MPS: both unchanged.
190 self.offset -= self.range & mask;
191 self.range = self.range.wrapping_add(lps.wrapping_sub(self.range) & mask);
192 // One table covers both transitions; `| 128` picks the LPS half.
193 self.ctx[ctx_idx] = TRANS[s | (mask as usize & 128)];
194 // MPS -> s&1; LPS -> (s&1)^1.
195 let bin = (s as u32 ^ mask) & 1;
196 self.renorm();
197 bin
198 }
199
200 /// Decodes a bypass (equiprobable) bin (spec §9.3.3.2.3).
201 #[inline(always)]
202 pub fn decode_bypass(&mut self) -> u32 {
203 self.tr("B");
204 self.offset = (self.offset << 1) | self.take(1);
205 if self.offset >= self.range {
206 self.offset -= self.range;
207 1
208 } else {
209 0
210 }
211 }
212
213 /// Decodes `n` bypass bins as an unsigned value (MSB first).
214 #[allow(dead_code)] // used by the syntax layer (next)
215 pub fn decode_bypass_bits(&mut self, n: u32) -> u32 {
216 let mut v = 0;
217 for _ in 0..n {
218 v = (v << 1) | self.decode_bypass();
219 }
220 v
221 }
222
223 /// Decodes the terminate bin (spec §9.3.3.2.4); `true` ends the slice (or
224 /// marks I_PCM). No renormalization on terminate.
225 pub fn decode_terminate(&mut self) -> bool {
226 self.tr("T");
227 self.range -= 2;
228 if self.offset >= self.range {
229 true
230 } else {
231 self.renorm();
232 false
233 }
234 }
235
236 // NB: the byte offset where byte-aligned `pcm_sample` data resumes after an
237 // I_PCM terminate is intentionally NOT provided here. This literal engine
238 // holds a 9-bit look-ahead window in `offset`, so the resume position is not
239 // simply `bit_pos` rounded up — it needs the over-read "given back" (cf.
240 // openh264's `RestoreCabacDecEngineToBS`, which backs up by `iBitsLeft >> 3`
241 // bytes). The correct accounting must be derived and validated against the
242 // I_PCM decode path; it will be added with the I_PCM CABAC syntax.
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 /// Literal-spec CABAC *encoder* (§9.3.4), the inverse of [`Cabac`]. Used only
250 /// to validate the decoder by round-trip — encode a bin sequence, decode it,
251 /// assert equality. Encoder and decoder are independent algorithms (encode
252 /// vs decode), so a shared latent bug is implausible; a clean round-trip over
253 /// thousands of mixed bins exercises the full range/offset evolution, every
254 /// `RANGE_LPS`/`STATE_TRANS` entry reached, and the bypass/terminate paths.
255 struct Enc {
256 low: u32,
257 range: u32,
258 outstanding: u32,
259 first: bool,
260 bits: Vec<u8>,
261 ctx: Vec<(u8, u8)>, // (state, mps)
262 }
263
264 fn init_ctx(qp: i32, init_idc: u32, is_i: bool) -> Vec<(u8, u8)> {
265 let model = if is_i { 0 } else { ((init_idc + 1) as usize).min(3) };
266 let q = qp.clamp(0, 51);
267 (0..460)
268 .map(|i| {
269 let (m, n) = CTX_INIT[i][model];
270 let pre = (((m as i32 * q) >> 4) + n as i32).clamp(1, 126);
271 if pre <= 63 {
272 ((63 - pre) as u8, 0)
273 } else {
274 ((pre - 64) as u8, 1)
275 }
276 })
277 .collect()
278 }
279
280 impl Enc {
281 fn new(qp: i32, init_idc: u32, is_i: bool) -> Self {
282 Enc {
283 low: 0,
284 range: 510,
285 outstanding: 0,
286 first: true,
287 bits: Vec::new(),
288 ctx: init_ctx(qp, init_idc, is_i),
289 }
290 }
291
292 fn put_bit(&mut self, b: u32) {
293 if self.first {
294 self.first = false;
295 } else {
296 self.bits.push(b as u8);
297 }
298 while self.outstanding > 0 {
299 self.bits.push((1 - b) as u8);
300 self.outstanding -= 1;
301 }
302 }
303
304 /// RenormE (§9.3.4.3.3).
305 fn renorm(&mut self) {
306 while self.range < 256 {
307 if self.low < 256 {
308 self.put_bit(0);
309 } else if self.low >= 512 {
310 self.low -= 512;
311 self.put_bit(1);
312 } else {
313 self.low -= 256;
314 self.outstanding += 1;
315 }
316 self.range <<= 1;
317 self.low <<= 1;
318 }
319 }
320
321 /// EncodeDecision (§9.3.4.3.1).
322 fn encode(&mut self, ctx_idx: usize, bin: u32) {
323 let (state, mps) = self.ctx[ctx_idx];
324 let q = ((self.range >> 6) & 3) as usize;
325 let lps = RANGE_LPS[state as usize][q] as u32;
326 self.range -= lps;
327 if bin != mps as u32 {
328 self.low += self.range;
329 self.range = lps;
330 let nm = if state == 0 { 1 - mps } else { mps };
331 self.ctx[ctx_idx] = (STATE_TRANS[state as usize][0], nm);
332 } else {
333 self.ctx[ctx_idx].0 = STATE_TRANS[state as usize][1];
334 }
335 self.renorm();
336 }
337
338 /// EncodeBypass (§9.3.4.3.2).
339 fn encode_bypass(&mut self, bin: u32) {
340 self.low <<= 1;
341 if bin != 0 {
342 self.low += self.range;
343 }
344 if self.low >= 1024 {
345 self.put_bit(1);
346 self.low -= 1024;
347 } else if self.low < 512 {
348 self.put_bit(0);
349 } else {
350 self.low -= 512;
351 self.outstanding += 1;
352 }
353 }
354
355 /// EncodeTerminate(1) + flush (§9.3.4.5 / EncodeFlush) — ends the stream.
356 fn finish(&mut self) -> Vec<u8> {
357 self.range -= 2;
358 self.low += self.range;
359 self.range = 2;
360 self.renorm();
361 self.put_bit((self.low >> 9) & 1);
362 let v = ((self.low >> 7) & 3) | 1;
363 self.bits.push(((v >> 1) & 1) as u8);
364 self.bits.push((v & 1) as u8);
365 // Pack MSB-first into bytes.
366 let mut out = vec![0u8; self.bits.len().div_ceil(8)];
367 for (i, &b) in self.bits.iter().enumerate() {
368 out[i / 8] |= b << (7 - (i % 8));
369 }
370 out
371 }
372 }
373
374 /// Deterministic xorshift RNG so the test is reproducible.
375 struct Rng(u32);
376 impl Rng {
377 fn next(&mut self) -> u32 {
378 self.0 ^= self.0 << 13;
379 self.0 ^= self.0 >> 17;
380 self.0 ^= self.0 << 5;
381 self.0
382 }
383 }
384
385 /// Encode a scripted mix of context-coded, bypass, and terminate bins, then
386 /// decode and assert every bin (and the terminate) round-trips exactly.
387 fn roundtrip(qp: i32, init_idc: u32, is_i: bool, seed: u32, n: usize) {
388 let mut rng = Rng(seed);
389 // (kind, ctx, bin): kind 0 = decision, 1 = bypass.
390 let mut script: Vec<(u8, usize, u32)> = Vec::with_capacity(n);
391 let mut enc = Enc::new(qp, init_idc, is_i);
392 for _ in 0..n {
393 let r = rng.next();
394 let kind = (r & 1) as u8;
395 let ctx = (r >> 1) as usize % 460;
396 let bin = (r >> 12) & 1;
397 script.push((kind, ctx, bin));
398 if kind == 0 {
399 enc.encode(ctx, bin);
400 } else {
401 enc.encode_bypass(bin);
402 }
403 }
404 let bytes = enc.finish();
405
406 let mut dec = Cabac::new(&bytes, 0, qp, init_idc, is_i);
407 for (i, &(kind, ctx, bin)) in script.iter().enumerate() {
408 let got = if kind == 0 {
409 dec.decode_decision(ctx)
410 } else {
411 dec.decode_bypass()
412 };
413 assert_eq!(got, bin, "bin {i} (kind {kind}, ctx {ctx}) mismatched");
414 }
415 assert!(dec.decode_terminate(), "terminate should signal end-of-stream");
416 }
417
418 #[test]
419 fn engine_roundtrip_many() {
420 // Sweep QP, init model, and many random scripts: every code path
421 // (LPS/MPS transitions across all 64 states, bypass, terminate, renorm).
422 for &qp in &[0, 12, 26, 37, 51] {
423 for &(idc, is_i) in &[(0u32, true), (0, false), (1, false), (2, false)] {
424 for seed in 1..=40u32 {
425 roundtrip(qp, idc, is_i, seed.wrapping_mul(2654435761), seed as usize * 53);
426 }
427 }
428 }
429 }
430
431 #[test]
432 fn engine_init_matches_spec() {
433 // ctxIdx 0 (I mb_type, m=20 n=-15) at QP 26: preCtxState =
434 // Clip3(1,126,(20*26>>4)-15) = 17 -> state 63-17 = 46, MPS 0.
435 let dec = Cabac::new(&[0xFF, 0xFF, 0xFF], 0, 26, 0, true);
436 // Packed as state*2 + mps (H-35): state 46, MPS 0 -> 92.
437 assert_eq!(dec.ctx[0] >> 1, 46, "state");
438 assert_eq!(dec.ctx[0] & 1, 0, "mps");
439 // Engine init: range 510, offset = first 9 bits of 0xFFFF = 0x1FF.
440 assert_eq!(dec.range, 510);
441 assert_eq!(dec.offset, 0x1FF);
442 }
443
444 /// H-35 oracle: for EVERY packed state and range quartile, the packed tables
445 /// must reproduce the literal spec derivation (RangeLPS, the bin value, and
446 /// both transitions including the state-0 MPS flip) exactly. 512 cases —
447 /// cheaper and stricter than trusting a corpus.
448 #[test]
449 fn packed_state_tables_match_spec_form() {
450 for s in 0usize..128 {
451 let (state, mps) = ((s >> 1) as u8, (s & 1) as u8);
452 for q in 0usize..4 {
453 assert_eq!(LPS_RANGE[q * 128 + s], RANGE_LPS[state as usize][q], "lps s={s} q={q}");
454 }
455 // MPS half: bin == mps, state advances, mps unchanged.
456 let mps_t = TRANS[s];
457 assert_eq!(mps_t >> 1, STATE_TRANS[state as usize][1], "mps-trans state s={s}");
458 assert_eq!(mps_t & 1, mps, "mps-trans mps s={s}");
459 // LPS half: bin == 1-mps, state falls back, mps flips only at state 0.
460 let lps_t = TRANS[128 + s];
461 let want_mps = if state == 0 { 1 - mps } else { mps };
462 assert_eq!(lps_t >> 1, STATE_TRANS[state as usize][0], "lps-trans state s={s}");
463 assert_eq!(lps_t & 1, want_mps, "lps-trans mps s={s}");
464 }
465 }
466
467 /// H-35 oracle #2: the BRANCHLESS mask arithmetic must equal the literal
468 /// `if offset >= range` form for every (range, offset, state) combination
469 /// the engine can present — the mask, the two conditional updates, the
470 /// transition-table half selection, and the bin value. This is the whole
471 /// risk surface of the branchless rewrite, checked exhaustively rather than
472 /// inferred from a corpus that happens to decode.
473 #[test]
474 fn branchless_mask_matches_conditional_form() {
475 // Reachable domain only: renorm guarantees `range` in 256..=510 on entry
476 // and the spec invariant `offset < range` holds throughout. (Widening
477 // past this tests states the engine cannot present — and the wrapped
478 // `range - lps` there makes BOTH forms meaningless, not just one.)
479 for s in 0usize..128 {
480 for range in [256u32, 257, 300, 383, 384, 400, 448, 509, 510] {
481 for offset in [0u32, 1, 127, 128, 255, 256, 300, 383, 384, 509] {
482 if offset >= range {
483 continue;
484 }
485 let q = ((range >> 6) & 3) as usize;
486 let lps = LPS_RANGE[q * 128 + s] as u32;
487 let r1 = range.wrapping_sub(lps);
488 // literal spec form
489 let (mut lr, mut lo, lbin, lctx) = if offset >= r1 {
490 (lps, offset - r1, (s as u32 & 1) ^ 1, TRANS[128 + s])
491 } else {
492 (r1, offset, s as u32 & 1, TRANS[s])
493 };
494 // branchless form, exactly as `decode_decision` computes it
495 let mask = ((r1 as i32 - offset as i32 - 1) >> 31) as u32;
496 let bo = offset - (r1 & mask);
497 let br = r1.wrapping_add(lps.wrapping_sub(r1) & mask);
498 let bctx = TRANS[s | (mask as usize & 128)];
499 let bbin = (s as u32 ^ mask) & 1;
500 // (silence unused-mut on the literal bindings)
501 lr += 0;
502 lo += 0;
503 assert_eq!((lr, lo, lbin, lctx), (br, bo, bbin, bctx), "s={s} range={range} offset={offset}");
504 }
505 }
506 }
507 }
508
509 #[test]
510 fn tables_match_spec_boundaries() {
511 assert_eq!(RANGE_LPS[0], [128, 176, 208, 240]);
512 assert_eq!(RANGE_LPS[63], [2, 2, 2, 2]);
513 assert_eq!(STATE_TRANS[0], [0, 1]);
514 assert_eq!(STATE_TRANS[63], [63, 63]);
515 assert_eq!(CTX_INIT[0][0], (20, -15));
516 }
517}