const NORM_ADJUST: [[i32; 3]; 6] = [
[10, 16, 13],
[11, 18, 14],
[13, 20, 16],
[14, 23, 18],
[16, 25, 20],
[18, 29, 23],
];
const QUANT_MF: [[i32; 3]; 6] = [
[13107, 5243, 8066],
[11916, 4660, 7490],
[10082, 4194, 6554],
[9362, 3647, 5825],
[8192, 3355, 5243],
[7282, 2893, 4559],
];
#[inline]
fn pos_group(i: usize, j: usize) -> usize {
match (i % 2, j % 2) {
(0, 0) => 0,
(1, 1) => 1,
_ => 2,
}
}
const POS_GROUP_FLAT: [usize; 16] = [0, 2, 0, 2, 2, 1, 2, 1, 0, 2, 0, 2, 2, 1, 2, 1];
const fn flatten_level_scale() -> [[i32; 16]; 6] {
let mut out = [[0i32; 16]; 6];
let mut m = 0;
while m < 6 {
let mut idx = 0;
while idx < 16 {
out[m][idx] = 16 * NORM_ADJUST[m][POS_GROUP_FLAT[idx]];
idx += 1;
}
m += 1;
}
out
}
const LEVEL_SCALE_FLAT: [[i32; 16]; 6] = flatten_level_scale();
#[inline]
fn fwd_1d(x0: i32, x1: i32, x2: i32, x3: i32) -> (i32, i32, i32, i32) {
let t0 = x0 + x3;
let t1 = x1 + x2;
let t2 = x1 - x2;
let t3 = x0 - x3;
(t0 + t1, 2 * t3 + t2, t0 - t1, t3 - 2 * t2)
}
#[inline]
fn inv_1d(d0: i32, d1: i32, d2: i32, d3: i32) -> (i32, i32, i32, i32) {
let e0 = d0 + d2;
let e1 = d0 - d2;
let e2 = (d1 >> 1) - d3;
let e3 = d1 + (d3 >> 1);
(e0 + e3, e1 + e2, e1 - e2, e0 - e3)
}
pub fn forward_core(block: &[i32; 16]) -> [i32; 16] {
let mut m = *block;
for r in 0..4 {
let (a, b, c, d) = fwd_1d(m[r * 4], m[r * 4 + 1], m[r * 4 + 2], m[r * 4 + 3]);
m[r * 4] = a;
m[r * 4 + 1] = b;
m[r * 4 + 2] = c;
m[r * 4 + 3] = d;
}
for c in 0..4 {
let (a, b, cc, d) = fwd_1d(m[c], m[4 + c], m[8 + c], m[12 + c]);
m[c] = a;
m[4 + c] = b;
m[8 + c] = cc;
m[12 + c] = d;
}
m
}
pub fn quantize(coeffs: &[i32; 16], qp: u8, dz_div: i64) -> [i32; 16] {
let mf_oh = &QUANT_MF_OH[qp as usize];
let ff = quant_dz_ff(qp, dz_div);
const POS: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7];
let mut out = [0i32; 16];
for idx in 0..16 {
let p = POS[idx];
let a = coeffs[idx].unsigned_abs() as i32;
let lvl = ((a + ff[p] as i32) * mf_oh[p] as i32) >> 16;
out[idx] = if coeffs[idx] < 0 { -lvl } else { lvl };
}
out
}
pub fn quant_dz_ff(qp: u8, dz_div: i64) -> [i16; 8] {
let m = (qp % 6) as usize;
let qbits = 15 + (qp / 6) as i64;
let f = (1i64 << qbits) / dz_div;
const GROUP8: [usize; 8] = [0, 2, 0, 2, 2, 1, 2, 1];
let mut ff = [0i16; 8];
for (i, slot) in ff.iter_mut().enumerate() {
let mfg = QUANT_MF[m][GROUP8[i]] as i64;
*slot = ((f + mfg / 2) / mfg) as i16;
}
ff
}
pub fn trellis_quant(coeffs: &[i32; 16], qp: u8, intra: bool, lambda: f64) -> [i32; 16] {
let m = (qp % 6) as usize;
let qbits = 15 + (qp / 6) as u32;
let scale = (1u64 << qbits) as f64;
let off: i64 = if intra { (1i64 << qbits) / 3 } else { (1i64 << qbits) / 6 };
let mut out = [0i32; 16];
for i in 0..4 {
for j in 0..4 {
let idx = i * 4 + j;
let w = coeffs[idx] as i64;
let mf = QUANT_MF[m][pos_group(i, j)] as i64;
let num = w.abs() * mf; let l_scalar = (num + off) >> qbits;
if l_scalar == 0 {
continue;
}
let lambda_q = lambda * (mf * mf) as f64 / (scale * scale) * 64.0;
let ideal = num as f64 / scale;
let mut best = l_scalar;
let mut best_j = f64::MAX;
for cand in [l_scalar - 1, l_scalar] {
let d = (ideal - cand as f64).powi(2);
let r = if cand == 0 {
0.0
} else {
2.0 + 2.0 * (64 - (cand as u64).leading_zeros()) as f64
};
let jj = d + lambda_q * r;
if jj < best_j {
best_j = jj;
best = cand;
}
}
out[idx] = if w < 0 { -best as i32 } else { best as i32 };
}
}
out
}
pub fn dequantize(levels: &[i32; 16], qp: u8) -> [i32; 16] {
let m = (qp % 6) as usize;
let shift = (qp / 6) as i32;
let ls = &LEVEL_SCALE_FLAT[m];
let mut out = [0i32; 16];
if qp >= 24 {
let sh = shift - 4;
for idx in 0..16 {
out[idx] = (levels[idx] * ls[idx]) << sh;
}
} else {
let add = 1 << (3 - shift);
let sh = 4 - shift;
for idx in 0..16 {
out[idx] = (levels[idx] * ls[idx] + add) >> sh;
}
}
out
}
pub fn dequantize_weighted(levels: &[i32; 16], qp: u8, weight: &[i32; 16]) -> [i32; 16] {
let m = (qp % 6) as usize;
let shift = (qp / 6) as i32;
let ls: [i32; 16] = std::array::from_fn(|idx| weight[idx] * NORM_ADJUST[m][POS_GROUP_FLAT[idx]]);
let mut out = [0i32; 16];
if qp >= 24 {
let sh = shift - 4;
for idx in 0..16 {
out[idx] = (levels[idx] * ls[idx]) << sh;
}
} else {
let add = 1 << (3 - shift);
let sh = 4 - shift;
for idx in 0..16 {
out[idx] = (levels[idx] * ls[idx] + add) >> sh;
}
}
out
}
pub fn inverse_core(coeffs: &[i32; 16]) -> [i32; 16] {
let mut m = *coeffs;
for r in 0..4 {
let (a, b, c, d) = inv_1d(m[r * 4], m[r * 4 + 1], m[r * 4 + 2], m[r * 4 + 3]);
m[r * 4] = a;
m[r * 4 + 1] = b;
m[r * 4 + 2] = c;
m[r * 4 + 3] = d;
}
for c in 0..4 {
let (a, b, cc, d) = inv_1d(m[c], m[4 + c], m[8 + c], m[12 + c]);
m[c] = a;
m[4 + c] = b;
m[8 + c] = cc;
m[12 + c] = d;
}
for v in m.iter_mut() {
*v = (*v + 32) >> 6;
}
m
}
pub fn forward_quant(residual: &[i32; 16], qp: u8, intra: bool) -> [i32; 16] {
quantize(&forward_core(residual), qp, if intra { 3 } else { 6 })
}
pub fn inverse_quant(levels: &[i32; 16], qp: u8) -> [i32; 16] {
inverse_core(&dequantize(levels, qp))
}
const NORM_ADJUST_8X8: [[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]
const fn pos_group_8x8(i: usize, j: usize) -> usize {
let (i4, j4) = (i % 4, j % 4);
if i4 == 0 && j4 == 0 {
0
} else if i % 2 == 1 && j % 2 == 1 {
1
} else if i4 == 2 && j4 == 2 {
2
} else if (i4 == 0 && j % 2 == 1) || (i % 2 == 1 && j4 == 0) {
3
} else if (i4 == 0 && j4 == 2) || (i4 == 2 && j4 == 0) {
4
} else {
5
}
}
const POS_GROUP_8X8_FLAT: [usize; 64] = {
let mut out = [0usize; 64];
let mut i = 0;
while i < 8 {
let mut j = 0;
while j < 8 {
out[i * 8 + j] = pos_group_8x8(i, j);
j += 1;
}
i += 1;
}
out
};
#[inline]
fn inv_1d_8x8(d: &[i32; 8]) -> [i32; 8] {
let a0 = d[0] + d[4];
let a4 = d[0] - d[4];
let a2 = (d[2] >> 1) - d[6];
let a6 = d[2] + (d[6] >> 1);
let b0 = a0 + a6;
let b2 = a4 + a2;
let b4 = a4 - a2;
let b6 = a0 - a6;
let a1 = -d[3] + d[5] - d[7] - (d[7] >> 1);
let a3 = d[1] + d[7] - d[3] - (d[3] >> 1);
let a5 = -d[1] + d[7] + d[5] + (d[5] >> 1);
let a7 = d[3] + d[5] + d[1] + (d[1] >> 1);
let b1 = a1 + (a7 >> 2);
let b7 = a7 - (a1 >> 2);
let b3 = a3 + (a5 >> 2);
let b5 = (a3 >> 2) - a5;
[b0 + b7, b2 + b5, b4 + b3, b6 + b1, b6 - b1, b4 - b3, b2 - b5, b0 - b7]
}
#[cfg(test)]
#[inline]
fn fwd_1d_8x8(s: &[i32; 8]) -> [i32; 8] {
let a0 = s[0] + s[7];
let a1 = s[1] + s[6];
let a2 = s[2] + s[5];
let a3 = s[3] + s[4];
let a4 = s[0] - s[7];
let a5 = s[1] - s[6];
let a6 = s[2] - s[5];
let a7 = s[3] - s[4];
let b0 = a0 + a3;
let b1 = a1 + a2;
let b2 = a0 - a3;
let b3 = a1 - a2;
let y0 = b0 + b1;
let y2 = b2 + (b3 >> 1);
let y4 = b0 - b1;
let y6 = (b2 >> 1) - b3;
let b4 = a5 + a6 + ((a4 >> 1) + a4);
let b5 = a4 - a7 - ((a6 >> 1) + a6);
let b6 = a4 + a7 - ((a5 >> 1) + a5);
let b7 = a5 - a6 + ((a7 >> 1) + a7);
let y1 = b4 + (b7 >> 2);
let y3 = b5 + (b6 >> 2);
let y5 = b6 - (b5 >> 2);
let y7 = (b4 >> 2) - b7;
[y0, y1, y2, y3, y4, y5, y6, y7]
}
pub fn inverse_core_8x8(coeffs: &[i32; 64]) -> [i32; 64] {
let mut m = *coeffs;
for r in 0..8 {
let row: [i32; 8] = std::array::from_fn(|k| m[r * 8 + k]);
let o = inv_1d_8x8(&row);
for k in 0..8 {
m[r * 8 + k] = o[k];
}
}
for c in 0..8 {
let col: [i32; 8] = std::array::from_fn(|k| m[k * 8 + c]);
let o = inv_1d_8x8(&col);
for k in 0..8 {
m[k * 8 + c] = o[k];
}
}
for v in m.iter_mut() {
*v = (*v + 32) >> 6;
}
m
}
#[cfg(test)]
fn forward_core_8x8(res: &[i32; 64]) -> [i32; 64] {
let mut m = *res;
for r in 0..8 {
let row: [i32; 8] = std::array::from_fn(|k| m[r * 8 + k]);
let o = fwd_1d_8x8(&row);
for k in 0..8 {
m[r * 8 + k] = o[k];
}
}
for c in 0..8 {
let col: [i32; 8] = std::array::from_fn(|k| m[k * 8 + c]);
let o = fwd_1d_8x8(&col);
for k in 0..8 {
m[k * 8 + c] = o[k];
}
}
m
}
pub fn dequantize_8x8(levels: &[i32; 64], qp: u8, weight: &[i32; 64]) -> [i32; 64] {
let m = (qp % 6) as usize;
let shift = (qp / 6) as i32;
let mut out = [0i32; 64];
if qp >= 36 {
let sh = shift - 6;
for idx in 0..64 {
let ls = weight[idx] * NORM_ADJUST_8X8[m][POS_GROUP_8X8_FLAT[idx]];
out[idx] = (levels[idx] * ls) << sh;
}
} else {
let add = 1 << (5 - shift);
let sh = 6 - shift;
for idx in 0..64 {
let ls = weight[idx] * NORM_ADJUST_8X8[m][POS_GROUP_8X8_FLAT[idx]];
out[idx] = (levels[idx] * ls + add) >> sh;
}
}
out
}
pub fn inverse_quant_8x8(levels: &[i32; 64], qp: u8, weight: &[i32; 64]) -> [i32; 64] {
inverse_core_8x8(&dequantize_8x8(levels, qp, weight))
}
#[inline]
fn hadamard_1d(a: i32, b: i32, c: i32, d: i32) -> (i32, i32, i32, i32) {
(a + b + c + d, a + b - c - d, a - b - c + d, a - b + c - d)
}
pub fn hadamard_4x4(block: &[i32; 16]) -> [i32; 16] {
let mut m = *block;
for r in 0..4 {
let (a, b, c, d) = hadamard_1d(m[r * 4], m[r * 4 + 1], m[r * 4 + 2], m[r * 4 + 3]);
m[r * 4] = a;
m[r * 4 + 1] = b;
m[r * 4 + 2] = c;
m[r * 4 + 3] = d;
}
for c in 0..4 {
let (a, b, cc, d) = hadamard_1d(m[c], m[4 + c], m[8 + c], m[12 + c]);
m[c] = a;
m[4 + c] = b;
m[8 + c] = cc;
m[12 + c] = d;
}
m
}
#[inline]
fn had4_simd(
a: wide::i32x4,
b: wide::i32x4,
c: wide::i32x4,
d: wide::i32x4,
) -> (wide::i32x4, wide::i32x4, wide::i32x4, wide::i32x4) {
(a + b + c + d, a + b - c - d, a - b - c + d, a - b + c - d)
}
fn satd_4x4_x4(b: [&[i32; 16]; 4]) -> i64 {
use wide::i32x4;
let mut v = [i32x4::from([0i32; 4]); 16];
for (p, slot) in v.iter_mut().enumerate() {
*slot = i32x4::from([b[0][p], b[1][p], b[2][p], b[3][p]]);
}
for r in 0..4 {
let i = r * 4;
let (a, c, d, e) = had4_simd(v[i], v[i + 1], v[i + 2], v[i + 3]);
(v[i], v[i + 1], v[i + 2], v[i + 3]) = (a, c, d, e);
}
for c in 0..4 {
let (a, b2, d, e) = had4_simd(v[c], v[c + 4], v[c + 8], v[c + 12]);
(v[c], v[c + 4], v[c + 8], v[c + 12]) = (a, b2, d, e);
}
let zero = i32x4::from([0i32; 4]);
let mut acc = zero;
for x in v {
acc += zero.max(x) - zero.min(x);
}
acc.to_array().iter().map(|&s| s as i64).sum()
}
pub fn satd_4x4_sum(blocks: &[[i32; 16]]) -> i64 {
let mut total = 0i64;
let mut chunks = blocks.chunks_exact(4);
for g in &mut chunks {
total += satd_4x4_x4([&g[0], &g[1], &g[2], &g[3]]);
}
for res in chunks.remainder() {
total += hadamard_4x4(res).iter().map(|&v| v.unsigned_abs() as i64).sum::<i64>();
}
total
}
#[inline]
fn fwd_1d_simd(
x0: wide::i32x4,
x1: wide::i32x4,
x2: wide::i32x4,
x3: wide::i32x4,
) -> (wide::i32x4, wide::i32x4, wide::i32x4, wide::i32x4) {
let t0 = x0 + x3;
let t1 = x1 + x2;
let t2 = x1 - x2;
let t3 = x0 - x3;
(t0 + t1, (t3 + t3) + t2, t0 - t1, t3 - (t2 + t2))
}
fn forward_core_x4(b: [&[i32; 16]; 4]) -> [[i32; 16]; 4] {
use wide::i32x4;
let mut v = [i32x4::from([0i32; 4]); 16];
for (p, slot) in v.iter_mut().enumerate() {
*slot = i32x4::from([b[0][p], b[1][p], b[2][p], b[3][p]]);
}
for r in 0..4 {
let i = r * 4;
let (a, c, d, e) = fwd_1d_simd(v[i], v[i + 1], v[i + 2], v[i + 3]);
(v[i], v[i + 1], v[i + 2], v[i + 3]) = (a, c, d, e);
}
for c in 0..4 {
let (a, b2, d, e) = fwd_1d_simd(v[c], v[c + 4], v[c + 8], v[c + 12]);
(v[c], v[c + 4], v[c + 8], v[c + 12]) = (a, b2, d, e);
}
let mut out = [[0i32; 16]; 4];
for (p, vp) in v.iter().enumerate() {
let a = vp.to_array();
for k in 0..4 {
out[k][p] = a[k];
}
}
out
}
#[inline]
fn fwd_1d_simd8(
x0: wide::i32x8,
x1: wide::i32x8,
x2: wide::i32x8,
x3: wide::i32x8,
) -> (wide::i32x8, wide::i32x8, wide::i32x8, wide::i32x8) {
let t0 = x0 + x3;
let t1 = x1 + x2;
let t2 = x1 - x2;
let t3 = x0 - x3;
(t0 + t1, (t3 + t3) + t2, t0 - t1, t3 - (t2 + t2))
}
fn forward_core_x8(b: [&[i32; 16]; 8]) -> [[i32; 16]; 8] {
use wide::i32x8;
let mut v = [i32x8::from([0i32; 8]); 16];
for (p, slot) in v.iter_mut().enumerate() {
*slot = i32x8::from([
b[0][p], b[1][p], b[2][p], b[3][p], b[4][p], b[5][p], b[6][p], b[7][p],
]);
}
for r in 0..4 {
let i = r * 4;
let (a, c, d, e) = fwd_1d_simd8(v[i], v[i + 1], v[i + 2], v[i + 3]);
(v[i], v[i + 1], v[i + 2], v[i + 3]) = (a, c, d, e);
}
for c in 0..4 {
let (a, b2, d, e) = fwd_1d_simd8(v[c], v[c + 4], v[c + 8], v[c + 12]);
(v[c], v[c + 4], v[c + 8], v[c + 12]) = (a, b2, d, e);
}
let mut out = [[0i32; 16]; 8];
for (p, vp) in v.iter().enumerate() {
let a = vp.to_array();
for k in 0..8 {
out[k][p] = a[k];
}
}
out
}
pub fn forward_dct_blocks(res: &[[i32; 16]], out: &mut [[i32; 16]]) {
let mut i = 0;
let mut c8 = res.chunks_exact(8);
for g in &mut c8 {
let r = forward_core_x8([&g[0], &g[1], &g[2], &g[3], &g[4], &g[5], &g[6], &g[7]]);
out[i..i + 8].clone_from_slice(&r);
i += 8;
}
let mut c4 = c8.remainder().chunks_exact(4);
for g in &mut c4 {
let r = forward_core_x4([&g[0], &g[1], &g[2], &g[3]]);
out[i..i + 4].clone_from_slice(&r);
i += 4;
}
for r in c4.remainder() {
out[i] = forward_core(r);
i += 1;
}
}
#[inline]
fn inv_1d_simd(
d0: wide::i32x4,
d1: wide::i32x4,
d2: wide::i32x4,
d3: wide::i32x4,
) -> (wide::i32x4, wide::i32x4, wide::i32x4, wide::i32x4) {
let e0 = d0 + d2;
let e1 = d0 - d2;
let e2 = (d1 >> 1) - d3;
let e3 = d1 + (d3 >> 1);
(e0 + e3, e1 + e2, e1 - e2, e0 - e3)
}
fn inverse_core_x4(b: [&[i32; 16]; 4]) -> [[i32; 16]; 4] {
use wide::i32x4;
let mut v = [i32x4::from([0i32; 4]); 16];
for (p, slot) in v.iter_mut().enumerate() {
*slot = i32x4::from([b[0][p], b[1][p], b[2][p], b[3][p]]);
}
for r in 0..4 {
let i = r * 4;
let (a, c, d, e) = inv_1d_simd(v[i], v[i + 1], v[i + 2], v[i + 3]);
(v[i], v[i + 1], v[i + 2], v[i + 3]) = (a, c, d, e);
}
for c in 0..4 {
let (a, b2, d, e) = inv_1d_simd(v[c], v[c + 4], v[c + 8], v[c + 12]);
(v[c], v[c + 4], v[c + 8], v[c + 12]) = (a, b2, d, e);
}
let off = i32x4::from([32i32; 4]);
for vp in v.iter_mut() {
*vp = (*vp + off) >> 6;
}
let mut out = [[0i32; 16]; 4];
for (p, vp) in v.iter().enumerate() {
let a = vp.to_array();
for k in 0..4 {
out[k][p] = a[k];
}
}
out
}
#[inline]
fn inv_1d_simd8(
d0: wide::i32x8,
d1: wide::i32x8,
d2: wide::i32x8,
d3: wide::i32x8,
) -> (wide::i32x8, wide::i32x8, wide::i32x8, wide::i32x8) {
let e0 = d0 + d2;
let e1 = d0 - d2;
let e2 = (d1 >> 1) - d3;
let e3 = d1 + (d3 >> 1);
(e0 + e3, e1 + e2, e1 - e2, e0 - e3)
}
fn inverse_core_x8(b: [&[i32; 16]; 8]) -> [[i32; 16]; 8] {
use wide::i32x8;
let mut v = [i32x8::from([0i32; 8]); 16];
for (p, slot) in v.iter_mut().enumerate() {
*slot = i32x8::from([
b[0][p], b[1][p], b[2][p], b[3][p], b[4][p], b[5][p], b[6][p], b[7][p],
]);
}
for r in 0..4 {
let i = r * 4;
let (a, c, d, e) = inv_1d_simd8(v[i], v[i + 1], v[i + 2], v[i + 3]);
(v[i], v[i + 1], v[i + 2], v[i + 3]) = (a, c, d, e);
}
for c in 0..4 {
let (a, b2, d, e) = inv_1d_simd8(v[c], v[c + 4], v[c + 8], v[c + 12]);
(v[c], v[c + 4], v[c + 8], v[c + 12]) = (a, b2, d, e);
}
let off = i32x8::from([32i32; 8]);
for vp in v.iter_mut() {
*vp = (*vp + off) >> 6;
}
let mut out = [[0i32; 16]; 8];
for (p, vp) in v.iter().enumerate() {
let a = vp.to_array();
for k in 0..8 {
out[k][p] = a[k];
}
}
out
}
pub fn inverse_dct_blocks(coeffs: &[[i32; 16]], out: &mut [[i32; 16]]) {
let mut i = 0;
let mut c8 = coeffs.chunks_exact(8);
for g in &mut c8 {
let r = inverse_core_x8([&g[0], &g[1], &g[2], &g[3], &g[4], &g[5], &g[6], &g[7]]);
out[i..i + 8].clone_from_slice(&r);
i += 8;
}
let mut c4 = c8.remainder().chunks_exact(4);
for g in &mut c4 {
let r = inverse_core_x4([&g[0], &g[1], &g[2], &g[3]]);
out[i..i + 4].clone_from_slice(&r);
i += 4;
}
for r in c4.remainder() {
out[i] = inverse_core(r);
i += 1;
}
}
pub fn forward_quant_luma_dc(dc: &[i32; 16], qp: u8, intra: bool) -> [i32; 16] {
let f = hadamard_4x4(dc);
let m = (qp % 6) as usize;
let qbits = 17 + (qp / 6) as u32;
let off: i64 = if intra { (1i64 << qbits) / 3 } else { (1i64 << qbits) / 6 };
let mf = QUANT_MF[m][0] as i64;
let mut out = [0i32; 16];
for (o, &fv) in out.iter_mut().zip(f.iter()) {
let level = ((fv.abs() as i64) * mf + off) >> qbits;
*o = if fv < 0 { -level as i32 } else { level as i32 };
}
out
}
pub fn inverse_quant_luma_dc(levels: &[i32; 16], qp: u8) -> [i32; 16] {
let g = hadamard_4x4(levels);
let m = (qp % 6) as usize;
let shift = (qp / 6) as i32;
let level_scale = 16 * NORM_ADJUST[m][0];
let mut out = [0i32; 16];
for (o, &gv) in out.iter_mut().zip(g.iter()) {
*o = if qp >= 36 {
(gv * level_scale) << (shift - 6)
} else {
(gv * level_scale + (1 << (5 - shift))) >> (6 - shift)
};
}
out
}
pub fn inverse_quant_luma_dc_weighted(levels: &[i32; 16], qp: u8, w00: i32) -> [i32; 16] {
let g = hadamard_4x4(levels);
let m = (qp % 6) as usize;
let shift = (qp / 6) as i32;
let level_scale = w00 * NORM_ADJUST[m][0];
let mut out = [0i32; 16];
for (o, &gv) in out.iter_mut().zip(g.iter()) {
*o = if qp >= 36 {
(gv * level_scale) << (shift - 6)
} else {
(gv * level_scale + (1 << (5 - shift))) >> (6 - shift)
};
}
out
}
pub fn inverse_quant_chroma_dc_weighted(levels: &[i32; 4], qp: u8, w00: i32) -> [i32; 4] {
let g = hadamard_2x2(levels);
let m = (qp % 6) as usize;
let shift = (qp / 6) as i32;
let level_scale = w00 * NORM_ADJUST[m][0];
let mut out = [0i32; 4];
for (o, &gv) in out.iter_mut().zip(g.iter()) {
*o = ((gv * level_scale) << shift) >> 5;
}
out
}
pub fn hadamard_2x2(dc: &[i32; 4]) -> [i32; 4] {
let (a, b, c, d) = (dc[0], dc[1], dc[2], dc[3]);
[a + b + c + d, a - b + c - d, a + b - c - d, a - b - c + d]
}
pub fn forward_quant_chroma_dc(dc: &[i32; 4], qp: u8, intra: bool) -> [i32; 4] {
let f = hadamard_2x2(dc);
let m = (qp % 6) as usize;
let qbits = 15 + (qp / 6) as u32;
let off: i64 = if intra { (1i64 << qbits) / 3 } else { (1i64 << qbits) / 6 };
let mf = QUANT_MF[m][0] as i64;
let mut out = [0i32; 4];
for (o, &fv) in out.iter_mut().zip(f.iter()) {
let level = ((fv.abs() as i64) * mf + 2 * off) >> (qbits + 1);
*o = if fv < 0 { -level as i32 } else { level as i32 };
}
out
}
pub fn inverse_quant_chroma_dc(levels: &[i32; 4], qp: u8) -> [i32; 4] {
let g = hadamard_2x2(levels);
let m = (qp % 6) as usize;
let shift = (qp / 6) as i32;
let level_scale = 16 * NORM_ADJUST[m][0];
let mut out = [0i32; 4];
for (o, &gv) in out.iter_mut().zip(g.iter()) {
*o = ((gv * level_scale) << shift) >> 5;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn batched_forward_dct_matches_scalar() {
let mut state = 0x9e37_79b9u32;
let mut next = || {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
((state >> 16) % 511) as i32 - 255
};
for n in 1..=18 {
let res: Vec<[i32; 16]> = (0..n).map(|_| std::array::from_fn(|_| next())).collect();
let mut out = vec![[0i32; 16]; n];
forward_dct_blocks(&res, &mut out);
for (r, o) in res.iter().zip(&out) {
assert_eq!(&forward_core(r), o, "n={n}");
}
}
}
#[test]
fn batched_inverse_dct_matches_scalar() {
let mut state = 0x0bad_f00du32;
let mut next = || {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
((state >> 12) % 8191) as i32 - 4095
};
for n in 1..=18 {
let coeffs: Vec<[i32; 16]> = (0..n).map(|_| std::array::from_fn(|_| next())).collect();
let mut out = vec![[0i32; 16]; n];
inverse_dct_blocks(&coeffs, &mut out);
for (c, o) in coeffs.iter().zip(&out) {
assert_eq!(&inverse_core(c), o, "n={n}");
}
}
}
#[test]
fn simd_satd_matches_scalar() {
let scalar = |res: &[i32; 16]| -> i64 {
hadamard_4x4(res).iter().map(|&v| v.unsigned_abs() as i64).sum()
};
let mut state = 0x1234_5678u32;
let mut next = || {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
((state >> 16) % 511) as i32 - 255
};
for n in 1..=20 {
let blocks: Vec<[i32; 16]> =
(0..n).map(|_| std::array::from_fn(|_| next())).collect();
let expect: i64 = blocks.iter().map(&scalar).sum();
assert_eq!(satd_4x4_sum(&blocks), expect, "n={n}");
}
}
#[test]
fn forward_inverse_core_are_consistent_scale() {
let block = [4i32; 16];
let w = forward_core(&block);
assert_eq!(w[0], 64, "DC coefficient");
for (k, &ac) in w.iter().enumerate().skip(1) {
assert_eq!(ac, 0, "AC[{k}] should be zero for a flat block");
}
}
#[test]
fn forward_core_8x8_flat_block_is_dc_only() {
let block = [4i32; 64];
let w = forward_core_8x8(&block);
assert_eq!(w[0], 256, "8×8 DC coefficient");
for (k, &ac) in w.iter().enumerate().skip(1) {
assert_eq!(ac, 0, "8×8 AC[{k}] should be zero for a flat block");
}
}
#[test]
fn inverse_core_8x8_dc_only_is_flat() {
let mut coeffs = [0i32; 64];
coeffs[0] = 256;
let r = inverse_core_8x8(&coeffs);
for (k, &v) in r.iter().enumerate() {
assert_eq!(v, (256 + 32) >> 6, "8×8 inverse DC pixel {k}");
}
}
#[test]
fn quant_dequant_8x8_round_trip_near_identity() {
let weight = [16i32; 64];
for &val in &[0i32, 4, -7, 31] {
let block = [val; 64];
let fwd = forward_core_8x8(&block);
let deq = dequantize_8x8(&fwd, 24, &weight);
let _ = deq; let recon = inverse_core_8x8(&fwd);
assert_eq!(recon, block, "flat 8×8 must round-trip through the core");
}
}
#[test]
fn dequant_8x8_flat_matches_levelscale() {
let mut levels = [0i32; 64];
levels[0] = 3;
let weight = [16i32; 64];
let d = dequantize_8x8(&levels, 30, &weight);
let ls = 16 * NORM_ADJUST_8X8[0][0];
assert_eq!(d[0], (3 * ls + 1) >> 1);
}
#[test]
fn inverse_core_is_row_first() {
let coeffs = [9, 2, -1, 2, -2, 2, -2, 1, -1, -2, 3, 5, -1, -1, -5, 3];
let mut rows_then_cols = coeffs;
for r in 0..4 {
let (a, b, c, d) = inv_1d(
rows_then_cols[r * 4],
rows_then_cols[r * 4 + 1],
rows_then_cols[r * 4 + 2],
rows_then_cols[r * 4 + 3],
);
rows_then_cols[r * 4] = a;
rows_then_cols[r * 4 + 1] = b;
rows_then_cols[r * 4 + 2] = c;
rows_then_cols[r * 4 + 3] = d;
}
for c in 0..4 {
let (a, b, cc, d) = inv_1d(
rows_then_cols[c],
rows_then_cols[4 + c],
rows_then_cols[8 + c],
rows_then_cols[12 + c],
);
rows_then_cols[c] = a;
rows_then_cols[4 + c] = b;
rows_then_cols[8 + c] = cc;
rows_then_cols[12 + c] = d;
}
let mut cols_then_rows = coeffs;
for c in 0..4 {
let (a, b, cc, d) = inv_1d(
cols_then_rows[c],
cols_then_rows[4 + c],
cols_then_rows[8 + c],
cols_then_rows[12 + c],
);
cols_then_rows[c] = a;
cols_then_rows[4 + c] = b;
cols_then_rows[8 + c] = cc;
cols_then_rows[12 + c] = d;
}
for r in 0..4 {
let (a, b, c, d) = inv_1d(
cols_then_rows[r * 4],
cols_then_rows[r * 4 + 1],
cols_then_rows[r * 4 + 2],
cols_then_rows[r * 4 + 3],
);
cols_then_rows[r * 4] = a;
cols_then_rows[r * 4 + 1] = b;
cols_then_rows[r * 4 + 2] = c;
cols_then_rows[r * 4 + 3] = d;
}
assert_ne!(rows_then_cols, cols_then_rows);
let expected: [i32; 16] =
core::array::from_fn(|k| (rows_then_cols[k] + 32) >> 6);
assert_eq!(inverse_core(&coeffs), expected);
}
#[test]
fn quant_dequant_roundtrip_is_near_identity() {
let residual: [i32; 16] = [
5, -3, 8, 0, 12, -7, 2, 1, -4, 6, 9, -2, 0, 3, -1, 7,
];
for qp in [0u8, 6, 12, 18, 26, 30, 37, 45, 51] {
let levels = forward_quant(&residual, qp, true);
let recon = inverse_quant(&levels, qp);
let tol = 2 + (1 << (qp / 6));
for k in 0..16 {
let diff = (recon[k] - residual[k]).abs();
assert!(
diff <= tol,
"qp {qp}: residual[{k}]={} recon={} diff={diff} tol={tol}",
residual[k],
recon[k]
);
}
}
}
#[test]
fn trellis_never_exceeds_scalar_magnitude() {
let coeffs: [i32; 16] = [120, -40, 8, 1, -15, 6, -1, 0, 3, -2, 1, 0, 0, 1, 0, 0];
let scalar = quantize(&coeffs, 26, 3);
let t = trellis_quant(&coeffs, 26, true, 50.0);
for k in 0..16 {
assert!(t[k].unsigned_abs() <= scalar[k].unsigned_abs(), "[{k}]");
assert!(t[k] == 0 || t[k].signum() == scalar[k].signum());
}
}
#[test]
fn zero_residual_stays_zero() {
let zero = [0i32; 16];
let levels = forward_quant(&zero, 28, true);
assert_eq!(levels, [0i32; 16]);
assert_eq!(inverse_quant(&levels, 28), [0i32; 16]);
}
#[test]
fn luma_dc_end_to_end_flat_block() {
for r in [3i32, 9, -5, 20] {
for qp in [0u8, 12, 24, 30] {
let w_dc = [16 * r; 16]; let z = forward_quant_luma_dc(&w_dc, qp, true);
let dcy = inverse_quant_luma_dc(&z, qp);
let tol = 1 + (1 << (qp / 6));
for (b, &dc) in dcy.iter().enumerate() {
let mut coeff = [0i32; 16];
coeff[0] = dc;
let res = inverse_core(&coeff);
for &v in &res {
assert!((v - r).abs() <= tol, "luma DC r={r} qp{qp} blk{b}: {v} vs {r}");
}
}
}
}
}
#[test]
fn chroma_dc_end_to_end_flat_block() {
for r in [4i32, -6, 11] {
for qp in [0u8, 18, 30] {
let dc = [16 * r; 4];
let z = forward_quant_chroma_dc(&dc, qp, true);
let dcy = inverse_quant_chroma_dc(&z, qp);
let tol = 1 + (1 << (qp / 6));
for &d in &dcy {
let mut coeff = [0i32; 16];
coeff[0] = d;
let res = inverse_core(&coeff);
for &v in &res {
assert!((v - r).abs() <= tol, "chroma DC r={r} qp{qp}: {v} vs {r}");
}
}
}
}
}
#[test]
fn hadamard_is_self_inverse_scaled() {
let x: [i32; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let twice = hadamard_4x4(&hadamard_4x4(&x));
for (k, (&a, &b)) in x.iter().zip(twice.iter()).enumerate() {
assert_eq!(b, a * 16, "[{k}]");
}
}
#[test]
fn low_qp_is_high_fidelity() {
let residual: [i32; 16] = [1, 2, 3, 4, -1, -2, -3, -4, 0, 1, 0, -1, 2, -2, 1, 0];
let levels = forward_quant(&residual, 0, true);
let recon = inverse_quant(&levels, 0);
for (k, (&r, &o)) in residual.iter().zip(recon.iter()).enumerate() {
assert!((o - r).abs() <= 1, "qp0 residual[{k}]={r} recon={o}");
}
}
}
pub const QUANT_MF_OH: [[i16; 8]; 52] = [
[26214, 16132, 26214, 16132, 16132, 10486, 16132, 10486],
[23832, 14980, 23832, 14980, 14980, 9320, 14980, 9320],
[20164, 13108, 20164, 13108, 13108, 8388, 13108, 8388],
[18724, 11650, 18724, 11650, 11650, 7294, 11650, 7294],
[16384, 10486, 16384, 10486, 10486, 6710, 10486, 6710],
[14564, 9118, 14564, 9118, 9118, 5786, 9118, 5786],
[13107, 8066, 13107, 8066, 8066, 5243, 8066, 5243],
[11916, 7490, 11916, 7490, 7490, 4660, 7490, 4660],
[10082, 6554, 10082, 6554, 6554, 4194, 6554, 4194],
[9362, 5825, 9362, 5825, 5825, 3647, 5825, 3647],
[8192, 5243, 8192, 5243, 5243, 3355, 5243, 3355],
[7282, 4559, 7282, 4559, 4559, 2893, 4559, 2893],
[6554, 4033, 6554, 4033, 4033, 2622, 4033, 2622],
[5958, 3745, 5958, 3745, 3745, 2330, 3745, 2330],
[5041, 3277, 5041, 3277, 3277, 2097, 3277, 2097],
[4681, 2913, 4681, 2913, 2913, 1824, 2913, 1824],
[4096, 2622, 4096, 2622, 2622, 1678, 2622, 1678],
[3641, 2280, 3641, 2280, 2280, 1447, 2280, 1447],
[3277, 2017, 3277, 2017, 2017, 1311, 2017, 1311],
[2979, 1873, 2979, 1873, 1873, 1165, 1873, 1165],
[2521, 1639, 2521, 1639, 1639, 1049, 1639, 1049],
[2341, 1456, 2341, 1456, 1456, 912, 1456, 912],
[2048, 1311, 2048, 1311, 1311, 839, 1311, 839],
[1821, 1140, 1821, 1140, 1140, 723, 1140, 723],
[1638, 1008, 1638, 1008, 1008, 655, 1008, 655],
[1490, 936, 1490, 936, 936, 583, 936, 583],
[1260, 819, 1260, 819, 819, 524, 819, 524],
[1170, 728, 1170, 728, 728, 456, 728, 456],
[1024, 655, 1024, 655, 655, 419, 655, 419],
[910, 570, 910, 570, 570, 362, 570, 362],
[819, 504, 819, 504, 504, 328, 504, 328],
[745, 468, 745, 468, 468, 291, 468, 291],
[630, 410, 630, 410, 410, 262, 410, 262],
[585, 364, 585, 364, 364, 228, 364, 228],
[512, 328, 512, 328, 328, 210, 328, 210],
[455, 285, 455, 285, 285, 181, 285, 181],
[410, 252, 410, 252, 252, 164, 252, 164],
[372, 234, 372, 234, 234, 146, 234, 146],
[315, 205, 315, 205, 205, 131, 205, 131],
[293, 182, 293, 182, 182, 114, 182, 114],
[256, 164, 256, 164, 164, 105, 164, 105],
[228, 142, 228, 142, 142, 90, 142, 90],
[205, 126, 205, 126, 126, 82, 126, 82],
[186, 117, 186, 117, 117, 73, 117, 73],
[158, 102, 158, 102, 102, 66, 102, 66],
[146, 91, 146, 91, 91, 57, 91, 57],
[128, 82, 128, 82, 82, 52, 82, 52],
[114, 71, 114, 71, 71, 45, 71, 45],
[102, 63, 102, 63, 63, 41, 63, 41],
[93, 59, 93, 59, 59, 36, 59, 36],
[79, 51, 79, 51, 51, 33, 51, 33],
[73, 46, 73, 46, 46, 28, 46, 28],
];
pub const QUANT_FF_OH: [[i16; 8]; 58] = [
[0, 1, 0, 1, 1, 1, 1, 1],
[0, 1, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 2, 1, 2],
[1, 1, 1, 1, 1, 2, 1, 2],
[1, 1, 1, 1, 1, 2, 1, 2],
[1, 1, 1, 1, 1, 2, 1, 2],
[1, 2, 1, 2, 2, 3, 2, 3],
[1, 2, 1, 2, 2, 3, 2, 3],
[1, 2, 1, 2, 2, 3, 2, 3],
[1, 2, 1, 2, 2, 4, 2, 4],
[2, 3, 2, 3, 3, 4, 3, 4],
[2, 3, 2, 3, 3, 5, 3, 5],
[2, 3, 2, 3, 3, 5, 3, 5],
[2, 4, 2, 4, 4, 6, 4, 6],
[3, 4, 3, 4, 4, 7, 4, 7],
[3, 5, 3, 5, 5, 8, 5, 8],
[3, 5, 3, 5, 5, 8, 5, 8],
[4, 6, 4, 6, 6, 9, 6, 9],
[4, 7, 4, 7, 7, 10, 7, 10],
[5, 8, 5, 8, 8, 12, 8, 12],
[5, 8, 5, 8, 8, 13, 8, 13],
[6, 10, 6, 10, 10, 15, 10, 15],
[7, 11, 7, 11, 11, 17, 11, 17],
[7, 12, 7, 12, 12, 19, 12, 19],
[9, 13, 9, 13, 13, 21, 13, 21],
[9, 15, 9, 15, 15, 24, 15, 24],
[11, 17, 11, 17, 17, 26, 17, 26],
[12, 19, 12, 19, 19, 30, 19, 30],
[13, 22, 13, 22, 22, 33, 22, 33],
[15, 23, 15, 23, 23, 38, 23, 38],
[17, 27, 17, 27, 27, 42, 27, 42],
[19, 30, 19, 30, 30, 48, 30, 48],
[21, 33, 21, 33, 33, 52, 33, 52],
[24, 38, 24, 38, 38, 60, 38, 60],
[27, 43, 27, 43, 43, 67, 43, 67],
[29, 47, 29, 47, 47, 75, 47, 75],
[35, 53, 35, 53, 53, 83, 53, 83],
[37, 60, 37, 60, 60, 96, 60, 96],
[43, 67, 43, 67, 67, 104, 67, 104],
[48, 77, 48, 77, 77, 121, 77, 121],
[53, 87, 53, 87, 87, 133, 87, 133],
[59, 93, 59, 93, 93, 150, 93, 150],
[69, 107, 69, 107, 107, 167, 107, 167],
[75, 120, 75, 120, 120, 192, 120, 192],
[85, 133, 85, 133, 133, 208, 133, 208],
[96, 153, 96, 153, 153, 242, 153, 242],
[107, 173, 107, 173, 173, 267, 173, 267],
[117, 187, 117, 187, 187, 300, 187, 300],
[139, 213, 139, 213, 213, 333, 213, 333],
[149, 240, 149, 240, 240, 383, 240, 383],
[171, 267, 171, 267, 267, 417, 267, 417],
[192, 307, 192, 307, 307, 483, 307, 483],
[213, 347, 213, 347, 347, 533, 347, 533],
[235, 373, 235, 373, 373, 600, 373, 600],
[277, 427, 277, 427, 427, 667, 427, 667],
[299, 480, 299, 480, 480, 767, 480, 767],
];