Skip to main content

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}