use rusty_h264_common::cabac_tables::{CTX_INIT, RANGE_LPS, STATE_TRANS};
fn init_ctx(qp: i32, init_idc: u32, is_i: bool) -> [(u8, u8); 460] {
let model = if is_i { 0 } else { ((init_idc + 1) as usize).min(3) };
let q = qp.clamp(0, 51);
let mut ctx = [(0u8, 0u8); 460];
for (i, slot) 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);
*slot = if pre <= 63 { ((63 - pre) as u8, 0) } else { ((pre - 64) as u8, 1) };
}
ctx
}
pub struct CabacEncoder {
low: u32,
range: u32,
outstanding: u32,
first: bool,
acc: u32,
nacc: u32,
out: Vec<u8>,
ctx: [(u8, u8); 460],
pub bins: u64,
}
impl CabacEncoder {
pub fn new(qp: i32, init_idc: u32, is_i: bool) -> Self {
CabacEncoder {
low: 0,
range: 510,
outstanding: 0,
first: true,
acc: 0,
nacc: 0,
out: Vec::with_capacity(4096),
ctx: init_ctx(qp, init_idc, is_i),
bins: 0,
}
}
#[inline]
fn push_packed(&mut self, b: u32) {
self.acc = (self.acc << 1) | b;
self.nacc += 1;
if self.nacc == 8 {
self.out.push(self.acc as u8);
self.acc = 0;
self.nacc = 0;
}
}
fn put_bit(&mut self, b: u32) {
if self.first {
self.first = false;
} else {
self.push_packed(b);
}
let inv = 1 - b;
while self.outstanding > 0 {
self.push_packed(inv);
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;
}
}
pub fn encode_decision(&mut self, ctx_idx: usize, bin: u32) {
let slot = &mut self.ctx[ctx_idx];
let (state, mps) = *slot;
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 };
*slot = (STATE_TRANS[state as usize][0], nm);
} else {
slot.0 = STATE_TRANS[state as usize][1];
}
self.renorm();
self.bins += 1;
}
pub 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;
}
self.bins += 1;
}
#[allow(dead_code)]
pub fn encode_bypass_bits(&mut self, val: u32, n: u32) {
for i in (0..n).rev() {
self.encode_bypass((val >> i) & 1);
}
}
pub fn encode_terminate(&mut self, end: bool) {
self.range -= 2;
if !end {
self.renorm();
} else {
self.low += self.range;
self.range = 2;
self.renorm();
self.put_bit((self.low >> 9) & 1);
let v = ((self.low >> 7) & 3) | 1;
self.push_packed((v >> 1) & 1);
self.push_packed(v & 1);
}
self.bins += 1;
}
#[inline]
pub fn pos(&self) -> u64 {
(self.out.len() as u64) * 8 + self.nacc as u64 + self.outstanding as u64
}
pub fn into_bytes(mut self) -> Vec<u8> {
if self.nacc > 0 {
self.out.push((self.acc << (8 - self.nacc)) as u8);
}
self.out
}
}
#[cfg(test)]
mod tests {
use super::*;
use rusty_h264_decoder::cabac_test::Cabac;
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 = CabacEncoder::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_decision(ctx, bin);
} else {
enc.encode_bypass(bin);
}
}
enc.encode_terminate(true);
let bytes = enc.into_bytes();
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);
}
}
}
}
}