use super::tables::{TRANS_IDX_LPS, TRANS_IDX_MPS};
#[derive(Debug, Clone, Copy)]
pub struct CabacContext {
p_state_idx: u8,
val_mps: u8,
}
impl CabacContext {
pub fn new(p_state_idx: u8, val_mps: u8) -> Self {
debug_assert!(p_state_idx <= 63);
debug_assert!(val_mps <= 1);
Self {
p_state_idx,
val_mps,
}
}
pub const fn non_adapting_276() -> Self {
Self {
p_state_idx: 63,
val_mps: 0,
}
}
pub fn p_state_idx(&self) -> u8 {
self.p_state_idx
}
pub fn val_mps(&self) -> u8 {
self.val_mps
}
#[inline]
pub fn update_mps(&mut self) {
self.p_state_idx = TRANS_IDX_MPS[self.p_state_idx as usize];
}
#[inline]
pub fn update_lps(&mut self) {
if self.p_state_idx == 0 {
self.val_mps ^= 1;
}
self.p_state_idx = TRANS_IDX_LPS[self.p_state_idx as usize];
}
}
impl Default for CabacContext {
fn default() -> Self {
Self::new(0, 0)
}
}
#[inline]
pub fn compute_initial_state(m: i32, n: i32, slice_qp_y: i32) -> CabacContext {
let qp_clipped = slice_qp_y.clamp(0, 51);
let pre_ctx_state_raw = ((m * qp_clipped) >> 4) + n;
let pre_ctx_state = pre_ctx_state_raw.clamp(1, 126);
if pre_ctx_state <= 63 {
CabacContext::new((63 - pre_ctx_state) as u8, 0)
} else {
CabacContext::new((pre_ctx_state - 64) as u8, 1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CabacInitSlot {
ISI,
PIdc0,
PIdc1,
PIdc2,
}
impl CabacInitSlot {
pub fn as_index(self) -> usize {
match self {
CabacInitSlot::ISI => 0,
CabacInitSlot::PIdc0 => 1,
CabacInitSlot::PIdc1 => 2,
CabacInitSlot::PIdc2 => 3,
}
}
}
pub fn initialize_contexts(
slot: CabacInitSlot,
slice_qp_y: i32,
) -> [CabacContext; 1024] {
use super::tables::CTX_INIT_MN;
let slot_idx = slot.as_index();
let mut contexts = [CabacContext::default(); 1024];
for ctx_idx in 0..1024 {
if ctx_idx == 276 {
contexts[ctx_idx] = CabacContext::non_adapting_276();
} else {
let (m, n) = CTX_INIT_MN[ctx_idx][slot_idx];
contexts[ctx_idx] = compute_initial_state(m as i32, n as i32, slice_qp_y);
}
}
contexts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn context_new_rejects_out_of_range() {
let _ = CabacContext::new(63, 1);
}
#[test]
fn update_mps_increments_state_except_saturation() {
let mut ctx = CabacContext::new(10, 0);
ctx.update_mps();
assert_eq!(ctx.p_state_idx(), 11);
assert_eq!(ctx.val_mps(), 0);
}
#[test]
fn update_mps_saturates_at_62() {
let mut ctx = CabacContext::new(62, 0);
ctx.update_mps();
assert_eq!(ctx.p_state_idx(), 62); }
#[test]
fn update_lps_from_state_0_flips_val_mps() {
let mut ctx = CabacContext::new(0, 0);
ctx.update_lps();
assert_eq!(ctx.val_mps(), 1); assert_eq!(ctx.p_state_idx(), 0); }
#[test]
fn update_lps_from_higher_state_does_not_flip_val_mps() {
let mut ctx = CabacContext::new(5, 1);
ctx.update_lps();
assert_eq!(ctx.val_mps(), 1); assert_eq!(ctx.p_state_idx(), TRANS_IDX_LPS[5]);
}
#[test]
fn non_adapting_276_is_max_p_state_val_mps_zero() {
let ctx = CabacContext::non_adapting_276();
assert_eq!(ctx.p_state_idx(), 63);
assert_eq!(ctx.val_mps(), 0);
}
#[test]
fn compute_initial_state_clips_pre_ctx_state() {
let ctx = compute_initial_state(20, -15, 26);
assert_eq!(ctx.p_state_idx(), 46);
assert_eq!(ctx.val_mps(), 0);
}
#[test]
fn compute_initial_state_val_mps_flip_above_63() {
let ctx = compute_initial_state(0, 80, 26);
assert_eq!(ctx.p_state_idx(), 16);
assert_eq!(ctx.val_mps(), 1);
}
#[test]
fn compute_initial_state_clamps_below_1() {
let ctx = compute_initial_state(-99, -99, 51);
assert_eq!(ctx.p_state_idx(), 62);
assert_eq!(ctx.val_mps(), 0);
}
#[test]
fn compute_initial_state_clamps_above_126() {
let ctx = compute_initial_state(127, 127, 51);
assert_eq!(ctx.p_state_idx(), 62);
assert_eq!(ctx.val_mps(), 1);
}
#[test]
fn compute_initial_state_clamps_slice_qp() {
let a = compute_initial_state(20, -15, -5);
let b = compute_initial_state(20, -15, 0);
assert_eq!(a.p_state_idx(), b.p_state_idx());
assert_eq!(a.val_mps(), b.val_mps());
}
#[test]
fn cabac_init_slot_indices() {
assert_eq!(CabacInitSlot::ISI.as_index(), 0);
assert_eq!(CabacInitSlot::PIdc0.as_index(), 1);
assert_eq!(CabacInitSlot::PIdc1.as_index(), 2);
assert_eq!(CabacInitSlot::PIdc2.as_index(), 3);
}
#[test]
fn initialize_contexts_i_slice_at_qp_26() {
let ctxs = initialize_contexts(CabacInitSlot::ISI, 26);
assert_eq!(ctxs[0].p_state_idx(), 46);
assert_eq!(ctxs[0].val_mps(), 0);
assert_eq!(ctxs[276].p_state_idx(), 63);
assert_eq!(ctxs[276].val_mps(), 0);
}
#[test]
fn initialize_contexts_276_always_non_adapting() {
for slot in [
CabacInitSlot::ISI,
CabacInitSlot::PIdc0,
CabacInitSlot::PIdc1,
CabacInitSlot::PIdc2,
] {
for qp in [0, 26, 51] {
let ctxs = initialize_contexts(slot, qp);
assert_eq!(ctxs[276].p_state_idx(), 63);
assert_eq!(ctxs[276].val_mps(), 0);
}
}
}
#[test]
fn initialize_contexts_differs_by_cabac_init_idc() {
let c0 = initialize_contexts(CabacInitSlot::PIdc0, 26);
let c2 = initialize_contexts(CabacInitSlot::PIdc2, 26);
assert_ne!(
(c0[11].p_state_idx(), c0[11].val_mps()),
(c2[11].p_state_idx(), c2[11].val_mps())
);
}
}