use super::binarization::{
binarize_egk_suffix, binarize_tu, binarize_unary, mb_qp_delta_remap,
mb_type_i_bins, mb_type_p_bins_prefix, sub_mb_type_p_bins,
};
use super::context::{initialize_contexts, CabacContext, CabacInitSlot};
use super::engine::CabacEngine;
use super::neighbor::{
compute_cbp_luma_ctx_idx_inc_bin, ctx_idx_inc_cbp_chroma, ctx_idx_inc_coded_block_flag,
ctx_idx_inc_coeff_abs_level, ctx_idx_inc_intra_chroma_pred_mode_bin0,
ctx_idx_inc_mb_qp_delta_bin0, ctx_idx_inc_mb_skip_flag, ctx_idx_inc_mb_type_bin0,
ctx_idx_inc_mvd_bin0, ctx_idx_inc_prior_bin, ctx_idx_inc_ref_idx_bin0,
ctx_idx_inc_sig_4x4, ctx_idx_inc_sig_chroma_dc, CabacNeighborContext,
};
pub mod ctx_offset {
pub const MB_TYPE_SI_PREFIX: u32 = 0;
pub const MB_TYPE_I: u32 = 3;
pub const MB_SKIP_FLAG_P: u32 = 11;
pub const MB_TYPE_P_PREFIX: u32 = 14;
pub const MB_TYPE_P_SUFFIX: u32 = 17;
pub const SUB_MB_TYPE_P: u32 = 21;
pub const MB_SKIP_FLAG_B: u32 = 24;
pub const MB_TYPE_B_PREFIX: u32 = 27;
pub const MB_TYPE_B_SUFFIX: u32 = 32;
pub const SUB_MB_TYPE_B: u32 = 36;
pub const MVD_L0_X: u32 = 40;
pub const MVD_L0_Y: u32 = 47;
pub const REF_IDX: u32 = 54;
pub const MB_QP_DELTA: u32 = 60;
pub const INTRA_CHROMA_PRED_MODE: u32 = 64;
pub const PREV_INTRA_PRED_MODE_FLAG: u32 = 68;
pub const REM_INTRA_PRED_MODE: u32 = 69;
pub const CBP_LUMA: u32 = 73;
pub const CBP_CHROMA: u32 = 77;
pub const CODED_BLOCK_FLAG_LOW: u32 = 85; pub const SIGNIFICANT_COEFF_FLAG_FRAME_LOW: u32 = 105;
pub const LAST_SIGNIFICANT_COEFF_FLAG_FRAME_LOW: u32 = 166;
pub const COEFF_ABS_LEVEL_MINUS1_LOW: u32 = 227;
pub const END_OF_SLICE_FLAG: u32 = 276;
pub const TRANSFORM_SIZE_8X8_FLAG: u32 = 399;
}
pub const CTX_BLOCK_CAT_OFFSET: [[u32; 5]; 4] = [
[0, 4, 8, 12, 16], [0, 15, 29, 44, 47], [0, 15, 29, 44, 47], [0, 10, 20, 30, 39], ];
pub const SIG_COEFF_FLAG_OFFSET_8X8_FRAME: [u8; 63] = [
0, 1, 2, 3, 4, 5, 5, 4, 4, 3, 3, 4, 4, 4, 5, 5, 4, 4, 4, 4, 3, 3, 6, 7, 7, 7, 8, 9, 10, 9, 8,
7, 7, 6, 11, 12, 13, 11, 6, 7, 8, 9, 14, 10, 9, 8, 6, 11, 12, 13, 11, 6, 9, 14, 10, 9, 11, 12,
13, 11, 14, 10, 12,
];
pub const LAST_COEFF_FLAG_OFFSET_8X8_FRAME: [u8; 63] = [
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8,
];
pub mod cat5_luma8x8 {
pub const SIG_BASE: u32 = 402;
pub const LAST_BASE: u32 = 417;
pub const ABS_BASE: u32 = 426;
}
#[rustfmt::skip]
#[allow(clippy::erasing_op, clippy::identity_op)]
pub const ZIGZAG_8X8: [u8; 64] = [
0 + 0 * 8, 1 + 0 * 8, 0 + 1 * 8, 0 + 2 * 8, 1 + 1 * 8, 2 + 0 * 8, 3 + 0 * 8,
2 + 1 * 8, 1 + 2 * 8, 0 + 3 * 8, 0 + 4 * 8, 1 + 3 * 8, 2 + 2 * 8, 3 + 1 * 8,
4 + 0 * 8, 5 + 0 * 8, 4 + 1 * 8, 3 + 2 * 8, 2 + 3 * 8, 1 + 4 * 8, 0 + 5 * 8,
0 + 6 * 8, 1 + 5 * 8, 2 + 4 * 8, 3 + 3 * 8, 4 + 2 * 8, 5 + 1 * 8, 6 + 0 * 8,
7 + 0 * 8, 6 + 1 * 8, 5 + 2 * 8, 4 + 3 * 8, 3 + 4 * 8, 2 + 5 * 8, 1 + 6 * 8,
0 + 7 * 8, 1 + 7 * 8, 2 + 6 * 8, 3 + 5 * 8, 4 + 4 * 8, 5 + 3 * 8, 6 + 2 * 8,
7 + 1 * 8, 7 + 2 * 8, 6 + 3 * 8, 5 + 4 * 8, 4 + 5 * 8, 3 + 6 * 8, 2 + 7 * 8,
3 + 7 * 8, 4 + 6 * 8, 5 + 5 * 8, 6 + 4 * 8, 7 + 3 * 8, 7 + 4 * 8, 6 + 5 * 8,
5 + 6 * 8, 4 + 7 * 8, 5 + 7 * 8, 6 + 6 * 8, 7 + 5 * 8, 7 + 6 * 8, 6 + 7 * 8,
7 + 7 * 8,
];
pub struct CabacEncoder {
pub engine: CabacEngine,
pub contexts: Box<[CabacContext; 1024]>,
pub neighbors: CabacNeighborContext,
}
impl CabacEncoder {
pub fn new_slice(slot: CabacInitSlot, slice_qp_y: i32, mb_width: usize) -> Self {
let contexts = Box::new(initialize_contexts(slot, slice_qp_y));
Self {
engine: CabacEngine::new(),
contexts,
neighbors: CabacNeighborContext::new(mb_width, slot),
}
}
pub fn finish(self) -> Vec<u8> {
self.engine.finish()
}
#[inline]
pub(crate) fn encode_dec(&mut self, bin: u8, ctx_idx: u32) {
let ctx = &mut self.contexts[ctx_idx as usize];
self.engine.encode_decision_with_ctx_idx(bin, ctx, ctx_idx);
}
#[inline]
fn encode_bypass(&mut self, bin: u8) {
self.engine.encode_bypass(bin);
}
#[inline]
fn encode_terminate(&mut self, bin: u8) {
self.engine.encode_terminate(bin);
}
}
pub fn encode_mb_type_i(enc: &mut CabacEncoder, mb_type: u32, mb_x: usize) {
debug_assert!(mb_type <= 25);
let bins = mb_type_i_bins(mb_type);
for (bin_idx, &bin) in bins.iter().enumerate() {
if bin_idx == 1 {
enc.engine.trace_label = "mb_type bin1 (term)".to_string();
enc.encode_terminate(bin);
continue;
}
let ctx_idx_inc = ctx_idx_inc_mb_type_bin(
enc,
mb_x,
ctx_offset::MB_TYPE_I,
bin_idx as u32,
bins,
);
let ctx_idx = ctx_offset::MB_TYPE_I + ctx_idx_inc;
enc.engine.trace_label = format!("mb_type bin{bin_idx}");
enc.encode_dec(bin, ctx_idx);
}
}
fn ctx_idx_inc_mb_type_bin(
enc: &CabacEncoder,
mb_x: usize,
ctx_idx_offset: u32,
bin_idx: u32,
prior_bins: &[u8],
) -> u32 {
if bin_idx == 0 {
return ctx_idx_inc_mb_type_bin0(&enc.neighbors, mb_x, ctx_idx_offset);
}
if let Some(inc) = ctx_idx_inc_prior_bin(ctx_idx_offset, bin_idx, prior_bins) {
return inc;
}
fn env_or(name: &str, default: u32) -> u32 {
std::env::var(name).ok().and_then(|s| s.parse().ok()).unwrap_or(default)
}
match (ctx_idx_offset, bin_idx) {
(3, 2) => 3, (3, 3) => 4, (3, 6) => 7, (14, 1) => 1, (17, 0) => env_or("PHASM_IIP_CTX_17_0", 0),
(17, 2) => env_or("PHASM_IIP_CTX_17_2", 1),
(17, 3) => env_or("PHASM_IIP_CTX_17_3", 2),
(17, 5) => env_or("PHASM_IIP_CTX_17_5", 3),
(17, 6) => env_or("PHASM_IIP_CTX_17_6", 3),
_ => 0,
}
}
pub fn encode_prev_intra4x4_pred_mode_flag(enc: &mut CabacEncoder, is_predicted: bool) {
enc.encode_dec(
if is_predicted { 1 } else { 0 },
ctx_offset::PREV_INTRA_PRED_MODE_FLAG,
);
}
pub fn encode_rem_intra4x4_pred_mode(enc: &mut CabacEncoder, rem: u8) {
debug_assert!(rem <= 7);
enc.encode_dec(rem & 1, ctx_offset::REM_INTRA_PRED_MODE);
enc.encode_dec((rem >> 1) & 1, ctx_offset::REM_INTRA_PRED_MODE);
enc.encode_dec((rem >> 2) & 1, ctx_offset::REM_INTRA_PRED_MODE);
}
pub fn encode_intra_chroma_pred_mode(enc: &mut CabacEncoder, mode: u8, mb_x: usize) {
debug_assert!(mode <= 3);
let mut bin_idx = 0usize;
let cond_a = ctx_idx_inc_intra_chroma_pred_mode_bin0(&enc.neighbors, mb_x);
binarize_tu(mode as u32, 3, &mut |bin| {
let ctx_inc = if bin_idx == 0 { cond_a } else { 3 };
let ctx_idx = ctx_offset::INTRA_CHROMA_PRED_MODE + ctx_inc;
enc.encode_dec(bin, ctx_idx);
bin_idx += 1;
});
}
pub fn encode_coded_block_pattern(enc: &mut CabacEncoder, cbp: u8, mb_x: usize) {
let luma = cbp & 0x0F;
let chroma = ((cbp >> 4) & 0x03) as u32;
let mut current_partial: u8 = 0;
for bin_idx in 0..4u32 {
let bin = (luma >> bin_idx) & 1;
let ctx_inc =
compute_cbp_luma_ctx_idx_inc_bin(bin_idx, current_partial, &enc.neighbors, mb_x);
enc.encode_dec(bin, ctx_offset::CBP_LUMA + ctx_inc);
current_partial |= bin << bin_idx;
}
let mut bin_idx = 0u32;
binarize_tu(chroma, 2, &mut |bin| {
let ctx_inc = ctx_idx_inc_cbp_chroma(&enc.neighbors, mb_x, bin_idx);
enc.encode_dec(bin, ctx_offset::CBP_CHROMA + ctx_inc);
bin_idx += 1;
});
}
pub fn encode_mb_qp_delta(enc: &mut CabacEncoder, qp_delta: i32) {
let mapped = mb_qp_delta_remap(qp_delta);
let mut bin_idx = 0u32;
binarize_unary(mapped, &mut |bin| {
let ctx_inc = if bin_idx == 0 {
ctx_idx_inc_mb_qp_delta_bin0(&enc.neighbors)
} else if bin_idx == 1 {
2 } else {
3 };
enc.encode_dec(bin, ctx_offset::MB_QP_DELTA + ctx_inc);
bin_idx += 1;
});
}
pub fn encode_coded_block_flag(
enc: &mut CabacEncoder,
is_coded: bool,
ctx_block_cat: u8,
mb_x: usize,
block_idx_in_mb_a: usize,
block_idx_in_mb_b: usize,
current_is_intra: bool,
) {
let ctx_inc = ctx_idx_inc_coded_block_flag(
&enc.neighbors,
mb_x,
ctx_block_cat,
block_idx_in_mb_a,
block_idx_in_mb_b,
current_is_intra,
);
let ctx_idx = ctx_offset::CODED_BLOCK_FLAG_LOW
+ CTX_BLOCK_CAT_OFFSET[0][ctx_block_cat as usize]
+ ctx_inc;
enc.encode_dec(if is_coded { 1 } else { 0 }, ctx_idx);
}
pub fn encode_end_of_slice_flag(enc: &mut CabacEncoder, is_last_mb: bool) {
enc.encode_terminate(if is_last_mb { 1 } else { 0 });
}
pub fn encode_transform_size_8x8_flag(
enc: &mut CabacEncoder,
flag: bool,
mb_x: usize,
) {
let inc = super::neighbor::ctx_idx_inc_transform_size_8x8_flag(&enc.neighbors, mb_x);
let ctx_idx = ctx_offset::TRANSFORM_SIZE_8X8_FLAG + inc;
enc.encode_dec(if flag { 1 } else { 0 }, ctx_idx);
}
pub fn encode_residual_block_cabac(
enc: &mut CabacEncoder,
scan_coeffs: &[i32],
start_idx: usize,
end_idx: usize,
ctx_block_cat: u8,
mb_x: usize,
block_idx_in_mb_a: usize,
block_idx_in_mb_b: usize,
current_is_intra: bool,
) -> bool {
let cbf_ctx_inc = super::neighbor::ctx_idx_inc_coded_block_flag(
&enc.neighbors,
mb_x,
ctx_block_cat,
block_idx_in_mb_a,
block_idx_in_mb_b,
current_is_intra,
);
encode_residual_block_cabac_with_cbf_inc(
enc, scan_coeffs, start_idx, end_idx, ctx_block_cat, cbf_ctx_inc,
)
}
pub fn encode_residual_block_cabac_with_cbf_inc(
enc: &mut CabacEncoder,
scan_coeffs: &[i32],
start_idx: usize,
end_idx: usize,
ctx_block_cat: u8,
cbf_ctx_idx_inc: u32,
) -> bool {
debug_assert!(end_idx < scan_coeffs.len());
debug_assert!(ctx_block_cat <= 4);
let has_nonzero = scan_coeffs[start_idx..=end_idx]
.iter()
.any(|&v| v != 0);
let cbf_ctx_idx = ctx_offset::CODED_BLOCK_FLAG_LOW
+ CTX_BLOCK_CAT_OFFSET[0][ctx_block_cat as usize]
+ cbf_ctx_idx_inc;
enc.encode_dec(if has_nonzero { 1 } else { 0 }, cbf_ctx_idx);
if !has_nonzero {
return false;
}
let sig_offset = ctx_offset::SIGNIFICANT_COEFF_FLAG_FRAME_LOW
+ CTX_BLOCK_CAT_OFFSET[1][ctx_block_cat as usize];
let last_offset = ctx_offset::LAST_SIGNIFICANT_COEFF_FLAG_FRAME_LOW
+ CTX_BLOCK_CAT_OFFSET[2][ctx_block_cat as usize];
let mut num_coeff = end_idx + 1;
let mut i = start_idx;
while i < num_coeff - 1 {
let level_list_idx = (i - start_idx) as u32;
let is_sig = scan_coeffs[i] != 0;
let sig_bin = if is_sig { 1 } else { 0 };
let ctx_sig_inc = if ctx_block_cat == 3 {
ctx_idx_inc_sig_chroma_dc(level_list_idx)
} else {
ctx_idx_inc_sig_4x4(level_list_idx)
};
enc.encode_dec(sig_bin, sig_offset + ctx_sig_inc);
if is_sig {
let is_last = scan_coeffs[i + 1..=end_idx]
.iter()
.all(|&v| v == 0);
let last_bin = if is_last { 1 } else { 0 };
let ctx_last_inc = if ctx_block_cat == 3 {
ctx_idx_inc_sig_chroma_dc(level_list_idx)
} else {
ctx_idx_inc_sig_4x4(level_list_idx)
};
enc.encode_dec(last_bin, last_offset + ctx_last_inc);
if is_last {
num_coeff = i + 1;
break;
}
}
i += 1;
}
let abs_offset = ctx_offset::COEFF_ABS_LEVEL_MINUS1_LOW
+ CTX_BLOCK_CAT_OFFSET[3][ctx_block_cat as usize];
let mut num_eq1 = 0u32;
let mut num_gt1 = 0u32;
for i in (start_idx..num_coeff).rev() {
if scan_coeffs[i] == 0 {
continue;
}
let abs_level_minus1 = scan_coeffs[i].unsigned_abs() - 1;
let prefix_len = abs_level_minus1.min(14);
for b in 0..prefix_len {
let ctx_inc = ctx_idx_inc_coeff_abs_level(
if b == 0 { 0 } else { 1 },
ctx_block_cat,
num_eq1,
num_gt1,
);
enc.encode_dec(1, abs_offset + ctx_inc);
}
if abs_level_minus1 < 14 {
let ctx_inc = ctx_idx_inc_coeff_abs_level(
if prefix_len == 0 { 0 } else { 1 },
ctx_block_cat,
num_eq1,
num_gt1,
);
enc.encode_dec(0, abs_offset + ctx_inc);
} else {
let suf_s = abs_level_minus1 - 14;
binarize_egk_suffix(suf_s, 0, &mut |bin| enc.encode_bypass(bin));
}
enc.encode_bypass(if scan_coeffs[i] < 0 { 1 } else { 0 });
if scan_coeffs[i].unsigned_abs() == 1 {
num_eq1 += 1;
} else {
num_gt1 += 1;
}
}
true
}
pub fn encode_residual_block_cabac_8x8(enc: &mut CabacEncoder, scan_coeffs: &[i32; 64]) {
let mut num_coeff = 64;
let mut i = 0;
while i < 63 {
let level_list_idx = i as u32;
let is_sig = scan_coeffs[i] != 0;
let sig_bin = if is_sig { 1 } else { 0 };
let sig_ctx = cat5_luma8x8::SIG_BASE
+ SIG_COEFF_FLAG_OFFSET_8X8_FRAME[level_list_idx as usize] as u32;
enc.encode_dec(sig_bin, sig_ctx);
if is_sig {
let is_last = scan_coeffs[i + 1..64].iter().all(|&v| v == 0);
let last_bin = if is_last { 1 } else { 0 };
let last_ctx = cat5_luma8x8::LAST_BASE
+ LAST_COEFF_FLAG_OFFSET_8X8_FRAME[level_list_idx as usize] as u32;
enc.encode_dec(last_bin, last_ctx);
if is_last {
num_coeff = i + 1;
break;
}
}
i += 1;
}
let mut num_eq1 = 0u32;
let mut num_gt1 = 0u32;
for i in (0..num_coeff).rev() {
if scan_coeffs[i] == 0 {
continue;
}
let abs_level_minus1 = scan_coeffs[i].unsigned_abs() - 1;
let prefix_len = abs_level_minus1.min(14);
for b in 0..prefix_len {
let ctx_inc = super::neighbor::ctx_idx_inc_coeff_abs_level(
if b == 0 { 0 } else { 1 },
5,
num_eq1,
num_gt1,
);
enc.encode_dec(1, cat5_luma8x8::ABS_BASE + ctx_inc);
}
if abs_level_minus1 < 14 {
let ctx_inc = super::neighbor::ctx_idx_inc_coeff_abs_level(
if prefix_len == 0 { 0 } else { 1 },
5,
num_eq1,
num_gt1,
);
enc.encode_dec(0, cat5_luma8x8::ABS_BASE + ctx_inc);
} else {
let suf_s = abs_level_minus1 - 14;
binarize_egk_suffix(suf_s, 0, &mut |bin| enc.encode_bypass(bin));
}
enc.encode_bypass(if scan_coeffs[i] < 0 { 1 } else { 0 });
if scan_coeffs[i].unsigned_abs() == 1 {
num_eq1 += 1;
} else {
num_gt1 += 1;
}
}
}
pub fn encode_mb_skip_flag(enc: &mut CabacEncoder, is_skip: bool, mb_x: usize) {
let ctx_inc = ctx_idx_inc_mb_skip_flag(&enc.neighbors, mb_x);
enc.encode_dec(
if is_skip { 1 } else { 0 },
ctx_offset::MB_SKIP_FLAG_P + ctx_inc,
);
}
pub fn encode_mb_skip_flag_b(enc: &mut CabacEncoder, is_skip: bool, mb_x: usize) {
let ctx_inc = ctx_idx_inc_mb_skip_flag(&enc.neighbors, mb_x);
enc.encode_dec(
if is_skip { 1 } else { 0 },
ctx_offset::MB_SKIP_FLAG_B + ctx_inc,
);
}
pub fn encode_mb_type_b(enc: &mut CabacEncoder, mb_type: u32, mb_x: usize) {
debug_assert!(mb_type <= 47, "B-slice mb_type out of range: {mb_type}");
let prefix: &[u8] = if mb_type <= 22 {
super::binarization::mb_type_b_bins(mb_type)
} else {
super::binarization::mb_type_b_intra_prefix()
};
for (bin_idx, &bin) in prefix.iter().enumerate() {
let ctx_inc = ctx_idx_inc_mb_type_bin(
enc,
mb_x,
ctx_offset::MB_TYPE_B_PREFIX,
bin_idx as u32,
prefix,
);
enc.encode_dec(bin, ctx_offset::MB_TYPE_B_PREFIX + ctx_inc);
}
if mb_type >= 23 {
let suffix_value = mb_type - 23;
let suffix_bins = mb_type_i_bins(suffix_value);
for (bin_idx, &bin) in suffix_bins.iter().enumerate() {
if bin_idx == 1 {
enc.encode_terminate(bin);
continue;
}
let ctx_inc = ctx_idx_inc_mb_type_bin(
enc,
mb_x,
ctx_offset::MB_TYPE_B_SUFFIX,
bin_idx as u32,
suffix_bins,
);
enc.encode_dec(bin, ctx_offset::MB_TYPE_B_SUFFIX + ctx_inc);
}
}
}
pub fn encode_mb_type_p(enc: &mut CabacEncoder, mb_type: u32, mb_x: usize) {
debug_assert!(mb_type != 4, "P_8x8ref0 is forbidden in CABAC");
debug_assert!(mb_type <= 30);
let prefix = mb_type_p_bins_prefix(mb_type);
for (bin_idx, &bin) in prefix.iter().enumerate() {
let ctx_inc = ctx_idx_inc_mb_type_bin(
enc,
mb_x,
ctx_offset::MB_TYPE_P_PREFIX,
bin_idx as u32,
prefix,
);
enc.encode_dec(bin, ctx_offset::MB_TYPE_P_PREFIX + ctx_inc);
}
if mb_type >= 5 {
let suffix_value = mb_type - 5;
let suffix_bins = mb_type_i_bins(suffix_value);
for (bin_idx, &bin) in suffix_bins.iter().enumerate() {
if bin_idx == 1 {
enc.encode_terminate(bin);
continue;
}
let ctx_inc = ctx_idx_inc_mb_type_bin(
enc,
mb_x,
ctx_offset::MB_TYPE_P_SUFFIX,
bin_idx as u32,
suffix_bins,
);
enc.encode_dec(bin, ctx_offset::MB_TYPE_P_SUFFIX + ctx_inc);
}
}
}
pub fn encode_sub_mb_type_p(enc: &mut CabacEncoder, sub_mb_type: u32) {
debug_assert!(sub_mb_type <= 3);
let bins = sub_mb_type_p_bins(sub_mb_type);
for (bin_idx, &bin) in bins.iter().enumerate() {
let ctx_inc = bin_idx as u32;
enc.encode_dec(bin, ctx_offset::SUB_MB_TYPE_P + ctx_inc);
}
}
pub fn encode_ref_idx(
enc: &mut CabacEncoder,
ref_idx: u32,
mb_x: usize,
block_idx_in_mb_a: usize,
block_idx_in_mb_b: usize,
) {
let mut bin_idx = 0u32;
binarize_unary(ref_idx, &mut |bin| {
let ctx_inc = match bin_idx {
0 => ctx_idx_inc_ref_idx_bin0(
&enc.neighbors,
mb_x,
block_idx_in_mb_a,
block_idx_in_mb_b,
),
1 => 4,
_ => 5,
};
enc.encode_dec(bin, ctx_offset::REF_IDX + ctx_inc);
bin_idx += 1;
});
}
pub fn encode_mvd(
enc: &mut CabacEncoder,
mvd: i32,
component: u8,
mb_x: usize,
block_idx_in_mb_a: usize,
block_idx_in_mb_b: usize,
) {
let bin0_inc = ctx_idx_inc_mvd_bin0(
&enc.neighbors,
mb_x,
block_idx_in_mb_a,
block_idx_in_mb_b,
component,
);
encode_mvd_with_bin0_inc(enc, mvd, component, bin0_inc);
}
pub fn encode_mvd_with_bin0_inc(
enc: &mut CabacEncoder,
mvd: i32,
component: u8,
bin0_ctx_idx_inc: u32,
) {
encode_mvd_with_bin0_inc_sign_override(enc, mvd, component, bin0_ctx_idx_inc, None);
}
pub fn encode_mvd_with_bin0_inc_sign_override(
enc: &mut CabacEncoder,
mvd: i32,
component: u8,
bin0_ctx_idx_inc: u32,
sign_override: Option<u8>,
) {
debug_assert!(component <= 1);
let abs_v = mvd.unsigned_abs();
let u_coff = 9u32;
let base_offset = if component == 0 {
ctx_offset::MVD_L0_X
} else {
ctx_offset::MVD_L0_Y
};
let prefix_val = abs_v.min(u_coff);
let mut bin_idx = 0u32;
binarize_tu(prefix_val, u_coff, &mut |bin| {
let ctx_inc = match bin_idx {
0 => bin0_ctx_idx_inc,
1 => 3,
2 => 4,
3 => 5,
_ => 6, };
enc.encode_dec(bin, base_offset + ctx_inc);
bin_idx += 1;
});
if abs_v >= u_coff {
let suf_s = abs_v - u_coff;
binarize_egk_suffix(suf_s, 3, &mut |bin| enc.encode_bypass(bin));
}
if mvd != 0 {
let sign_bin = match sign_override {
Some(b) => {
debug_assert!(b <= 1, "sign override must be 0 or 1");
b
}
None => if mvd > 0 { 0 } else { 1 },
};
enc.encode_bypass(sign_bin);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fresh_enc() -> CabacEncoder {
CabacEncoder::new_slice(CabacInitSlot::ISI, 26, 4)
}
#[test]
fn encode_mb_type_i_single_bin_i_nxn() {
let mut enc = fresh_enc();
encode_mb_type_i(&mut enc, 0, 0);
encode_end_of_slice_flag(&mut enc, true);
let bytes = enc.finish();
assert!(!bytes.is_empty());
assert_ne!(*bytes.last().unwrap(), 0);
}
#[test]
fn encode_mb_type_i_i16x16_variant() {
let mut enc = fresh_enc();
encode_mb_type_i(&mut enc, 5, 0); encode_end_of_slice_flag(&mut enc, true);
let bytes = enc.finish();
assert!(!bytes.is_empty());
}
#[test]
fn encode_intra_chroma_pred_mode_dc_emits_single_bin() {
let mut enc = fresh_enc();
encode_intra_chroma_pred_mode(&mut enc, 0, 0);
let before = enc.engine.bin_count();
assert_eq!(before, 1); }
#[test]
fn encode_intra_chroma_pred_mode_plane_emits_three_bins() {
let mut enc = fresh_enc();
encode_intra_chroma_pred_mode(&mut enc, 3, 0);
assert_eq!(enc.engine.bin_count(), 3); }
#[test]
fn encode_coded_block_pattern_no_residual() {
let mut enc = fresh_enc();
encode_coded_block_pattern(&mut enc, 0, 0); assert_eq!(enc.engine.bin_count(), 5);
}
#[test]
fn encode_coded_block_pattern_full_cbp() {
let mut enc = fresh_enc();
encode_coded_block_pattern(&mut enc, 47, 0); assert_eq!(enc.engine.bin_count(), 6);
}
#[test]
fn encode_mb_qp_delta_zero_emits_single_bin() {
let mut enc = fresh_enc();
encode_mb_qp_delta(&mut enc, 0);
assert_eq!(enc.engine.bin_count(), 1);
}
#[test]
fn encode_mb_qp_delta_nonzero() {
let mut enc = fresh_enc();
encode_mb_qp_delta(&mut enc, 3);
assert_eq!(enc.engine.bin_count(), 6);
}
#[test]
fn encode_coded_block_flag_emits_one_bin() {
let mut enc = fresh_enc();
encode_coded_block_flag(&mut enc, true, 2, 0, 0, 0, true);
assert_eq!(enc.engine.bin_count(), 1);
}
#[test]
fn encode_residual_block_cabac_all_zero_just_cbf() {
let mut enc = fresh_enc();
let coeffs = [0i32; 16];
let result = encode_residual_block_cabac(&mut enc, &coeffs, 0, 15, 2, 0, 0, 0, true);
assert!(!result); assert_eq!(enc.engine.bin_count(), 1);
}
#[test]
fn encode_residual_block_cabac_single_coeff() {
let mut enc = fresh_enc();
let mut coeffs = [0i32; 16];
coeffs[0] = 1; let result = encode_residual_block_cabac(&mut enc, &coeffs, 0, 15, 2, 0, 0, 0, true);
assert!(result);
let n = enc.engine.bin_count();
assert!(n >= 4);
}
#[test]
fn zigzag_8x8_is_permutation() {
let mut seen = [false; 64];
for &v in ZIGZAG_8X8.iter() {
assert!(v < 64, "out-of-range entry {v}");
assert!(!seen[v as usize], "duplicate entry {v}");
seen[v as usize] = true;
}
assert!(seen.iter().all(|&b| b));
assert_eq!(ZIGZAG_8X8[0], 0);
assert_eq!(ZIGZAG_8X8[63], 63);
}
#[test]
fn sig_coeff_offset_8x8_in_range() {
for &v in SIG_COEFF_FLAG_OFFSET_8X8_FRAME.iter() {
assert!(v <= 14);
}
for &v in LAST_COEFF_FLAG_OFFSET_8X8_FRAME.iter() {
assert!(v <= 8);
}
}
#[test]
fn cat5_ctx_offsets_match_spec() {
assert_eq!(cat5_luma8x8::SIG_BASE, 402);
assert_eq!(cat5_luma8x8::LAST_BASE, 417);
assert_eq!(cat5_luma8x8::ABS_BASE, 426);
}
#[test]
fn encode_residual_block_cabac_8x8_all_zero_emits_single_sig_zero() {
let mut enc = fresh_enc();
let coeffs = [0i32; 64];
encode_residual_block_cabac_8x8(&mut enc, &coeffs);
assert_eq!(enc.engine.bin_count(), 63);
}
#[test]
fn encode_residual_block_cabac_8x8_single_coeff() {
let mut enc = fresh_enc();
let mut coeffs = [0i32; 64];
coeffs[0] = 1; encode_residual_block_cabac_8x8(&mut enc, &coeffs);
let n = enc.engine.bin_count();
assert!(n >= 4, "expected ≥ 4 bins for single-coef 8×8, got {n}");
}
#[test]
fn encode_residual_block_cabac_sign() {
let mut enc = fresh_enc();
let mut coeffs = [0i32; 16];
coeffs[0] = -2;
encode_residual_block_cabac(&mut enc, &coeffs, 0, 15, 2, 0, 0, 0, true);
assert!(enc.engine.bin_count() >= 5);
}
#[test]
fn encode_mb_skip_flag_zero() {
let mut enc = fresh_enc();
encode_mb_skip_flag(&mut enc, false, 0);
assert_eq!(enc.engine.bin_count(), 1);
}
#[test]
fn encode_mb_type_p_p_16x16() {
let mut enc = fresh_enc();
encode_mb_type_p(&mut enc, 0, 0);
assert_eq!(enc.engine.bin_count(), 3);
}
#[test]
fn encode_sub_mb_type_p_8x8() {
let mut enc = fresh_enc();
encode_sub_mb_type_p(&mut enc, 0);
assert_eq!(enc.engine.bin_count(), 1); }
#[test]
fn encode_ref_idx_zero_emits_single_bin() {
let mut enc = fresh_enc();
encode_ref_idx(&mut enc, 0, 0, 0, 0);
assert_eq!(enc.engine.bin_count(), 1); }
#[test]
fn encode_mvd_zero_prefix_only() {
let mut enc = fresh_enc();
encode_mvd(&mut enc, 0, 0, 0, 0, 0);
assert_eq!(enc.engine.bin_count(), 1);
}
#[test]
fn encode_mvd_negative_with_sign() {
let mut enc = fresh_enc();
encode_mvd(&mut enc, -3, 0, 0, 0, 0);
assert_eq!(enc.engine.bin_count(), 5);
}
#[test]
fn encode_mvd_saturated_with_suffix() {
let mut enc = fresh_enc();
encode_mvd(&mut enc, 9, 0, 0, 0, 0);
assert_eq!(enc.engine.bin_count(), 14);
}
#[test]
fn encode_mvd_sign_override_flips_bin_only() {
let mut a = fresh_enc();
encode_mvd(&mut a, 3, 0, 0, 0, 0);
let bins_a = a.engine.bin_count();
let mut b = fresh_enc();
encode_mvd_with_bin0_inc_sign_override(&mut b, 3, 0, 0, Some(1));
let bins_b = b.engine.bin_count();
assert_eq!(bins_a, bins_b,
"sign override must NOT change bin count (magnitude bins identical)");
let _ = a.finish();
let _ = b.finish();
}
#[test]
fn encode_mvd_sign_override_none_matches_baseline() {
let mut a = fresh_enc();
encode_mvd_with_bin0_inc(&mut a, -7, 1, 0);
let bytes_a = a.finish();
let mut b = fresh_enc();
encode_mvd_with_bin0_inc_sign_override(&mut b, -7, 1, 0, None);
let bytes_b = b.finish();
assert_eq!(bytes_a, bytes_b,
"override=None must produce byte-identical output to baseline");
}
#[test]
fn encode_mvd_sign_override_ignored_for_zero() {
let mut a = fresh_enc();
encode_mvd(&mut a, 0, 0, 0, 0, 0);
let bytes_a = a.finish();
let mut b = fresh_enc();
encode_mvd_with_bin0_inc_sign_override(&mut b, 0, 0, 0, Some(1));
let bytes_b = b.finish();
assert_eq!(bytes_a, bytes_b,
"override at mvd=0 must be a no-op (no sign bin emitted)");
}
#[test]
fn encode_mb_type_p_intra_in_p_i_nxn() {
let mut enc = fresh_enc();
encode_mb_type_p(&mut enc, 5, 0);
assert_eq!(enc.engine.bin_count(), 2); }
#[test]
fn encode_mb_type_p_intra_in_p_i_pcm() {
let mut enc = fresh_enc();
encode_mb_type_p(&mut enc, 30, 0);
assert_eq!(enc.engine.bin_count(), 3);
}
#[test]
fn encode_full_p_mb_inter_no_residual() {
let mut enc = fresh_enc();
encode_mb_skip_flag(&mut enc, false, 0);
encode_mb_type_p(&mut enc, 0, 0); encode_ref_idx(&mut enc, 0, 0, 0, 0); encode_mvd(&mut enc, 2, 0, 0, 0, 0); encode_mvd(&mut enc, -1, 1, 0, 0, 0); encode_coded_block_pattern(&mut enc, 0, 0); encode_mb_qp_delta(&mut enc, 0); encode_end_of_slice_flag(&mut enc, true);
let bytes = enc.finish();
assert!(!bytes.is_empty());
}
#[test]
fn encode_full_p_skip_mb() {
let mut enc = fresh_enc();
encode_mb_skip_flag(&mut enc, true, 0);
encode_end_of_slice_flag(&mut enc, true);
let bytes = enc.finish();
assert!(!bytes.is_empty());
}
#[test]
fn encode_full_p_8x8_with_sub_mb_types() {
let mut enc = fresh_enc();
encode_mb_skip_flag(&mut enc, false, 0);
encode_mb_type_p(&mut enc, 3, 0); for sub in &[0u32, 1, 2, 3] {
encode_sub_mb_type_p(&mut enc, *sub);
}
encode_mvd(&mut enc, 0, 0, 0, 0, 0);
encode_mvd(&mut enc, 0, 1, 0, 0, 0);
encode_coded_block_pattern(&mut enc, 0, 0);
encode_mb_qp_delta(&mut enc, 0);
encode_end_of_slice_flag(&mut enc, true);
let bytes = enc.finish();
assert!(!bytes.is_empty());
}
#[test]
fn encode_full_i_mb_i_nxn_4x4_modes() {
let mut enc = fresh_enc();
encode_mb_type_i(&mut enc, 0, 0); for _ in 0..16 {
encode_prev_intra4x4_pred_mode_flag(&mut enc, true);
}
encode_intra_chroma_pred_mode(&mut enc, 0, 0); encode_coded_block_pattern(&mut enc, 0, 0); encode_mb_qp_delta(&mut enc, 0);
encode_end_of_slice_flag(&mut enc, true);
let bytes = enc.finish();
assert!(!bytes.is_empty());
}
fn fresh_b_enc() -> CabacEncoder {
CabacEncoder::new_slice(CabacInitSlot::PIdc0, 26, 4)
}
#[test]
fn encode_mb_skip_flag_b_zero_emits_one_bin() {
let mut enc = fresh_b_enc();
encode_mb_skip_flag_b(&mut enc, false, 0);
assert_eq!(enc.engine.bin_count(), 1);
}
#[test]
fn encode_mb_skip_flag_b_one_emits_one_bin() {
let mut enc = fresh_b_enc();
encode_mb_skip_flag_b(&mut enc, true, 0);
assert_eq!(enc.engine.bin_count(), 1);
}
#[test]
fn encode_mb_type_b_direct_one_bin() {
let mut enc = fresh_b_enc();
encode_mb_type_b(&mut enc, 0, 0); assert_eq!(enc.engine.bin_count(), 1);
}
#[test]
fn encode_mb_type_b_l0_three_bins() {
let mut enc = fresh_b_enc();
encode_mb_type_b(&mut enc, 1, 0); assert_eq!(enc.engine.bin_count(), 3);
}
#[test]
fn encode_mb_type_b_l1_three_bins() {
let mut enc = fresh_b_enc();
encode_mb_type_b(&mut enc, 2, 0); assert_eq!(enc.engine.bin_count(), 3);
}
#[test]
fn encode_mb_type_b_bi_six_bins() {
let mut enc = fresh_b_enc();
encode_mb_type_b(&mut enc, 3, 0); assert_eq!(enc.engine.bin_count(), 6);
}
#[test]
fn encode_mb_type_b_8x8_six_bins() {
let mut enc = fresh_b_enc();
encode_mb_type_b(&mut enc, 22, 0); assert_eq!(enc.engine.bin_count(), 6);
}
#[test]
fn encode_full_b_skip_mb() {
let mut enc = fresh_b_enc();
encode_mb_skip_flag_b(&mut enc, true, 0);
encode_end_of_slice_flag(&mut enc, true);
let bytes = enc.finish();
assert!(!bytes.is_empty());
}
}