pub const LAMBDA_TAB: [u16; 52] = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6, 6, 7, 8, 9, 10, 11, 13, 14, 16, 18, 20, 23, 25, 29, 32, 36, 40, 45, 51, 57, 64, 72, 81, 91, ];
pub const LAMBDA2_TAB: [i32; 52] = [
14, 18, 22, 28, 36, 45, 57, 72, 91, 115, 145, 182, 230, 290, 365, 460, 580, 731, 921, 1161, 1462, 1843, 2322, 2925, 3686, 4644, 5851, 7372, 9289, 11703, 14745, 18578, 23407, 29491, 37156, 46814, 58982, 74313, 93628, 117964, 148626, 187257, 235929, 297252, 374514, 471859, 594505, 749029, 943718, 1189010, 1498059, 1887436, ];
pub const TRELLIS_LAMBDA2_TAB: [[i32; 52]; 2] = [
[
46, 58, 73, 92, 117, 147, 185, 233, 294, 370, 466, 587, 740, 932, 1174, 1480, 1864, 2349, 2959, 3729, 4698, 5919, 7457, 9395, 11837, 14914, 18791, 23675, 29828, 37582, 47350, 59657, 75163, 94700, 119314, 150326, 189399, 238628, 300652, 378798, 477256, 601304, 757596, 954511, 1202609, 1515192, 1909023, 2405218, 3030385, 3818045, 4810436, 6060769, ],
[
27, 34, 43, 54, 68, 86, 108, 136, 172, 216, 273, 343, 433, 545, 687, 865, 1090, 1374, 1731, 2180, 2747, 3461, 4361, 5494, 6922, 8721, 10988, 13844, 17443, 21977, 27689, 34886, 43953, 55378, 69772, 87907, 110756, 139544, 175814, 221512, 279087, 351628, 443023, 558174, 703256, 886047, 1116349, 1406511, 1772093, 2232698, 2813023, 3544187, ],
];
#[inline]
pub fn lambda_for_qp(qp: u8) -> u16 {
LAMBDA_TAB[qp.min(51) as usize]
}
#[inline]
pub fn lambda2_for_qp(qp: u8) -> i32 {
LAMBDA2_TAB[qp.min(51) as usize]
}
#[inline]
pub fn trellis_lambda2_for_qp(qp: u8, is_intra: bool) -> i32 {
let row = if is_intra { 1 } else { 0 };
TRELLIS_LAMBDA2_TAB[row][qp.min(51) as usize]
}
use super::bitstream_writer::{BitSink, BitSizer};
use super::partition_decision::PMbChoice;
use super::partition_state::EncoderMvGrid;
use super::quantization::{forward_quantize_4x4, trellis_quantize_4x4, QuantParams, QuantSlice};
use super::reconstruction::raster_to_scan_levels;
use super::reference_buffer::ReconFrame;
use super::transform::{forward_dct_4x4, forward_hadamard_2x2};
use super::inter_mode::{cbp_to_codenum_inter, luma_8x8_cbp_mask, pack_cbp};
use super::quantization::forward_quantize_dc_chroma;
#[allow(unused_imports)]
use crate::codec::h264::cavlc_writer::{encode_cavlc_block, CavlcBlockType}; use crate::codec::h264::transform::{dequant_4x4, inverse_4x4_integer, inverse_chroma_dc_2x2_hadamard};
pub fn residual_block_bits_cavlc(
coeffs: &[i32],
nc: i8,
block_type: CavlcBlockType,
) -> u32 {
crate::codec::h264::cavlc_size::residual_block_bits_cavlc(coeffs, nc, block_type)
.unwrap_or(0)
}
pub fn p_mb_header_bits_cavlc(
choice: &PMbChoice,
grid: &mut EncoderMvGrid,
mb_x: usize,
mb_y: usize,
cbp_codenum: u32,
qp_delta_or_none: Option<i32>,
) -> u32 {
use super::encoder::emit_mvds_and_update_grid;
let mut sz = BitSizer::new();
sz.write_ue(choice.mb_type_codenum());
let snap = grid.snapshot_mb(mb_x, mb_y);
emit_mvds_and_update_grid(&mut sz, grid, mb_x, mb_y, choice);
grid.restore_mb(&snap);
sz.write_ue(cbp_codenum);
if let Some(qp_delta) = qp_delta_or_none {
sz.write_se(qp_delta);
}
sz.bits_written() as u32
}
#[allow(clippy::too_many_arguments)]
pub fn p_mb_total_bits_cavlc(
choice: &PMbChoice,
grid: &mut EncoderMvGrid,
mb_x: usize,
mb_y: usize,
cbp_codenum: u32,
qp_delta_or_none: Option<i32>,
cbp_luma_8x8: u8,
cbp_chroma: u8,
luma_ac_scans: &[[i32; 16]; 16],
luma_nc: &[i8; 16],
chroma: Option<CavlcChromaBits>,
) -> u32 {
let mut bits =
p_mb_header_bits_cavlc(choice, grid, mb_x, mb_y, cbp_codenum, qp_delta_or_none);
for k in 0..16 {
if cbp_luma_8x8 & (1 << (k / 4)) != 0 {
bits = bits.saturating_add(residual_block_bits_cavlc(
&luma_ac_scans[k],
luma_nc[k],
CavlcBlockType::Luma4x4,
));
}
}
if cbp_chroma != 0
&& let Some(chroma) = chroma {
bits = bits.saturating_add(residual_block_bits_cavlc(
&chroma.cb_dc_scan,
-1,
CavlcBlockType::ChromaDc,
));
bits = bits.saturating_add(residual_block_bits_cavlc(
&chroma.cr_dc_scan,
-1,
CavlcBlockType::ChromaDc,
));
if cbp_chroma == 2 {
for i in 0..4 {
bits = bits.saturating_add(residual_block_bits_cavlc(
&chroma.cb_ac_scans[i],
chroma.cb_nc[i],
CavlcBlockType::ChromaAc,
));
bits = bits.saturating_add(residual_block_bits_cavlc(
&chroma.cr_ac_scans[i],
chroma.cr_nc[i],
CavlcBlockType::ChromaAc,
));
}
}
}
bits
}
#[derive(Debug, Clone)]
pub struct CavlcChromaBits {
pub cb_dc_scan: [i32; 4],
pub cr_dc_scan: [i32; 4],
pub cb_ac_scans: [[i32; 15]; 4],
pub cr_ac_scans: [[i32; 15]; 4],
pub cb_nc: [i8; 4],
pub cr_nc: [i8; 4],
}
#[derive(Debug, Clone, Copy)]
pub struct ChromaRdoInputs<'a> {
pub src_cb: &'a [[u8; 8]; 8],
pub src_cr: &'a [[u8; 8]; 8],
pub qp_c: u8,
}
fn chroma_component_rdo(
src_c: &[[u8; 8]; 8],
pred_c: &[[u8; 8]; 8],
qp_c: u8,
) -> (u64, u32) {
let qp = QuantParams { qp: qp_c, slice: QuantSlice::Inter };
let trellis_enable = true;
let mut ac_levels = [[[0i32; 4]; 4]; 4];
let mut dc_grid = [[0i32; 2]; 2];
for sby in 0..2 {
for sbx in 0..2 {
let mut sub_res = [[0i32; 4]; 4];
for dy in 0..4 {
for dx in 0..4 {
sub_res[dy][dx] = src_c[sby * 4 + dy][sbx * 4 + dx] as i32
- pred_c[sby * 4 + dy][sbx * 4 + dx] as i32;
}
}
let mut coeffs = forward_dct_4x4(&sub_res);
dc_grid[sby][sbx] = coeffs[0][0];
coeffs[0][0] = 0;
ac_levels[sby * 2 + sbx] =
trellis_quantize_4x4(&coeffs, qp, trellis_enable)
.unwrap_or_else(|_| forward_quantize_4x4(&coeffs, qp));
}
}
let dc_hadamard = forward_hadamard_2x2(&dc_grid);
let dc_levels = forward_quantize_dc_chroma(&dc_hadamard, qp_c, QuantSlice::Inter);
let dc_recon = inverse_chroma_dc_2x2_hadamard(&dc_levels, qp_c as i32);
let mut recon_c = [[0u8; 8]; 8];
for sby in 0..2 {
for sbx in 0..2 {
let levels = &ac_levels[sby * 2 + sbx];
let dq = dequant_4x4(levels, qp_c as i32, false);
let mut with_dc = dq;
with_dc[0][0] = with_dc[0][0].wrapping_add(dc_recon[sby][sbx]);
let recon_res = inverse_4x4_integer(&with_dc);
for dy in 0..4 {
for dx in 0..4 {
let v = pred_c[sby * 4 + dy][sbx * 4 + dx] as i32 + recon_res[dy][dx];
recon_c[sby * 4 + dy][sbx * 4 + dx] = v.clamp(0, 255) as u8;
}
}
}
}
let mut d: u64 = 0;
for dy in 0..8 {
for dx in 0..8 {
let diff = src_c[dy][dx] as i64 - recon_c[dy][dx] as i64;
d += (diff * diff) as u64;
}
}
let dc_flat: [i32; 4] = [
dc_levels[0][0], dc_levels[0][1], dc_levels[1][0], dc_levels[1][1],
];
let mut r_bits = crate::codec::h264::cavlc_size::residual_block_bits_cavlc(
&dc_flat, -1, CavlcBlockType::ChromaDc,
).unwrap_or(0);
for sb in 0..4 {
let scan = raster_to_scan_levels(&ac_levels[sb]);
let ac_only: [i32; 15] = {
let mut a = [0i32; 15];
a.copy_from_slice(&scan[1..16]);
a
};
r_bits = r_bits.saturating_add(
crate::codec::h264::cavlc_size::residual_block_bits_cavlc(
&ac_only, -1, CavlcBlockType::ChromaAc,
).unwrap_or(0),
);
}
(d, r_bits)
}
fn predict_chroma_dc(reference: &ReconFrame, component: u8, mb_x: usize, mb_y: usize) -> [[u8; 8]; 8] {
let cx = mb_x * 8;
let cy = mb_y * 8;
let have_top = cy > 0;
let have_left = cx > 0;
let dc = match (have_top, have_left) {
(true, true) => {
let mut sum: i32 = 0;
for d in 0..8 {
sum += reference.chroma_at(component, (cx + d) as u32, (cy - 1) as u32) as i32;
sum += reference.chroma_at(component, (cx - 1) as u32, (cy + d) as u32) as i32;
}
(sum + 8) >> 4
}
(true, false) => {
let mut sum: i32 = 0;
for d in 0..8 {
sum += reference.chroma_at(component, (cx + d) as u32, (cy - 1) as u32) as i32;
}
(sum + 4) >> 3
}
(false, true) => {
let mut sum: i32 = 0;
for d in 0..8 {
sum += reference.chroma_at(component, (cx - 1) as u32, (cy + d) as u32) as i32;
}
(sum + 4) >> 3
}
(false, false) => 128,
};
let v = dc.clamp(0, 255) as u8;
[[v; 8]; 8]
}
#[inline]
pub fn nc_luma_free(grid: &[u8], frame_w4: usize, bx: usize, by: usize) -> i8 {
let left = if bx > 0 {
let v = grid[by * frame_w4 + (bx - 1)];
if v == 0xFF { None } else { Some(v) }
} else {
None
};
let top = if by > 0 {
let v = grid[(by - 1) * frame_w4 + bx];
if v == 0xFF { None } else { Some(v) }
} else {
None
};
match (left, top) {
(None, None) => 0,
(Some(v), None) | (None, Some(v)) => v.min(16) as i8,
(Some(a), Some(b)) => (((a as u16 + b as u16 + 1) >> 1).min(16)) as i8,
}
}
#[derive(Debug, Clone, Copy)]
pub struct PMbRdoResult {
pub choice: PMbChoice,
pub d_luma: u64,
pub r_bits: u32,
pub cost: u64,
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
pub fn evaluate_p_mb_rdo(
choice: &PMbChoice,
src_y: &[[u8; 16]; 16],
reference: &ReconFrame,
grid: &mut EncoderMvGrid,
mb_x: usize,
mb_y: usize,
mb_qp: u8,
total_coeff_grid: &[u8],
frame_w4: usize,
chroma_inputs: Option<ChromaRdoInputs<'_>>,
) -> PMbRdoResult {
use crate::codec::h264::macroblock::BLOCK_INDEX_TO_POS;
let pred_y = super::encoder::build_luma_prediction(reference, mb_x, mb_y, choice);
let inter_qp = QuantParams { qp: mb_qp, slice: QuantSlice::Inter };
let trellis_enable = true;
let mut luma_ac_levels = [[[0i32; 4]; 4]; 16];
let mut luma_nonzero = [false; 16];
let mut recon_y = [[0u8; 16]; 16];
for k in 0..16 {
let (bx, by) = BLOCK_INDEX_TO_POS[k];
let sby = by as usize;
let sbx = bx as usize;
let mut sub_res = [[0i32; 4]; 4];
for dy in 0..4 {
for dx in 0..4 {
sub_res[dy][dx] = src_y[sby * 4 + dy][sbx * 4 + dx] as i32
- pred_y[sby * 4 + dy][sbx * 4 + dx] as i32;
}
}
let coeffs = forward_dct_4x4(&sub_res);
let levels = trellis_quantize_4x4(&coeffs, inter_qp, trellis_enable)
.unwrap_or_else(|_| forward_quantize_4x4(&coeffs, inter_qp));
luma_ac_levels[k] = levels;
luma_nonzero[k] = levels.iter().any(|row| row.iter().any(|&v| v != 0));
let dq = dequant_4x4(&levels, mb_qp as i32, false);
let recon_res = inverse_4x4_integer(&dq);
for dy in 0..4 {
for dx in 0..4 {
let v = pred_y[sby * 4 + dy][sbx * 4 + dx] as i32 + recon_res[dy][dx];
recon_y[sby * 4 + dy][sbx * 4 + dx] = v.clamp(0, 255) as u8;
}
}
}
let mut d_luma: u64 = 0;
for dy in 0..16 {
for dx in 0..16 {
let diff = src_y[dy][dx] as i64 - recon_y[dy][dx] as i64;
d_luma += (diff * diff) as u64;
}
}
let drift_factor_q8: u64 = std::env::var("PHASM_INTER_DRIFT_FACTOR_Q8")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(256);
let d_luma_drift = (d_luma * drift_factor_q8.max(1)) >> 8;
let cbp_luma_8x8 = luma_8x8_cbp_mask(&luma_nonzero);
let cbp_chroma = 0u8;
let cbp_value = pack_cbp(cbp_luma_8x8, cbp_chroma);
let cbp_codenum = cbp_to_codenum_inter(cbp_value).unwrap_or(0);
let qp_delta_or_none = if cbp_value != 0 { Some(0i32) } else { None };
let mut luma_ac_scans = [[0i32; 16]; 16];
for k in 0..16 {
luma_ac_scans[k] = raster_to_scan_levels(&luma_ac_levels[k]);
}
let mut luma_nc = [0i8; 16];
for k in 0..16 {
let (bx_off, by_off) = BLOCK_INDEX_TO_POS[k];
let abs_bx = mb_x * 4 + bx_off as usize;
let abs_by = mb_y * 4 + by_off as usize;
luma_nc[k] = nc_luma_free(total_coeff_grid, frame_w4, abs_bx, abs_by);
}
let r_bits = p_mb_total_bits_cavlc(
choice,
grid,
mb_x,
mb_y,
cbp_codenum,
qp_delta_or_none,
cbp_luma_8x8,
cbp_chroma,
&luma_ac_scans,
&luma_nc,
None,
);
let psy_d = apply_psy_rd(src_y, &recon_y, mb_qp);
let (chroma_d, chroma_r) = if let Some(ci) = chroma_inputs {
let pred_cb = super::encoder::build_chroma_prediction(reference, 0, mb_x, mb_y, choice);
let pred_cr = super::encoder::build_chroma_prediction(reference, 1, mb_x, mb_y, choice);
let (dcb, rcb) = chroma_component_rdo(ci.src_cb, &pred_cb, ci.qp_c);
let (dcr, rcr) = chroma_component_rdo(ci.src_cr, &pred_cr, ci.qp_c);
(dcb + dcr, rcb + rcr)
} else {
(0, 0)
};
let lambda2_denom: u64 = std::env::var("PHASM_RDO_LAMBDA_DENOM")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(1);
let lambda2 = (lambda2_for_qp(mb_qp) as u64) / lambda2_denom.max(1);
let total_r = (r_bits as u64).saturating_add(chroma_r as u64);
let cost = d_luma_drift + psy_d + chroma_d + ((total_r * lambda2) >> 8);
PMbRdoResult {
choice: *choice,
d_luma,
r_bits: total_r as u32,
cost,
}
}
fn apply_psy_rd(src_y: &[[u8; 16]; 16], recon_y: &[[u8; 16]; 16], _mb_qp: u8) -> u64 {
let strength: u64 = std::env::var("PHASM_PSY_RD_STRENGTH")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(64);
if strength == 0 {
return 0;
}
use super::intra_predictor::hadamard_ac_sum_16x16;
let src_ac = hadamard_ac_sum_16x16(src_y);
let recon_ac = hadamard_ac_sum_16x16(recon_y) as i64;
let ac_diff = (src_ac as i64 - recon_ac).unsigned_abs();
let low: u32 = std::env::var("PHASM_PSY_CLAMP_LOW")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(500);
let high: u32 = std::env::var("PHASM_PSY_CLAMP_HIGH")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(3000);
let clamped_strength = if src_ac <= low {
0
} else if src_ac >= high {
strength
} else {
let num = (src_ac - low) as u64;
let den = (high - low) as u64;
strength.saturating_mul(num) / den.max(1)
};
(ac_diff as u64).saturating_mul(clamped_strength)
}
#[allow(clippy::too_many_arguments)]
pub fn evaluate_intra_in_p_rdo(
src_y: &[[u8; 16]; 16],
intra_pred_y: &[[u8; 16]; 16],
i16x16_mode: u32, chroma_pred_mode: u32, total_coeff_grid: &[u8],
frame_w4: usize,
mb_x: usize,
mb_y: usize,
mb_qp: u8,
chroma_inputs: Option<ChromaRdoInputs<'_>>,
reference_for_chroma: Option<&ReconFrame>,
) -> (u64, u32, u64) {
use crate::codec::h264::macroblock::BLOCK_INDEX_TO_POS;
let inter_qp = QuantParams { qp: mb_qp, slice: QuantSlice::Inter };
let trellis_enable = true;
let mut luma_ac_levels = [[[0i32; 4]; 4]; 16];
let mut luma_nonzero = [false; 16];
let mut recon_y = [[0u8; 16]; 16];
for k in 0..16 {
let (bx, by) = BLOCK_INDEX_TO_POS[k];
let sby = by as usize;
let sbx = bx as usize;
let mut sub_res = [[0i32; 4]; 4];
for dy in 0..4 {
for dx in 0..4 {
sub_res[dy][dx] = src_y[sby * 4 + dy][sbx * 4 + dx] as i32
- intra_pred_y[sby * 4 + dy][sbx * 4 + dx] as i32;
}
}
let coeffs = forward_dct_4x4(&sub_res);
let levels = trellis_quantize_4x4(&coeffs, inter_qp, trellis_enable)
.unwrap_or_else(|_| forward_quantize_4x4(&coeffs, inter_qp));
luma_ac_levels[k] = levels;
luma_nonzero[k] = levels.iter().any(|row| row.iter().any(|&v| v != 0));
let dq = dequant_4x4(&levels, mb_qp as i32, false);
let recon_res = inverse_4x4_integer(&dq);
for dy in 0..4 {
for dx in 0..4 {
let v = intra_pred_y[sby * 4 + dy][sbx * 4 + dx] as i32 + recon_res[dy][dx];
recon_y[sby * 4 + dy][sbx * 4 + dx] = v.clamp(0, 255) as u8;
}
}
}
let mut d_luma: u64 = 0;
for dy in 0..16 {
for dx in 0..16 {
let diff = src_y[dy][dx] as i64 - recon_y[dy][dx] as i64;
d_luma += (diff * diff) as u64;
}
}
let mb_type_p_codenum = 6 + i16x16_mode;
let any_luma_nonzero = luma_nonzero.iter().any(|&b| b);
let _cbp_luma_flag: u32 = if any_luma_nonzero { 15 } else { 0 };
let cbp_chroma: u32 = 0;
let _ = cbp_chroma;
let mut sz = BitSizer::new();
sz.write_ue(mb_type_p_codenum + if any_luma_nonzero { 12 } else { 0 });
sz.write_ue(chroma_pred_mode);
if any_luma_nonzero {
sz.write_se(0); }
let mut r_bits = sz.bits_written() as u32;
let mut luma_ac_scans = [[0i32; 16]; 16];
for k in 0..16 {
luma_ac_scans[k] = raster_to_scan_levels(&luma_ac_levels[k]);
}
for k in 0..16 {
let (bx_off, by_off) = BLOCK_INDEX_TO_POS[k];
let abs_bx = mb_x * 4 + bx_off as usize;
let abs_by = mb_y * 4 + by_off as usize;
let nc = nc_luma_free(total_coeff_grid, frame_w4, abs_bx, abs_by);
r_bits = r_bits.saturating_add(
crate::codec::h264::cavlc_size::residual_block_bits_cavlc(
&luma_ac_scans[k],
nc,
CavlcBlockType::Luma4x4,
)
.unwrap_or(0),
);
}
let psy_d = apply_psy_rd(src_y, &recon_y, mb_qp);
let (chroma_d, chroma_r) = match (chroma_inputs, reference_for_chroma) {
(Some(ci), Some(reference)) => {
let pred_cb = predict_chroma_dc(reference, 0, mb_x, mb_y);
let pred_cr = predict_chroma_dc(reference, 1, mb_x, mb_y);
let (dcb, rcb) = chroma_component_rdo(ci.src_cb, &pred_cb, ci.qp_c);
let (dcr, rcr) = chroma_component_rdo(ci.src_cr, &pred_cr, ci.qp_c);
(dcb + dcr, rcb + rcr)
}
_ => (0, 0),
};
let _ = chroma_pred_mode;
let lambda2_denom: u64 = std::env::var("PHASM_RDO_LAMBDA_DENOM")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(1);
let lambda2 = (lambda2_for_qp(mb_qp) as u64) / lambda2_denom.max(1);
let total_r = (r_bits as u64).saturating_add(chroma_r as u64);
let cost = d_luma + psy_d + chroma_d + ((total_r * lambda2) >> 8);
(d_luma, total_r as u32, cost)
}
#[cfg(test)]
mod tests {
use super::*;
#[inline]
fn pow2_round_u16(x: f64) -> u16 {
(x.exp2()).round().max(1.0) as u16
}
#[inline]
fn pow2_round_i32(x: f64) -> i32 {
(x.exp2()).round() as i32
}
#[test]
fn lambda_tab_matches_formula() {
for qp in 0..=51 {
let expected = pow2_round_u16((qp as f64 - 12.0) / 6.0);
assert_eq!(
LAMBDA_TAB[qp as usize], expected,
"LAMBDA_TAB[{qp}] = {} but formula gives {expected}",
LAMBDA_TAB[qp as usize]
);
}
}
#[test]
fn lambda2_tab_matches_formula() {
for qp in 0..=51 {
let exponent = qp as f64 / 3.0 + 4.0;
let expected = (0.9_f64 * exponent.exp2()).trunc() as i32;
assert_eq!(
LAMBDA2_TAB[qp as usize], expected,
"LAMBDA2_TAB[{qp}] = {} but formula gives {expected}",
LAMBDA2_TAB[qp as usize]
);
}
}
#[test]
fn trellis_tab_matches_formula() {
let inter_factor = 0.85_f64.powi(2);
let intra_factor = 0.65_f64.powi(2);
for qp in 0..=51 {
let base = (qp as f64 / 3.0 + 6.0).exp2();
let inter_expected = (inter_factor * base).round() as i32;
let intra_expected = (intra_factor * base).round() as i32;
assert_eq!(
TRELLIS_LAMBDA2_TAB[0][qp as usize], inter_expected,
"TRELLIS_LAMBDA2_TAB[inter][{qp}] drift"
);
assert_eq!(
TRELLIS_LAMBDA2_TAB[1][qp as usize], intra_expected,
"TRELLIS_LAMBDA2_TAB[intra][{qp}] drift"
);
}
}
#[test]
fn helpers_saturate_past_qp51() {
assert_eq!(lambda_for_qp(51), lambda_for_qp(60));
assert_eq!(lambda2_for_qp(51), lambda2_for_qp(99));
assert_eq!(trellis_lambda2_for_qp(51, true), trellis_lambda2_for_qp(200, true));
}
use super::super::bitstream_writer::BitWriter;
fn sizer_matches_writer(input: &dyn Fn(&mut dyn BitSink)) {
let mut w = BitWriter::new();
input(&mut w);
let mut sz = BitSizer::new();
input(&mut sz);
assert_eq!(
w.bits_written(),
sz.bits_written(),
"BitWriter vs BitSizer bit count mismatch"
);
}
#[test]
fn sizer_matches_writer_for_ue() {
for v in [0u32, 1, 7, 14, 63, 1023, 65535] {
sizer_matches_writer(&move |s| s.write_ue(v));
}
}
#[test]
fn sizer_matches_writer_for_se() {
for v in [-32767i32, -100, -3, -1, 0, 1, 3, 100, 32767] {
sizer_matches_writer(&move |s| s.write_se(v));
}
}
#[test]
fn sizer_matches_writer_for_write_bits() {
sizer_matches_writer(&|s| {
s.write_bits(0b1010_1100, 8);
s.write_bits(0x1234_5678, 32);
s.write_bit(true);
s.write_bit(false);
s.write_bits(0b11, 2);
});
}
#[test]
fn sizer_matches_writer_for_cavlc_luma_block_mixed_coeffs() {
let coeffs = [5i32, -3, 2, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let mut w = BitWriter::new();
encode_cavlc_block(&mut w, &coeffs, 0, CavlcBlockType::Luma4x4).unwrap();
let mut sz = BitSizer::new();
encode_cavlc_block(&mut sz, &coeffs, 0, CavlcBlockType::Luma4x4).unwrap();
assert_eq!(w.bits_written(), sz.bits_written());
assert_eq!(
sz.bits_written() as u32,
residual_block_bits_cavlc(&coeffs, 0, CavlcBlockType::Luma4x4)
);
}
#[test]
fn sizer_matches_writer_for_all_empty_cavlc_blocks() {
let types_zeros: &[(CavlcBlockType, &[i32])] = &[
(CavlcBlockType::Luma4x4, &[0; 16]),
(CavlcBlockType::Intra16x16Ac, &[0; 15]),
(CavlcBlockType::ChromaAc, &[0; 15]),
(CavlcBlockType::ChromaDc, &[0; 4]),
];
for (bt, zeros) in types_zeros {
let nc = if matches!(*bt, CavlcBlockType::ChromaDc) { -1 } else { 0 };
let mut w = BitWriter::new();
encode_cavlc_block(&mut w, zeros, nc, *bt).unwrap();
let mut sz = BitSizer::new();
encode_cavlc_block(&mut sz, zeros, nc, *bt).unwrap();
assert_eq!(
w.bits_written(),
sz.bits_written(),
"mismatch on {bt:?}"
);
}
}
#[test]
fn p_mb_header_bits_matches_writer_for_p16x16() {
use super::super::bitstream_writer::BitWriter;
use super::super::encoder::emit_mvds_and_update_grid;
use super::super::motion_estimation::MotionVector;
use super::super::partition_decision::PMbChoice;
use super::super::partition_state::EncoderMvGrid;
let choice = PMbChoice::P16x16 {
mv: MotionVector { mv_x: 7, mv_y: -3 },
};
let mut grid_a = EncoderMvGrid::new(4, 4);
grid_a.fill(0, 0, 4, 4, MotionVector { mv_x: 1, mv_y: 1 }, 0);
let mut grid_b = grid_a.clone();
let mut w = BitWriter::new();
emit_mvds_and_update_grid(&mut w, &mut grid_a, 2, 2, &choice);
let mut sz = BitSizer::new();
emit_mvds_and_update_grid(&mut sz, &mut grid_b, 2, 2, &choice);
assert_eq!(
<BitWriter as BitSink>::bits_written(&w),
sz.bits_written()
);
}
#[test]
fn sizer_matches_writer_for_high_magnitude_levels() {
let coeffs = [127i32, 64, 31, 15, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let mut w = BitWriter::new();
encode_cavlc_block(&mut w, &coeffs, 0, CavlcBlockType::Luma4x4).unwrap();
let mut sz = BitSizer::new();
encode_cavlc_block(&mut sz, &coeffs, 0, CavlcBlockType::Luma4x4).unwrap();
assert_eq!(w.bits_written(), sz.bits_written());
}
#[test]
fn lambda2_matches_formula_reasonably() {
for &qp in &[12, 18, 24, 30, 36, 42] {
let l = LAMBDA_TAB[qp] as i64;
let expected = (l * l * 2304 + 5) / 10; let actual = LAMBDA2_TAB[qp] as i64;
assert!(
(expected - actual).abs() <= 2,
"qp={}: expected ≈ {}, got {}",
qp,
expected,
actual
);
}
}
}