#![allow(clippy::field_reassign_with_default)]
use super::context::CabacInitSlot;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MbTypeClass {
INxN,
I16x16,
IPCM,
SI,
PInter,
PSkip,
BSkipOrDirect,
BInter,
}
impl MbTypeClass {
pub fn is_intra(self) -> bool {
matches!(
self,
MbTypeClass::INxN | MbTypeClass::I16x16 | MbTypeClass::IPCM | MbTypeClass::SI
)
}
pub fn is_inter(self) -> bool {
matches!(
self,
MbTypeClass::PInter
| MbTypeClass::PSkip
| MbTypeClass::BSkipOrDirect
| MbTypeClass::BInter
)
}
pub fn is_skip(self) -> bool {
matches!(self, MbTypeClass::PSkip | MbTypeClass::BSkipOrDirect)
}
}
#[derive(Debug, Clone, Copy)]
pub struct CabacNeighborMB {
pub mb_type: MbTypeClass,
pub mb_skip_flag: bool,
pub intra_chroma_pred_mode: u8, pub cbp_luma: u8, pub cbp_chroma: u8, pub mb_qp_delta: i32, pub coded_block_flag_cat: [u16; 5],
pub abs_mvd_comp: [[i16; 16]; 2],
pub abs_mvd_comp_l1: [[i16; 16]; 2],
pub ref_idx_l0: [i8; 16],
pub transform_size_8x8_flag: bool,
}
impl Default for CabacNeighborMB {
fn default() -> Self {
Self {
mb_type: MbTypeClass::INxN,
mb_skip_flag: false,
intra_chroma_pred_mode: 0,
cbp_luma: 0,
cbp_chroma: 0,
mb_qp_delta: 0,
coded_block_flag_cat: [0; 5],
abs_mvd_comp: [[0; 16]; 2],
abs_mvd_comp_l1: [[0; 16]; 2],
ref_idx_l0: [0; 16],
transform_size_8x8_flag: false,
}
}
}
pub struct CabacNeighborContext {
mb_width: usize,
top_row: Vec<Option<CabacNeighborMB>>,
left: Option<CabacNeighborMB>,
prev_mb: Option<CabacNeighborMB>,
pub slice_slot: CabacInitSlot,
}
impl CabacNeighborContext {
pub fn new(mb_width: usize, slice_slot: CabacInitSlot) -> Self {
Self {
mb_width,
top_row: vec![None; mb_width],
left: None,
prev_mb: None,
slice_slot,
}
}
pub fn reset(&mut self) {
for slot in self.top_row.iter_mut() {
*slot = None;
}
self.left = None;
self.prev_mb = None;
}
pub fn neighbor_a(&self, _mb_x: usize) -> Option<&CabacNeighborMB> {
self.left.as_ref()
}
pub fn neighbor_b(&self, mb_x: usize) -> Option<&CabacNeighborMB> {
self.top_row.get(mb_x)?.as_ref()
}
pub fn prev_mb(&self) -> Option<&CabacNeighborMB> {
self.prev_mb.as_ref()
}
pub fn commit(&mut self, mb_x: usize, mb: CabacNeighborMB) {
if mb_x < self.top_row.len() {
self.top_row[mb_x] = Some(mb);
}
self.left = Some(mb);
self.prev_mb = Some(mb);
}
pub fn new_row(&mut self) {
self.left = None;
}
}
pub fn ctx_idx_inc_mb_skip_flag(ctx: &CabacNeighborContext, mb_x: usize) -> u32 {
let cond = |n: Option<&CabacNeighborMB>| -> u32 {
match n {
None => 0,
Some(mb) if mb.mb_skip_flag => 0,
Some(_) => 1,
}
};
cond(ctx.neighbor_a(mb_x)) + cond(ctx.neighbor_b(mb_x))
}
pub fn ctx_idx_inc_mb_type_bin0(
ctx: &CabacNeighborContext,
mb_x: usize,
ctx_idx_offset: u32,
) -> u32 {
if ctx_idx_offset == 14 {
return 0;
}
let cond = |n: Option<&CabacNeighborMB>| -> u32 {
let mb = match n {
None => return 0,
Some(mb) => mb,
};
match ctx_idx_offset {
0 => {
if mb.mb_type == MbTypeClass::SI { 0 } else { 1 }
}
3 => {
if mb.mb_type == MbTypeClass::INxN { 0 } else { 1 }
}
27 => {
if matches!(mb.mb_type, MbTypeClass::BSkipOrDirect) {
0
} else {
1
}
}
_ => 0,
}
};
cond(ctx.neighbor_a(mb_x)) + cond(ctx.neighbor_b(mb_x))
}
pub fn ctx_idx_inc_prior_bin(ctx_idx_offset: u32, bin_idx: u32, prior_bins: &[u8]) -> Option<u32> {
match (ctx_idx_offset, bin_idx) {
(3, 4) => Some(if prior_bins[3] != 0 { 5 } else { 6 }),
(3, 5) => Some(if prior_bins[3] != 0 { 6 } else { 7 }),
(14, 2) => Some(if prior_bins[1] != 1 { 2 } else { 3 }),
(17, 4) => Some(if prior_bins[3] != 0 { 2 } else { 3 }),
(27, 2) => Some(if prior_bins[1] != 0 { 4 } else { 5 }),
(32, 4) => Some(if prior_bins[3] != 0 { 2 } else { 3 }),
(36, 2) => Some(if prior_bins[1] != 0 { 2 } else { 3 }),
_ => None,
}
}
pub fn ctx_idx_inc_mvd_bin0(
ctx: &CabacNeighborContext,
mb_x: usize,
block_idx_in_mb_a: usize,
block_idx_in_mb_b: usize,
component: u8,
) -> u32 {
ctx_idx_inc_mvd_bin0_per_list(
ctx, mb_x, block_idx_in_mb_a, block_idx_in_mb_b, component, 0,
)
}
pub fn ctx_idx_inc_mvd_bin0_per_list(
ctx: &CabacNeighborContext,
mb_x: usize,
block_idx_in_mb_a: usize,
block_idx_in_mb_b: usize,
component: u8,
list: u8,
) -> u32 {
let read = |n: &CabacNeighborMB, blk: usize| -> u32 {
let arr = match list {
1 => &n.abs_mvd_comp_l1,
_ => &n.abs_mvd_comp, };
arr[component as usize][blk].unsigned_abs() as u32
};
let abs_a = ctx.neighbor_a(mb_x).map_or(0, |n| read(n, block_idx_in_mb_a));
let abs_b = ctx.neighbor_b(mb_x).map_or(0, |n| read(n, block_idx_in_mb_b));
let sum = abs_a + abs_b;
if sum < 3 {
0
} else if sum > 32 {
2
} else {
1
}
}
pub fn ctx_idx_inc_ref_idx_bin0(
ctx: &CabacNeighborContext,
mb_x: usize,
block_idx_in_mb_a: usize,
block_idx_in_mb_b: usize,
) -> u32 {
let cond = |n: Option<&CabacNeighborMB>, blk: usize| -> u32 {
match n {
None => 0,
Some(mb) if mb.mb_type.is_skip() || mb.mb_type.is_intra() => 0,
Some(mb) if mb.ref_idx_l0[blk] == 0 => 0,
Some(_) => 1,
}
};
cond(ctx.neighbor_a(mb_x), block_idx_in_mb_a)
+ 2 * cond(ctx.neighbor_b(mb_x), block_idx_in_mb_b)
}
pub fn ctx_idx_inc_mb_qp_delta_bin0(ctx: &CabacNeighborContext) -> u32 {
let Some(prev) = ctx.prev_mb() else {
return 0;
};
if prev.mb_type == MbTypeClass::IPCM || prev.mb_type.is_skip() {
return 0;
}
if prev.mb_qp_delta == 0 {
0
} else {
1
}
}
pub fn ctx_idx_inc_intra_chroma_pred_mode_bin0(
ctx: &CabacNeighborContext,
mb_x: usize,
) -> u32 {
let cond = |n: Option<&CabacNeighborMB>| -> u32 {
match n {
None => 0,
Some(mb) if mb.mb_type.is_inter() || mb.mb_type == MbTypeClass::IPCM => 0,
Some(mb) if mb.intra_chroma_pred_mode == 0 => 0,
Some(_) => 1,
}
};
cond(ctx.neighbor_a(mb_x)) + cond(ctx.neighbor_b(mb_x))
}
pub fn ctx_idx_inc_cbp_luma(
ctx: &CabacNeighborContext,
mb_x: usize,
bin_idx: u32,
) -> u32 {
let cross_neighbor_bit = bin_idx;
let cond = |n: Option<&CabacNeighborMB>| cbp_luma_cross_cond(n, cross_neighbor_bit);
cond(ctx.neighbor_a(mb_x)) + 2 * cond(ctx.neighbor_b(mb_x))
}
#[inline]
fn cbp_luma_cross_cond(n: Option<&CabacNeighborMB>, neighbor_bit: u32) -> u32 {
match n {
None => 0,
Some(mb) if mb.mb_type == MbTypeClass::IPCM => 1,
Some(mb) => {
if mb.cbp_luma & (1u8 << neighbor_bit) != 0 {
0
} else {
1
}
}
}
}
pub fn compute_cbp_luma_ctx_idx_inc_bin(
bin_idx: u32,
current_partial_cbp_luma: u8,
neighbors: &CabacNeighborContext,
mb_x: usize,
) -> u32 {
let cur_bx = (bin_idx & 1) as u8;
let cur_by = ((bin_idx >> 1) & 1) as u8;
let cond_a = if cur_bx > 0 {
let neighbor_bin = cur_by * 2 + (cur_bx - 1);
if (current_partial_cbp_luma >> neighbor_bin) & 1 == 0 {
1
} else {
0
}
} else {
let neighbor_bin = (cur_by * 2 + 1) as u32;
cbp_luma_cross_cond(neighbors.neighbor_a(mb_x), neighbor_bin)
};
let cond_b = if cur_by > 0 {
let neighbor_bin = (cur_by - 1) * 2 + cur_bx;
if (current_partial_cbp_luma >> neighbor_bin) & 1 == 0 {
1
} else {
0
}
} else {
let neighbor_bin = (2 + cur_bx) as u32;
cbp_luma_cross_cond(neighbors.neighbor_b(mb_x), neighbor_bin)
};
cond_a + 2 * cond_b
}
pub fn ctx_idx_inc_cbp_chroma(
ctx: &CabacNeighborContext,
mb_x: usize,
bin_idx: u32,
) -> u32 {
let cond = |n: Option<&CabacNeighborMB>| -> u32 {
match n {
None => 0,
Some(mb) if mb.mb_type == MbTypeClass::IPCM => 1,
Some(mb) if mb.mb_type.is_skip() => 0,
Some(mb) => {
if bin_idx == 0 {
if mb.cbp_chroma != 0 { 1 } else { 0 }
} else if mb.cbp_chroma == 2 { 1 } else { 0 }
}
}
};
let base = cond(ctx.neighbor_a(mb_x)) + 2 * cond(ctx.neighbor_b(mb_x));
base + if bin_idx == 1 { 4 } else { 0 }
}
pub fn ctx_idx_inc_coded_block_flag(
ctx: &CabacNeighborContext,
mb_x: usize,
ctx_block_cat: u8,
block_idx_in_mb_a: usize,
block_idx_in_mb_b: usize,
current_is_intra: bool,
) -> u32 {
let cond = |n: Option<&CabacNeighborMB>, blk: usize| -> u32 {
match n {
None => if current_is_intra { 1 } else { 0 },
Some(mb) if mb.mb_type == MbTypeClass::IPCM => 1,
Some(mb) => ((neighbor_cbf_bitmap(mb, ctx_block_cat) >> blk) & 1) as u32,
}
};
cond(ctx.neighbor_a(mb_x), block_idx_in_mb_a)
+ 2 * cond(ctx.neighbor_b(mb_x), block_idx_in_mb_b)
}
#[inline]
fn neighbor_cbf_bitmap(mb: &CabacNeighborMB, ctx_block_cat: u8) -> u16 {
match ctx_block_cat {
1 | 2 => mb.coded_block_flag_cat[1] | mb.coded_block_flag_cat[2],
_ => mb.coded_block_flag_cat[ctx_block_cat as usize],
}
}
pub fn ctx_idx_inc_sig_4x4(level_list_idx: u32) -> u32 {
level_list_idx
}
pub fn ctx_idx_inc_sig_chroma_dc(level_list_idx: u32) -> u32 {
level_list_idx.min(2)
}
pub fn ctx_idx_inc_coeff_abs_level(
bin_idx: u32,
ctx_block_cat: u8,
num_decod_abs_level_eq1: u32,
num_decod_abs_level_gt1: u32,
) -> u32 {
if bin_idx == 0 {
if num_decod_abs_level_gt1 != 0 {
0
} else {
(1 + num_decod_abs_level_eq1).min(4)
}
} else {
let cap = 4 - if ctx_block_cat == 3 { 1 } else { 0 };
5 + num_decod_abs_level_gt1.min(cap)
}
}
pub fn ctx_idx_inc_transform_size_8x8_flag(
ctx: &CabacNeighborContext,
mb_x: usize,
) -> u32 {
let cond = |n: Option<&CabacNeighborMB>| -> u32 {
match n {
Some(mb) if mb.transform_size_8x8_flag => 1,
_ => 0,
}
};
let _ = cond;
let a = match ctx.neighbor_a(mb_x) {
Some(mb) if mb.transform_size_8x8_flag => 1,
_ => 0,
};
let b = match ctx.neighbor_b(mb_x) {
Some(mb) if mb.transform_size_8x8_flag => 1,
_ => 0,
};
a + b
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CurrentMbCbf {
pub cat: [u16; 5],
}
impl CurrentMbCbf {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn get(&self, cat: u8, block_idx: usize) -> bool {
(self.cat[cat as usize] >> block_idx) & 1 != 0
}
#[inline]
pub fn set(&mut self, cat: u8, block_idx: usize, value: bool) {
if value {
self.cat[cat as usize] |= 1 << block_idx;
}
}
#[inline]
pub fn to_neighbor_cbf(&self) -> [u16; 5] {
self.cat
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CurrentMbMvdAbs {
pub comp: [[i16; 16]; 2],
}
impl CurrentMbMvdAbs {
pub fn new() -> Self {
Self::default()
}
pub fn fill_region(&mut self, bx0: u8, by0: u8, w: u8, h: u8, abs_mvd_x: i16, abs_mvd_y: i16) {
for dy in 0..h {
for dx in 0..w {
let idx = block_pos_to_luma_idx(bx0 + dx, by0 + dy);
self.comp[0][idx] = abs_mvd_x;
self.comp[1][idx] = abs_mvd_y;
}
}
}
#[inline]
pub fn get(&self, component: u8, block_idx: usize) -> i16 {
self.comp[component as usize][block_idx]
}
#[inline]
pub fn to_neighbor(&self) -> [[i16; 16]; 2] {
self.comp
}
}
pub fn compute_mvd_ctx_idx_inc_bin0(
current_mb: &CurrentMbMvdAbs,
neighbors: &CabacNeighborContext,
mb_x: usize,
cur_bx: u8,
cur_by: u8,
component: u8,
) -> u32 {
let abs_a: u32 = if cur_bx > 0 {
current_mb
.get(component, block_pos_to_luma_idx(cur_bx - 1, cur_by))
.unsigned_abs() as u32
} else {
match neighbors.neighbor_a(mb_x) {
Some(mb) => mb.abs_mvd_comp[component as usize]
[block_pos_to_luma_idx(3, cur_by)]
.unsigned_abs() as u32,
None => 0,
}
};
let abs_b: u32 = if cur_by > 0 {
current_mb
.get(component, block_pos_to_luma_idx(cur_bx, cur_by - 1))
.unsigned_abs() as u32
} else {
match neighbors.neighbor_b(mb_x) {
Some(mb) => mb.abs_mvd_comp[component as usize]
[block_pos_to_luma_idx(cur_bx, 3)]
.unsigned_abs() as u32,
None => 0,
}
};
let sum = abs_a + abs_b;
if sum < 3 {
0
} else if sum > 32 {
2
} else {
1
}
}
#[inline]
pub fn block_pos_to_luma_idx(bx: u8, by: u8) -> usize {
debug_assert!(bx < 4 && by < 4);
let bx = bx as usize;
let by = by as usize;
4 * (2 * (by / 2) + (bx / 2)) + (2 * (by % 2) + (bx % 2))
}
#[inline]
pub fn block_pos_to_chroma_ac_idx(i_cb_cr: u8, bx: u8, by: u8) -> usize {
debug_assert!(bx < 2 && by < 2 && i_cb_cr < 2);
(i_cb_cr as usize) * 4 + (by as usize) * 2 + (bx as usize)
}
pub fn compute_cbf_ctx_idx_inc_luma_dc(
neighbors: &CabacNeighborContext,
mb_x: usize,
) -> u32 {
ctx_idx_inc_coded_block_flag(neighbors, mb_x, 0, 0, 0, true)
}
pub fn compute_cbf_ctx_idx_inc_luma_ac(
current_mb: &CurrentMbCbf,
neighbors: &CabacNeighborContext,
mb_x: usize,
bx: u8,
by: u8,
current_is_intra: bool,
) -> u32 {
const CAT: u8 = 1;
let cond_term_a = if bx > 0 {
current_mb.get(CAT, block_pos_to_luma_idx(bx - 1, by)) as u32
} else {
cbf_cross_mb_condterm(
neighbors.neighbor_a(mb_x),
CAT,
block_pos_to_luma_idx(3, by),
current_is_intra,
)
};
let cond_term_b = if by > 0 {
current_mb.get(CAT, block_pos_to_luma_idx(bx, by - 1)) as u32
} else {
cbf_cross_mb_condterm(
neighbors.neighbor_b(mb_x),
CAT,
block_pos_to_luma_idx(bx, 3),
current_is_intra,
)
};
cond_term_a + 2 * cond_term_b
}
pub fn compute_cbf_ctx_idx_inc_luma_4x4(
current_mb: &CurrentMbCbf,
neighbors: &CabacNeighborContext,
mb_x: usize,
bx: u8,
by: u8,
current_is_intra: bool,
) -> u32 {
const CAT: u8 = 2;
let cond_term_a = if bx > 0 {
current_mb.get(CAT, block_pos_to_luma_idx(bx - 1, by)) as u32
} else {
cbf_cross_mb_condterm(
neighbors.neighbor_a(mb_x),
CAT,
block_pos_to_luma_idx(3, by),
current_is_intra,
)
};
let cond_term_b = if by > 0 {
current_mb.get(CAT, block_pos_to_luma_idx(bx, by - 1)) as u32
} else {
cbf_cross_mb_condterm(
neighbors.neighbor_b(mb_x),
CAT,
block_pos_to_luma_idx(bx, 3),
current_is_intra,
)
};
cond_term_a + 2 * cond_term_b
}
pub fn compute_cbf_ctx_idx_inc_chroma_dc(
neighbors: &CabacNeighborContext,
mb_x: usize,
i_cb_cr: u8,
current_is_intra: bool,
) -> u32 {
debug_assert!(i_cb_cr < 2);
ctx_idx_inc_coded_block_flag(
neighbors,
mb_x,
3,
i_cb_cr as usize,
i_cb_cr as usize,
current_is_intra,
)
}
pub fn compute_cbf_ctx_idx_inc_chroma_ac(
current_mb: &CurrentMbCbf,
neighbors: &CabacNeighborContext,
mb_x: usize,
i_cb_cr: u8,
bx: u8,
by: u8,
current_is_intra: bool,
) -> u32 {
debug_assert!(i_cb_cr < 2 && bx < 2 && by < 2);
const CAT: u8 = 4;
let cond_term_a = if bx > 0 {
current_mb.get(CAT, block_pos_to_chroma_ac_idx(i_cb_cr, bx - 1, by)) as u32
} else {
cbf_cross_mb_condterm(
neighbors.neighbor_a(mb_x),
CAT,
block_pos_to_chroma_ac_idx(i_cb_cr, 1, by),
current_is_intra,
)
};
let cond_term_b = if by > 0 {
current_mb.get(CAT, block_pos_to_chroma_ac_idx(i_cb_cr, bx, by - 1)) as u32
} else {
cbf_cross_mb_condterm(
neighbors.neighbor_b(mb_x),
CAT,
block_pos_to_chroma_ac_idx(i_cb_cr, bx, 1),
current_is_intra,
)
};
cond_term_a + 2 * cond_term_b
}
#[inline]
fn cbf_cross_mb_condterm(
n: Option<&CabacNeighborMB>,
ctx_block_cat: u8,
blk: usize,
current_is_intra: bool,
) -> u32 {
match n {
None => if current_is_intra { 1 } else { 0 },
Some(mb) if mb.mb_type == MbTypeClass::IPCM => 1,
Some(mb) => ((neighbor_cbf_bitmap(mb, ctx_block_cat) >> blk) & 1) as u32,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_ctx() -> CabacNeighborContext {
CabacNeighborContext::new(4, CabacInitSlot::ISI)
}
fn p_inter_mb() -> CabacNeighborMB {
CabacNeighborMB {
mb_type: MbTypeClass::PInter,
..Default::default()
}
}
fn p_skip_mb() -> CabacNeighborMB {
CabacNeighborMB {
mb_type: MbTypeClass::PSkip,
mb_skip_flag: true,
..Default::default()
}
}
fn intra_nxn_mb() -> CabacNeighborMB {
CabacNeighborMB {
mb_type: MbTypeClass::INxN,
..Default::default()
}
}
#[test]
fn mb_skip_flag_no_neighbors_is_zero() {
let ctx = make_ctx();
assert_eq!(ctx_idx_inc_mb_skip_flag(&ctx, 0), 0);
}
#[test]
fn mb_skip_flag_with_both_non_skip_neighbors_is_two() {
let mut ctx = make_ctx();
ctx.top_row[0] = Some(p_inter_mb());
ctx.left = Some(p_inter_mb());
assert_eq!(ctx_idx_inc_mb_skip_flag(&ctx, 0), 2);
}
#[test]
fn mb_skip_flag_skip_neighbor_contributes_zero() {
let mut ctx = make_ctx();
ctx.top_row[0] = Some(p_skip_mb());
ctx.left = Some(p_inter_mb());
assert_eq!(ctx_idx_inc_mb_skip_flag(&ctx, 0), 1);
}
#[test]
fn mb_type_bin0_i_slice_nxn_contributes_zero() {
let mut ctx = make_ctx();
ctx.left = Some(intra_nxn_mb());
ctx.top_row[0] = Some(CabacNeighborMB {
mb_type: MbTypeClass::I16x16,
..Default::default()
});
assert_eq!(ctx_idx_inc_mb_type_bin0(&ctx, 0, 3), 1);
}
#[test]
fn mb_type_bin0_p_slice_uses_fixed_zero() {
let mut ctx = make_ctx();
ctx.left = Some(p_inter_mb());
ctx.top_row[0] = Some(intra_nxn_mb());
assert_eq!(ctx_idx_inc_mb_type_bin0(&ctx, 0, 14), 0);
ctx.left = Some(intra_nxn_mb());
ctx.top_row[0] = Some(intra_nxn_mb());
assert_eq!(ctx_idx_inc_mb_type_bin0(&ctx, 0, 14), 0);
}
#[test]
fn prior_bin_table_9_41_lookups() {
assert_eq!(ctx_idx_inc_prior_bin(3, 4, &[0, 0, 0, 1]), Some(5));
assert_eq!(ctx_idx_inc_prior_bin(3, 4, &[0, 0, 0, 0]), Some(6));
assert_eq!(ctx_idx_inc_prior_bin(14, 2, &[0, 0]), Some(2));
assert_eq!(ctx_idx_inc_prior_bin(14, 2, &[0, 1]), Some(3));
assert_eq!(ctx_idx_inc_prior_bin(99, 0, &[]), None);
}
#[test]
fn mvd_bin0_range_test() {
let mut ctx = make_ctx();
let mut a = CabacNeighborMB::default();
a.abs_mvd_comp[0][0] = 1;
let mut b = CabacNeighborMB::default();
b.abs_mvd_comp[0][0] = 1;
ctx.left = Some(a);
ctx.top_row[0] = Some(b);
assert_eq!(ctx_idx_inc_mvd_bin0(&ctx, 0, 0, 0, 0), 0);
a.abs_mvd_comp[0][0] = 10;
b.abs_mvd_comp[0][0] = 10;
ctx.left = Some(a);
ctx.top_row[0] = Some(b);
assert_eq!(ctx_idx_inc_mvd_bin0(&ctx, 0, 0, 0, 0), 1);
a.abs_mvd_comp[0][0] = 40;
ctx.left = Some(a);
assert_eq!(ctx_idx_inc_mvd_bin0(&ctx, 0, 0, 0, 0), 2);
}
#[test]
fn ref_idx_bin0_zero_ref_contributes_zero() {
let mut ctx = make_ctx();
let mut mb = p_inter_mb();
mb.ref_idx_l0[0] = 0; ctx.left = Some(mb);
ctx.top_row[0] = Some(mb);
assert_eq!(ctx_idx_inc_ref_idx_bin0(&ctx, 0, 0, 0), 0);
}
#[test]
fn ref_idx_bin0_nonzero_ref_contributes() {
let mut ctx = make_ctx();
let mut a = p_inter_mb();
a.ref_idx_l0[0] = 1;
let b = p_inter_mb(); ctx.left = Some(a);
ctx.top_row[0] = Some(b);
assert_eq!(ctx_idx_inc_ref_idx_bin0(&ctx, 0, 0, 0), 1);
}
#[test]
fn mb_qp_delta_prev_zero_or_unavail_is_zero() {
let ctx = make_ctx();
assert_eq!(ctx_idx_inc_mb_qp_delta_bin0(&ctx), 0);
}
#[test]
fn mb_qp_delta_prev_nonzero_is_one() {
let mut ctx = make_ctx();
let mut prev = intra_nxn_mb();
prev.mb_qp_delta = 3;
ctx.prev_mb = Some(prev);
assert_eq!(ctx_idx_inc_mb_qp_delta_bin0(&ctx), 1);
}
#[test]
fn sig_4x4_returns_level_list_idx() {
assert_eq!(ctx_idx_inc_sig_4x4(0), 0);
assert_eq!(ctx_idx_inc_sig_4x4(7), 7);
assert_eq!(ctx_idx_inc_sig_4x4(14), 14);
}
#[test]
fn sig_chroma_dc_clamps_at_2() {
assert_eq!(ctx_idx_inc_sig_chroma_dc(0), 0);
assert_eq!(ctx_idx_inc_sig_chroma_dc(1), 1);
assert_eq!(ctx_idx_inc_sig_chroma_dc(2), 2);
assert_eq!(ctx_idx_inc_sig_chroma_dc(5), 2);
}
#[test]
fn coeff_abs_level_bin0_no_prior_gt1() {
assert_eq!(ctx_idx_inc_coeff_abs_level(0, 2, 0, 0), 1);
assert_eq!(ctx_idx_inc_coeff_abs_level(0, 2, 3, 0), 4);
assert_eq!(ctx_idx_inc_coeff_abs_level(0, 2, 10, 0), 4);
}
#[test]
fn coeff_abs_level_bin0_with_prior_gt1_is_zero() {
assert_eq!(ctx_idx_inc_coeff_abs_level(0, 2, 5, 1), 0);
}
#[test]
fn coeff_abs_level_subsequent_bins_chroma_dc_cap() {
assert_eq!(ctx_idx_inc_coeff_abs_level(1, 3, 0, 0), 5);
assert_eq!(ctx_idx_inc_coeff_abs_level(1, 3, 0, 10), 5 + 3); assert_eq!(ctx_idx_inc_coeff_abs_level(1, 2, 0, 10), 5 + 4);
}
#[test]
fn commit_and_reset_cycle() {
let mut ctx = make_ctx();
ctx.commit(0, intra_nxn_mb());
assert!(ctx.neighbor_a(1).is_some());
assert!(ctx.neighbor_b(0).is_some());
ctx.reset();
assert!(ctx.neighbor_a(0).is_none());
assert!(ctx.neighbor_b(0).is_none());
assert!(ctx.prev_mb().is_none());
}
#[test]
fn new_row_clears_left_only() {
let mut ctx = make_ctx();
ctx.commit(0, intra_nxn_mb());
ctx.new_row();
assert!(ctx.neighbor_a(0).is_none());
assert!(ctx.neighbor_b(0).is_some());
}
#[test]
fn block_pos_to_luma_idx_matches_block_index_to_pos() {
use crate::codec::h264::macroblock::BLOCK_INDEX_TO_POS;
for (expected_idx, &(bx, by)) in BLOCK_INDEX_TO_POS.iter().enumerate() {
let idx = block_pos_to_luma_idx(bx, by);
assert_eq!(
idx, expected_idx,
"block_pos_to_luma_idx({bx},{by}) should be {expected_idx}, got {idx}"
);
}
}
#[test]
fn block_pos_to_chroma_ac_idx_layout() {
assert_eq!(block_pos_to_chroma_ac_idx(0, 0, 0), 0);
assert_eq!(block_pos_to_chroma_ac_idx(0, 1, 0), 1);
assert_eq!(block_pos_to_chroma_ac_idx(0, 0, 1), 2);
assert_eq!(block_pos_to_chroma_ac_idx(0, 1, 1), 3);
assert_eq!(block_pos_to_chroma_ac_idx(1, 0, 0), 4);
assert_eq!(block_pos_to_chroma_ac_idx(1, 1, 1), 7);
}
#[test]
fn current_mb_cbf_set_get_roundtrip() {
let mut c = CurrentMbCbf::new();
assert!(!c.get(1, 5));
c.set(1, 5, true);
assert!(c.get(1, 5));
assert!(!c.get(1, 4));
assert!(!c.get(2, 5));
c.set(1, 5, false); assert!(c.get(1, 5));
}
#[test]
fn compute_cbf_ctx_idx_inc_luma_dc_cross_mb_only() {
let mut ctx = make_ctx();
assert_eq!(compute_cbf_ctx_idx_inc_luma_dc(&ctx, 0), 3);
let mut nb = CabacNeighborMB::default();
nb.mb_type = MbTypeClass::I16x16;
nb.coded_block_flag_cat[0] = 0b1; ctx.commit(0, nb);
assert_eq!(compute_cbf_ctx_idx_inc_luma_dc(&ctx, 1), 3);
}
#[test]
fn compute_cbf_ctx_idx_inc_luma_ac_interior_uses_current_mb() {
let ctx = make_ctx();
let mut cur = CurrentMbCbf::new();
let idx_00 = block_pos_to_luma_idx(0, 0);
cur.set(1, idx_00, true);
let inc = compute_cbf_ctx_idx_inc_luma_ac(&cur, &ctx, 0, 1, 0, true);
assert_eq!(inc, 3, "condTermA=1 + 2*condTermB(intra unavail)=2 = 3");
let inc = compute_cbf_ctx_idx_inc_luma_ac(&cur, &ctx, 0, 1, 0, false);
assert_eq!(inc, 1, "condTermA=1 + 2*condTermB(inter unavail)=0 = 1");
}
#[test]
fn compute_cbf_ctx_idx_inc_luma_ac_edge_uses_cross_mb() {
let mut ctx = make_ctx();
let cur = CurrentMbCbf::new();
assert_eq!(compute_cbf_ctx_idx_inc_luma_ac(&cur, &ctx, 0, 0, 0, true), 3);
assert_eq!(compute_cbf_ctx_idx_inc_luma_ac(&cur, &ctx, 0, 0, 0, false), 0);
let mut nb = CabacNeighborMB::default();
nb.mb_type = MbTypeClass::I16x16;
nb.coded_block_flag_cat[1] = 1 << 5; ctx.commit(0, nb);
let inc = compute_cbf_ctx_idx_inc_luma_ac(&cur, &ctx, 1, 0, 0, true);
assert_eq!(inc, 3);
let inc = compute_cbf_ctx_idx_inc_luma_ac(&cur, &ctx, 1, 0, 0, false);
assert_eq!(inc, 1);
}
#[test]
fn mvd_bin0_l0_l1_independent_states() {
let mut ctx = make_ctx();
let mut a = CabacNeighborMB::default();
a.abs_mvd_comp[0][0] = 10; ctx.left = Some(a);
let mut b = CabacNeighborMB::default();
b.abs_mvd_comp_l1[0][0] = 10; ctx.top_row[0] = Some(b);
assert_eq!(ctx_idx_inc_mvd_bin0_per_list(&ctx, 0, 0, 0, 0, 0), 1);
assert_eq!(ctx_idx_inc_mvd_bin0_per_list(&ctx, 0, 0, 0, 0, 1), 1);
}
#[test]
fn mvd_bin0_l1_only_neighbour_returns_zero_for_l0_inc() {
let mut ctx = make_ctx();
let mut a = CabacNeighborMB::default();
a.abs_mvd_comp_l1[0][0] = 50; ctx.left = Some(a);
assert_eq!(ctx_idx_inc_mvd_bin0_per_list(&ctx, 0, 0, 0, 0, 0), 0);
assert_eq!(ctx_idx_inc_mvd_bin0_per_list(&ctx, 0, 0, 0, 0, 1), 2);
}
#[test]
fn mvd_bin0_l0_only_neighbour_returns_zero_for_l1_inc() {
let mut ctx = make_ctx();
let mut a = CabacNeighborMB::default();
a.abs_mvd_comp[0][0] = 50; ctx.left = Some(a);
assert_eq!(ctx_idx_inc_mvd_bin0_per_list(&ctx, 0, 0, 0, 0, 0), 2);
assert_eq!(ctx_idx_inc_mvd_bin0_per_list(&ctx, 0, 0, 0, 0, 1), 0);
}
#[test]
fn legacy_mvd_bin0_matches_per_list_with_l0() {
let mut ctx = make_ctx();
let mut a = CabacNeighborMB::default();
a.abs_mvd_comp[0][0] = 5;
a.abs_mvd_comp[1][0] = 7;
a.abs_mvd_comp_l1[0][0] = 100; a.abs_mvd_comp_l1[1][0] = 100;
ctx.left = Some(a);
for component in 0..=1u8 {
let legacy = ctx_idx_inc_mvd_bin0(&ctx, 0, 0, 0, component);
let per_list = ctx_idx_inc_mvd_bin0_per_list(&ctx, 0, 0, 0, component, 0);
assert_eq!(legacy, per_list, "component {component}: legacy {legacy} != per-list {per_list}");
}
}
#[test]
fn compute_cbf_ctx_idx_inc_chroma_ac_interior() {
let ctx = make_ctx();
let mut cur = CurrentMbCbf::new();
cur.set(4, block_pos_to_chroma_ac_idx(0, 0, 0), true);
let inc = compute_cbf_ctx_idx_inc_chroma_ac(&cur, &ctx, 0, 0, 1, 0, true);
assert_eq!(inc, 3);
let inc = compute_cbf_ctx_idx_inc_chroma_ac(&cur, &ctx, 0, 0, 1, 0, false);
assert_eq!(inc, 1);
}
}