use crate::cabac_tables::{CTX_INIT, RANGE_LPS, STATE_TRANS};
#[derive(Clone, Copy)]
struct Ctx {
state: u8,
mps: u8,
}
pub struct Cabac<'a> {
data: &'a [u8],
bit_pos: usize,
range: u32,
offset: u32,
ctx: [Ctx; 460],
}
impl<'a> Cabac<'a> {
pub fn new(data: &'a [u8], start_byte: usize, qp: i32, init_idc: u32, is_i: bool) -> Self {
let model = if is_i { 0 } else { (init_idc + 1) as usize };
let q = qp.clamp(0, 51);
let mut ctx = [Ctx { state: 0, mps: 0 }; 460];
for (i, c) in ctx.iter_mut().enumerate() {
let (m, n) = CTX_INIT[i][model];
let pre = (((m as i32 * q) >> 4) + n as i32).clamp(1, 126);
*c = if pre <= 63 {
Ctx { state: (63 - pre) as u8, mps: 0 }
} else {
Ctx { state: (pre - 64) as u8, mps: 1 }
};
}
let mut e = Cabac { data, bit_pos: start_byte * 8, range: 510, offset: 0, ctx };
e.offset = e.read_bits(9);
e
}
#[inline]
fn read_bit(&mut self) -> u32 {
let byte = self.bit_pos / 8;
if byte >= self.data.len() {
self.bit_pos += 1;
return 0;
}
let bit = (self.data[byte] >> (7 - (self.bit_pos % 8))) & 1;
self.bit_pos += 1;
bit as u32
}
#[inline]
fn read_bits(&mut self, n: u32) -> u32 {
let mut v = 0;
for _ in 0..n {
v = (v << 1) | self.read_bit();
}
v
}
#[inline]
fn renorm(&mut self) {
while self.range < 256 {
self.range <<= 1;
self.offset = (self.offset << 1) | self.read_bit();
}
}
pub fn decode_decision(&mut self, ctx_idx: usize) -> u32 {
let state = self.ctx[ctx_idx].state;
let mps = self.ctx[ctx_idx].mps;
let q = ((self.range >> 6) & 3) as usize;
let lps = RANGE_LPS[state as usize][q] as u32;
self.range -= lps;
let bin;
let (new_state, new_mps);
if self.offset >= self.range {
bin = 1 - mps;
self.offset -= self.range;
self.range = lps;
new_mps = if state == 0 { 1 - mps } else { mps };
new_state = STATE_TRANS[state as usize][0];
} else {
bin = mps;
new_mps = mps;
new_state = STATE_TRANS[state as usize][1];
}
self.ctx[ctx_idx].state = new_state;
self.ctx[ctx_idx].mps = new_mps;
self.renorm();
bin as u32
}
pub fn decode_bypass(&mut self) -> u32 {
self.offset = (self.offset << 1) | self.read_bit();
if self.offset >= self.range {
self.offset -= self.range;
1
} else {
0
}
}
#[allow(dead_code)] pub fn decode_bypass_bits(&mut self, n: u32) -> u32 {
let mut v = 0;
for _ in 0..n {
v = (v << 1) | self.decode_bypass();
}
v
}
pub fn decode_terminate(&mut self) -> bool {
self.range -= 2;
if self.offset >= self.range {
true
} else {
self.renorm();
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Enc {
low: u32,
range: u32,
outstanding: u32,
first: bool,
bits: Vec<u8>,
ctx: Vec<(u8, u8)>, }
fn init_ctx(qp: i32, init_idc: u32, is_i: bool) -> Vec<(u8, u8)> {
let model = if is_i { 0 } else { (init_idc + 1) as usize };
let q = qp.clamp(0, 51);
(0..460)
.map(|i| {
let (m, n) = CTX_INIT[i][model];
let pre = (((m as i32 * q) >> 4) + n as i32).clamp(1, 126);
if pre <= 63 {
((63 - pre) as u8, 0)
} else {
((pre - 64) as u8, 1)
}
})
.collect()
}
impl Enc {
fn new(qp: i32, init_idc: u32, is_i: bool) -> Self {
Enc {
low: 0,
range: 510,
outstanding: 0,
first: true,
bits: Vec::new(),
ctx: init_ctx(qp, init_idc, is_i),
}
}
fn put_bit(&mut self, b: u32) {
if self.first {
self.first = false;
} else {
self.bits.push(b as u8);
}
while self.outstanding > 0 {
self.bits.push((1 - b) as u8);
self.outstanding -= 1;
}
}
fn renorm(&mut self) {
while self.range < 256 {
if self.low < 256 {
self.put_bit(0);
} else if self.low >= 512 {
self.low -= 512;
self.put_bit(1);
} else {
self.low -= 256;
self.outstanding += 1;
}
self.range <<= 1;
self.low <<= 1;
}
}
fn encode(&mut self, ctx_idx: usize, bin: u32) {
let (state, mps) = self.ctx[ctx_idx];
let q = ((self.range >> 6) & 3) as usize;
let lps = RANGE_LPS[state as usize][q] as u32;
self.range -= lps;
if bin != mps as u32 {
self.low += self.range;
self.range = lps;
let nm = if state == 0 { 1 - mps } else { mps };
self.ctx[ctx_idx] = (STATE_TRANS[state as usize][0], nm);
} else {
self.ctx[ctx_idx].0 = STATE_TRANS[state as usize][1];
}
self.renorm();
}
fn encode_bypass(&mut self, bin: u32) {
self.low <<= 1;
if bin != 0 {
self.low += self.range;
}
if self.low >= 1024 {
self.put_bit(1);
self.low -= 1024;
} else if self.low < 512 {
self.put_bit(0);
} else {
self.low -= 512;
self.outstanding += 1;
}
}
fn finish(&mut self) -> Vec<u8> {
self.range -= 2;
self.low += self.range;
self.range = 2;
self.renorm();
self.put_bit((self.low >> 9) & 1);
let v = ((self.low >> 7) & 3) | 1;
self.bits.push(((v >> 1) & 1) as u8);
self.bits.push((v & 1) as u8);
let mut out = vec![0u8; self.bits.len().div_ceil(8)];
for (i, &b) in self.bits.iter().enumerate() {
out[i / 8] |= b << (7 - (i % 8));
}
out
}
}
struct Rng(u32);
impl Rng {
fn next(&mut self) -> u32 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 17;
self.0 ^= self.0 << 5;
self.0
}
}
fn roundtrip(qp: i32, init_idc: u32, is_i: bool, seed: u32, n: usize) {
let mut rng = Rng(seed);
let mut script: Vec<(u8, usize, u32)> = Vec::with_capacity(n);
let mut enc = Enc::new(qp, init_idc, is_i);
for _ in 0..n {
let r = rng.next();
let kind = (r & 1) as u8;
let ctx = (r >> 1) as usize % 460;
let bin = (r >> 12) & 1;
script.push((kind, ctx, bin));
if kind == 0 {
enc.encode(ctx, bin);
} else {
enc.encode_bypass(bin);
}
}
let bytes = enc.finish();
let mut dec = Cabac::new(&bytes, 0, qp, init_idc, is_i);
for (i, &(kind, ctx, bin)) in script.iter().enumerate() {
let got = if kind == 0 {
dec.decode_decision(ctx)
} else {
dec.decode_bypass()
};
assert_eq!(got, bin, "bin {i} (kind {kind}, ctx {ctx}) mismatched");
}
assert!(dec.decode_terminate(), "terminate should signal end-of-stream");
}
#[test]
fn engine_roundtrip_many() {
for &qp in &[0, 12, 26, 37, 51] {
for &(idc, is_i) in &[(0u32, true), (0, false), (1, false), (2, false)] {
for seed in 1..=40u32 {
roundtrip(qp, idc, is_i, seed.wrapping_mul(2654435761), seed as usize * 53);
}
}
}
}
#[test]
fn engine_init_matches_spec() {
let dec = Cabac::new(&[0xFF, 0xFF, 0xFF], 0, 26, 0, true);
assert_eq!(dec.ctx[0].state, 46);
assert_eq!(dec.ctx[0].mps, 0);
assert_eq!(dec.range, 510);
assert_eq!(dec.offset, 0x1FF);
}
#[test]
fn tables_match_spec_boundaries() {
assert_eq!(RANGE_LPS[0], [128, 176, 208, 240]);
assert_eq!(RANGE_LPS[63], [2, 2, 2, 2]);
assert_eq!(STATE_TRANS[0], [0, 1]);
assert_eq!(STATE_TRANS[63], [63, 63]);
assert_eq!(CTX_INIT[0][0], (20, -15));
}
}