#![allow(dead_code)]
use crate::bitstream::{BitError, BitReader};
#[inline]
fn bin_trace_enabled() -> bool {
use std::sync::OnceLock;
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| std::env::var_os("OXIDEAV_H264_BIN_TRACE").is_some())
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum CabacError {
#[error("bitstream read failed: {0}")]
Bitstream(#[from] BitError),
#[error("invalid cabac_init_idc {0} (must be 0..=2 or absent for I-slice)")]
InvalidInitIdc(u32),
#[error("invalid SliceQPY {0} (must be -36..=+51)")]
InvalidSliceQp(i32),
#[error("CABAC EG escape suffix exceeded 31 leading-one bits")]
EgEscapeSuffixOverflow,
}
pub type CabacResult<T> = Result<T, CabacError>;
pub(crate) static RANGE_TAB_LPS: [[u8; 4]; 64] = [
[128, 176, 208, 240], [128, 167, 197, 227], [128, 158, 187, 216], [123, 150, 178, 205], [116, 142, 169, 195], [111, 135, 160, 185], [105, 128, 152, 175], [100, 122, 144, 166], [95, 116, 137, 158], [90, 110, 130, 150], [85, 104, 123, 142], [81, 99, 117, 135], [77, 94, 111, 128], [73, 89, 105, 122], [69, 85, 100, 116], [66, 80, 95, 110], [62, 76, 90, 104], [59, 72, 86, 99], [56, 69, 81, 94], [53, 65, 77, 89], [51, 62, 73, 85], [48, 59, 69, 80], [46, 56, 66, 76], [43, 53, 63, 72], [41, 50, 59, 69], [39, 48, 56, 65], [37, 45, 54, 62], [35, 43, 51, 59], [33, 41, 48, 56], [32, 39, 46, 53], [30, 37, 43, 50], [29, 35, 41, 48], [27, 33, 39, 45], [26, 31, 37, 43], [24, 30, 35, 41], [23, 28, 33, 39], [22, 27, 32, 37], [21, 26, 30, 35], [20, 24, 29, 33], [19, 23, 27, 31], [18, 22, 26, 30], [17, 21, 25, 28], [16, 20, 23, 27], [15, 19, 22, 25], [14, 18, 21, 24], [14, 17, 20, 23], [13, 16, 19, 22], [12, 15, 18, 21], [12, 14, 17, 20], [11, 14, 16, 19], [11, 13, 15, 18], [10, 12, 15, 17], [10, 12, 14, 16], [9, 11, 13, 15], [9, 11, 12, 14], [8, 10, 12, 14], [8, 9, 11, 13], [7, 9, 11, 12], [7, 9, 10, 12], [7, 8, 10, 11], [6, 8, 9, 11], [6, 7, 9, 10], [6, 7, 8, 9], [2, 2, 2, 2], ];
pub(crate) static TRANS_IDX_LPS: [u8; 64] = [
0, 0, 1, 2, 2, 4, 4, 5, 6, 7, 8, 9, 9, 11, 11, 12, 13, 13, 15, 15, 16, 16, 18, 18, 19, 19, 21, 21, 22, 22, 23, 24, 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 30, 30, 31, 32, 32, 33, 33, 33, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 63,
];
pub(crate) static TRANS_IDX_MPS: [u8; 64] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 62, 63,
];
#[inline]
fn clip3_i32(lo: i32, hi: i32, v: i32) -> i32 {
if v < lo {
lo
} else if v > hi {
hi
} else {
v
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CtxState {
pub state_idx: u8,
pub val_mps: u8,
}
impl CtxState {
pub fn init(m: i32, n: i32, slice_qp_y: i32) -> CabacResult<Self> {
if !(-36..=51).contains(&slice_qp_y) {
return Err(CabacError::InvalidSliceQp(slice_qp_y));
}
let clipped_qp = clip3_i32(0, 51, slice_qp_y);
let pre = clip3_i32(1, 126, ((m * clipped_qp) >> 4) + n);
let (state_idx, val_mps) = if pre <= 63 {
((63 - pre) as u8, 0u8)
} else {
((pre - 64) as u8, 1u8)
};
Ok(Self { state_idx, val_mps })
}
}
pub fn validate_init_idc(init_idc: Option<u32>) -> CabacResult<Option<u32>> {
match init_idc {
None => Ok(None),
Some(v) if v <= 2 => Ok(Some(v)),
Some(v) => Err(CabacError::InvalidInitIdc(v)),
}
}
pub struct CabacDecoder<'a> {
cod_i_range: u32,
cod_i_offset: u32,
reader: BitReader<'a>,
bin_count: u64,
}
impl<'a> core::fmt::Debug for CabacDecoder<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CabacDecoder")
.field("cod_i_range", &self.cod_i_range)
.field("cod_i_offset", &self.cod_i_offset)
.field("reader_position", &self.reader.position())
.finish()
}
}
impl<'a> CabacDecoder<'a> {
pub fn new(mut reader: BitReader<'a>) -> CabacResult<Self> {
let cod_i_offset = reader.u(9)?;
Ok(Self {
cod_i_range: 510,
cod_i_offset,
reader,
bin_count: 0,
})
}
pub fn bin_count(&self) -> u64 {
self.bin_count
}
pub fn position(&self) -> (usize, u8) {
self.reader.position()
}
#[cfg(test)]
fn cod_i_range(&self) -> u32 {
self.cod_i_range
}
#[cfg(test)]
fn cod_i_offset(&self) -> u32 {
self.cod_i_offset
}
pub fn debug_range(&self) -> u32 {
self.cod_i_range
}
pub fn debug_offset(&self) -> u32 {
self.cod_i_offset
}
pub fn decode_decision(&mut self, ctx: &mut CtxState) -> CabacResult<u8> {
let trace_on = bin_trace_enabled();
let pre_state = ctx.state_idx;
let pre_mps = ctx.val_mps;
let pre_range = self.cod_i_range;
let pre_offset = self.cod_i_offset;
let q_idx = ((self.cod_i_range >> 6) & 0b11) as usize;
let cod_i_range_lps = RANGE_TAB_LPS[ctx.state_idx as usize][q_idx] as u32;
self.cod_i_range -= cod_i_range_lps;
let bin_val: u8;
if self.cod_i_offset >= self.cod_i_range {
bin_val = 1 - ctx.val_mps;
self.cod_i_offset -= self.cod_i_range;
self.cod_i_range = cod_i_range_lps;
if ctx.state_idx == 0 {
ctx.val_mps = 1 - ctx.val_mps;
}
ctx.state_idx = TRANS_IDX_LPS[ctx.state_idx as usize];
} else {
bin_val = ctx.val_mps;
ctx.state_idx = TRANS_IDX_MPS[ctx.state_idx as usize];
}
self.renorm_d()?;
self.bin_count = self.bin_count.wrapping_add(1);
if trace_on {
eprintln!(
"[BIN {:>8}] DD pre_state={:>2} pre_mps={} pre_range={:>4} pre_offset={:>4} bin={} post_state={:>2} post_mps={} post_range={:>4} post_offset={:>4}",
self.bin_count,
pre_state, pre_mps, pre_range, pre_offset,
bin_val,
ctx.state_idx, ctx.val_mps, self.cod_i_range, self.cod_i_offset,
);
}
Ok(bin_val)
}
pub fn decode_bypass(&mut self) -> CabacResult<u8> {
let trace_on = bin_trace_enabled();
let pre_range = self.cod_i_range;
let pre_offset = self.cod_i_offset;
self.cod_i_offset = (self.cod_i_offset << 1) | self.reader.u(1)?;
let bin = if self.cod_i_offset >= self.cod_i_range {
self.cod_i_offset -= self.cod_i_range;
1
} else {
0
};
self.bin_count = self.bin_count.wrapping_add(1);
if trace_on {
eprintln!(
"[BIN {:>8}] BP pre_range={:>4} pre_offset={:>4} bin={} post_range={:>4} post_offset={:>4}",
self.bin_count,
pre_range, pre_offset,
bin,
self.cod_i_range, self.cod_i_offset,
);
}
Ok(bin)
}
pub fn decode_terminate(&mut self) -> CabacResult<u8> {
let trace_on = bin_trace_enabled();
let pre_range = self.cod_i_range;
let pre_offset = self.cod_i_offset;
self.cod_i_range -= 2;
let bin = if self.cod_i_offset >= self.cod_i_range {
1
} else {
self.renorm_d()?;
0
};
self.bin_count = self.bin_count.wrapping_add(1);
if trace_on {
eprintln!(
"[BIN {:>8}] TT pre_range={:>4} pre_offset={:>4} bin={} post_range={:>4} post_offset={:>4}",
self.bin_count,
pre_range, pre_offset,
bin,
self.cod_i_range, self.cod_i_offset,
);
}
Ok(bin)
}
fn renorm_d(&mut self) -> CabacResult<()> {
while self.cod_i_range < 256 {
self.cod_i_range <<= 1;
self.cod_i_offset = (self.cod_i_offset << 1) | self.reader.u(1)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ctx_init_table_9_12_ctx0_mid_qp() {
let c = CtxState::init(20, -15, 26).unwrap();
assert_eq!(
c,
CtxState {
state_idx: 46,
val_mps: 0
}
);
}
#[test]
fn ctx_init_table_9_12_ctx1_mid_qp() {
let c = CtxState::init(2, 54, 26).unwrap();
assert_eq!(
c,
CtxState {
state_idx: 6,
val_mps: 0
}
);
}
#[test]
fn ctx_init_negative_m_picks_mps_one() {
let c = CtxState::init(-28, 127, 26).unwrap();
assert_eq!(
c,
CtxState {
state_idx: 17,
val_mps: 1
}
);
}
#[test]
fn ctx_init_low_qp_clips_to_one() {
let c = CtxState::init(20, -15, 0).unwrap();
assert_eq!(
c,
CtxState {
state_idx: 62,
val_mps: 0
}
);
}
#[test]
fn ctx_init_high_qp() {
let c = CtxState::init(20, -15, 51).unwrap();
assert_eq!(
c,
CtxState {
state_idx: 15,
val_mps: 0
}
);
}
#[test]
fn ctx_init_rejects_out_of_range_qp() {
assert_eq!(
CtxState::init(20, -15, -37).unwrap_err(),
CabacError::InvalidSliceQp(-37),
);
assert_eq!(
CtxState::init(20, -15, 52).unwrap_err(),
CabacError::InvalidSliceQp(52),
);
}
#[test]
fn ctx_init_accepts_negative_slice_qp_at_high_bitdepth() {
let c = CtxState::init(20, -15, -12).expect("negative SliceQPY at high bit depth");
assert_eq!(
c,
CtxState {
state_idx: 62,
val_mps: 0,
}
);
}
#[test]
fn validate_init_idc_accepts_absent_and_0_to_2() {
assert_eq!(validate_init_idc(None).unwrap(), None);
for v in 0u32..=2 {
assert_eq!(validate_init_idc(Some(v)).unwrap(), Some(v));
}
assert_eq!(
validate_init_idc(Some(3)).unwrap_err(),
CabacError::InvalidInitIdc(3),
);
}
#[test]
fn engine_new_reads_9_bit_offset() {
let data = [0b1010_1010u8, 0b1000_0000];
let r = BitReader::new(&data);
let dec = CabacDecoder::new(r).unwrap();
assert_eq!(dec.cod_i_range(), 510);
assert_eq!(dec.cod_i_offset(), 0b1_0101_0101);
}
#[test]
fn engine_new_all_zeros_offset() {
let data = [0x00, 0x00];
let dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
assert_eq!(dec.cod_i_range(), 510);
assert_eq!(dec.cod_i_offset(), 0);
}
#[test]
fn decode_decision_mps_path_no_renorm() {
let data = [0x00, 0x00];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
let mut ctx = CtxState {
state_idx: 0,
val_mps: 0,
};
let bin = dec.decode_decision(&mut ctx).unwrap();
assert_eq!(bin, 0);
assert_eq!(
ctx,
CtxState {
state_idx: 1,
val_mps: 0
}
);
assert_eq!(dec.cod_i_range(), 270);
assert_eq!(dec.cod_i_offset(), 0);
}
#[test]
fn decode_decision_mps_path_with_renorm() {
let data = [0x00, 0x00];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
dec.cod_i_range = 270;
dec.cod_i_offset = 0;
let mut ctx = CtxState {
state_idx: 1,
val_mps: 0,
};
let bin = dec.decode_decision(&mut ctx).unwrap();
assert_eq!(bin, 0);
assert_eq!(
ctx,
CtxState {
state_idx: 2,
val_mps: 0
}
);
assert_eq!(dec.cod_i_range(), 284);
assert_eq!(dec.cod_i_offset(), 0);
}
#[test]
fn decode_decision_lps_path_flips_mps_at_state_zero() {
let data = [0b1111_1110u8, 0b1000_0000];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
assert_eq!(dec.cod_i_offset(), 509);
let mut ctx = CtxState {
state_idx: 0,
val_mps: 0,
};
let bin = dec.decode_decision(&mut ctx).unwrap();
assert_eq!(bin, 1);
assert_eq!(
ctx,
CtxState {
state_idx: 0,
val_mps: 1
}
);
assert_eq!(dec.cod_i_range(), 480);
assert_eq!(dec.cod_i_offset(), 478);
}
#[test]
fn decode_bypass_sequence_of_ones() {
let data = [0b1111_1110u8, 0b1010_1010];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
assert_eq!(dec.cod_i_offset(), 509);
assert_eq!(dec.decode_bypass().unwrap(), 1);
assert_eq!(dec.cod_i_offset(), 508);
assert_eq!(dec.decode_bypass().unwrap(), 1);
assert_eq!(dec.cod_i_offset(), 507);
assert_eq!(dec.decode_bypass().unwrap(), 1);
assert_eq!(dec.cod_i_offset(), 504);
assert_eq!(dec.decode_bypass().unwrap(), 1);
assert_eq!(dec.cod_i_offset(), 499);
assert_eq!(dec.cod_i_range(), 510);
}
#[test]
fn decode_bypass_zero_branch() {
let data = [0x00, 0b0100_0000];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
assert_eq!(dec.cod_i_offset(), 0);
assert_eq!(dec.decode_bypass().unwrap(), 0);
assert_eq!(dec.cod_i_offset(), 1);
}
#[test]
fn decode_terminate_not_yet() {
let data = [0x00, 0x00];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
assert_eq!(dec.decode_terminate().unwrap(), 0);
assert_eq!(dec.cod_i_range(), 508);
assert_eq!(dec.cod_i_offset(), 0);
}
#[test]
fn decode_terminate_fires() {
let data = [0b1111_1110u8, 0b1000_0000];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
assert_eq!(dec.cod_i_offset(), 509);
assert_eq!(dec.decode_terminate().unwrap(), 1);
assert_eq!(dec.cod_i_range(), 508);
assert_eq!(dec.cod_i_offset(), 509);
}
#[test]
fn range_tab_lps_sentinel_cells() {
assert_eq!(RANGE_TAB_LPS[0], [128, 176, 208, 240]);
assert_eq!(RANGE_TAB_LPS[63], [2, 2, 2, 2]);
assert_eq!(RANGE_TAB_LPS[15], [66, 80, 95, 110]);
assert_eq!(RANGE_TAB_LPS[32], [27, 33, 39, 45]);
assert_eq!(RANGE_TAB_LPS[47], [12, 15, 18, 21]);
}
#[test]
fn trans_idx_tables_sentinel_cells() {
assert_eq!(TRANS_IDX_LPS[0], 0);
assert_eq!(TRANS_IDX_MPS[0], 1);
assert_eq!(TRANS_IDX_LPS[24], 19);
assert_eq!(TRANS_IDX_MPS[24], 25);
assert_eq!(TRANS_IDX_LPS[63], 63);
assert_eq!(TRANS_IDX_MPS[63], 63);
assert_eq!(TRANS_IDX_MPS[62], 62);
assert_eq!(TRANS_IDX_LPS[62], 38);
}
#[test]
fn trans_idx_tables_are_monotonic_nondecreasing() {
for i in 1..64 {
assert!(TRANS_IDX_LPS[i] >= TRANS_IDX_LPS[i - 1]);
assert!(TRANS_IDX_MPS[i] >= TRANS_IDX_MPS[i - 1]);
}
}
#[test]
fn decode_decision_bit_accounting_mps_then_lps() {
let data = [0b1111_1110u8, 0b1000_0000, 0b0000_0000];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
assert_eq!(dec.cod_i_offset(), 509);
let mut ctx1 = CtxState {
state_idx: 0,
val_mps: 0,
};
assert_eq!(dec.decode_decision(&mut ctx1).unwrap(), 1);
assert_eq!(dec.cod_i_range(), 480);
assert_eq!(dec.cod_i_offset(), 478);
assert_eq!(
ctx1,
CtxState {
state_idx: 0,
val_mps: 1
}
);
assert_eq!(dec.bin_count(), 1);
let mut ctx2 = CtxState {
state_idx: 5,
val_mps: 1,
};
assert_eq!(dec.decode_decision(&mut ctx2).unwrap(), 0);
assert_eq!(dec.cod_i_range(), 370);
assert_eq!(dec.cod_i_offset(), 366);
assert_eq!(
ctx2,
CtxState {
state_idx: TRANS_IDX_LPS[5],
val_mps: 1
}
);
assert_eq!(dec.bin_count(), 2);
}
#[test]
fn decode_bypass_ten_bit_register_width() {
let data = [0b1111_1110u8, 0b1111_1111u8];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
for _ in 0..4 {
assert_eq!(dec.decode_bypass().unwrap(), 1);
assert_eq!(dec.cod_i_offset(), 509);
}
assert_eq!(dec.cod_i_range(), 510);
assert_eq!(dec.bin_count(), 4);
}
#[test]
fn decode_terminate_zero_then_decision_continues() {
let data = [0x00u8, 0x00, 0x00];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
assert_eq!(dec.decode_terminate().unwrap(), 0);
assert_eq!(dec.cod_i_range(), 508);
let mut ctx = CtxState {
state_idx: 0,
val_mps: 0,
};
assert_eq!(dec.decode_decision(&mut ctx).unwrap(), 0);
assert_eq!(dec.cod_i_range(), 268);
assert_eq!(dec.cod_i_offset(), 0);
}
#[test]
fn range_tab_lps_row_31_exact() {
assert_eq!(RANGE_TAB_LPS[31], [29, 35, 41, 48]);
}
#[test]
fn trans_idx_tables_direction_invariant() {
for i in 0..64 {
assert!(
TRANS_IDX_LPS[i] as usize <= i,
"transIdxLPS[{}] = {} must be <= {}",
i,
TRANS_IDX_LPS[i],
i,
);
assert!(
TRANS_IDX_MPS[i] as usize >= i,
"transIdxMPS[{}] = {} must be >= {}",
i,
TRANS_IDX_MPS[i],
i,
);
}
}
}