#![allow(dead_code)]
use alloc::vec::Vec;
use super::inter::MotionVector;
use super::picture::DecodedFrame;
const LUMA_FILTER: [[i16; 8]; 4] = [
[0, 0, 0, 64, 0, 0, 0, 0], [-1, 4, -10, 58, 17, -5, 1, 0], [-1, 4, -11, 40, 40, -11, 4, -1], [0, 1, -5, 17, 58, -10, 4, -1], ];
const CHROMA_FILTER: [[i16; 4]; 8] = [
[0, 64, 0, 0], [-2, 58, 10, -2], [-4, 54, 16, -2], [-6, 46, 28, -4], [-4, 36, 36, -4], [-4, 28, 46, -6], [-2, 16, 54, -4], [-2, 10, 58, -2], ];
#[derive(Default)]
pub struct McScratch {
pub buf: Vec<i32>,
}
pub struct McBlock {
pub xp: u32,
pub yp: u32,
pub w: u32,
pub h: u32,
pub bit_depth: u8,
}
pub fn mc_luma(
ref_frame: &DecodedFrame,
mv: MotionVector,
blk: &McBlock,
pred: &mut [i16],
bi_pred: bool,
scratch: &mut McScratch,
) {
let ref_plane = &ref_frame.y_plane;
let stride = ref_frame.width as i32;
let pic_w = ref_frame.width as i32;
let pic_h = ref_frame.height as i32;
let (w, h) = (blk.w, blk.h);
let int_x = (blk.xp as i32) + (mv.x as i32 >> 2);
let int_y = (blk.yp as i32) + (mv.y as i32 >> 2);
let frac_x = (mv.x as i32 & 3) as usize;
let frac_y = (mv.y as i32 & 3) as usize;
let shift1 = blk.bit_depth as i32 - 8 + 6;
let offset1 = 1i32 << (shift1 - 1);
let max_val = (1i32 << blk.bit_depth) - 1;
let internal_shift = 14 - blk.bit_depth as i32;
if frac_x == 0 && frac_y == 0 {
for j in 0..h as i32 {
for i in 0..w as i32 {
let sx = (int_x + i).clamp(0, pic_w - 1);
let sy = (int_y + j).clamp(0, pic_h - 1);
let val = ref_plane[(sy * stride + sx) as usize] as i32;
pred[(j as u32 * w + i as u32) as usize] = if bi_pred {
(val << internal_shift) as i16
} else {
val as i16
};
}
}
} else if frac_y == 0 {
let coeff = &LUMA_FILTER[frac_x];
for j in 0..h as i32 {
let sy = (int_y + j).clamp(0, pic_h - 1);
for i in 0..w as i32 {
let mut sum = 0i32;
for k in 0..8i32 {
let sx = (int_x + i + k - 3).clamp(0, pic_w - 1);
sum += ref_plane[(sy * stride + sx) as usize] as i32 * coeff[k as usize] as i32;
}
pred[(j as u32 * w + i as u32) as usize] = if bi_pred {
sum as i16 } else {
((sum + offset1) >> shift1).clamp(0, max_val) as i16
};
}
}
} else if frac_x == 0 {
let coeff = &LUMA_FILTER[frac_y];
for j in 0..h as i32 {
for i in 0..w as i32 {
let sx = (int_x + i).clamp(0, pic_w - 1);
let mut sum = 0i32;
for k in 0..8i32 {
let sy = (int_y + j + k - 3).clamp(0, pic_h - 1);
sum += ref_plane[(sy * stride + sx) as usize] as i32 * coeff[k as usize] as i32;
}
pred[(j as u32 * w + i as u32) as usize] = if bi_pred {
sum as i16
} else {
((sum + offset1) >> shift1).clamp(0, max_val) as i16
};
}
}
} else {
let tmp_w = w as i32;
let tmp_h = h as i32 + 7;
let tmp_len = (tmp_w * tmp_h) as usize;
scratch.buf.resize(tmp_len, 0);
scratch.buf[..tmp_len].fill(0);
let tmp = &mut scratch.buf;
let coeff_h = &LUMA_FILTER[frac_x];
for j in 0..tmp_h {
let sy = (int_y + j - 3).clamp(0, pic_h - 1);
for i in 0..tmp_w {
let mut sum = 0i32;
for k in 0..8i32 {
let sx = (int_x + i + k - 3).clamp(0, pic_w - 1);
sum +=
ref_plane[(sy * stride + sx) as usize] as i32 * coeff_h[k as usize] as i32;
}
tmp[(j * tmp_w + i) as usize] = sum;
}
}
let coeff_v = &LUMA_FILTER[frac_y];
let shift2 = 6i32;
if bi_pred {
for j in 0..h as i32 {
for i in 0..w as i32 {
let mut sum = 0i64;
for k in 0..8i32 {
sum +=
tmp[((j + k) * tmp_w + i) as usize] as i64 * coeff_v[k as usize] as i64;
}
pred[(j as u32 * w + i as u32) as usize] = (sum >> shift2) as i16;
}
}
} else {
let total_shift = shift1 + shift2;
let total_offset = 1i64 << (total_shift - 1);
for j in 0..h as i32 {
for i in 0..w as i32 {
let mut sum = 0i64;
for k in 0..8i32 {
sum +=
tmp[((j + k) * tmp_w + i) as usize] as i64 * coeff_v[k as usize] as i64;
}
pred[(j as u32 * w + i as u32) as usize] =
(((sum + total_offset) >> total_shift) as i32).clamp(0, max_val) as i16;
}
}
}
}
}
pub struct ChromaRef<'a> {
pub plane: &'a [u16],
pub stride: usize,
pub height: u32,
pub sub_x: u32,
pub sub_y: u32,
}
pub fn mc_chroma(
cref: &ChromaRef<'_>,
mv: MotionVector,
blk: &McBlock,
pred: &mut [i16],
bi_pred: bool,
scratch: &mut McScratch,
) {
let cmv_x = if cref.sub_x > 1 {
mv.x as i32
} else {
mv.x as i32 * 2
};
let cmv_y = if cref.sub_y > 1 {
mv.y as i32
} else {
mv.y as i32 * 2
};
let c_stride = cref.stride as i32;
let c_w = cref.stride as i32;
let c_h = cref.height as i32;
let (w, h) = (blk.w, blk.h);
let int_x = (blk.xp as i32) + (cmv_x >> 3);
let int_y = (blk.yp as i32) + (cmv_y >> 3);
let frac_x = (cmv_x & 7) as usize;
let frac_y = (cmv_y & 7) as usize;
let shift1 = blk.bit_depth as i32 - 8 + 6;
let offset1 = 1i32 << (shift1 - 1);
let max_val = (1i32 << blk.bit_depth) - 1;
let internal_shift = 14 - blk.bit_depth as i32;
let fetch = |sx: i32, sy: i32| -> i32 {
let sx = sx.clamp(0, c_w - 1);
let sy = sy.clamp(0, c_h - 1);
let idx = (sy * c_stride + sx) as usize;
if idx < cref.plane.len() {
cref.plane[idx] as i32
} else {
0
}
};
if frac_x == 0 && frac_y == 0 {
for j in 0..h as i32 {
for i in 0..w as i32 {
let val = fetch(int_x + i, int_y + j);
pred[(j as u32 * w + i as u32) as usize] = if bi_pred {
(val << internal_shift) as i16
} else {
val as i16
};
}
}
} else if frac_y == 0 {
let coeff = &CHROMA_FILTER[frac_x];
for j in 0..h as i32 {
for i in 0..w as i32 {
let mut sum = 0i32;
for k in 0..4i32 {
sum += fetch(int_x + i + k - 1, int_y + j) * coeff[k as usize] as i32;
}
pred[(j as u32 * w + i as u32) as usize] = if bi_pred {
sum as i16
} else {
((sum + offset1) >> shift1).clamp(0, max_val) as i16
};
}
}
} else if frac_x == 0 {
let coeff = &CHROMA_FILTER[frac_y];
for j in 0..h as i32 {
for i in 0..w as i32 {
let mut sum = 0i32;
for k in 0..4i32 {
sum += fetch(int_x + i, int_y + j + k - 1) * coeff[k as usize] as i32;
}
pred[(j as u32 * w + i as u32) as usize] = if bi_pred {
sum as i16
} else {
((sum + offset1) >> shift1).clamp(0, max_val) as i16
};
}
}
} else {
let tmp_w = w as i32;
let tmp_h = h as i32 + 3;
let tmp_len = (tmp_w * tmp_h) as usize;
scratch.buf.resize(tmp_len, 0);
scratch.buf[..tmp_len].fill(0);
let tmp = &mut scratch.buf;
let coeff_h = &CHROMA_FILTER[frac_x];
for j in 0..tmp_h {
for i in 0..tmp_w {
let mut sum = 0i32;
for k in 0..4i32 {
sum += fetch(int_x + i + k - 1, int_y + j - 1) * coeff_h[k as usize] as i32;
}
tmp[(j * tmp_w + i) as usize] = sum;
}
}
let coeff_v = &CHROMA_FILTER[frac_y];
let shift2 = 6i32; if bi_pred {
for j in 0..h as i32 {
for i in 0..w as i32 {
let mut sum = 0i64;
for k in 0..4i32 {
sum +=
tmp[((j + k) * tmp_w + i) as usize] as i64 * coeff_v[k as usize] as i64;
}
pred[(j as u32 * w + i as u32) as usize] = (sum >> shift2) as i16;
}
}
} else {
let total_shift = shift1 + shift2;
let total_offset = 1i64 << (total_shift - 1);
for j in 0..h as i32 {
for i in 0..w as i32 {
let mut sum = 0i64;
for k in 0..4i32 {
sum +=
tmp[((j + k) * tmp_w + i) as usize] as i64 * coeff_v[k as usize] as i64;
}
pred[(j as u32 * w + i as u32) as usize] =
(((sum + total_offset) >> total_shift) as i32).clamp(0, max_val) as i16;
}
}
}
}
}
pub fn blend_uni(pred: &[i16], plane: &mut [u16], plane_stride: usize, blk: &McBlock) {
for j in 0..blk.h {
for i in 0..blk.w {
let src_idx = (j * blk.w + i) as usize;
let dst_idx = (blk.yp + j) as usize * plane_stride + (blk.xp + i) as usize;
if src_idx < pred.len() && dst_idx < plane.len() {
plane[dst_idx] = pred[src_idx] as u16;
}
}
}
}
pub fn blend_bi(
pred_l0: &[i16],
pred_l1: &[i16],
plane: &mut [u16],
plane_stride: usize,
blk: &McBlock,
) {
let max_val = (1i32 << blk.bit_depth) - 1;
let shift = 15 - blk.bit_depth as i32; let offset = 1i32 << (shift - 1); for j in 0..blk.h {
for i in 0..blk.w {
let src_idx = (j * blk.w + i) as usize;
let dst_idx = (blk.yp + j) as usize * plane_stride + (blk.xp + i) as usize;
if src_idx < pred_l0.len() && src_idx < pred_l1.len() && dst_idx < plane.len() {
let val = ((pred_l0[src_idx] as i32 + pred_l1[src_idx] as i32 + offset) >> shift)
.clamp(0, max_val);
plane[dst_idx] = val as u16;
}
}
}
}
pub fn add_residual_inter(plane: &mut [u16], plane_stride: usize, residual: &[i16], blk: &McBlock) {
let max_val = (1i32 << blk.bit_depth) - 1;
for j in 0..blk.h {
for i in 0..blk.w {
let res_idx = (j * blk.w + i) as usize;
let dst_idx = (blk.yp + j) as usize * plane_stride + (blk.xp + i) as usize;
if res_idx < residual.len() && dst_idx < plane.len() {
let val = plane[dst_idx] as i32 + residual[res_idx] as i32;
plane[dst_idx] = val.clamp(0, max_val) as u16;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_luma_filter_sum() {
for f in &LUMA_FILTER {
assert_eq!(f.iter().sum::<i16>(), 64);
}
}
#[test]
fn test_chroma_filter_sum() {
for f in &CHROMA_FILTER {
assert_eq!(f.iter().sum::<i16>(), 64);
}
}
#[test]
fn test_mc_luma_integer_pos() {
let mut frame = DecodedFrame::with_params(8, 8, 8, 1).unwrap();
for y in 0..8u32 {
for x in 0..8u32 {
frame.y_plane[(y * 8 + x) as usize] = (y * 8 + x) as u16;
}
}
let mut pred = vec![0i16; 4 * 4];
let blk = McBlock {
xp: 2,
yp: 2,
w: 4,
h: 4,
bit_depth: 8,
};
let mut scratch = McScratch::default();
mc_luma(
&frame,
MotionVector::ZERO,
&blk,
&mut pred,
false,
&mut scratch,
);
assert_eq!(pred[0], 18); assert_eq!(pred[5], 27); }
#[test]
fn test_mc_luma_vpel_constant() {
let mut frame = DecodedFrame::with_params(16, 16, 8, 1).unwrap();
for p in &mut frame.y_plane {
*p = 100;
}
let mut pred = vec![0i16; 4 * 4];
let blk = McBlock {
xp: 4,
yp: 4,
w: 4,
h: 4,
bit_depth: 8,
};
let mut scratch = McScratch::default();
mc_luma(
&frame,
MotionVector { x: 0, y: 1 },
&blk,
&mut pred,
false,
&mut scratch,
);
for &v in &pred {
assert_eq!(v, 100, "constant ref should give exact value");
}
}
#[test]
fn test_mc_luma_hpel_gradient() {
let mut frame = DecodedFrame::with_params(16, 16, 8, 1).unwrap();
for y in 0..16u32 {
for x in 0..16u32 {
frame.y_plane[(y * 16 + x) as usize] = (x * 16) as u16;
}
}
let mut pred = vec![0i16; 1];
let blk = McBlock {
xp: 4,
yp: 4,
w: 1,
h: 1,
bit_depth: 8,
};
let mut scratch = McScratch::default();
mc_luma(
&frame,
MotionVector::ZERO,
&blk,
&mut pred,
false,
&mut scratch,
);
assert_eq!(pred[0], 64);
mc_luma(
&frame,
MotionVector { x: 2, y: 0 },
&blk,
&mut pred,
false,
&mut scratch,
);
#[allow(clippy::identity_op, clippy::neg_multiply)]
let expected =
(-1 * 16 + 4 * 32 - 11 * 48 + 40 * 64 + 40 * 80 - 11 * 96 + 4 * 112 - 1 * 128 + 32)
>> 6;
assert_eq!(pred[0], expected as i16, "half-pel horizontal");
}
#[test]
fn test_mc_luma_bipred_blend() {
let mut frame = DecodedFrame::with_params(16, 16, 8, 1).unwrap();
for p in &mut frame.y_plane {
*p = 100;
}
let mut pred0 = vec![0i16; 1];
let mut pred1 = vec![0i16; 1];
let blk = McBlock {
xp: 4,
yp: 4,
w: 1,
h: 1,
bit_depth: 8,
};
let mut scratch = McScratch::default();
mc_luma(
&frame,
MotionVector::ZERO,
&blk,
&mut pred0,
true,
&mut scratch,
);
mc_luma(
&frame,
MotionVector::ZERO,
&blk,
&mut pred1,
true,
&mut scratch,
);
assert_eq!(pred0[0], 6400);
assert_eq!(pred1[0], 6400);
let mut plane = vec![0u16; 256];
blend_bi(&pred0, &pred1, &mut plane, 16, &blk);
assert_eq!(plane[4 * 16 + 4], 100);
}
#[test]
fn test_blend_uni() {
let pred = [100i16, 200, 50, 150];
let mut plane = vec![0u16; 16];
let blk = McBlock {
xp: 1,
yp: 1,
w: 2,
h: 2,
bit_depth: 8,
};
blend_uni(&pred, &mut plane, 4, &blk);
assert_eq!(plane[5], 100); assert_eq!(plane[6], 200); assert_eq!(plane[9], 50); assert_eq!(plane[10], 150); }
}