#![allow(dead_code)]
use super::cdf_tables::{
DEFAULT_MAG_CLASS_CDF, DEFAULT_NONZERO_CDF, DEFAULT_SIGN_CDF, DEFAULT_SKIP_CDF,
K_MAX, MAG_CLASS_CDF_LEN, MAG_CLASS_SYMS, NONZERO_CDF_LEN, SIGN_CDF_LEN, SKIP_CDF_LEN,
};
use super::ec::{BoolDecoder, BoolEncoder};
const N_POS_CLASSES: usize = 2;
pub struct CoeffCdfContext {
skip: [u16; SKIP_CDF_LEN],
nonzero: [[u16; NONZERO_CDF_LEN]; N_POS_CLASSES],
sign: [[u16; SIGN_CDF_LEN]; N_POS_CLASSES],
mag_class: [[u16; MAG_CLASS_CDF_LEN]; N_POS_CLASSES],
}
impl Default for CoeffCdfContext {
fn default() -> Self {
Self {
skip: DEFAULT_SKIP_CDF,
nonzero: [DEFAULT_NONZERO_CDF; N_POS_CLASSES],
sign: [DEFAULT_SIGN_CDF; N_POS_CLASSES],
mag_class: [DEFAULT_MAG_CLASS_CDF; N_POS_CLASSES],
}
}
}
impl CoeffCdfContext {
pub fn new() -> Self {
Self::default()
}
#[inline(always)]
fn pos_class(scan_idx: usize) -> usize {
(scan_idx != 0) as usize
}
}
pub fn encode_coeffs(enc: &mut BoolEncoder, ctx: &mut CoeffCdfContext, coeffs: &[i32; 16]) {
let all_zero = coeffs.iter().all(|&c| c == 0);
enc.encode_symbol_adapt(all_zero as usize, &mut ctx.skip, SKIP_CDF_LEN - 1);
if all_zero {
return;
}
for (i, &coeff) in coeffs.iter().enumerate() {
encode_single_coeff(enc, ctx, i, coeff);
}
}
pub fn decode_coeffs(dec: &mut BoolDecoder<'_>, ctx: &mut CoeffCdfContext) -> [i32; 16] {
let skip = dec.decode_symbol_adapt(&mut ctx.skip, SKIP_CDF_LEN - 1);
if skip != 0 {
return [0i32; 16];
}
let mut out = [0i32; 16];
for (i, v) in out.iter_mut().enumerate() {
*v = decode_single_coeff(dec, ctx, i);
}
out
}
fn encode_single_coeff(enc: &mut BoolEncoder, ctx: &mut CoeffCdfContext, scan_idx: usize, coeff: i32) {
let pc = CoeffCdfContext::pos_class(scan_idx);
let nz = (coeff != 0) as usize;
enc.encode_symbol_adapt(nz, &mut ctx.nonzero[pc], NONZERO_CDF_LEN - 1);
if coeff == 0 {
return;
}
let neg = (coeff < 0) as usize;
enc.encode_symbol_adapt(neg, &mut ctx.sign[pc], SIGN_CDF_LEN - 1);
let mag_minus_1 = coeff.unsigned_abs() - 1; let k = magnitude_class(mag_minus_1);
enc.encode_symbol_adapt(k, &mut ctx.mag_class[pc], MAG_CLASS_SYMS);
if k < K_MAX {
if k > 0 {
let suffix = (mag_minus_1 as u64 + 1) - (1u64 << k);
enc.write_bypass_uint(suffix, k as u32);
}
} else {
encode_exp_golomb(enc, mag_minus_1 as u64);
}
}
fn decode_single_coeff(dec: &mut BoolDecoder<'_>, ctx: &mut CoeffCdfContext, scan_idx: usize) -> i32 {
let pc = CoeffCdfContext::pos_class(scan_idx);
let nz = dec.decode_symbol_adapt(&mut ctx.nonzero[pc], NONZERO_CDF_LEN - 1);
if nz == 0 {
return 0;
}
let neg = dec.decode_symbol_adapt(&mut ctx.sign[pc], SIGN_CDF_LEN - 1);
let k = dec.decode_symbol_adapt(&mut ctx.mag_class[pc], MAG_CLASS_SYMS);
let mag_minus_1: u64 = if k < K_MAX {
if k == 0 {
0
} else {
let suffix = dec.read_bypass_uint(k as u32);
(1u64 << k) + suffix - 1
}
} else {
decode_exp_golomb(dec)
};
let mag = (mag_minus_1 + 1) as i32;
if neg != 0 { -mag } else { mag }
}
#[inline(always)]
fn magnitude_class(mag_minus_1: u32) -> usize {
if mag_minus_1 == 0 {
return 0;
}
let k = (u32::BITS - (mag_minus_1 + 1).leading_zeros() - 1) as usize;
k.min(K_MAX)
}
fn encode_exp_golomb(enc: &mut BoolEncoder, val: u64) {
let k = if val == 0 {
0u32
} else {
63 - (val + 1).leading_zeros()
};
for _ in 0..k {
enc.encode_bit(false);
}
enc.encode_bit(true);
if k > 0 {
let suffix = (val + 1) - (1u64 << k);
enc.write_bypass_uint(suffix, k);
}
}
fn decode_exp_golomb(dec: &mut BoolDecoder<'_>) -> u64 {
let mut k = 0u32;
loop {
let bit = dec.decode_bit();
if bit {
break; }
k += 1;
if k > 40 {
break;
}
}
if k == 0 {
return 0;
}
let suffix = dec.read_bypass_uint(k);
(1u64 << k) + suffix - 1
}
#[cfg(test)]
mod tests {
use super::*;
use crate::av1::ec::{BoolDecoder, BoolEncoder};
fn roundtrip_coeffs(coeffs: &[i32; 16]) -> [i32; 16] {
let mut enc = BoolEncoder::new();
let mut ectx = CoeffCdfContext::new();
encode_coeffs(&mut enc, &mut ectx, coeffs);
let bytes = enc.finish();
let mut dec = BoolDecoder::new(&bytes);
let mut dctx = CoeffCdfContext::new();
decode_coeffs(&mut dec, &mut dctx)
}
#[test]
fn test_all_zero_round_trip() {
let coeffs = [0i32; 16];
let decoded = roundtrip_coeffs(&coeffs);
assert_eq!(decoded, coeffs, "all-zero block must round-trip exactly");
}
#[test]
fn test_all_ones_round_trip() {
let coeffs = [1i32; 16];
let decoded = roundtrip_coeffs(&coeffs);
assert_eq!(decoded, coeffs);
}
#[test]
fn test_all_negative_ones_round_trip() {
let coeffs = [-1i32; 16];
let decoded = roundtrip_coeffs(&coeffs);
assert_eq!(decoded, coeffs);
}
#[test]
fn test_mixed_round_trip() {
let coeffs: [i32; 16] = [
100, -200, 0, 50, -50, 1, -1, 255, -255, 128, -128, 0, 0, 0, 4095, -4095,
];
let decoded = roundtrip_coeffs(&coeffs);
assert_eq!(decoded, coeffs);
}
#[test]
fn test_large_magnitudes_round_trip() {
let coeffs: [i32; 16] = [
4080, -4080, 2048, -2048, 1024, -1024, 512, -512, 256, -256, 127, -127, 63, -63, 31,
-31,
];
let decoded = roundtrip_coeffs(&coeffs);
assert_eq!(decoded, coeffs);
}
#[test]
fn test_dc_only_round_trip() {
let mut coeffs = [0i32; 16];
coeffs[0] = 1000;
let decoded = roundtrip_coeffs(&coeffs);
assert_eq!(decoded, coeffs);
}
#[test]
fn test_skip_flag_skips_all_coeffs() {
let coeffs = [0i32; 16];
let mut enc = BoolEncoder::new();
let mut ectx = CoeffCdfContext::new();
encode_coeffs(&mut enc, &mut ectx, &coeffs);
let bytes = enc.finish();
let mut dec = BoolDecoder::new(&bytes);
let mut dctx = CoeffCdfContext::new();
let decoded = decode_coeffs(&mut dec, &mut dctx);
assert_eq!(decoded, [0i32; 16]);
}
#[test]
fn test_exp_golomb_known_values() {
for val in [0u64, 1, 2, 3, 4, 7, 8, 15, 100, 1000, 4095] {
let mut enc = BoolEncoder::new();
encode_exp_golomb(&mut enc, val);
let bytes = enc.finish();
let mut dec = BoolDecoder::new(&bytes);
let got = decode_exp_golomb(&mut dec);
assert_eq!(got, val, "exp-Golomb mismatch for val={val}: got={got}");
}
}
#[test]
fn test_multiple_blocks_in_sequence() {
let blocks: [[i32; 16]; 4] = [
[0; 16],
[1, -1, 2, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[-50, 50, -50, 50, -50, 50, -50, 50, -50, 50, -50, 50, -50, 50, -50, 50],
];
let mut enc = BoolEncoder::new();
let mut ectx = CoeffCdfContext::new();
for block in &blocks {
encode_coeffs(&mut enc, &mut ectx, block);
}
let bytes = enc.finish();
let mut dec = BoolDecoder::new(&bytes);
let mut dctx = CoeffCdfContext::new();
for (i, expected) in blocks.iter().enumerate() {
let got = decode_coeffs(&mut dec, &mut dctx);
assert_eq!(got, *expected, "block {i} mismatch");
}
}
#[test]
fn test_low_entropy_compresses() {
let mut block = [0i32; 16];
block[0] = 7;
let mut enc_adapt = BoolEncoder::new();
let mut ctx_adapt = CoeffCdfContext::new();
for _ in 0..64 {
encode_coeffs(&mut enc_adapt, &mut ctx_adapt, &block);
}
let adaptive_len = enc_adapt.finish().len();
let mut enc_flat = BoolEncoder::new();
for _ in 0..64 {
let mut ctx_fresh = CoeffCdfContext::new();
encode_coeffs(&mut enc_flat, &mut ctx_fresh, &block);
}
let flat_len = enc_flat.finish().len();
assert!(
adaptive_len < flat_len,
"adaptive ({adaptive_len} bytes) should be smaller than per-block-fresh \
({flat_len} bytes) after 64 identical DC blocks"
);
let mut enc3 = BoolEncoder::new();
let mut ectx = CoeffCdfContext::new();
for _ in 0..64 {
encode_coeffs(&mut enc3, &mut ectx, &block);
}
let bytes = enc3.finish();
let mut dec = BoolDecoder::new(&bytes);
let mut dctx = CoeffCdfContext::new();
for i in 0..64 {
let got = decode_coeffs(&mut dec, &mut dctx);
assert_eq!(got, block, "round-trip mismatch at block {i}");
}
}
}