use crate::cdf::{QuantizerParams, DCT_DCT, TX_4X4};
use crate::encoder::forward_quantize::forward_quantize;
use crate::encoder::forward_transform_2d::forward_transform_2d;
use crate::encoder::forward_wht::forward_wht_4x4;
use crate::inter_pred::{reconstruct_inter_block, InterModeInfo, RefFrameStoreEntry, EIGHTTAP};
use crate::Error;
#[derive(Debug, Clone, Copy)]
pub struct EncRefPlane<'a> {
pub plane: &'a [u16],
pub stride: usize,
pub width: u32,
pub height: u32,
}
#[allow(clippy::too_many_arguments)]
pub fn predict_inter_block_single(
reference: &EncRefPlane<'_>,
mv: [i16; 2],
plane: u8,
x: i32,
y: i32,
w: usize,
h: usize,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
interp_filter: u8,
out: &mut [u16],
) -> Result<(), Error> {
if out.len() < w * h {
return Err(Error::PartitionWalkOutOfRange);
}
let store = [RefFrameStoreEntry {
plane: reference.plane,
stride: reference.stride,
upscaled_width: reference.width,
width: reference.width,
height: reference.height,
}];
let ref_frame_idx = [0u8];
let mode_info = InterModeInfo {
ref_frame: crate::uncompressed_header_tail::LAST_FRAME as u8,
mv,
};
if x < 0 || y < 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let dst_w = (x as usize) + w;
let dst_h = (y as usize) + h;
let mut dst = vec![0u16; dst_w * dst_h];
reconstruct_inter_block(
mode_info,
&ref_frame_idx,
&store,
plane,
x,
y,
w,
h,
bit_depth,
subsampling_x,
subsampling_y,
reference.width,
reference.height,
interp_filter,
interp_filter,
&mut dst,
dst_w,
)?;
for i in 0..h {
let src = ((y as usize) + i) * dst_w + (x as usize);
let d = i * w;
out[d..d + w].copy_from_slice(&dst[src..src + w]);
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct InterResidualLeaf {
pub quant: Vec<i32>,
pub recon: [u8; 16],
pub pred: [u8; 16],
}
pub fn encode_inter_block_residual_4x4(
input: &[u8; 16],
pred: &[u8; 16],
plane: u8,
lossless: bool,
qp: &QuantizerParams,
) -> Result<InterResidualLeaf, Error> {
let mut residual = [0i64; 16];
for k in 0..16 {
residual[k] = input[k] as i64 - pred[k] as i64;
}
let coeffs = if lossless {
forward_wht_4x4(&residual).to_vec()
} else {
forward_transform_2d(&residual, TX_4X4, DCT_DCT, false)
};
let qm = qp.seg_qm_level[plane as usize][0];
let quant = forward_quantize(&coeffs, TX_4X4, plane, 0, DCT_DCT, qm, qp);
let dequant = crate::cdf::dequantize_step1(&quant, TX_4X4, plane, 0, DCT_DCT, qm, qp);
let resid_back = crate::transform::inverse_transform_2d(&dequant, TX_4X4, DCT_DCT, 8, lossless);
let mut recon = [0u8; 16];
for k in 0..16 {
let v = pred[k] as i64 + resid_back[k];
recon[k] = v.clamp(0, 255) as u8;
}
Ok(InterResidualLeaf {
quant,
recon,
pred: {
let mut p = [0u8; 16];
p.copy_from_slice(pred);
p
},
})
}
#[allow(clippy::too_many_arguments)]
pub fn estimate_motion_4x4_full_search(
reference: &EncRefPlane<'_>,
input: &[u8; 16],
x: i32,
y: i32,
bit_depth: u8,
search: i32,
) -> Result<[i16; 2], Error> {
let mut best_mv = [0i16; 2];
let mut best_sad = u32::MAX;
let mut best_cost = i32::MAX; for dr in -search..=search {
for dc in -search..=search {
let mv = [(dr * 8) as i16, (dc * 8) as i16];
let sad = block_sad_at_mv(reference, input, x, y, mv, bit_depth)?;
let cost = dr.abs() + dc.abs();
if sad < best_sad || (sad == best_sad && cost < best_cost) {
best_sad = sad;
best_cost = cost;
best_mv = mv;
}
}
}
Ok(best_mv)
}
fn block_sad_at_mv(
reference: &EncRefPlane<'_>,
input: &[u8; 16],
x: i32,
y: i32,
mv: [i16; 2],
bit_depth: u8,
) -> Result<u32, Error> {
let mut pred = [0u16; 16];
predict_inter_block_single(
reference, mv, 0, x, y, 4, 4, bit_depth, 0, 0, EIGHTTAP, &mut pred,
)?;
let mut sad = 0u32;
for k in 0..16 {
sad += (input[k] as i32 - pred[k] as i32).unsigned_abs();
}
Ok(sad)
}
#[allow(clippy::too_many_arguments)]
pub fn estimate_motion_4x4_subpel(
reference: &EncRefPlane<'_>,
input: &[u8; 16],
x: i32,
y: i32,
bit_depth: u8,
search: i32,
) -> Result<[i16; 2], Error> {
let mut best = estimate_motion_4x4_full_search(reference, input, x, y, bit_depth, search)?;
let mut best_sad = block_sad_at_mv(reference, input, x, y, best, bit_depth)?;
for &step in &[4i16, 2, 1] {
loop {
let mut improved = false;
let centre = best;
for &(dr, dc) in &[
(-step, 0i16),
(step, 0),
(0, -step),
(0, step),
(-step, -step),
(-step, step),
(step, -step),
(step, step),
] {
let cand = [centre[0] + dr, centre[1] + dc];
let sad = block_sad_at_mv(reference, input, x, y, cand, bit_depth)?;
let cand_cost = (cand[0].abs() as i32) + (cand[1].abs() as i32);
let best_cost = (best[0].abs() as i32) + (best[1].abs() as i32);
if sad < best_sad || (sad == best_sad && cand_cost < best_cost) {
best_sad = sad;
best = cand;
improved = true;
}
}
if !improved {
break;
}
}
}
Ok(best)
}
#[derive(Debug, Clone)]
pub struct EncodedInterFrameY {
pub width: u32,
pub height: u32,
pub mvs: Vec<[i16; 2]>,
pub recon: Vec<u8>,
pub quant: Vec<Vec<i32>>,
}
pub fn encode_inter_frame_y(
input: &[u8],
width: u32,
height: u32,
reference: &EncRefPlane<'_>,
lossless: bool,
qp: &QuantizerParams,
search: i32,
) -> Result<EncodedInterFrameY, Error> {
encode_inter_frame_y_opt(input, width, height, reference, lossless, qp, search, false)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_inter_frame_y_opt(
input: &[u8],
width: u32,
height: u32,
reference: &EncRefPlane<'_>,
lossless: bool,
qp: &QuantizerParams,
search: i32,
subpel: bool,
) -> Result<EncodedInterFrameY, Error> {
if width % 4 != 0 || height % 4 != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let w = width as usize;
let h = height as usize;
if input.len() < w * h {
return Err(Error::PartitionWalkOutOfRange);
}
let mi_cols = (width / 4) as usize;
let mi_rows = (height / 4) as usize;
let mut mvs = vec![[0i16; 2]; mi_rows * mi_cols];
let mut quant = Vec::with_capacity(mi_rows * mi_cols);
let mut recon = vec![0u8; w * h];
for mi_r in 0..mi_rows {
for mi_c in 0..mi_cols {
let row0 = mi_r * 4;
let col0 = mi_c * 4;
let mut blk = [0u8; 16];
for i in 0..4 {
for j in 0..4 {
blk[i * 4 + j] = input[(row0 + i) * w + (col0 + j)];
}
}
let mv = if subpel {
estimate_motion_4x4_subpel(
reference,
&blk,
col0 as i32,
row0 as i32,
qp.bit_depth,
search,
)?
} else {
estimate_motion_4x4_full_search(
reference,
&blk,
col0 as i32,
row0 as i32,
qp.bit_depth,
search,
)?
};
let mut pred16 = [0u16; 16];
predict_inter_block_single(
reference,
mv,
0,
col0 as i32,
row0 as i32,
4,
4,
qp.bit_depth,
0,
0,
EIGHTTAP,
&mut pred16,
)?;
let pred: [u8; 16] = std::array::from_fn(|k| pred16[k] as u8);
let leaf = encode_inter_block_residual_4x4(&blk, &pred, 0, lossless, qp)?;
for i in 0..4 {
for j in 0..4 {
recon[(row0 + i) * w + (col0 + j)] = leaf.recon[i * 4 + j];
}
}
mvs[mi_r * mi_cols + mi_c] = mv;
quant.push(leaf.quant);
}
}
Ok(EncodedInterFrameY {
width,
height,
mvs,
recon,
quant,
})
}
#[derive(Debug, Clone)]
pub struct EncodedInterFrameYuv {
pub width: u32,
pub height: u32,
pub mvs: Vec<[i16; 2]>,
pub recon_y: Vec<u8>,
pub recon_u: Vec<u8>,
pub recon_v: Vec<u8>,
}
#[allow(clippy::too_many_arguments)]
pub fn encode_inter_frame_yuv(
input_y: &[u8],
input_u: &[u8],
input_v: &[u8],
width: u32,
height: u32,
ref_y: &EncRefPlane<'_>,
ref_u: &EncRefPlane<'_>,
ref_v: &EncRefPlane<'_>,
lossless: bool,
qp: &QuantizerParams,
search: i32,
subpel: bool,
) -> Result<EncodedInterFrameYuv, Error> {
if width % 8 != 0 || height % 8 != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
let enc_y =
encode_inter_frame_y_opt(input_y, width, height, ref_y, lossless, qp, search, subpel)?;
let mi_cols = (width / 4) as usize;
let cw = (width / 2) as usize;
let ch = (height / 2) as usize;
if input_u.len() < cw * ch || input_v.len() < cw * ch {
return Err(Error::PartitionWalkOutOfRange);
}
let c_mi_cols = cw / 4;
let c_mi_rows = ch / 4;
let mut recon_u = vec![0u8; cw * ch];
let mut recon_v = vec![0u8; cw * ch];
for (plane, (input_c, recon_c, ref_c)) in [
(1u8, (input_u, &mut recon_u, ref_u)),
(2u8, (input_v, &mut recon_v, ref_v)),
] {
for c_mi_r in 0..c_mi_rows {
for c_mi_c in 0..c_mi_cols {
let crow0 = c_mi_r * 4;
let ccol0 = c_mi_c * 4;
let luma_cell = (c_mi_r * 2) * mi_cols + (c_mi_c * 2);
let mv = enc_y.mvs[luma_cell];
let mut blk = [0u8; 16];
for i in 0..4 {
for j in 0..4 {
blk[i * 4 + j] = input_c[(crow0 + i) * cw + (ccol0 + j)];
}
}
let mut pred16 = [0u16; 16];
predict_inter_block_single(
ref_c,
mv,
plane,
ccol0 as i32,
crow0 as i32,
4,
4,
qp.bit_depth,
1,
1,
EIGHTTAP,
&mut pred16,
)?;
let pred: [u8; 16] = std::array::from_fn(|k| pred16[k] as u8);
let leaf = encode_inter_block_residual_4x4(&blk, &pred, plane, lossless, qp)?;
for i in 0..4 {
for j in 0..4 {
recon_c[(crow0 + i) * cw + (ccol0 + j)] = leaf.recon[i * 4 + j];
}
}
}
}
}
Ok(EncodedInterFrameYuv {
width,
height,
mvs: enc_y.mvs,
recon_y: enc_y.recon,
recon_u,
recon_v,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn qp_lossless() -> QuantizerParams {
QuantizerParams::neutral(0, 8)
}
fn qp_lossy(q: u8) -> QuantizerParams {
QuantizerParams::neutral(q, 8)
}
fn ref_plane(width: u32, height: u32, f: impl Fn(u32, u32) -> u16) -> Vec<u16> {
let mut v = vec![0u16; (width * height) as usize];
for r in 0..height {
for c in 0..width {
v[(r * width + c) as usize] = f(r, c);
}
}
v
}
#[test]
fn zero_mv_prediction_copies_reference_block() {
let w = 16u32;
let h = 16u32;
let plane = ref_plane(w, h, |r, c| ((r * 16 + c) % 256) as u16);
let reference = EncRefPlane {
plane: &plane,
stride: w as usize,
width: w,
height: h,
};
let mut out = [0u16; 16];
predict_inter_block_single(
&reference,
[0, 0],
0,
4,
4,
4,
4,
8,
0,
0,
EIGHTTAP,
&mut out,
)
.unwrap();
for i in 0..4 {
for j in 0..4 {
let want = plane[((4 + i) * w as usize) + (4 + j)];
assert_eq!(out[i * 4 + j], want, "({i},{j})");
}
}
}
#[test]
fn integer_mv_prediction_shifts_reference_block() {
let w = 24u32;
let h = 24u32;
let plane = ref_plane(w, h, |r, c| ((r * 7 + c * 3) % 256) as u16);
let reference = EncRefPlane {
plane: &plane,
stride: w as usize,
width: w,
height: h,
};
let mut out = [0u16; 16];
predict_inter_block_single(
&reference,
[8, 16],
0,
8,
8,
4,
4,
8,
0,
0,
EIGHTTAP,
&mut out,
)
.unwrap();
for i in 0..4 {
for j in 0..4 {
let want = plane[((8 + 1 + i) * w as usize) + (8 + 2 + j)];
assert_eq!(out[i * 4 + j], want, "shifted ({i},{j})");
}
}
}
#[test]
fn lossless_inter_residual_reconstructs_input_bit_exact() {
let pred: [u8; 16] = std::array::from_fn(|k| (k * 11 % 256) as u8);
let input: [u8; 16] = std::array::from_fn(|k| ((k * 37 + 5) % 256) as u8);
let leaf = encode_inter_block_residual_4x4(&input, &pred, 0, true, &qp_lossless()).unwrap();
assert_eq!(leaf.recon, input, "lossless inter residual is bit-exact");
}
#[test]
fn lossy_inter_residual_zero_when_pred_equals_input() {
let input: [u8; 16] = std::array::from_fn(|k| ((k * 13) % 200 + 10) as u8);
let leaf =
encode_inter_block_residual_4x4(&input, &input, 0, false, &qp_lossy(64)).unwrap();
assert!(
leaf.quant.iter().all(|&c| c == 0),
"zero residual ⇒ zero coeffs"
);
assert_eq!(leaf.recon, input, "zero residual ⇒ recon == input");
}
#[test]
fn full_search_recovers_known_translation() {
let w = 32u32;
let h = 32u32;
let plane = ref_plane(w, h, |r, c| ((r * 9 + c * 5) % 256) as u16);
let reference = EncRefPlane {
plane: &plane,
stride: w as usize,
width: w,
height: h,
};
let (bx, by) = (12usize, 12usize);
let input: [u8; 16] = std::array::from_fn(|k| {
let i = k / 4;
let j = k % 4;
plane[((by + 1 + i) * w as usize) + (bx + 2 + j)] as u8
});
let mv = estimate_motion_4x4_full_search(&reference, &input, bx as i32, by as i32, 8, 3)
.unwrap();
assert_eq!(mv, [8, 16], "full search recovers the (+1,+2) translation");
}
#[test]
fn full_search_zero_mv_for_static_match() {
let w = 20u32;
let h = 20u32;
let plane = ref_plane(w, h, |r, c| ((r + c) % 64) as u16 + 30);
let reference = EncRefPlane {
plane: &plane,
stride: w as usize,
width: w,
height: h,
};
let (bx, by) = (8usize, 8usize);
let input: [u8; 16] = std::array::from_fn(|k| {
let i = k / 4;
let j = k % 4;
plane[((by + i) * w as usize) + (bx + j)] as u8
});
let mv = estimate_motion_4x4_full_search(&reference, &input, bx as i32, by as i32, 8, 2)
.unwrap();
assert_eq!(mv, [0, 0], "static block ⇒ zero MV");
}
#[test]
fn predict_rejects_short_out_buffer() {
let plane = vec![0u16; 16];
let reference = EncRefPlane {
plane: &plane,
stride: 4,
width: 4,
height: 4,
};
let mut out = [0u16; 4];
let r = predict_inter_block_single(
&reference,
[0, 0],
0,
0,
0,
4,
4,
8,
0,
0,
EIGHTTAP,
&mut out,
);
assert!(r.is_err(), "short out buffer rejected");
}
use crate::inter_pred::{
reconstruct_inter_frame, InterModeInfoGrid, PlaneReconContext, RefFrameStoreEntry,
};
fn decoder_frame_prediction(enc: &EncodedInterFrameY, reference: &EncRefPlane<'_>) -> Vec<u16> {
let mi_cols = (enc.width / 4) as usize;
let mi_rows = (enc.height / 4) as usize;
let cells = mi_rows * mi_cols;
let last = crate::uncompressed_header_tail::LAST_FRAME;
let mi_sizes = vec![crate::cdf::BLOCK_4X4; cells];
let is_inters = vec![1u8; cells];
let mut ref_frames = vec![0i8; cells * 2];
let mut mvs = vec![0i16; cells * 4];
let interp_filters = vec![EIGHTTAP; cells * 2];
let zeros = vec![0u8; cells];
for cell in 0..cells {
ref_frames[cell * 2] = last as i8;
ref_frames[cell * 2 + 1] = -1; mvs[cell * 4] = enc.mvs[cell][0]; mvs[cell * 4 + 1] = enc.mvs[cell][1]; }
let order_hints_by_ref = [0i32; 8];
let grid = InterModeInfoGrid {
mi_sizes: &mi_sizes,
is_inters: &is_inters,
ref_frames: &ref_frames,
mvs: &mvs,
interp_filters: &interp_filters,
compound_types: &zeros,
wedge_indices: &zeros,
wedge_signs: &zeros,
mask_types: &zeros,
interintra_modes: &zeros,
wedge_interintras: &zeros,
interintra_wedge_indices: &zeros,
order_hint_bits: 7,
current_order_hint: 0,
order_hints_by_ref: &order_hints_by_ref,
mi_rows: mi_rows as u32,
mi_cols: mi_cols as u32,
bit_depth: 8,
warp: None,
obmc: None,
};
let store = [RefFrameStoreEntry {
plane: reference.plane,
stride: reference.stride,
upscaled_width: reference.width,
width: reference.width,
height: reference.height,
}];
let ref_frame_idx = [0u8];
let mut curr = vec![0u16; (enc.width * enc.height) as usize];
let mut planes = [PlaneReconContext {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: reference.width,
frame_height: reference.height,
curr: &mut curr,
curr_stride: enc.width as usize,
}];
reconstruct_inter_frame(&grid, &ref_frame_idx, &mut planes).unwrap();
curr
}
#[test]
fn encoder_motion_field_round_trips_through_decoder_frame_walk() {
let w = 32u32;
let h = 24u32;
let plane = ref_plane(w + 16, h + 16, |r, c| {
(((r * 13) ^ (c * 7)).wrapping_add(r * c) % 256) as u16
});
let rw = w + 16;
let reference = EncRefPlane {
plane: &plane,
stride: rw as usize,
width: rw,
height: h + 16,
};
let mut input = vec![0u8; (w * h) as usize];
for r in 0..h as usize {
for c in 0..w as usize {
input[r * w as usize + c] = plane[(r + 2) * rw as usize + (c + 3)] as u8;
}
}
let qp = qp_lossy(48);
let enc = encode_inter_frame_y(&input, w, h, &reference, false, &qp, 4).unwrap();
let mut enc_pred = vec![0u16; (w * h) as usize];
let mi_cols = (w / 4) as usize;
for (cell, mv) in enc.mvs.iter().enumerate() {
let mi_r = cell / mi_cols;
let mi_c = cell % mi_cols;
let mut p = [0u16; 16];
predict_inter_block_single(
&reference,
*mv,
0,
(mi_c * 4) as i32,
(mi_r * 4) as i32,
4,
4,
8,
0,
0,
EIGHTTAP,
&mut p,
)
.unwrap();
for i in 0..4 {
for j in 0..4 {
enc_pred[(mi_r * 4 + i) * w as usize + (mi_c * 4 + j)] = p[i * 4 + j];
}
}
}
let dec_pred = decoder_frame_prediction(&enc, &reference);
assert_eq!(
enc_pred, dec_pred,
"encoder MC prediction == decoder frame-walk prediction"
);
}
#[test]
fn lossless_inter_frame_reconstruction_is_bit_exact() {
let w = 16u32;
let h = 16u32;
let plane = ref_plane(w + 8, h + 8, |r, c| ((r * 5 + c * 11) % 256) as u16);
let reference = EncRefPlane {
plane: &plane,
stride: (w + 8) as usize,
width: w + 8,
height: h + 8,
};
let input: Vec<u8> = (0..(w * h)).map(|k| ((k * 17 + 3) % 256) as u8).collect();
let enc = encode_inter_frame_y(&input, w, h, &reference, true, &qp_lossless(), 2).unwrap();
assert_eq!(enc.recon, input, "lossless inter frame recon is bit-exact");
}
#[test]
fn lossy_inter_frame_static_match_is_zero_residual() {
let w = 16u32;
let h = 16u32;
let plane = ref_plane(w + 8, h + 8, |r, c| ((r * 3 + c) % 100 + 20) as u16);
let reference = EncRefPlane {
plane: &plane,
stride: (w + 8) as usize,
width: w + 8,
height: h + 8,
};
let mut input = vec![0u8; (w * h) as usize];
for r in 0..h as usize {
for c in 0..w as usize {
input[r * w as usize + c] = plane[r * (w + 8) as usize + c] as u8;
}
}
let enc = encode_inter_frame_y(&input, w, h, &reference, false, &qp_lossy(80), 2).unwrap();
assert!(
enc.mvs.iter().all(|mv| *mv == [0, 0]),
"static match ⇒ all zero MVs"
);
assert_eq!(enc.recon, input, "zero residual ⇒ recon == input");
}
#[test]
fn subpel_estimate_never_worse_than_integer() {
let w = 40u32;
let h = 40u32;
let plane = ref_plane(w, h, |r, c| {
(((r * 3 + c * 2) as f32 * 1.7).sin().abs() * 200.0) as u16 + 10
});
let reference = EncRefPlane {
plane: &plane,
stride: w as usize,
width: w,
height: h,
};
let (bx, by) = (16usize, 16usize);
let input: [u8; 16] = std::array::from_fn(|k| {
let i = k / 4;
let j = k % 4;
let a = plane[(by + i) * w as usize + (bx + j)] as u32;
let b = plane[(by + i) * w as usize + (bx + j + 1)] as u32;
((a + b) / 2) as u8
});
let int_mv =
estimate_motion_4x4_full_search(&reference, &input, bx as i32, by as i32, 8, 3)
.unwrap();
let sub_mv =
estimate_motion_4x4_subpel(&reference, &input, bx as i32, by as i32, 8, 3).unwrap();
let int_sad = block_sad_at_mv(&reference, &input, bx as i32, by as i32, int_mv, 8).unwrap();
let sub_sad = block_sad_at_mv(&reference, &input, bx as i32, by as i32, sub_mv, 8).unwrap();
assert!(
sub_sad <= int_sad,
"sub-pel SAD {sub_sad} <= integer {int_sad}"
);
}
#[test]
fn subpel_frame_round_trips_through_decoder_frame_walk() {
let w = 24u32;
let h = 20u32;
let rw = w + 12;
let plane = ref_plane(rw, h + 12, |r, c| {
(((r * 11) ^ (c * 5)).wrapping_add(r + c) % 256) as u16
});
let reference = EncRefPlane {
plane: &plane,
stride: rw as usize,
width: rw,
height: h + 12,
};
let mut input = vec![0u8; (w * h) as usize];
for r in 0..h as usize {
for c in 0..w as usize {
let a = plane[(r + 1) * rw as usize + (c + 1)] as u32;
let b = plane[(r + 1) * rw as usize + (c + 2)] as u32;
input[r * w as usize + c] = ((a + b) / 2) as u8;
}
}
let qp = qp_lossy(40);
let enc = encode_inter_frame_y_opt(&input, w, h, &reference, false, &qp, 3, true).unwrap();
let mut enc_pred = vec![0u16; (w * h) as usize];
let mi_cols = (w / 4) as usize;
for (cell, mv) in enc.mvs.iter().enumerate() {
let mi_r = cell / mi_cols;
let mi_c = cell % mi_cols;
let mut p = [0u16; 16];
predict_inter_block_single(
&reference,
*mv,
0,
(mi_c * 4) as i32,
(mi_r * 4) as i32,
4,
4,
8,
0,
0,
EIGHTTAP,
&mut p,
)
.unwrap();
for i in 0..4 {
for j in 0..4 {
enc_pred[(mi_r * 4 + i) * w as usize + (mi_c * 4 + j)] = p[i * 4 + j];
}
}
}
let dec_pred = decoder_frame_prediction(&enc, &reference);
assert_eq!(enc_pred, dec_pred, "sub-pel field round-trips");
assert!(
enc.mvs.iter().any(|mv| mv[0] % 8 != 0 || mv[1] % 8 != 0),
"sub-pel refinement produced a fractional MV"
);
}
#[allow(clippy::type_complexity)]
fn decoder_frame_prediction_yuv(
width: u32,
height: u32,
mvs: &[[i16; 2]],
ref_y: &EncRefPlane<'_>,
ref_u: &EncRefPlane<'_>,
ref_v: &EncRefPlane<'_>,
) -> (Vec<u16>, Vec<u16>, Vec<u16>) {
let mi_cols = (width / 4) as usize;
let mi_rows = (height / 4) as usize;
let cells = mi_rows * mi_cols;
let last = crate::uncompressed_header_tail::LAST_FRAME;
let mi_sizes = vec![crate::cdf::BLOCK_4X4; cells];
let is_inters = vec![1u8; cells];
let mut ref_frames = vec![0i8; cells * 2];
let mut grid_mvs = vec![0i16; cells * 4];
let interp_filters = vec![EIGHTTAP; cells * 2];
let zeros = vec![0u8; cells];
for cell in 0..cells {
ref_frames[cell * 2] = last as i8;
ref_frames[cell * 2 + 1] = -1;
grid_mvs[cell * 4] = mvs[cell][0];
grid_mvs[cell * 4 + 1] = mvs[cell][1];
}
let order_hints_by_ref = [0i32; 8];
let grid = InterModeInfoGrid {
mi_sizes: &mi_sizes,
is_inters: &is_inters,
ref_frames: &ref_frames,
mvs: &grid_mvs,
interp_filters: &interp_filters,
compound_types: &zeros,
wedge_indices: &zeros,
wedge_signs: &zeros,
mask_types: &zeros,
interintra_modes: &zeros,
wedge_interintras: &zeros,
interintra_wedge_indices: &zeros,
order_hint_bits: 7,
current_order_hint: 0,
order_hints_by_ref: &order_hints_by_ref,
mi_rows: mi_rows as u32,
mi_cols: mi_cols as u32,
bit_depth: 8,
warp: None,
obmc: None,
};
let ref_frame_idx = [0u8];
let store_y = [RefFrameStoreEntry {
plane: ref_y.plane,
stride: ref_y.stride,
upscaled_width: ref_y.width,
width: ref_y.width,
height: ref_y.height,
}];
let store_u = [RefFrameStoreEntry {
plane: ref_u.plane,
stride: ref_u.stride,
upscaled_width: ref_u.width,
width: ref_u.width,
height: ref_u.height,
}];
let store_v = [RefFrameStoreEntry {
plane: ref_v.plane,
stride: ref_v.stride,
upscaled_width: ref_v.width,
width: ref_v.width,
height: ref_v.height,
}];
let cw = (width / 2) as usize;
let ch = (height / 2) as usize;
let mut curr_y = vec![0u16; (width * height) as usize];
let mut curr_u = vec![0u16; cw * ch];
let mut curr_v = vec![0u16; cw * ch];
{
let mut planes = [
PlaneReconContext {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store_y,
frame_width: ref_y.width,
frame_height: ref_y.height,
curr: &mut curr_y,
curr_stride: width as usize,
},
PlaneReconContext {
plane: 1,
subsampling_x: 1,
subsampling_y: 1,
frame_store: &store_u,
frame_width: ref_u.width,
frame_height: ref_u.height,
curr: &mut curr_u,
curr_stride: cw,
},
PlaneReconContext {
plane: 2,
subsampling_x: 1,
subsampling_y: 1,
frame_store: &store_v,
frame_width: ref_v.width,
frame_height: ref_v.height,
curr: &mut curr_v,
curr_stride: cw,
},
];
reconstruct_inter_frame(&grid, &ref_frame_idx, &mut planes).unwrap();
}
(curr_y, curr_u, curr_v)
}
#[test]
fn yuv_inter_frame_round_trips_chroma_through_decoder_frame_walk() {
let w = 32u32;
let h = 24u32;
let rw = w + 16;
let rh = h + 16;
let cw = w / 2;
let chh = h / 2;
let crw = rw / 2;
let crh = rh / 2;
let plane_y = ref_plane(rw, rh, |r, c| (((r * 7) ^ (c * 3)) % 256) as u16);
let plane_u = ref_plane(crw, crh, |r, c| ((r * 5 + c * 9) % 256) as u16);
let plane_v = ref_plane(crw, crh, |r, c| ((r * 11 + c) % 256) as u16);
let ref_y = EncRefPlane {
plane: &plane_y,
stride: rw as usize,
width: rw,
height: rh,
};
let ref_u = EncRefPlane {
plane: &plane_u,
stride: crw as usize,
width: crw,
height: crh,
};
let ref_v = EncRefPlane {
plane: &plane_v,
stride: crw as usize,
width: crw,
height: crh,
};
let mut in_y = vec![0u8; (w * h) as usize];
for r in 0..h as usize {
for c in 0..w as usize {
in_y[r * w as usize + c] = plane_y[(r + 2) * rw as usize + (c + 3)] as u8;
}
}
let mut in_u = vec![0u8; (cw * chh) as usize];
let mut in_v = vec![0u8; (cw * chh) as usize];
for r in 0..chh as usize {
for c in 0..cw as usize {
in_u[r * cw as usize + c] = plane_u[(r + 1) * crw as usize + (c + 1)] as u8;
in_v[r * cw as usize + c] = plane_v[(r + 1) * crw as usize + (c + 1)] as u8;
}
}
let qp = qp_lossy(48);
let enc = encode_inter_frame_yuv(
&in_y, &in_u, &in_v, w, h, &ref_y, &ref_u, &ref_v, false, &qp, 3, false,
)
.unwrap();
let mi_cols = (w / 4) as usize;
let c_mi_cols = (cw / 4) as usize;
let c_mi_rows = (chh / 4) as usize;
let mut enc_pred_u = vec![0u16; (cw * chh) as usize];
let mut enc_pred_v = vec![0u16; (cw * chh) as usize];
for (plane, (ref_c, pred_c)) in [
(1u8, (&ref_u, &mut enc_pred_u)),
(2u8, (&ref_v, &mut enc_pred_v)),
] {
for c_mi_r in 0..c_mi_rows {
for c_mi_c in 0..c_mi_cols {
let mv = enc.mvs[(c_mi_r * 2) * mi_cols + (c_mi_c * 2)];
let mut p = [0u16; 16];
predict_inter_block_single(
ref_c,
mv,
plane,
(c_mi_c * 4) as i32,
(c_mi_r * 4) as i32,
4,
4,
8,
1,
1,
EIGHTTAP,
&mut p,
)
.unwrap();
for i in 0..4 {
for j in 0..4 {
pred_c[(c_mi_r * 4 + i) * cw as usize + (c_mi_c * 4 + j)] =
p[i * 4 + j];
}
}
}
}
}
let (_dy, du, dv) = decoder_frame_prediction_yuv(w, h, &enc.mvs, &ref_y, &ref_u, &ref_v);
assert_eq!(enc_pred_u, du, "U chroma prediction round-trips");
assert_eq!(enc_pred_v, dv, "V chroma prediction round-trips");
}
#[test]
fn lossless_yuv_inter_frame_reconstruction_is_bit_exact() {
let w = 16u32;
let h = 16u32;
let cw = w / 2;
let chh = h / 2;
let plane_y = ref_plane(w + 8, h + 8, |r, c| ((r * 5 + c * 3) % 256) as u16);
let plane_u = ref_plane(cw + 4, chh + 4, |r, c| ((r + c * 2) % 256) as u16);
let plane_v = ref_plane(cw + 4, chh + 4, |r, c| ((r * 2 + c) % 256) as u16);
let ref_y = EncRefPlane {
plane: &plane_y,
stride: (w + 8) as usize,
width: w + 8,
height: h + 8,
};
let ref_u = EncRefPlane {
plane: &plane_u,
stride: (cw + 4) as usize,
width: cw + 4,
height: chh + 4,
};
let ref_v = EncRefPlane {
plane: &plane_v,
stride: (cw + 4) as usize,
width: cw + 4,
height: chh + 4,
};
let in_y: Vec<u8> = (0..(w * h)).map(|k| ((k * 13 + 7) % 256) as u8).collect();
let in_u: Vec<u8> = (0..(cw * chh))
.map(|k| ((k * 19 + 1) % 256) as u8)
.collect();
let in_v: Vec<u8> = (0..(cw * chh))
.map(|k| ((k * 23 + 9) % 256) as u8)
.collect();
let enc = encode_inter_frame_yuv(
&in_y,
&in_u,
&in_v,
w,
h,
&ref_y,
&ref_u,
&ref_v,
true,
&qp_lossless(),
2,
false,
)
.unwrap();
assert_eq!(enc.recon_y, in_y, "lossless Y bit-exact");
assert_eq!(enc.recon_u, in_u, "lossless U bit-exact");
assert_eq!(enc.recon_v, in_v, "lossless V bit-exact");
}
#[test]
fn encode_inter_frame_rejects_misaligned_dims() {
let plane = vec![0u16; 64];
let reference = EncRefPlane {
plane: &plane,
stride: 8,
width: 8,
height: 8,
};
let input = vec![0u8; 30];
let r = encode_inter_frame_y(&input, 6, 5, &reference, true, &qp_lossless(), 1);
assert!(r.is_err(), "non-multiple-of-4 dims rejected");
}
}