#![allow(dead_code)]
pub use crate::transform::{qp_c_table_lookup, qp_y_to_qp_c};
const NORM_ADJUST_4X4_V: [[i32; 3]; 6] = [
[10, 16, 13],
[11, 18, 14],
[13, 20, 16],
[14, 23, 18],
[16, 25, 20],
[18, 29, 23],
];
#[inline]
fn norm_adjust_4x4(m: usize, i: usize, j: usize) -> i32 {
let row = &NORM_ADJUST_4X4_V[m];
match (i & 1, j & 1) {
(0, 0) => row[0],
(1, 1) => row[1],
_ => row[2],
}
}
pub fn forward_core_4x4(x: &[i32; 16]) -> [i32; 16] {
let mut h = [0i32; 16];
for j in 0..4 {
let x0 = x[j];
let x1 = x[4 + j];
let x2 = x[8 + j];
let x3 = x[12 + j];
h[j] = x0 + x1 + x2 + x3;
h[4 + j] = 2 * x0 + x1 - x2 - 2 * x3;
h[8 + j] = x0 - x1 - x2 + x3;
h[12 + j] = x0 - 2 * x1 + 2 * x2 - x3;
}
let mut w = [0i32; 16];
for i in 0..4 {
let base = i * 4;
let h0 = h[base];
let h1 = h[base + 1];
let h2 = h[base + 2];
let h3 = h[base + 3];
w[base] = h0 + h1 + h2 + h3;
w[base + 1] = 2 * h0 + h1 - h2 - 2 * h3;
w[base + 2] = h0 - h1 - h2 + h3;
w[base + 3] = h0 - 2 * h1 + 2 * h2 - h3;
}
w
}
fn forward_mf_4x4(m: usize) -> [i32; 16] {
const MF_TABLE: [[i32; 3]; 6] = [
[13107, 5243, 8066],
[11916, 4660, 7490],
[10082, 4194, 6554],
[9362, 3647, 5825],
[8192, 3355, 5243],
[7282, 2893, 4559],
];
let mut mf = [0i32; 16];
for i in 0..4 {
for j in 0..4 {
let class = match (i & 1, j & 1) {
(0, 0) => 0,
(1, 1) => 1,
_ => 2,
};
mf[i * 4 + j] = MF_TABLE[m][class];
}
}
mf
}
fn forward_mf_for(m: usize) -> [i32; 16] {
forward_mf_4x4(m)
}
#[inline]
fn q_bits(qp: i32) -> i32 {
15 + qp / 6
}
pub fn quantize_4x4(w: &[i32; 16], qp: i32, is_intra: bool) -> [i32; 16] {
debug_assert!(
(0..=87).contains(&qp),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let m = (qp.rem_euclid(6)) as usize;
let mf = forward_mf_for(m);
let qb = q_bits(qp);
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 16];
for k in 0..16 {
let abs_w = (w[k] as i64).unsigned_abs();
let prod = abs_w * mf[k] as u64;
let level = ((prod as i64 + f) >> qb) as i32;
z[k] = if w[k] < 0 { -level } else { level };
}
z
}
pub fn quantize_4x4_ac(w: &[i32; 16], qp: i32, is_intra: bool) -> [i32; 16] {
let mut z = quantize_4x4(w, qp, is_intra);
z[0] = 0;
z
}
pub fn quantize_4x4_w(w: &[i32; 16], qp: i32, is_intra: bool, wscale: &[i32; 16]) -> [i32; 16] {
debug_assert!(
(0..=87).contains(&qp),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let m = (qp.rem_euclid(6)) as usize;
let mf = forward_mf_for(m);
let qb = q_bits(qp);
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 16];
for k in 0..16 {
let abs_w = (w[k] as i64).unsigned_abs();
let prod = (abs_w * mf[k] as u64 * 16) / (wscale[k].max(1) as u64);
let level = ((prod as i64 + f) >> qb) as i32;
z[k] = if w[k] < 0 { -level } else { level };
}
z
}
pub fn quantize_4x4_ac_w(w: &[i32; 16], qp: i32, is_intra: bool, wscale: &[i32; 16]) -> [i32; 16] {
let mut z = quantize_4x4_w(w, qp, is_intra, wscale);
z[0] = 0;
z
}
pub fn forward_hadamard_4x4(dc: &[i32; 16]) -> [i32; 16] {
let mut t = [0i32; 16];
for j in 0..4 {
t[j] = dc[j] + dc[4 + j] + dc[8 + j] + dc[12 + j];
t[4 + j] = dc[j] + dc[4 + j] - dc[8 + j] - dc[12 + j];
t[8 + j] = dc[j] - dc[4 + j] - dc[8 + j] + dc[12 + j];
t[12 + j] = dc[j] - dc[4 + j] + dc[8 + j] - dc[12 + j];
}
let mut f = [0i32; 16];
for i in 0..4 {
let base = i * 4;
let t0 = t[base];
let t1 = t[base + 1];
let t2 = t[base + 2];
let t3 = t[base + 3];
f[base] = t0 + t1 + t2 + t3;
f[base + 1] = t0 + t1 - t2 - t3;
f[base + 2] = t0 - t1 - t2 + t3;
f[base + 3] = t0 - t1 + t2 - t3;
}
f
}
pub fn quantize_luma_dc(coeff: &[i32; 16], qp: i32, is_intra: bool) -> [i32; 16] {
debug_assert!(
(0..=87).contains(&qp),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let m = (qp.rem_euclid(6)) as usize;
let mf = forward_mf_for(m)[0]; let qb = q_bits(qp) + 2;
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 16];
for k in 0..16 {
let abs_c = (coeff[k] as i64).unsigned_abs();
let prod = abs_c * mf as u64;
let level = ((prod as i64 + f) >> qb) as i32;
z[k] = if coeff[k] < 0 { -level } else { level };
}
z
}
pub fn quantize_luma_dc_w(coeff: &[i32; 16], qp: i32, is_intra: bool, w00: i32) -> [i32; 16] {
debug_assert!(
(0..=87).contains(&qp),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let m = (qp.rem_euclid(6)) as usize;
let mf = forward_mf_for(m)[0];
let qb = q_bits(qp) + 2;
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 16];
for k in 0..16 {
let abs_c = (coeff[k] as i64).unsigned_abs();
let prod = (abs_c * mf as u64 * 16) / (w00.max(1) as u64);
let level = ((prod as i64 + f) >> qb) as i32;
z[k] = if coeff[k] < 0 { -level } else { level };
}
z
}
pub fn forward_hadamard_2x2(dc: &[i32; 4]) -> [i32; 4] {
let c00 = dc[0];
let c01 = dc[1];
let c10 = dc[2];
let c11 = dc[3];
let t00 = c00 + c10;
let t01 = c01 + c11;
let t10 = c00 - c10;
let t11 = c01 - c11;
[t00 + t01, t00 - t01, t10 + t11, t10 - t11]
}
pub fn quantize_chroma_dc(coeff: &[i32; 4], qp_c: i32, is_intra: bool) -> [i32; 4] {
debug_assert!(
(0..=87).contains(&qp_c),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let m = (qp_c.rem_euclid(6)) as usize;
let mf = forward_mf_for(m)[0];
let qb = q_bits(qp_c) + 1;
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 4];
for k in 0..4 {
let abs_c = (coeff[k] as i64).unsigned_abs();
let prod = abs_c * mf as u64;
let level = ((prod as i64 + f) >> qb) as i32;
z[k] = if coeff[k] < 0 { -level } else { level };
}
z
}
pub fn quantize_chroma_dc_w(coeff: &[i32; 4], qp_c: i32, is_intra: bool, w00: i32) -> [i32; 4] {
debug_assert!(
(0..=87).contains(&qp_c),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let m = (qp_c.rem_euclid(6)) as usize;
let mf = forward_mf_for(m)[0];
let qb = q_bits(qp_c) + 1;
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 4];
for k in 0..4 {
let abs_c = (coeff[k] as i64).unsigned_abs();
let prod = (abs_c * mf as u64 * 16) / (w00.max(1) as u64);
let level = ((prod as i64 + f) >> qb) as i32;
z[k] = if coeff[k] < 0 { -level } else { level };
}
z
}
pub fn forward_hadamard_4x2(dc: &[i32; 8]) -> [i32; 8] {
let c00 = dc[0];
let c01 = dc[1];
let c10 = dc[2];
let c11 = dc[3];
let c20 = dc[4];
let c21 = dc[5];
let c30 = dc[6];
let c31 = dc[7];
let t00 = c00 + c10 + c20 + c30;
let t01 = c01 + c11 + c21 + c31;
let t10 = c00 + c10 - c20 - c30;
let t11 = c01 + c11 - c21 - c31;
let t20 = c00 - c10 - c20 + c30;
let t21 = c01 - c11 - c21 + c31;
let t30 = c00 - c10 + c20 - c30;
let t31 = c01 - c11 + c21 - c31;
[
t00 + t01,
t00 - t01,
t10 + t11,
t10 - t11,
t20 + t21,
t20 - t21,
t30 + t31,
t30 - t31,
]
}
pub fn quantize_chroma_dc_422(coeff: &[i32; 8], qp_c: i32, is_intra: bool) -> [i32; 8] {
quantize_chroma_dc_422_w(coeff, qp_c, is_intra, 16)
}
pub fn quantize_chroma_dc_422_w(coeff: &[i32; 8], qp_c: i32, is_intra: bool, w00: i32) -> [i32; 8] {
debug_assert!(
(0..=87).contains(&qp_c),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let qp_dc = qp_c + 3;
let m = (qp_dc.rem_euclid(6)) as usize;
let mf = forward_mf_for(m)[0];
let qb = q_bits(qp_dc) + 1;
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 8];
for k in 0..8 {
let abs_c = (coeff[k] as i64).unsigned_abs();
let prod = (abs_c * mf as u64 * 16) / (w00.max(1) as u64);
let level = ((prod as i64 + f) >> qb) as i32;
z[k] = if coeff[k] < 0 { -level } else { level };
}
z
}
pub fn scan_chroma_dc_422(rowmajor_4x2: &[i32; 8]) -> [i32; 8] {
let c00 = rowmajor_4x2[0];
let c01 = rowmajor_4x2[1];
let c10 = rowmajor_4x2[2];
let c11 = rowmajor_4x2[3];
let c20 = rowmajor_4x2[4];
let c21 = rowmajor_4x2[5];
let c30 = rowmajor_4x2[6];
let c31 = rowmajor_4x2[7];
[c00, c10, c01, c20, c30, c11, c21, c31]
}
fn approx_cabac_coeff_cost_16ths(abs_level: i32, pos: usize) -> i64 {
let _ = pos;
let sig: i64 = 16;
let sign: i64 = 16;
let abs = abs_level.max(0) as i64;
let prefix: i64 = if abs <= 1 {
16
} else if abs <= 14 {
12 * (abs - 1) + 16
} else {
12 * 13 + 16 + 24 * (abs - 14)
};
sig + sign + prefix
}
fn approx_cabac_zero_cost_16ths(pos: usize) -> i64 {
let p = pos.min(15) as i64;
8 - (p * 6) / 15
}
pub fn trellis_refine_4x4_ac(
residual: &[i32; 16],
w: &[i32; 16],
z: &[i32; 16],
qp: i32,
lambda_q16: u64,
skip_dc: bool,
) -> [i32; 16] {
let _ = w;
if lambda_q16 == 0 {
return *z;
}
let dequant_inverse = |levels: &[i32; 16]| -> [i32; 16] {
crate::transform::inverse_transform_4x4(levels, qp, &crate::transform::FLAT_4X4_16, 8)
.unwrap_or([0i32; 16])
};
let ssd = |r_hat: &[i32; 16]| -> u64 {
let mut s: u64 = 0;
for k in 0..16 {
let d = (residual[k] - r_hat[k]) as i64;
s = s.saturating_add((d * d) as u64);
}
s
};
let mut z_cur = *z;
let start_k = 15usize;
let end_k = if skip_dc { 1usize } else { 0usize };
let mut r_base = dequant_inverse(&z_cur);
let mut d_base = ssd(&r_base);
let mut r_base_16 = d_base.saturating_mul(16);
let count_nz = |levels: &[i32; 16]| -> usize { levels.iter().filter(|&&v| v != 0).count() };
let approx_block_rate_16ths = |levels: &[i32; 16]| -> i64 {
let mut r: i64 = 0;
let mut last_nz: i32 = -1;
for (k, &lvl) in levels.iter().enumerate().take(16).skip(1) {
if lvl != 0 {
last_nz = k as i32;
}
}
for (k, &lvl) in levels.iter().enumerate().take(16).skip(1) {
if lvl != 0 {
r += approx_cabac_coeff_cost_16ths(lvl.unsigned_abs() as i32, k);
r += 16;
} else if (k as i32) < last_nz {
r += approx_cabac_zero_cost_16ths(k);
}
}
if last_nz >= 0 {
r += 16;
}
r
};
let mut r_rate_16 = approx_block_rate_16ths(&z_cur);
let mut j_base = r_base_16.saturating_add((lambda_q16 as i64 * r_rate_16) as u64);
for k in (end_k..=start_k).rev() {
let lvl = z_cur[k];
if lvl == 0 {
continue;
}
let cand = lvl - lvl.signum();
let mut z_try = z_cur;
z_try[k] = cand;
let r_hat = dequant_inverse(&z_try);
let d_try = ssd(&r_hat);
let d_try_16 = d_try.saturating_mul(16);
let r_try_16 = approx_block_rate_16ths(&z_try);
let j_try = d_try_16.saturating_add((lambda_q16 as i64 * r_try_16) as u64);
if j_try < j_base {
z_cur = z_try;
r_base = r_hat;
d_base = d_try;
r_base_16 = d_try_16;
r_rate_16 = r_try_16;
j_base = j_try;
if z_cur[k] != 0 {
let lvl2 = z_cur[k];
let cand2 = lvl2 - lvl2.signum();
let mut z_try2 = z_cur;
z_try2[k] = cand2;
let r_hat2 = dequant_inverse(&z_try2);
let d_try2 = ssd(&r_hat2);
let d_try2_16 = d_try2.saturating_mul(16);
let r_try2_16 = approx_block_rate_16ths(&z_try2);
let j_try2 = d_try2_16.saturating_add((lambda_q16 as i64 * r_try2_16) as u64);
if j_try2 < j_base {
z_cur = z_try2;
r_base = r_hat2;
d_base = d_try2;
r_base_16 = d_try2_16;
r_rate_16 = r_try2_16;
j_base = j_try2;
}
}
}
}
let _ = (r_base, d_base, r_base_16, r_rate_16, count_nz);
z_cur
}
pub fn trellis_lambda_q16(qp: i32) -> u64 {
let qp = qp.clamp(0, 51) as f64;
let lambda = 0.85_f64 * 2f64.powf((qp - 12.0) / 3.0) * (2.0 / 3.0);
(lambda.max(0.0).round() as u64).max(1)
}
pub const ZIGZAG_4X4_FWD: [usize; 16] = [0, 1, 5, 6, 2, 4, 7, 12, 3, 8, 11, 13, 9, 10, 14, 15];
pub fn zigzag_scan_4x4(coeffs: &[i32; 16]) -> [i32; 16] {
let mut out = [0i32; 16];
for (raster, &scan) in ZIGZAG_4X4_FWD.iter().enumerate() {
out[scan] = coeffs[raster];
}
out
}
pub fn zigzag_scan_4x4_ac(coeffs: &[i32; 16]) -> [i32; 16] {
let mut out = [0i32; 16];
for raster in 1..16 {
let scan = ZIGZAG_4X4_FWD[raster];
debug_assert!(scan >= 1);
out[scan - 1] = coeffs[raster];
}
out
}
const FORWARD_8X8_E: [[i32; 8]; 8] = [
[8, 8, 8, 8, 8, 8, 8, 8],
[12, 10, 6, 3, -3, -6, -10, -12],
[8, 4, -4, -8, -8, -4, 4, 8],
[10, -3, -12, -6, 6, 12, 3, -10],
[8, -8, -8, 8, 8, -8, -8, 8],
[6, -12, 3, 10, -10, -3, 12, -6],
[4, -8, 8, -4, -4, 8, -8, 4],
[3, -6, 10, -12, 12, -10, 6, -3],
];
pub fn forward_core_8x8(x: &[i32; 64]) -> [i32; 64] {
let mut h = [0i64; 64];
for r in 0..8 {
for j in 0..8 {
let mut acc = 0i64;
for k in 0..8 {
acc += FORWARD_8X8_E[r][k] as i64 * x[k * 8 + j] as i64;
}
h[r * 8 + j] = acc;
}
}
let mut w = [0i32; 64];
for i in 0..8 {
for c in 0..8 {
let mut acc = 0i64;
for k in 0..8 {
acc += h[i * 8 + k] * FORWARD_8X8_E[c][k] as i64;
}
w[i * 8 + c] = acc as i32;
}
}
w
}
const NORM_ADJUST_8X8_V: [[i32; 6]; 6] = [
[20, 18, 32, 19, 25, 24],
[22, 19, 35, 21, 28, 26],
[26, 23, 42, 24, 33, 31],
[28, 25, 45, 26, 35, 33],
[32, 28, 51, 30, 40, 38],
[36, 32, 58, 34, 46, 43],
];
#[inline]
fn norm_adjust_8x8_class(i: usize, j: usize) -> usize {
let im4 = i & 3;
let jm4 = j & 3;
let im2 = i & 1;
let jm2 = j & 1;
if im4 == 0 && jm4 == 0 {
0
} else if im2 == 1 && jm2 == 1 {
1
} else if im4 == 2 && jm4 == 2 {
2
} else if (im4 == 0 && jm2 == 1) || (im2 == 1 && jm4 == 0) {
3
} else if (im4 == 0 && jm4 == 2) || (im4 == 2 && jm4 == 0) {
4
} else {
5
}
}
#[inline]
fn forward_mf_8x8(m: usize, class: usize) -> i64 {
let v = NORM_ADJUST_8X8_V[m][class] as i64;
((1i64 << 18) + v / 2) / v
}
#[inline]
fn q_bits_8x8(qp: i32) -> i32 {
22 + qp / 6
}
pub fn quantize_8x8(w: &[i32; 64], qp: i32, is_intra: bool) -> [i32; 64] {
debug_assert!(
(0..=87).contains(&qp),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let m = qp.rem_euclid(6) as usize;
let qb = q_bits_8x8(qp);
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 64];
for i in 0..8 {
for j in 0..8 {
let idx = i * 8 + j;
let class = norm_adjust_8x8_class(i, j);
let mf = forward_mf_8x8(m, class);
let abs_w = (w[idx] as i64).unsigned_abs();
let level = ((abs_w as i64 * mf + f) >> qb) as i32;
z[idx] = if w[idx] < 0 { -level } else { level };
}
}
z
}
pub fn quantize_8x8_w(w: &[i32; 64], qp: i32, is_intra: bool, wscale: &[i32; 64]) -> [i32; 64] {
debug_assert!(
(0..=87).contains(&qp),
"qP′ range 0..=51 + QpBdOffset (14-bit: 87)"
);
let m = qp.rem_euclid(6) as usize;
let qb = q_bits_8x8(qp);
let f = if is_intra {
(1i64 << qb) / 3
} else {
(1i64 << qb) / 6
};
let mut z = [0i32; 64];
for i in 0..8 {
for j in 0..8 {
let idx = i * 8 + j;
let class = norm_adjust_8x8_class(i, j);
let mf = forward_mf_8x8(m, class);
let abs_w = (w[idx] as i64).unsigned_abs() as i64;
let prod = (abs_w * mf * 16) / (wscale[idx].max(1) as i64);
let level = ((prod + f) >> qb) as i32;
z[idx] = if w[idx] < 0 { -level } else { level };
}
}
z
}
pub const ZIGZAG_8X8_FWD: [usize; 64] = [
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,
52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
];
pub fn zigzag_scan_8x8(coeffs: &[i32; 64]) -> [i32; 64] {
let mut out = [0i32; 64];
for (scan, &raster) in ZIGZAG_8X8_FWD.iter().enumerate() {
out[scan] = coeffs[raster];
}
out
}
#[rustfmt::skip]
pub const FIELD_8X8_FWD: [usize; 64] = [
0, 8, 16, 1, 9, 24, 32, 17, 2, 25, 40, 48, 56, 33, 10, 3,
18, 41, 49, 57, 26, 11, 4, 19, 34, 42, 50, 58, 27, 12, 5, 20,
35, 43, 51, 59, 28, 13, 6, 21, 36, 44, 52, 60, 29, 14, 22, 37,
45, 53, 61, 30, 7, 15, 38, 46, 54, 62, 23, 31, 39, 47, 55, 63,
];
pub fn field_scan_8x8(coeffs: &[i32; 64]) -> [i32; 64] {
let mut out = [0i32; 64];
for (scan, &raster) in FIELD_8X8_FWD.iter().enumerate() {
out[scan] = coeffs[raster];
}
out
}
#[rustfmt::skip]
pub const FIELD_8X8_FWD_CAVLC_SPLIT: [usize; 64] = [
0, 8, 16, 1, 2, 25, 40, 48, 9, 24, 32, 17, 56, 33, 10, 3,
26, 11, 4, 19, 35, 43, 51, 59, 45, 53, 61, 30, 28, 13, 6, 21,
34, 42, 50, 58, 18, 41, 49, 57, 27, 12, 5, 20, 36, 44, 52, 60,
7, 15, 38, 46, 54, 62, 23, 31, 29, 14, 22, 37, 39, 47, 55, 63,
];
pub fn field_scan_8x8_for_cavlc_split(coeffs: &[i32; 64]) -> [i32; 64] {
let mut out = [0i32; 64];
for (scan, &raster) in FIELD_8X8_FWD_CAVLC_SPLIT.iter().enumerate() {
out[scan] = coeffs[raster];
}
out
}
pub fn scan_8x8_for_cavlc(coeffs: &[i32; 64], field: bool) -> [i32; 64] {
if field {
field_scan_8x8_for_cavlc_split(coeffs)
} else {
zigzag_scan_8x8(coeffs)
}
}
pub fn deinterleave_8x8_to_4x4(scan8: &[i32; 64]) -> [[i32; 16]; 4] {
let mut blocks = [[0i32; 16]; 4];
for i4x4 in 0..4 {
for i in 0..16 {
blocks[i4x4][i] = scan8[4 * i + i4x4];
}
}
blocks
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transform::{default_scaling_list_8x8_flat, inverse_transform_8x8};
use crate::transform::{
inverse_hadamard_chroma_dc_420, inverse_hadamard_luma_dc_16x16, inverse_scan_4x4_zigzag,
inverse_transform_4x4, FLAT_4X4_16,
};
#[test]
fn forward_8x8_basis_is_orthogonal() {
for (a, row_a) in FORWARD_8X8_E.iter().enumerate() {
for (b, row_b) in FORWARD_8X8_E.iter().enumerate() {
let dot: i32 = (0..8).map(|k| row_a[k] * row_b[k]).sum();
if a == b {
assert!(dot > 0, "row {a} has zero norm");
} else {
assert_eq!(dot, 0, "rows {a},{b} not orthogonal");
}
}
}
}
#[test]
fn forward_8x8_zigzag_inverts_decoder_scan() {
let raster: [i32; 64] = std::array::from_fn(|k| k as i32);
let scan = zigzag_scan_8x8(&raster);
let mut back = [0i32; 64];
for (s, &r) in ZIGZAG_8X8_FWD.iter().enumerate() {
back[r] = scan[s];
}
assert_eq!(back, raster);
}
#[test]
fn forward_8x8_zero_residual_round_trips_to_zero() {
let sl8 = default_scaling_list_8x8_flat();
for &qp in &[0, 12, 26, 36, 51] {
let w = forward_core_8x8(&[0; 64]);
assert_eq!(w, [0; 64]);
let z = quantize_8x8(&w, qp, true);
assert_eq!(z, [0; 64]);
let r = inverse_transform_8x8(&z, qp, &sl8, 8).unwrap();
assert_eq!(r, [0; 64]);
}
}
#[test]
fn forward_8x8_round_trip_recovers_residual() {
let sl8 = default_scaling_list_8x8_flat();
let mut res = [0i32; 64];
for i in 0..8 {
for j in 0..8 {
let ramp = (i as i32 * 6 + j as i32 * 3) - 42;
let ripple = if (i + j) % 2 == 0 { 5 } else { -5 };
res[i * 8 + j] = ramp + ripple;
}
}
for &qp in &[16, 22, 26, 30] {
let w = forward_core_8x8(&res);
let z_raster = quantize_8x8(&w, qp, true);
let scan = zigzag_scan_8x8(&z_raster);
let blocks = deinterleave_8x8_to_4x4(&scan);
let mut scan_back = [0i32; 64];
for (i4x4, blk) in blocks.iter().enumerate() {
for (i, &c) in blk.iter().enumerate() {
scan_back[4 * i + i4x4] = c;
}
}
assert_eq!(scan_back, scan, "de-interleave round trip failed");
let mut coeffs = [0i32; 64];
for (s, &r) in ZIGZAG_8X8_FWD.iter().enumerate() {
coeffs[r] = scan[s];
}
let recon = inverse_transform_8x8(&coeffs, qp, &sl8, 8).unwrap();
let step = 1 << (qp / 6);
let max_err = (0..64).map(|k| (recon[k] - res[k]).abs()).max().unwrap();
assert!(
max_err <= 4 * step + 2,
"qp={qp} max_err={max_err} step={step}"
);
}
}
#[test]
fn forward_inverse_4x4_zero_residual_round_trips_to_zero() {
let zero = [0i32; 16];
let qp = 26;
let w = forward_core_4x4(&zero);
assert_eq!(w, [0; 16]);
let z = quantize_4x4(&w, qp, true);
assert_eq!(z, [0; 16]);
let r = inverse_transform_4x4(&z, qp, &FLAT_4X4_16, 8).unwrap();
assert_eq!(r, [0; 16]);
}
#[test]
fn forward_inverse_4x4_large_residual_round_trips_within_tolerance() {
let r: [i32; 16] = [
80, -64, 48, -32, 64, -48, 32, -16, 48, -32, 16, 0, 32, -16, 0, 16,
];
let qp = 26;
let w = forward_core_4x4(&r);
let z_raster = quantize_4x4(&w, qp, true);
let r_hat = inverse_transform_4x4(&z_raster, qp, &FLAT_4X4_16, 8).unwrap();
for k in 0..16 {
let err = (r_hat[k] - r[k]).abs();
assert!(
err <= 16,
"k={k}: r={} r_hat={} err={}",
r[k],
r_hat[k],
err
);
}
}
#[test]
fn zigzag_round_trip() {
let raster: [i32; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let scan = zigzag_scan_4x4(&raster);
let raster_back = inverse_scan_4x4_zigzag(&scan);
assert_eq!(raster_back, raster);
}
#[test]
fn zigzag_ac_round_trip() {
use crate::transform::inverse_scan_4x4_zigzag_ac;
let raster: [i32; 16] = [99, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let scan_ac = zigzag_scan_4x4_ac(&raster);
let raster_back = inverse_scan_4x4_zigzag_ac(&scan_ac);
assert_eq!(raster_back[0], 0);
for k in 1..16 {
assert_eq!(
raster_back[k], raster[k],
"k={k} expected {} got {}",
raster[k], raster_back[k]
);
}
}
#[test]
fn luma_dc_uniform_residual_round_trips_within_tolerance() {
let qp = 26;
for d in [16, 32, -32, 64, -64, 128] {
let mut dc_coeffs = [0i32; 16];
for slot in &mut dc_coeffs {
*slot = 16 * d;
}
let f = forward_hadamard_4x4(&dc_coeffs);
let z = quantize_luma_dc(&f, qp, true);
let dc_y = inverse_hadamard_luma_dc_16x16(&z, qp, &FLAT_4X4_16, 8).unwrap();
for (k, &dc) in dc_y.iter().enumerate() {
let r = (dc + 32) >> 6;
let err = (r - d).abs();
assert!(err <= (d.abs() / 4 + 8), "d={d} k={k} r={r} err={err}");
}
}
}
#[test]
fn chroma_dc_uniform_residual_round_trips_within_tolerance() {
let qp_c = 26;
for d in [16, 32, -32, 64, -64, 128] {
let dc_coeffs = [16 * d; 4];
let f = forward_hadamard_2x2(&dc_coeffs);
let z = quantize_chroma_dc(&f, qp_c, true);
let dc_c = inverse_hadamard_chroma_dc_420(&z, qp_c, &FLAT_4X4_16, 8).unwrap();
for (k, &dc) in dc_c.iter().enumerate() {
let r = (dc + 32) >> 6;
let err = (r - d).abs();
assert!(err <= (d.abs() / 4 + 8), "d={d} k={k} r={r} err={err}");
}
}
}
#[test]
fn chroma_dc_422_uniform_residual_round_trips_within_tolerance() {
use crate::transform::inverse_hadamard_chroma_dc_422;
let qp_c = 26;
for d in [16i32, 32, -32, 64, -64, 128] {
let dc_coeffs = [16 * d; 8];
let f = forward_hadamard_4x2(&dc_coeffs);
let z = quantize_chroma_dc_422(&f, qp_c, true);
let scan = scan_chroma_dc_422(&z);
let dc_c = inverse_hadamard_chroma_dc_422(&scan, qp_c, &FLAT_4X4_16, 8).unwrap();
for (k, &dc) in dc_c.iter().enumerate() {
let r = (dc + 32) >> 6;
let err = (r - d).abs();
assert!(err <= (d.abs() / 4 + 16), "d={d} k={k} r={r} err={err}",);
}
}
}
#[test]
fn chroma_dc_422_scan_matches_decoder_inverse() {
let rm: [i32; 8] = [10, 20, 30, 40, 50, 60, 70, 80];
let scan = scan_chroma_dc_422(&rm);
assert_eq!(scan, [10, 30, 20, 50, 70, 40, 60, 80]);
}
}
#[derive(Debug, Clone)]
pub struct TrellisCtxCosts {
sig: [[i64; 2]; 16],
last: [[i64; 2]; 16],
abs0: [[i64; 2]; 5],
abs1: [[i64; 2]; 5],
}
fn bin_cost_table_16ths() -> [[i64; 2]; 64] {
let mut t = [[0i64; 2]; 64];
for (st, row) in t.iter_mut().enumerate() {
let mut p = 0.0f64;
for q in 0..4usize {
let r = 256.0 + 64.0 * q as f64 + 32.0;
p += crate::cabac::RANGE_TAB_LPS[st][q] as f64 / r;
}
p /= 4.0;
let p = p.clamp(1e-4, 0.5);
row[0] = (-(p.log2()) * 16.0).round() as i64;
row[1] = (-((1.0 - p).log2()) * 16.0).round() as i64;
}
t
}
impl TrellisCtxCosts {
pub fn new(
slice_kind: crate::cabac_ctx::SliceKind,
cabac_init_idc: Option<u32>,
slice_qp: i32,
cat: u32,
) -> Self {
let ctxs = crate::cabac_ctx::CabacContexts::init(slice_kind, cabac_init_idc, slice_qp)
.expect("context init");
let tab = bin_cost_table_16ths();
let (sig_last_off, abs_off): (usize, usize) = match cat {
0 => (0, 0),
1 => (15, 10),
2 => (29, 20),
3 => (44, 30),
_ => (47, 39),
};
let cost = |ctx_idx: usize, bin: u32| -> i64 {
let st = ctxs.at(ctx_idx);
let mps = u32::from(st.val_mps);
let row = tab[st.state_idx as usize];
if bin == mps {
row[1]
} else {
row[0]
}
};
let mut sig = [[0i64; 2]; 16];
let mut last = [[0i64; 2]; 16];
for (i, (s, l)) in sig.iter_mut().zip(last.iter_mut()).enumerate() {
*s = [
cost(105 + sig_last_off + i, 0),
cost(105 + sig_last_off + i, 1),
];
*l = [
cost(166 + sig_last_off + i, 0),
cost(166 + sig_last_off + i, 1),
];
}
let mut abs0 = [[0i64; 2]; 5];
let mut abs1 = [[0i64; 2]; 5];
for k in 0..5usize {
abs0[k] = [cost(227 + abs_off + k, 0), cost(227 + abs_off + k, 1)];
abs1[k] = [
cost(227 + abs_off + 5 + k, 0),
cost(227 + abs_off + 5 + k, 1),
];
}
Self {
sig,
last,
abs0,
abs1,
}
}
fn abs_level_cost(&self, abs_level: i32, eq1: usize, gt1: usize) -> i64 {
let m = abs_level - 1;
let inc0 = if gt1 != 0 { 0 } else { (1 + eq1).min(4) };
let incn = gt1.min(4);
let mut r = if m == 0 {
self.abs0[inc0][0]
} else {
self.abs0[inc0][1]
};
if m > 0 {
let ones = (m - 1).min(13) as i64;
r += ones * self.abs1[incn][1];
if m < 14 {
r += self.abs1[incn][0];
} else {
let v = (m - 14) as u32 + 1;
let k = 32 - v.leading_zeros() - 1;
r += 16 * (2 * k as i64 + 1);
}
}
r + 16 }
}
fn trellis_ctx_costs_cached(
slice_kind: crate::cabac_ctx::SliceKind,
cabac_init_idc: Option<u32>,
qp: i32,
cat: u32,
) -> std::rc::Rc<TrellisCtxCosts> {
use std::cell::RefCell;
use std::collections::HashMap;
type Key = (u8, Option<u32>, i32, u32);
thread_local! {
static CACHE: RefCell<HashMap<Key, std::rc::Rc<TrellisCtxCosts>>> =
RefCell::new(HashMap::new());
}
let key = (slice_kind as u8, cabac_init_idc, qp, cat);
CACHE.with(|c| {
c.borrow_mut()
.entry(key)
.or_insert_with(|| {
std::rc::Rc::new(TrellisCtxCosts::new(slice_kind, cabac_init_idc, qp, cat))
})
.clone()
})
}
fn coefficient_energy_weights(qp: i32) -> [f64; 16] {
let m = qp.rem_euclid(6) as usize;
let mf = forward_mf_for(m);
let qb = q_bits(qp);
let mut g = [0f64; 16];
for k in 0..16 {
let mut z = [0i32; 16];
let level = 64i32;
z[k] = level;
let r = crate::transform::inverse_transform_4x4(&z, qp, &crate::transform::FLAT_4X4_16, 8)
.unwrap_or([0i32; 16]);
let e: f64 = r.iter().map(|&v| (v as f64) * (v as f64)).sum();
let step = (1u64 << qb) as f64 / mf[k] as f64;
let dw = level as f64 * step;
g[k] = if dw > 0.0 { e / (dw * dw) } else { 0.0 };
}
g
}
#[allow(clippy::too_many_arguments)]
pub fn trellis_refine_4x4_ac_full(
residual: &[i32; 16],
w: &[i32; 16],
z: &[i32; 16],
qp: i32,
lambda_q16: u64,
skip_dc: bool,
cat: u32,
slice_kind: crate::cabac_ctx::SliceKind,
cabac_init_idc: Option<u32>,
) -> [i32; 16] {
if lambda_q16 == 0 {
return *z;
}
let costs = trellis_ctx_costs_cached(slice_kind, cabac_init_idc, qp, cat);
let g = coefficient_energy_weights(qp);
let m = qp.rem_euclid(6) as usize;
let mf = forward_mf_for(m);
let qb = q_bits(qp);
let lam = lambda_q16 as f64;
let first = usize::from(skip_dc);
let mut raster_of: Vec<usize> = vec![0; 16];
for (raster, &scan) in ZIGZAG_4X4_FWD.iter().enumerate() {
raster_of[scan] = raster;
}
let list: Vec<usize> = (first..16).map(|s| raster_of[s]).collect();
let n = list.len();
let absw: Vec<f64> = list.iter().map(|&k| (w[k] as f64).abs()).collect();
let q: Vec<i32> = list.iter().map(|&k| z[k].abs()).collect();
let step: Vec<f64> = list
.iter()
.map(|&k| (1u64 << qb) as f64 / mf[k] as f64)
.collect();
let gw: Vec<f64> = list.iter().map(|&k| g[k]).collect();
if q.iter().all(|&v| v == 0) {
return *z;
}
let dist = |i: usize, l: i32| -> f64 {
let d = absw[i] - l as f64 * step[i];
16.0 * gw[i] * d * d
};
let cands = |i: usize| -> Vec<i32> {
let mut c = vec![0];
if q[i] >= 1 {
c.push(q[i]);
}
if q[i] >= 2 {
c.push(q[i] - 1);
}
c
};
let mut best_j = (0..n).map(|i| dist(i, 0)).sum::<f64>();
let mut best_levels: Vec<i32> = vec![0; n];
const NS: usize = 25;
let idx = |eq1: usize, gt1: usize| eq1 * 5 + gt1;
for last in (0..n).rev() {
if q[last] == 0 {
continue;
}
let tail_d: f64 = (last + 1..n).map(|i| dist(i, 0)).sum();
let mut cur = vec![(f64::INFINITY, Vec::<i32>::new()); NS];
for &l in &cands(last) {
if l == 0 {
continue;
}
let r = costs.sig[last][1] + costs.last[last][1] + costs.abs_level_cost(l, 0, 0) + 16;
let j = tail_d + dist(last, l) + lam * r as f64;
let eq1 = usize::from(l == 1);
let gt1 = usize::from(l > 1);
let s = idx(eq1, gt1);
if j < cur[s].0 {
let mut lv = vec![0i32; n];
lv[last] = l;
cur[s] = (j, lv);
}
}
for i in (0..last).rev() {
let mut next = vec![(f64::INFINITY, Vec::<i32>::new()); NS];
for (s, entry) in cur.iter().enumerate() {
if !entry.0.is_finite() {
continue;
}
let (eq1, gt1) = (s / 5, s % 5);
for &l in &cands(i) {
let (j, ns) = if l == 0 {
(cur[s].0 + dist(i, 0) + lam * costs.sig[i][0] as f64, s)
} else {
let r =
costs.sig[i][1] + costs.last[i][0] + costs.abs_level_cost(l, eq1, gt1);
let ne = (eq1 + usize::from(l == 1)).min(4);
let ng = (gt1 + usize::from(l > 1)).min(4);
(cur[s].0 + dist(i, l) + lam * r as f64, idx(ne, ng))
};
if j < next[ns].0 {
let mut lv = cur[s].1.clone();
lv[i] = l;
next[ns] = (j, lv);
}
}
}
cur = next;
}
for (j, lv) in cur.into_iter() {
if j < best_j {
best_j = j;
best_levels = lv;
}
}
}
let mut z_full = *z;
for (i, &k) in list.iter().enumerate() {
let l = best_levels[i];
z_full[k] = if w[k] < 0 { -l } else { l };
}
if skip_dc {
z_full[0] = z[0];
}
let rate_model = |levels: &[i32; 16]| -> i64 {
let lv: Vec<i32> = list.iter().map(|&k| levels[k].abs()).collect();
let last = match lv.iter().rposition(|&v| v != 0) {
Some(p) => p,
None => return 0,
};
let (mut eq1, mut gt1) = (0usize, 0usize);
let mut r = 16i64;
for i in (0..=last).rev() {
let l = lv[i];
if l == 0 {
r += costs.sig[i][0];
} else {
r += costs.sig[i][1] + costs.last[i][usize::from(i == last)];
r += costs.abs_level_cost(l, eq1, gt1);
eq1 = (eq1 + usize::from(l == 1)).min(4);
gt1 = (gt1 + usize::from(l > 1)).min(4);
}
}
r
};
let spatial_j = |levels: &[i32; 16]| -> f64 {
let r_hat =
crate::transform::inverse_transform_4x4(levels, qp, &crate::transform::FLAT_4X4_16, 8)
.unwrap_or([0i32; 16]);
let mut ssd = 0f64;
for k in 0..16 {
let d = (residual[k] - r_hat[k]) as f64;
ssd += d * d;
}
16.0 * ssd + lam * rate_model(levels) as f64
};
let z_lite = trellis_refine_4x4_ac(residual, w, z, qp, lambda_q16, skip_dc);
let mut best = (*z, spatial_j(z));
for cand in [z_lite, z_full] {
let j = spatial_j(&cand);
if j < best.1 {
best = (cand, j);
}
}
best.0
}
thread_local! {
static TRELLIS_FULL: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn set_trellis_full(full: bool) {
TRELLIS_FULL.with(|f| f.set(full));
}
#[allow(clippy::too_many_arguments)]
pub fn trellis_refine_4x4_ac_dispatch(
residual: &[i32; 16],
w: &[i32; 16],
z: &[i32; 16],
qp: i32,
lambda_q16: u64,
skip_dc: bool,
cat: u32,
slice_kind: crate::cabac_ctx::SliceKind,
cabac_init_idc: Option<u32>,
) -> [i32; 16] {
if TRELLIS_FULL.with(|f| f.get()) {
trellis_refine_4x4_ac_full(
residual,
w,
z,
qp,
lambda_q16,
skip_dc,
cat,
slice_kind,
cabac_init_idc,
)
} else {
trellis_refine_4x4_ac(residual, w, z, qp, lambda_q16, skip_dc)
}
}