Skip to main content

yscv_video/
h264_cabac.rs

1//! H.264 CABAC (Context-based Adaptive Binary Arithmetic Coding) entropy decoder.
2//!
3//! CABAC is the entropy coding method used in H.264 Main and High profiles.
4//! It provides 9--14 % bitrate savings over CAVLC at the cost of higher
5//! decoding complexity.  This module implements a *minimal* CABAC decoder
6//! covering the most common syntax elements:
7//!
8//! - `mb_type`
9//! - `coded_block_flag`
10//! - `significant_coeff_flag`
11//! - `last_significant_coeff_flag`
12//! - `coeff_abs_level_minus1`
13//!
14//! The arithmetic engine follows ITU-T H.264 section 9.3 (Table 9-45 state
15//! transitions and Table 9-48 range LPS values).
16
17// ---------------------------------------------------------------------------
18// State-transition tables (H.264 spec Table 9-45)
19// ---------------------------------------------------------------------------
20
21/// State transition after decoding the **Most Probable Symbol** (MPS).
22/// Indexed by `pStateIdx` (0..=63).
23#[rustfmt::skip]
24static TRANSITION_MPS: [u8; 64] = [
25     1,  2,  3,  4,  5,  6,  7,  8,
26     9, 10, 11, 12, 13, 14, 15, 16,
27    17, 18, 19, 20, 21, 22, 23, 24,
28    25, 26, 27, 28, 29, 30, 31, 32,
29    33, 34, 35, 36, 37, 38, 39, 40,
30    41, 42, 43, 44, 45, 46, 47, 48,
31    49, 50, 51, 52, 53, 54, 55, 56,
32    57, 58, 59, 60, 61, 62, 62, 63,
33];
34
35/// State transition after decoding the **Least Probable Symbol** (LPS).
36/// Indexed by `pStateIdx` (0..=63).
37#[rustfmt::skip]
38static TRANSITION_LPS: [u8; 64] = [
39     0,  0,  1,  2,  2,  4,  4,  5,
40     6,  7,  8,  9,  9, 11, 11, 12,
41    13, 13, 15, 15, 16, 16, 18, 18,
42    19, 19, 21, 21, 22, 22, 23, 24,
43    24, 25, 26, 26, 27, 27, 28, 29,
44    29, 30, 30, 30, 31, 32, 32, 33,
45    33, 33, 34, 34, 35, 35, 35, 36,
46    36, 36, 37, 37, 37, 38, 38, 63,
47];
48
49// ---------------------------------------------------------------------------
50// Range LPS table (H.264 spec Table 9-48)
51// ---------------------------------------------------------------------------
52
53/// `RANGE_TABLE[pStateIdx][qRangeIdx]` — the LPS sub-range for each
54/// probability state and quarter-range index.
55///
56/// 64 rows x 4 columns.  Values taken directly from ITU-T H.264.
57#[rustfmt::skip]
58static RANGE_TABLE: [[u16; 4]; 64] = [
59    [128, 176, 208, 240],
60    [128, 167, 197, 227],
61    [128, 158, 187, 216],
62    [123, 150, 178, 205],
63    [116, 142, 169, 195],
64    [111, 135, 160, 185],
65    [105, 128, 152, 175],
66    [100, 122, 144, 166],
67    [ 95, 116, 137, 158],
68    [ 90, 110, 130, 150],
69    [ 85, 104, 123, 142],
70    [ 81,  99, 117, 135],
71    [ 77,  94, 111, 128],
72    [ 73,  89, 105, 122],
73    [ 69,  85, 100, 116],
74    [ 66,  80,  95, 110],
75    [ 62,  76,  90, 104],
76    [ 59,  72,  86,  99],
77    [ 56,  69,  81,  94],
78    [ 53,  65,  77,  89],
79    [ 51,  62,  73,  85],
80    [ 48,  59,  69,  80],
81    [ 46,  56,  66,  76],
82    [ 43,  53,  63,  72],
83    [ 41,  50,  59,  69],
84    [ 39,  48,  56,  65],
85    [ 37,  45,  54,  62],
86    [ 35,  43,  51,  59],
87    [ 33,  41,  48,  56],
88    [ 32,  39,  46,  53],
89    [ 30,  37,  43,  50],
90    [ 29,  35,  41,  48],
91    [ 27,  33,  39,  45],
92    [ 26,  31,  37,  43],
93    [ 24,  30,  35,  41],
94    [ 23,  28,  33,  39],
95    [ 22,  27,  32,  37],
96    [ 21,  26,  30,  35],
97    [ 20,  24,  29,  33],
98    [ 19,  23,  27,  31],
99    [ 18,  22,  26,  30],
100    [ 17,  21,  25,  28],
101    [ 16,  20,  23,  27],
102    [ 15,  19,  22,  25],
103    [ 14,  18,  21,  24],
104    [ 14,  17,  20,  23],
105    [ 13,  16,  19,  22],
106    [ 12,  15,  18,  21],
107    [ 12,  14,  17,  20],
108    [ 11,  14,  16,  19],
109    [ 11,  13,  15,  18],
110    [ 10,  12,  15,  17],
111    [ 10,  12,  14,  16],
112    [  9,  11,  13,  15],
113    [  9,  11,  12,  14],
114    [  8,  10,  12,  14],
115    [  8,   9,  11,  13],
116    [  7,   9,  11,  12],
117    [  7,   9,  10,  12],
118    [  7,   8,  10,  11],
119    [  6,   8,   9,  11],
120    [  6,   7,   9,  10],
121    [  6,   7,   8,   9],
122    [  2,   2,   2,   2],
123];
124
125// ---------------------------------------------------------------------------
126// Context model
127// ---------------------------------------------------------------------------
128
129/// Number of context variables used in H.264 CABAC.
130pub const NUM_CABAC_CONTEXTS: usize = 460;
131
132/// Adaptive probability context model for CABAC (H.264, 9.3.1).
133#[derive(Debug, Clone)]
134pub struct CabacContext {
135    /// Probability state index (0 = equiprobable, 63 = most skewed).
136    pub state: u8,
137    /// Most Probable Symbol value.
138    pub mps: bool,
139}
140
141impl CabacContext {
142    /// Create a context initialised from a (slope, offset) init_value at a
143    /// given slice QP.
144    pub fn new(slice_qp: i32, init_value: i16) -> Self {
145        let m = (init_value >> 4) * 5 - 45;
146        let n = ((init_value & 15) << 3) - 16;
147        let pre = ((m * (slice_qp.clamp(0, 51) as i16 - 16)) >> 4) + n;
148        let pre = pre.clamp(1, 126);
149
150        if pre <= 63 {
151            CabacContext {
152                state: (63 - pre) as u8,
153                mps: false,
154            }
155        } else {
156            CabacContext {
157                state: (pre - 64) as u8,
158                mps: true,
159            }
160        }
161    }
162
163    /// Create a default equiprobable context.
164    pub fn equiprobable() -> Self {
165        CabacContext {
166            state: 0,
167            mps: false,
168        }
169    }
170}
171
172/// Initialise a full set of CABAC context variables for a given QP.
173///
174/// Uses the default init_value table from the H.264 spec (simplified:
175/// all contexts start at init_value = 0x7E which maps to roughly
176/// equiprobable).  A production decoder would use the per-context tables
177/// from Tables 9-12 through 9-23.
178pub fn init_cabac_contexts(slice_qp: i32) -> Vec<CabacContext> {
179    // Default init value for all contexts (mid-probability).
180    let default_init: i16 = 0x7E; // 126 -> state near equiprobable
181    (0..NUM_CABAC_CONTEXTS)
182        .map(|_| CabacContext::new(slice_qp, default_init))
183        .collect()
184}
185
186// ---------------------------------------------------------------------------
187// CABAC arithmetic decoding engine (H.264, 9.3.3)
188// ---------------------------------------------------------------------------
189
190/// CABAC binary arithmetic decoder for H.264.
191pub struct CabacDecoder<'a> {
192    data: &'a [u8],
193    offset: usize,
194    bits_left: u32,
195    /// Current arithmetic coding range (9-bit, initialised to 510).
196    range: u32,
197    /// Current arithmetic coding value.
198    value: u32,
199}
200
201impl<'a> CabacDecoder<'a> {
202    /// Construct a new CABAC decoder from RBSP payload bytes.
203    ///
204    /// The slice must start at the first CABAC-coded byte (after the slice
205    /// header has been fully consumed).
206    pub fn new(data: &'a [u8]) -> Self {
207        let mut dec = CabacDecoder {
208            data,
209            offset: 0,
210            bits_left: 0,
211            range: 510,
212            value: 0,
213        };
214        // Bootstrap: read 9 bits into `value` (spec 9.3.2.2).
215        dec.value = dec.read_bits(9);
216        dec
217    }
218
219    // ------------------------------------------------------------------
220    // Bit-level I/O
221    // ------------------------------------------------------------------
222
223    fn read_bit(&mut self) -> u32 {
224        if self.bits_left == 0 {
225            if self.offset < self.data.len() {
226                self.bits_left = 8;
227                self.offset += 1;
228            } else {
229                return 0;
230            }
231        }
232        self.bits_left -= 1;
233        let byte = self.data[self.offset - 1];
234        (u32::from(byte) >> self.bits_left) & 1
235    }
236
237    fn read_bits(&mut self, n: u32) -> u32 {
238        let mut val = 0u32;
239        for _ in 0..n {
240            val = (val << 1) | self.read_bit();
241        }
242        val
243    }
244
245    // ------------------------------------------------------------------
246    // Renormalization (spec 9.3.3.2.2)
247    // ------------------------------------------------------------------
248
249    fn renorm(&mut self) {
250        while self.range < 256 {
251            self.range <<= 1;
252            self.value = (self.value << 1) | self.read_bit();
253        }
254    }
255
256    // ------------------------------------------------------------------
257    // Core decoding primitives
258    // ------------------------------------------------------------------
259
260    /// Decode a single context-modelled binary decision.
261    pub fn decode_decision(&mut self, ctx: &mut CabacContext) -> bool {
262        let q_idx = (self.range >> 6) & 3;
263        let lps_range = RANGE_TABLE[ctx.state as usize][q_idx as usize] as u32;
264        self.range -= lps_range;
265
266        if self.value < self.range {
267            // MPS path
268            ctx.state = TRANSITION_MPS[ctx.state as usize];
269            self.renorm();
270            ctx.mps
271        } else {
272            // LPS path
273            self.value -= self.range;
274            self.range = lps_range;
275            if ctx.state == 0 {
276                ctx.mps = !ctx.mps;
277            }
278            ctx.state = TRANSITION_LPS[ctx.state as usize];
279            self.renorm();
280            !ctx.mps
281        }
282    }
283
284    /// Decode a bypass bin (equiprobable, no context update).
285    pub fn decode_bypass(&mut self) -> bool {
286        self.value = (self.value << 1) | self.read_bit();
287        if self.value >= self.range {
288            self.value -= self.range;
289            true
290        } else {
291            false
292        }
293    }
294
295    /// Decode a terminate bin (used for end_of_slice_flag).
296    pub fn decode_terminate(&mut self) -> bool {
297        self.range -= 2;
298        if self.value >= self.range {
299            true
300        } else {
301            self.renorm();
302            false
303        }
304    }
305
306    /// Returns the number of unconsumed bytes remaining.
307    pub fn bytes_remaining(&self) -> usize {
308        let full_bytes = self.data.len().saturating_sub(self.offset);
309        if self.bits_left > 0 {
310            full_bytes + 1
311        } else {
312            full_bytes
313        }
314    }
315}
316
317// ---------------------------------------------------------------------------
318// Binarization schemes (H.264, 9.3.2)
319// ---------------------------------------------------------------------------
320
321/// Decode a unary-coded value (sequence of 1s terminated by 0, or max bins).
322pub fn decode_unary(decoder: &mut CabacDecoder<'_>, ctx: &mut CabacContext, max_bins: u32) -> u32 {
323    let mut val = 0u32;
324    while val < max_bins {
325        if decoder.decode_decision(ctx) {
326            val += 1;
327        } else {
328            return val;
329        }
330    }
331    val
332}
333
334/// Decode a truncated-unary coded value.
335pub fn decode_truncated_unary(
336    decoder: &mut CabacDecoder<'_>,
337    ctx: &mut CabacContext,
338    max_val: u32,
339) -> u32 {
340    if max_val == 0 {
341        return 0;
342    }
343    let mut val = 0u32;
344    while val < max_val {
345        if decoder.decode_decision(ctx) {
346            val += 1;
347        } else {
348            return val;
349        }
350    }
351    val
352}
353
354/// Decode a fixed-length code of `n` bits using bypass decoding.
355pub fn decode_fixed_length(decoder: &mut CabacDecoder<'_>, n: u32) -> u32 {
356    let mut val = 0u32;
357    for _ in 0..n {
358        val = (val << 1) | (decoder.decode_bypass() as u32);
359    }
360    val
361}
362
363/// Decode a k-th order Exp-Golomb coded value using bypass bins.
364pub fn decode_exp_golomb_bypass(decoder: &mut CabacDecoder<'_>, k: u32) -> u32 {
365    let mut order = 0u32;
366    // Count leading 1-bits (prefix)
367    while decoder.decode_bypass() {
368        order += 1;
369        if order > 16 {
370            return 0; // safety limit
371        }
372    }
373    // Read (order + k) suffix bits
374    let suffix_len = order + k;
375    let mut val = (1u32 << order) - 1;
376    if suffix_len > 0 {
377        val += decode_fixed_length(decoder, suffix_len);
378    }
379    val
380}
381
382// ---------------------------------------------------------------------------
383// H.264 syntax element decoders
384// ---------------------------------------------------------------------------
385
386/// Context indices for the common syntax elements.
387pub mod ctx {
388    // mb_type contexts for I-slices (Table 9-34): 3..=10
389    pub const MB_TYPE_I_START: usize = 3;
390    // mb_type contexts for P-slices (Table 9-34): 14..=20
391    pub const MB_TYPE_P_START: usize = 14;
392    // coded_block_flag (Table 9-34): 85..=88 for luma
393    pub const CODED_BLOCK_FLAG_LUMA: usize = 85;
394    // significant_coeff_flag (Table 9-34): 105..=165
395    pub const SIGNIFICANT_COEFF_START: usize = 105;
396    // last_significant_coeff_flag: 166..=226
397    pub const LAST_SIGNIFICANT_COEFF_START: usize = 166;
398    // coeff_abs_level_minus1: 227..=275
399    pub const COEFF_ABS_LEVEL_START: usize = 227;
400}
401
402/// Decode `mb_type` for an I-slice macroblock.
403///
404/// Binarization: prefix = TU(max=25), suffix depends on value.
405/// Simplified: decode the prefix as a truncated-unary with context
406/// index offset from `MB_TYPE_I_START`.
407pub fn decode_mb_type_i_slice(
408    decoder: &mut CabacDecoder<'_>,
409    contexts: &mut [CabacContext],
410) -> u32 {
411    // bin 0: context index 3 (or 3 + ctx_inc depending on neighbours)
412    let ci = ctx::MB_TYPE_I_START;
413    if !decoder.decode_decision(&mut contexts[ci]) {
414        return 0; // I_4x4
415    }
416    // bin 1: terminate check for I_PCM (context 4)
417    if decoder.decode_terminate() {
418        return 25; // I_PCM
419    }
420    // bins 2..N: decode sub-type using contexts 5+
421    let sub = decode_truncated_unary(decoder, &mut contexts[ci + 2], 23);
422    1 + sub // I_16x16 variants (1..=24)
423}
424
425/// Decode `mb_type` for a P-slice macroblock.
426pub fn decode_mb_type_p_slice(
427    decoder: &mut CabacDecoder<'_>,
428    contexts: &mut [CabacContext],
429) -> u32 {
430    let ci = ctx::MB_TYPE_P_START;
431    if !decoder.decode_decision(&mut contexts[ci]) {
432        // P_L0_16x16 (0) or sub-partition modes
433        if !decoder.decode_decision(&mut contexts[ci + 1]) {
434            return 0; // P_L0_16x16
435        }
436        if !decoder.decode_decision(&mut contexts[ci + 2]) {
437            return 1; // P_L0_L0_16x8
438        }
439        return 2; // P_L0_L0_8x16
440    }
441    if !decoder.decode_decision(&mut contexts[ci + 3]) {
442        return 3; // P_8x8
443    }
444    // Intra modes in P-slice: decode as I-slice mb_type + 5
445    let intra_type = decode_mb_type_i_slice(decoder, contexts);
446    5 + intra_type
447}
448
449/// Decode `coded_block_flag` for a 4x4 luma block.
450pub fn decode_coded_block_flag(
451    decoder: &mut CabacDecoder<'_>,
452    contexts: &mut [CabacContext],
453    ctx_offset: usize,
454) -> bool {
455    let ci = ctx::CODED_BLOCK_FLAG_LUMA + ctx_offset.min(3);
456    decoder.decode_decision(&mut contexts[ci])
457}
458
459/// Decode residual coefficients for one 4x4 block via CABAC.
460///
461/// Returns a vector of up to 16 coefficients in zig-zag scan order.
462pub fn decode_residual_block_cabac(
463    decoder: &mut CabacDecoder<'_>,
464    contexts: &mut [CabacContext],
465    max_num_coeff: usize,
466) -> Vec<i32> {
467    let mut coeffs = vec![0i32; max_num_coeff];
468
469    // 1) Decode significance map
470    let mut significant = vec![false; max_num_coeff];
471    let mut last = vec![false; max_num_coeff];
472    let mut num_coeff = 0usize;
473
474    for i in 0..max_num_coeff - 1 {
475        let sig_ci = ctx::SIGNIFICANT_COEFF_START + i.min(14);
476        significant[i] = decoder.decode_decision(&mut contexts[sig_ci]);
477        if significant[i] {
478            let last_ci = ctx::LAST_SIGNIFICANT_COEFF_START + i.min(14);
479            last[i] = decoder.decode_decision(&mut contexts[last_ci]);
480            num_coeff += 1;
481            if last[i] {
482                break;
483            }
484        }
485    }
486    // Last coeff is implicitly significant if we haven't hit last yet
487    if num_coeff > 0 && !last.iter().any(|&l| l) {
488        significant[max_num_coeff - 1] = true;
489        num_coeff += 1;
490    }
491
492    if num_coeff == 0 {
493        return coeffs;
494    }
495
496    // 2) Decode coefficient levels in reverse scan order
497    let sig_positions: Vec<usize> = (0..max_num_coeff).filter(|&i| significant[i]).collect();
498
499    let mut num_gt1 = 0u32;
500    let mut num_eq1 = 0u32;
501
502    for &pos in sig_positions.iter().rev() {
503        // coeff_abs_level_minus1 prefix (truncated unary, max 14)
504        let ctx_cat = (num_gt1.min(4)) as usize;
505        let ci = ctx::COEFF_ABS_LEVEL_START + ctx_cat;
506        let prefix = decode_truncated_unary(decoder, &mut contexts[ci], 14);
507        let abs_level = if prefix < 14 {
508            prefix + 1
509        } else {
510            // suffix via Exp-Golomb bypass
511            let suffix = decode_exp_golomb_bypass(decoder, 0);
512            prefix + 1 + suffix
513        };
514
515        // Sign bit (bypass)
516        let sign = decoder.decode_bypass();
517        coeffs[pos] = if sign {
518            -(abs_level as i32)
519        } else {
520            abs_level as i32
521        };
522
523        if abs_level == 1 {
524            num_eq1 += 1;
525        }
526        if abs_level > 1 {
527            num_gt1 += 1;
528        }
529    }
530
531    let _ = num_eq1; // used by context derivation in full decoder
532
533    coeffs
534}
535
536/// Identifies the entropy coding mode from a PPS.
537#[derive(Debug, Clone, Copy, PartialEq, Eq)]
538pub enum EntropyCodingMode {
539    /// Context-Adaptive Variable-Length Coding (Baseline).
540    Cavlc,
541    /// Context-Adaptive Binary Arithmetic Coding (Main/High).
542    Cabac,
543}
544
545impl EntropyCodingMode {
546    /// Determine entropy coding mode from `entropy_coding_mode_flag`.
547    pub fn from_flag(flag: bool) -> Self {
548        if flag {
549            EntropyCodingMode::Cabac
550        } else {
551            EntropyCodingMode::Cavlc
552        }
553    }
554}
555
556// ---------------------------------------------------------------------------
557// Tests
558// ---------------------------------------------------------------------------
559
560#[cfg(test)]
561mod tests {
562    use super::*;
563
564    #[test]
565    fn test_cabac_context_init_equiprobable() {
566        let ctx = CabacContext::equiprobable();
567        assert_eq!(ctx.state, 0);
568        assert!(!ctx.mps);
569    }
570
571    #[test]
572    fn test_cabac_context_init_from_value() {
573        // init_value 0x7E = 126 -> slope = (126>>4)*5-45 = 7*5-45 = -10
574        // offset = ((126&15)<<3)-16 = (14<<3)-16 = 96
575        // init_state = ((-10)*(26-16))>>4 + 96 = (-100>>4)+96 = -7+96 = 89
576        // pre = clamp(89,1,126) = 89
577        // 89 > 63 -> state = 89-64 = 25, mps = true
578        let ctx = CabacContext::new(26, 0x7E);
579        assert_eq!(ctx.state, 25);
580        assert!(ctx.mps);
581    }
582
583    #[test]
584    fn test_cabac_decode_bypass_deterministic() {
585        // All-zero data -> bypass always returns false (value stays below range).
586        let data = [0x00, 0x00, 0x00, 0x00];
587        let mut dec = CabacDecoder::new(&data);
588        for _ in 0..8 {
589            assert!(!dec.decode_bypass());
590        }
591    }
592
593    #[test]
594    fn test_cabac_decode_terminate_on_end() {
595        // Range starts at 510. After subtracting 2, range = 508.
596        // If value >= 508, terminate returns true.
597        // With all-ones data, value will be large.
598        let data = [0xFF, 0xFF, 0xFF, 0xFF];
599        let mut dec = CabacDecoder::new(&data);
600        // value after init = first 9 bits of 0xFFFF... = 0x1FF = 511
601        // range = 510, range -= 2 = 508, value (511) >= 508 -> true
602        assert!(dec.decode_terminate());
603    }
604
605    #[test]
606    fn test_cabac_decode_decision_updates_state() {
607        let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
608        let mut dec = CabacDecoder::new(&data);
609        let mut ctx = CabacContext::equiprobable();
610        let initial_state = ctx.state;
611
612        // After a decision the state should change.
613        let _bin = dec.decode_decision(&mut ctx);
614        // The state may or may not differ from initial (depends on MPS/LPS),
615        // but the function should not panic.
616        assert!(ctx.state <= 63);
617        let _ = initial_state;
618    }
619
620    #[test]
621    fn test_decode_unary_zero() {
622        // With all-zero data, decode_decision on an equiprobable context
623        // with value=0 should return the MPS (false) immediately,
624        // giving unary value 0.
625        let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
626        let mut dec = CabacDecoder::new(&data);
627        let mut ctx = CabacContext::equiprobable();
628        let val = decode_unary(&mut dec, &mut ctx, 10);
629        // Value should be 0 since MPS = false -> decode_decision returns false
630        // on MPS path (value < range for all-zero data).
631        assert_eq!(val, 0);
632    }
633
634    #[test]
635    fn test_fixed_length_decode() {
636        // All-ones data: bypass bits should all be 1.
637        let data = [0xFF, 0xFF, 0xFF, 0xFF];
638        let mut dec = CabacDecoder::new(&data);
639        let val = decode_fixed_length(&mut dec, 3);
640        // 3 bypass bits from all-1s stream should produce 0b111 = 7.
641        assert_eq!(val, 7);
642    }
643
644    #[test]
645    fn test_entropy_coding_mode_from_flag() {
646        assert_eq!(
647            EntropyCodingMode::from_flag(false),
648            EntropyCodingMode::Cavlc
649        );
650        assert_eq!(EntropyCodingMode::from_flag(true), EntropyCodingMode::Cabac);
651    }
652
653    #[test]
654    fn test_init_cabac_contexts_count() {
655        let contexts = init_cabac_contexts(26);
656        assert_eq!(contexts.len(), NUM_CABAC_CONTEXTS);
657    }
658
659    #[test]
660    fn test_decode_residual_block_length() {
661        // Verify that decode_residual_block_cabac always returns the
662        // requested number of coefficients regardless of input data.
663        let data = [0x00; 32];
664        let mut dec = CabacDecoder::new(&data);
665        let mut contexts = init_cabac_contexts(26);
666        let coeffs = decode_residual_block_cabac(&mut dec, &mut contexts, 16);
667        assert_eq!(coeffs.len(), 16);
668
669        // Also verify with max_num_coeff = 4 (chroma DC).
670        let data2 = [0x00; 32];
671        let mut dec2 = CabacDecoder::new(&data2);
672        let mut contexts2 = init_cabac_contexts(26);
673        let coeffs2 = decode_residual_block_cabac(&mut dec2, &mut contexts2, 4);
674        assert_eq!(coeffs2.len(), 4);
675    }
676
677    #[test]
678    fn test_transition_table_bounds() {
679        // Verify all transition table entries are in [0, 63].
680        for &s in TRANSITION_MPS.iter() {
681            assert!(s <= 63);
682        }
683        for &s in TRANSITION_LPS.iter() {
684            assert!(s <= 63);
685        }
686    }
687
688    #[test]
689    fn test_range_table_positive() {
690        // All range table entries should be > 0.
691        for row in RANGE_TABLE.iter() {
692            for &val in row.iter() {
693                assert!(val > 0);
694            }
695        }
696    }
697}