#![allow(clippy::too_many_arguments)]
use crate::deblock::{
alpha_from_index, beta_from_index, derive_boundary_strength, filter_edge, tc0_from, BsInputs,
EdgeSamples, FilterParams, Plane,
};
use crate::inter_pred::{weighted_pred_explicit, BiPredMode, WeightedEntry};
use crate::intra_pred::{
filter_samples_8x8, predict_16x16, predict_4x4, predict_8x8, predict_chroma,
ChromaArrayType as IpChromaArrayType, Intra16x16Mode, Intra4x4Mode, Intra8x8Mode,
IntraChromaMode, Neighbour4x4Availability, Samples16x16, Samples4x4, Samples8x8, SamplesChroma,
};
use crate::macroblock_layer::{Macroblock, MbType, SubMbType};
use crate::mb_grid::{MbGrid, MbInfo};
use crate::mv_deriv::{
derive_b_spatial_direct_with_d, derive_median_mvpred, derive_mvpred_with_d,
derive_p_skip_mv_with_d, Mv, MvpredInputs, MvpredShape, NeighbourMv,
};
use crate::picture::Picture;
use crate::pps::Pps;
use crate::ref_store::RefPicProvider;
use crate::simd::{interpolate_chroma, interpolate_luma};
use crate::slice_data::SliceData;
use crate::slice_header::{PredWeightTable, SliceHeader, SliceType};
use crate::sp_transform::{
sp_chroma_non_switching, sp_chroma_switching, sp_luma_non_switching, sp_luma_switching,
};
use crate::sps::Sps;
use crate::transform::{
intra_bypass_dpcm, inverse_hadamard_chroma_dc_420, inverse_hadamard_chroma_dc_422,
inverse_hadamard_luma_dc_16x16, inverse_transform_4x4, inverse_transform_4x4_dc_preserved,
inverse_transform_8x8, qp_bd_offset, qp_y_to_qp_c_with_bd_offset, select_scaling_list_4x4,
select_scaling_list_8x8, TransformError,
};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ReconstructError {
#[error("unsupported mb_type: {0:?}")]
UnsupportedMbType(String),
#[error("unsupported ChromaArrayType: {0}")]
UnsupportedChromaArrayType(u32),
#[error("SP/SI slice unsupported configuration: {0}")]
SpSiUnsupported(String),
#[error("field / MBAFF pictures are not supported in this reconstruction pass")]
FieldOrMbaffNotSupported,
#[error("transform error: {0}")]
Transform(#[from] TransformError),
#[error("intra pred error: (internal — out of bounds)")]
IntraPredOutOfBounds,
#[error("reference picture {list}:{idx} not available in the ref store")]
MissingRefPic { list: u8, idx: u32 },
#[error("unsupported inter mb_type for this scope: {0}")]
UnsupportedInterMbType(String),
#[error("inter MC against zero-dim reference picture (width={width}, height={height})")]
InvalidRefDims { width: u32, height: u32 },
#[error("inter MC against a non-existing (frame_num-gap) reference picture (§8.2.5.2)")]
NonExistingRef,
#[error("intra mode {mode} requires unavailable neighbour samples (§{clause})")]
IntraModeNeighboursUnavailable {
mode: &'static str,
clause: &'static str,
},
}
fn require_intra_4x4_mode(
mode: Intra4x4Mode,
av: &Neighbour4x4Availability,
) -> Result<(), ReconstructError> {
if crate::intra_pred::intra_4x4_mode_permitted(mode, av) {
Ok(())
} else {
Err(ReconstructError::IntraModeNeighboursUnavailable {
mode: match mode {
Intra4x4Mode::Vertical => "Intra_4x4_Vertical",
Intra4x4Mode::Horizontal => "Intra_4x4_Horizontal",
Intra4x4Mode::Dc => "Intra_4x4_DC",
Intra4x4Mode::DiagonalDownLeft => "Intra_4x4_Diagonal_Down_Left",
Intra4x4Mode::DiagonalDownRight => "Intra_4x4_Diagonal_Down_Right",
Intra4x4Mode::VerticalRight => "Intra_4x4_Vertical_Right",
Intra4x4Mode::HorizontalDown => "Intra_4x4_Horizontal_Down",
Intra4x4Mode::VerticalLeft => "Intra_4x4_Vertical_Left",
Intra4x4Mode::HorizontalUp => "Intra_4x4_Horizontal_Up",
},
clause: "8.3.1.2",
})
}
}
fn require_intra_8x8_mode(
mode: Intra8x8Mode,
av: &Neighbour4x4Availability,
) -> Result<(), ReconstructError> {
if crate::intra_pred::intra_8x8_mode_permitted(mode, av) {
Ok(())
} else {
Err(ReconstructError::IntraModeNeighboursUnavailable {
mode: match mode {
Intra8x8Mode::Vertical => "Intra_8x8_Vertical",
Intra8x8Mode::Horizontal => "Intra_8x8_Horizontal",
Intra8x8Mode::Dc => "Intra_8x8_DC",
Intra8x8Mode::DiagonalDownLeft => "Intra_8x8_Diagonal_Down_Left",
Intra8x8Mode::DiagonalDownRight => "Intra_8x8_Diagonal_Down_Right",
Intra8x8Mode::VerticalRight => "Intra_8x8_Vertical_Right",
Intra8x8Mode::HorizontalDown => "Intra_8x8_Horizontal_Down",
Intra8x8Mode::VerticalLeft => "Intra_8x8_Vertical_Left",
Intra8x8Mode::HorizontalUp => "Intra_8x8_Horizontal_Up",
},
clause: "8.3.2.2",
})
}
}
fn require_intra_16x16_mode(
mode: Intra16x16Mode,
av: &Neighbour4x4Availability,
) -> Result<(), ReconstructError> {
if crate::intra_pred::intra_16x16_mode_permitted(mode, av) {
Ok(())
} else {
Err(ReconstructError::IntraModeNeighboursUnavailable {
mode: match mode {
Intra16x16Mode::Vertical => "Intra_16x16_Vertical",
Intra16x16Mode::Horizontal => "Intra_16x16_Horizontal",
Intra16x16Mode::Dc => "Intra_16x16_DC",
Intra16x16Mode::Plane => "Intra_16x16_Plane",
},
clause: "8.3.3",
})
}
}
fn require_intra_chroma_mode(
mode: IntraChromaMode,
av: &Neighbour4x4Availability,
) -> Result<(), ReconstructError> {
if crate::intra_pred::intra_chroma_mode_permitted(mode, av) {
Ok(())
} else {
Err(ReconstructError::IntraModeNeighboursUnavailable {
mode: match mode {
IntraChromaMode::Dc => "Intra_Chroma_DC",
IntraChromaMode::Horizontal => "Intra_Chroma_Horizontal",
IntraChromaMode::Vertical => "Intra_Chroma_Vertical",
IntraChromaMode::Plane => "Intra_Chroma_Plane",
},
clause: "8.3.4",
})
}
}
const LUMA_4X4_XY: [(i32, i32); 16] = [
(0, 0),
(4, 0),
(0, 4),
(4, 4),
(8, 0),
(12, 0),
(8, 4),
(12, 4),
(0, 8),
(4, 8),
(0, 12),
(4, 12),
(8, 8),
(12, 8),
(8, 12),
(12, 12),
];
const LUMA_8X8_XY: [(i32, i32); 4] = [(0, 0), (8, 0), (0, 8), (8, 8)];
const ZIGZAG_8X8: [(usize, usize); 64] = [
(0, 0),
(0, 1),
(1, 0),
(2, 0),
(1, 1),
(0, 2),
(0, 3),
(1, 2),
(2, 1),
(3, 0),
(4, 0),
(3, 1),
(2, 2),
(1, 3),
(0, 4),
(0, 5),
(1, 4),
(2, 3),
(3, 2),
(4, 1),
(5, 0),
(6, 0),
(5, 1),
(4, 2),
(3, 3),
(2, 4),
(1, 5),
(0, 6),
(0, 7),
(1, 6),
(2, 5),
(3, 4),
(4, 3),
(5, 2),
(6, 1),
(7, 0),
(7, 1),
(6, 2),
(5, 3),
(4, 4),
(3, 5),
(2, 6),
(1, 7),
(2, 7),
(3, 6),
(4, 5),
(5, 4),
(6, 3),
(7, 2),
(7, 3),
(6, 4),
(5, 5),
(4, 6),
(3, 7),
(4, 7),
(5, 6),
(6, 5),
(7, 4),
(7, 5),
(6, 6),
(5, 7),
(6, 7),
(7, 6),
(7, 7),
];
fn inverse_scan_8x8_zigzag(levels: &[i32; 64]) -> [i32; 64] {
let mut out = [0i32; 64];
for (k, &(i, j)) in ZIGZAG_8X8.iter().enumerate() {
out[i * 8 + j] = levels[k];
}
out
}
#[rustfmt::skip]
const FIELD_SCAN_POS_8X8: [u8; 64] = [
0, 3, 8, 15, 22, 30, 38, 52,
1, 4, 14, 21, 29, 37, 45, 53,
2, 7, 16, 23, 31, 39, 46, 58,
5, 9, 20, 28, 36, 44, 51, 59,
6, 13, 24, 32, 40, 47, 54, 60,
10, 17, 25, 33, 41, 48, 55, 61,
11, 18, 26, 34, 42, 49, 56, 62,
12, 19, 27, 35, 43, 50, 57, 63,
];
pub(crate) fn inverse_scan_8x8_field(levels: &[i32; 64]) -> [i32; 64] {
let mut out = [0i32; 64];
for (pos, &scan_idx) in FIELD_SCAN_POS_8X8.iter().enumerate() {
out[pos] = levels[scan_idx as usize];
}
out
}
#[inline]
fn inv_scan_4x4(levels: &[i32; 16], field: bool) -> [i32; 16] {
if field {
crate::transform::inverse_scan_4x4_field(levels)
} else {
crate::transform::inverse_scan_4x4_zigzag(levels)
}
}
#[inline]
fn inv_scan_4x4_ac(levels: &[i32; 16], field: bool) -> [i32; 16] {
if field {
crate::transform::inverse_scan_4x4_field_ac(levels)
} else {
crate::transform::inverse_scan_4x4_zigzag_ac(levels)
}
}
#[inline]
fn inv_scan_8x8(levels: &[i32; 64], field: bool) -> [i32; 64] {
if field {
inverse_scan_8x8_field(levels)
} else {
inverse_scan_8x8_zigzag(levels)
}
}
fn mb_sample_origin(
grid: &MbGrid,
mb_addr: u32,
mbaff_frame_flag: bool,
mb_field_decoding_flag: bool,
) -> (i32, i32) {
if mbaff_frame_flag {
let (x, y) = crate::mb_address::mbaff_mb_to_sample_xy(
mb_addr,
grid.width_in_mbs,
mb_field_decoding_flag,
);
(x as i32, y as i32)
} else {
let (mb_x, mb_y) = grid.mb_xy(mb_addr);
((mb_x as i32) * 16, (mb_y as i32) * 16)
}
}
#[derive(Debug, Clone, Copy)]
struct MbWriter {
mb_px: i32,
mb_py: i32,
mb_field: bool,
mbaff_frame: bool,
pair_px: i32,
pair_py: i32,
mb_is_top: bool,
chroma_array_type: u32,
}
impl MbWriter {
fn new(
grid: &MbGrid,
mb_addr: u32,
mb_px: i32,
mb_py: i32,
mbaff_frame: bool,
mb_field: bool,
chroma_array_type: u32,
) -> Self {
let (pair_px, pair_py, mb_is_top) = if mbaff_frame {
let w = grid.width_in_mbs.max(1);
let pair_idx = mb_addr / 2;
let pair_x = ((pair_idx % w) as i32) * 16;
let pair_y = ((pair_idx / w) as i32) * 32;
let is_top = mb_addr % 2 == 0;
(pair_x, pair_y, is_top)
} else {
(mb_px, mb_py, true)
};
Self {
mb_px,
mb_py,
mb_field,
mbaff_frame,
pair_px,
pair_py,
mb_is_top,
chroma_array_type,
}
}
#[inline]
fn luma_y_stride(&self) -> i32 {
if self.mb_field {
2
} else {
1
}
}
#[inline]
fn chroma_y_stride(&self) -> i32 {
if self.mb_field {
2
} else {
1
}
}
#[inline]
fn chroma_mb_px(&self) -> i32 {
let (sub_w, _) = chroma_subsample(self.chroma_array_type);
if sub_w == 0 {
return 0;
}
self.pair_px / sub_w
}
#[inline]
fn chroma_mb_py(&self) -> i32 {
let (_, sub_h) = chroma_subsample(self.chroma_array_type);
if sub_h == 0 {
return 0;
}
if self.mbaff_frame {
let (_, mb_h_c) = chroma_mb_dims(self.chroma_array_type);
let mb_h_c = mb_h_c as i32;
let chroma_pair_y = self.pair_py / sub_h;
if self.mb_field {
chroma_pair_y + if self.mb_is_top { 0 } else { 1 }
} else {
chroma_pair_y + if self.mb_is_top { 0 } else { mb_h_c }
}
} else {
self.mb_py / sub_h
}
}
#[inline]
fn set_luma(&self, pic: &mut Picture, x_in_mb: i32, y_in_mb: i32, v: i32) {
let ax = self.mb_px + x_in_mb;
let ay = self.mb_py + y_in_mb * self.luma_y_stride();
pic.set_luma(ax, ay, v);
}
#[inline]
fn set_cb(&self, pic: &mut Picture, x_in_mb: i32, y_in_mb: i32, v: i32) {
let ax = self.chroma_mb_px() + x_in_mb;
let ay = self.chroma_mb_py() + y_in_mb * self.chroma_y_stride();
pic.set_cb(ax, ay, v);
}
#[inline]
fn set_cr(&self, pic: &mut Picture, x_in_mb: i32, y_in_mb: i32, v: i32) {
let ax = self.chroma_mb_px() + x_in_mb;
let ay = self.chroma_mb_py() + y_in_mb * self.chroma_y_stride();
pic.set_cr(ax, ay, v);
}
}
pub fn reconstruct_slice<R: RefPicProvider>(
slice_data: &SliceData,
slice_header: &SliceHeader,
sps: &Sps,
pps: &Pps,
ref_pics: &R,
pic: &mut Picture,
grid: &mut MbGrid,
) -> Result<(), ReconstructError> {
reconstruct_slice_no_deblock(slice_data, slice_header, sps, pps, ref_pics, pic, grid)?;
let bit_depth_y = 8 + sps.bit_depth_luma_minus8;
let bit_depth_c = 8 + sps.bit_depth_chroma_minus8;
let mbaff_frame_flag = sps.mb_adaptive_frame_field_flag && !slice_header.field_pic_flag;
let deblock_enabled = slice_header.disable_deblocking_filter_idc != 1;
let deblock_env_off = deblock_no_op_cached();
if deblock_enabled && !deblock_env_off {
let alpha_off = slice_header.slice_alpha_c0_offset_div2 * 2;
let beta_off = slice_header.slice_beta_offset_div2 * 2;
deblock_picture_full(
pic,
grid,
alpha_off,
beta_off,
bit_depth_y,
bit_depth_c,
pps,
mbaff_frame_flag,
slice_header.field_pic_flag,
&slice_data.mb_field_decoding_flags,
);
}
Ok(())
}
pub fn reconstruct_slice_no_deblock<R: RefPicProvider>(
slice_data: &SliceData,
slice_header: &SliceHeader,
sps: &Sps,
pps: &Pps,
ref_pics: &R,
pic: &mut Picture,
grid: &mut MbGrid,
) -> Result<(), ReconstructError> {
let chroma_array_type = sps.chroma_array_type();
let mbaff_frame_flag = sps.mb_adaptive_frame_field_flag && !slice_header.field_pic_flag;
grid.mbaff_frame_flag = mbaff_frame_flag;
let bit_depth_y = 8 + sps.bit_depth_luma_minus8;
let bit_depth_c = 8 + sps.bit_depth_chroma_minus8;
let slice_qp_y = 26 + pps.pic_init_qp_minus26 + slice_header.slice_qp_delta;
let mut prev_qp_y = slice_qp_y;
let sp_si_ctx = sp_si_ctx_for_slice(slice_header, sps, pps, chroma_array_type)?;
let (alpha_off, beta_off) = (
slice_header.slice_alpha_c0_offset_div2 * 2,
slice_header.slice_beta_offset_div2 * 2,
);
let deblock_enabled = slice_header.disable_deblocking_filter_idc != 1;
let mut curr_addr = slice_header.first_mb_in_slice * (1 + u32::from(mbaff_frame_flag));
let mb_map = crate::mb_address::slice_mb_to_slice_group_map(sps, pps, slice_header);
let total = slice_data.macroblocks.len();
for (idx, mb) in slice_data.macroblocks.iter().enumerate() {
let mb_field_decoding_flag = slice_data
.mb_field_decoding_flags
.get(idx)
.copied()
.unwrap_or(false);
let mb_qp_y = if mb.mb_type.is_i_pcm() || mb.is_skip {
prev_qp_y
} else {
next_qp_y(prev_qp_y, mb.mb_qp_delta, sps.bit_depth_luma_minus8)
};
let current_slice_id = slice_header.first_mb_in_slice as i32;
if mb.mb_type.is_intra() {
reconstruct_mb_intra(
mb,
curr_addr,
mb_qp_y,
chroma_array_type,
bit_depth_y,
bit_depth_c,
sps,
pps,
pic,
grid,
mbaff_frame_flag,
mb_field_decoding_flag,
current_slice_id,
(mbaff_frame_flag && mb_field_decoding_flag) || slice_header.field_pic_flag,
luma_scaling_plane(sps, slice_header),
sp_si_ctx,
)?;
} else {
reconstruct_mb_inter(
mb,
curr_addr,
mb_qp_y,
chroma_array_type,
bit_depth_y,
bit_depth_c,
slice_header,
sps,
pps,
ref_pics,
pic,
grid,
mbaff_frame_flag,
mb_field_decoding_flag,
current_slice_id,
sp_si_ctx,
)?;
}
if let Some(info) = grid.get_mut(curr_addr) {
info.available = true;
info.is_intra = mb.mb_type.is_intra();
info.in_sp_si_slice = sp_si_ctx.is_some();
info.is_i_pcm = mb.mb_type.is_i_pcm();
info.is_intra_nxn = mb.mb_type.is_i_nxn();
info.mb_type_raw = mb.mb_type_raw;
info.qp_y = mb_qp_y;
info.cbp_luma = (mb.coded_block_pattern & 0x0F) as u8;
info.cbp_chroma = ((mb.coded_block_pattern >> 4) & 0x03) as u8;
info.transform_size_8x8_flag = mb.transform_size_8x8_flag;
info.luma_nonzero_4x4 = compute_luma_nonzero_mask(mb);
info.chroma_nonzero_4x4 = compute_chroma_nonzero_mask(mb, chroma_array_type);
if let Some(pred) = mb.mb_pred.as_ref() {
info.intra_chroma_pred_mode = pred.intra_chroma_pred_mode;
}
info.slice_id = slice_header.first_mb_in_slice as i32;
info.mb_field_decoding_flag = mb_field_decoding_flag;
}
prev_qp_y = mb_qp_y;
if idx + 1 < total {
curr_addr = crate::mb_address::advance_mb_addr(curr_addr, mb_map.as_deref());
if curr_addr as usize >= grid.info.len() {
break;
}
}
}
let _ = (
deblock_enabled,
alpha_off,
beta_off,
bit_depth_y,
bit_depth_c,
);
Ok(())
}
fn deblock_no_op_cached() -> bool {
use std::sync::OnceLock;
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| std::env::var("OXIDEAV_H264_NO_DEBLOCK").is_ok())
}
fn recon_debug_enabled() -> bool {
use std::sync::OnceLock;
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| std::env::var("OXIDEAV_H264_RECON_DEBUG").is_ok())
}
fn recon_debug_mb_target() -> Option<u32> {
use std::sync::OnceLock;
static CACHED: OnceLock<Option<u32>> = OnceLock::new();
*CACHED.get_or_init(|| {
std::env::var("OXIDEAV_H264_RECON_DEBUG_MB")
.ok()
.and_then(|s| s.parse::<u32>().ok())
})
}
fn deblock_trace_enabled() -> bool {
use std::sync::OnceLock;
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| std::env::var_os("OXIDEAV_H264_DEBLOCK_TRACE").is_some())
}
#[inline]
fn next_qp_y(prev_qp_y: i32, mb_qp_delta: i32, bit_depth_luma_minus8: u32) -> i32 {
let qp_bd_offset_y = 6 * bit_depth_luma_minus8 as i32;
let modulus = 52 + qp_bd_offset_y;
let raw = prev_qp_y + mb_qp_delta + 52 + 2 * qp_bd_offset_y;
raw.rem_euclid(modulus) - qp_bd_offset_y
}
#[allow(clippy::too_many_arguments)]
pub fn deblock_picture_full(
pic: &mut Picture,
grid: &MbGrid,
alpha_off: i32,
beta_off: i32,
bit_depth_y: u32,
bit_depth_c: u32,
pps: &Pps,
mbaff_frame_flag: bool,
field_pic: bool,
mb_field_flags: &[bool],
) {
let deblock_env_off = deblock_no_op_cached();
if deblock_env_off {
return;
}
deblock_picture(
pic,
grid,
alpha_off,
beta_off,
bit_depth_y,
bit_depth_c,
pps,
mbaff_frame_flag,
field_pic,
mb_field_flags,
);
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct SpSiCtx {
pub qs_y: i32,
pub switching: bool,
}
fn sp_si_ctx_for_slice(
slice_header: &SliceHeader,
sps: &Sps,
pps: &Pps,
chroma_array_type: u32,
) -> Result<Option<SpSiCtx>, ReconstructError> {
if !matches!(slice_header.slice_type, SliceType::SP | SliceType::SI) {
return Ok(None);
}
let qs_y = 26 + pps.pic_init_qs_minus26 + slice_header.slice_qs_delta;
if !(0..=51).contains(&qs_y) {
return Err(ReconstructError::SpSiUnsupported(format!(
"QSY {qs_y} out of the §7.4.3 range 0..=51"
)));
}
if chroma_array_type != 1 {
return Err(ReconstructError::SpSiUnsupported(format!(
"ChromaArrayType {chroma_array_type} (SP/SI is 4:2:0-only, A.2.3)"
)));
}
if sps.bit_depth_luma_minus8 != 0 || sps.bit_depth_chroma_minus8 != 0 {
return Err(ReconstructError::SpSiUnsupported(
"SP/SI at >8-bit depth".into(),
));
}
if sps.qpprime_y_zero_transform_bypass_flag {
return Err(ReconstructError::SpSiUnsupported(
"SP/SI with qpprime_y_zero_transform_bypass_flag".into(),
));
}
Ok(Some(SpSiCtx {
qs_y,
switching: slice_header.slice_type == SliceType::SI || slice_header.sp_for_switch_flag,
}))
}
#[allow(clippy::too_many_arguments)]
fn sp_reconstruct_chroma_plane(
mb: &Macroblock,
plane: u8,
cbp_chroma: u8,
pred: &[i32],
switching: bool,
qp_c: i32,
qs_c: i32,
weight_scale: &[i32; 16],
field_scan: bool,
writer: &MbWriter,
pic: &mut Picture,
bit_depth_c: u32,
) -> Result<(), ReconstructError> {
let mut pred64 = [0i32; 64];
pred64.copy_from_slice(&pred[..64]);
let mut dc_levels = [0i32; 4];
if cbp_chroma > 0 {
let dc_block = if plane == 0 {
&mb.residual_chroma_dc_cb
} else {
&mb.residual_chroma_dc_cr
};
for (k, v) in dc_block.iter().take(4).enumerate() {
dc_levels[k] = *v;
}
}
let mut ac = [[0i32; 16]; 4];
if cbp_chroma == 2 {
let ac_blocks = if plane == 0 {
&mb.residual_chroma_ac_cb
} else {
&mb.residual_chroma_ac_cr
};
for (blk, dst) in ac.iter_mut().enumerate() {
let scan = ac_blocks.get(blk).copied().unwrap_or([0i32; 16]);
*dst = inv_scan_4x4_ac(&scan, field_scan);
}
}
let c_blocks = if switching {
sp_chroma_switching(&pred64, &dc_levels, &ac, qs_c)
} else {
sp_chroma_non_switching(&pred64, &dc_levels, &ac, qp_c, qs_c, weight_scale)
};
for (blk, c) in c_blocks.iter().enumerate() {
let r = inverse_transform_4x4_dc_preserved(c, qs_c, weight_scale, bit_depth_c)?;
let (bx, by) = chroma_block_xy(1, blk);
for yy in 0..4 {
for xx in 0..4 {
let v = clip_sample(r[yy * 4 + xx], bit_depth_c);
if plane == 0 {
writer.set_cb(pic, bx + xx as i32, by + yy as i32, v);
} else {
writer.set_cr(pic, bx + xx as i32, by + yy as i32, v);
}
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_mb_intra(
mb: &Macroblock,
mb_addr: u32,
qp_y: i32,
chroma_array_type: u32,
bit_depth_y: u32,
bit_depth_c: u32,
sps: &Sps,
pps: &Pps,
pic: &mut Picture,
grid: &mut MbGrid,
mbaff_frame_flag: bool,
mb_field_decoding_flag: bool,
current_slice_id: i32,
field_scan: bool,
luma_plane: usize,
sp_si: Option<SpSiCtx>,
) -> Result<(), ReconstructError> {
let (mb_px, mb_py) = mb_sample_origin(grid, mb_addr, mbaff_frame_flag, mb_field_decoding_flag);
let writer = MbWriter::new(
grid,
mb_addr,
mb_px,
mb_py,
mbaff_frame_flag,
mb_field_decoding_flag,
chroma_array_type,
);
if let Some(info) = grid.get_mut(mb_addr) {
info.slice_id = current_slice_id;
info.mb_field_decoding_flag = mb_field_decoding_flag;
info.is_intra = true;
}
match &mb.mb_type {
MbType::IPcm => {
let pcm = mb.pcm_samples.as_ref().ok_or_else(|| {
ReconstructError::UnsupportedMbType("I_PCM without samples".into())
})?;
for y in 0..16 {
for x in 0..16 {
let v = pcm.luma[(y * 16 + x) as usize] as i32;
writer.set_luma(pic, x, y, v);
}
}
let (cw, ch) = chroma_mb_dims(chroma_array_type);
let mut k = 0usize;
for y in 0..ch {
for x in 0..cw {
let v = pcm.chroma_cb[k] as i32;
writer.set_cb(pic, x as i32, y as i32, v);
k += 1;
}
}
k = 0;
for y in 0..ch {
for x in 0..cw {
let v = pcm.chroma_cr[k] as i32;
writer.set_cr(pic, x as i32, y as i32, v);
k += 1;
}
}
}
MbType::Intra16x16(cfg) => {
reconstruct_intra_16x16(
luma_plane,
mb,
cfg.pred_mode,
cfg.cbp_luma,
cfg.cbp_chroma,
qp_y,
chroma_array_type,
bit_depth_y,
bit_depth_c,
mb_px,
mb_py,
mb_addr,
&writer,
sps,
pps,
pic,
grid,
current_slice_id,
field_scan,
)?;
}
MbType::INxN | MbType::SI => {
let si_qs = if mb.mb_type.is_si() {
let ctx = sp_si.ok_or_else(|| {
ReconstructError::UnsupportedMbType("SI macroblock outside an SI slice".into())
})?;
Some(ctx.qs_y)
} else {
None
};
reconstruct_intra_nxn(
luma_plane,
mb,
qp_y,
bit_depth_y,
mb_px,
mb_py,
mb_addr,
&writer,
sps,
pps,
pic,
grid,
current_slice_id,
field_scan,
si_qs,
)?;
if chroma_array_type == 3 {
reconstruct_chroma_intra_nxn_444(
mb,
qp_y,
bit_depth_c,
mb_px,
mb_py,
mb_addr,
&writer,
sps,
pps,
pic,
grid,
current_slice_id,
field_scan,
)?;
} else {
reconstruct_chroma_intra(
mb,
qp_y,
chroma_array_type,
bit_depth_c,
mb_px,
mb_py,
mb_addr,
&writer,
sps,
pps,
pic,
grid,
current_slice_id,
field_scan,
si_qs,
)?;
}
}
_ => {
return Err(ReconstructError::UnsupportedMbType(format!(
"{:?}",
mb.mb_type
)));
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_intra_16x16(
luma_plane: usize,
mb: &Macroblock,
pred_mode_idx: u8,
cbp_luma: u8,
_cbp_chroma: u8,
qp_y: i32,
chroma_array_type: u32,
bit_depth_y: u32,
bit_depth_c: u32,
mb_px: i32,
mb_py: i32,
mb_addr: u32,
writer: &MbWriter,
sps: &Sps,
pps: &Pps,
pic: &mut Picture,
grid: &MbGrid,
current_slice_id: i32,
field_scan: bool,
) -> Result<(), ReconstructError> {
let samples = gather_samples_16x16(
pic,
grid,
mb_px,
mb_py,
current_slice_id,
pps.constrained_intra_pred_flag,
mb_addr,
);
let mode = Intra16x16Mode::from_index(pred_mode_idx).ok_or_else(|| {
ReconstructError::UnsupportedMbType(format!("Intra_16x16 pred_mode {}", pred_mode_idx))
})?;
require_intra_16x16_mode(mode, &samples.availability)?;
let mut pred = [0i32; 256];
predict_16x16(mode, &samples, bit_depth_y, &mut pred);
let sl4 = select_scaling_list_4x4(luma_plane, sps, pps);
let qp_bd_offset_y = qp_bd_offset(sps.bit_depth_luma_minus8);
let qp_prime_y = qp_y + qp_bd_offset_y;
let bypass = transform_bypass_active(sps, qp_prime_y);
let dc_levels = mb.residual_luma_dc.as_ref().copied().unwrap_or([0i32; 16]);
let dc_matrix = inv_scan_4x4(&dc_levels, field_scan);
let dc_y = if bypass {
dc_matrix
} else {
inverse_hadamard_luma_dc_16x16(&dc_matrix, qp_prime_y, &sl4, bit_depth_y)?
};
let mut rmb = [0i32; 256];
#[allow(clippy::needless_range_loop)] for block_idx in 0..16usize {
let (bx, by) = LUMA_4X4_XY[block_idx];
let mut coeffs = if cbp_luma == 15 {
let ac = mb
.residual_luma
.get(block_idx)
.copied()
.unwrap_or([0i32; 16]);
inv_scan_4x4_ac(&ac, field_scan)
} else {
[0i32; 16]
};
let dc_row = (by / 4) as usize;
let dc_col = (bx / 4) as usize;
coeffs[0] = dc_y[dc_row * 4 + dc_col];
let residual = if bypass {
coeffs
} else {
inverse_transform_4x4_dc_preserved(&coeffs, qp_prime_y, &sl4, bit_depth_y)?
};
for yy in 0..4usize {
for xx in 0..4usize {
rmb[(by as usize + yy) * 16 + bx as usize + xx] = residual[yy * 4 + xx];
}
}
}
if bypass && pred_mode_idx <= 1 {
intra_bypass_dpcm(&mut rmb, 16, 16, pred_mode_idx == 1);
}
for y in 0..16usize {
for x in 0..16usize {
let v = clip_sample(pred[y * 16 + x] + rmb[y * 16 + x], bit_depth_y);
writer.set_luma(pic, x as i32, y as i32, v);
}
}
reconstruct_chroma_intra(
mb,
qp_y,
chroma_array_type,
bit_depth_c,
mb_px,
mb_py,
mb_addr,
writer,
sps,
pps,
pic,
grid,
current_slice_id,
field_scan,
None,
)?;
Ok(())
}
fn intra_neighbour_mb_info(
grid: &MbGrid,
mb_addr: Option<u32>,
constrained_intra_pred: bool,
current_slice_id: i32,
) -> Option<&MbInfo> {
let addr = mb_addr?;
let info = grid.get(addr)?;
if !info.available {
return None;
}
if constrained_intra_pred && !info.is_intra {
return None;
}
if current_slice_id >= 0 && info.slice_id >= 0 && info.slice_id != current_slice_id {
return None;
}
Some(info)
}
fn mb_is_intra_nxn(info: &MbInfo) -> bool {
info.is_intra && !info.is_i_pcm && info.is_intra_nxn
}
fn mb_is_intra_4x4(info: &MbInfo) -> bool {
mb_is_intra_nxn(info) && !info.transform_size_8x8_flag
}
fn mb_is_intra_8x8(info: &MbInfo) -> bool {
mb_is_intra_nxn(info) && info.transform_size_8x8_flag
}
fn neighbour_4x4_addr(
grid: &MbGrid,
mb_addr: u32,
bx: i32,
by: i32,
xd: i32,
yd: i32,
) -> Option<(u32, usize)> {
let xn = bx + xd;
let yn = by + yd;
let (addr, xw, yw) = if grid.mbaff_frame_flag {
mbaff_neigh_loc_luma(grid, mb_addr, xn, yn)?
} else {
let [mb_a, mb_b, _mb_c, mb_d] = grid.neighbour_mb_addrs(mb_addr);
let mb_n = if xn < 0 && yn < 0 {
mb_d
} else if xn < 0 && (0..16).contains(&yn) {
mb_a
} else if (0..16).contains(&xn) && yn < 0 {
mb_b
} else if (0..16).contains(&xn) && (0..16).contains(&yn) {
Some(mb_addr)
} else {
None
};
let addr = mb_n?;
let xw = (xn + 16) % 16;
let yw = (yn + 16) % 16;
(addr, xw, yw)
};
let blk = (8 * (yw / 8) + 4 * (xw / 8) + 2 * ((yw % 8) / 4) + ((xw % 8) / 4)) as usize;
Some((addr, blk))
}
fn mbaff_neigh_loc_luma(grid: &MbGrid, mb_addr: u32, xn: i32, yn: i32) -> Option<(u32, i32, i32)> {
let pic_w = grid.width_in_mbs;
let pair = crate::mb_address::mbaff_pair_neighbour_addrs(mb_addr, pic_w);
let curr_field = grid
.get(mb_addr)
.map(|i| i.mb_field_decoding_flag)
.unwrap_or(false);
let curr_mb_frame_flag = !curr_field;
let mb_is_top = mb_addr % 2 == 0;
crate::mb_address::mbaff_neigh_location(
xn,
yn,
16,
16,
curr_mb_frame_flag,
mb_is_top,
mb_addr,
pair,
|addr| {
!grid
.get(addr)
.map(|i| i.mb_field_decoding_flag)
.unwrap_or(false)
},
)
}
fn mbaff_neigh_loc_plane(
grid: &MbGrid,
mb_addr: u32,
xn: i32,
yn: i32,
max_w: i32,
max_h: i32,
) -> Option<(u32, i32, i32)> {
let pic_w = grid.width_in_mbs;
let pair = crate::mb_address::mbaff_pair_neighbour_addrs(mb_addr, pic_w);
let curr_field = grid
.get(mb_addr)
.map(|i| i.mb_field_decoding_flag)
.unwrap_or(false);
crate::mb_address::mbaff_neigh_location(
xn,
yn,
max_w,
max_h,
!curr_field,
mb_addr % 2 == 0,
mb_addr,
pair,
|addr| {
!grid
.get(addr)
.map(|i| i.mb_field_decoding_flag)
.unwrap_or(false)
},
)
}
fn mbaff_abs_luma(grid: &MbGrid, addr: u32, xw: i32, yw: i32) -> (i32, i32) {
let field = grid
.get(addr)
.map(|i| i.mb_field_decoding_flag)
.unwrap_or(false);
let (ox, oy) = mb_sample_origin(grid, addr, true, field);
(ox + xw, oy + yw * if field { 2 } else { 1 })
}
fn mbaff_abs_chroma(
grid: &MbGrid,
addr: u32,
xw: i32,
yw: i32,
chroma_array_type: u32,
) -> (i32, i32) {
let field = grid
.get(addr)
.map(|i| i.mb_field_decoding_flag)
.unwrap_or(false);
let w = grid.width_in_mbs.max(1);
let pair_idx = addr / 2;
let is_top = addr % 2 == 0;
let (sub_w, sub_h) = chroma_subsample(chroma_array_type);
if sub_w == 0 || sub_h == 0 {
return (0, 0);
}
let (_, mb_h_c) = chroma_mb_dims(chroma_array_type);
let pair_cx = ((pair_idx % w) as i32) * 16 / sub_w;
let pair_cy = ((pair_idx / w) as i32) * 32 / sub_h;
let (oy, stride) = if field {
(pair_cy + if is_top { 0 } else { 1 }, 2)
} else {
(pair_cy + if is_top { 0 } else { mb_h_c as i32 }, 1)
};
(pair_cx + xw, oy + yw * stride)
}
fn mbaff_neigh_mb_usable(
grid: &MbGrid,
mb_addr: u32,
addr: u32,
current_slice_id: i32,
constrained_intra_pred: bool,
) -> bool {
if addr == mb_addr {
return true;
}
let Some(info) = grid.get(addr) else {
return false;
};
if !info.available {
return false;
}
if current_slice_id >= 0 && info.slice_id >= 0 && info.slice_id != current_slice_id {
return false;
}
if constrained_intra_pred && !info.is_intra {
return false;
}
true
}
fn mbaff_neigh_sample(
pic: &Picture,
grid: &MbGrid,
mb_addr: u32,
plane: u8,
xn: i32,
yn: i32,
current_slice_id: i32,
constrained_intra_pred: bool,
) -> Option<i32> {
let (addr, xw, yw, ax, ay) = if plane == 0 {
let (addr, xw, yw) = mbaff_neigh_loc_luma(grid, mb_addr, xn, yn)?;
let (ax, ay) = mbaff_abs_luma(grid, addr, xw, yw);
(addr, xw, yw, ax, ay)
} else {
let (mb_w_c, mb_h_c) = chroma_mb_dims(pic.chroma_array_type);
let (addr, xw, yw) =
mbaff_neigh_loc_plane(grid, mb_addr, xn, yn, mb_w_c as i32, mb_h_c as i32)?;
let (ax, ay) = mbaff_abs_chroma(grid, addr, xw, yw, pic.chroma_array_type);
(addr, xw, yw, ax, ay)
};
let _ = (xw, yw);
if !mbaff_neigh_mb_usable(
grid,
mb_addr,
addr,
current_slice_id,
constrained_intra_pred,
) {
return None;
}
Some(match plane {
0 => pic.luma_at(ax, ay),
1 => pic.cb_at(ax, ay),
_ => pic.cr_at(ax, ay),
})
}
#[allow(clippy::too_many_arguments)]
fn mbaff_gather_edge<const N: usize>(
pic: &Picture,
grid: &MbGrid,
mb_addr: u32,
plane: u8,
x0: i32,
y0: i32,
dx: i32,
dy: i32,
current_slice_id: i32,
cip: bool,
) -> ([i32; N], bool) {
let mut out = [0i32; N];
let mut avail = true;
for (i, slot) in out.iter_mut().enumerate() {
match mbaff_neigh_sample(
pic,
grid,
mb_addr,
plane,
x0 + dx * i as i32,
y0 + dy * i as i32,
current_slice_id,
cip,
) {
Some(v) => *slot = v,
None => avail = false,
}
}
(out, avail)
}
fn neighbour_8x8_addr(
grid: &MbGrid,
mb_addr: u32,
blk8: usize,
xd: i32,
yd: i32,
) -> Option<(u32, usize)> {
let xn = ((blk8 as i32) % 2) * 8 + xd;
let yn = ((blk8 as i32) / 2) * 8 + yd;
let (addr, xw, yw) = if grid.mbaff_frame_flag {
mbaff_neigh_loc_luma(grid, mb_addr, xn, yn)?
} else {
let [mb_a, mb_b, _mb_c, mb_d] = grid.neighbour_mb_addrs(mb_addr);
let mb_n = if xn < 0 && yn < 0 {
mb_d
} else if xn < 0 && (0..16).contains(&yn) {
mb_a
} else if (0..16).contains(&xn) && yn < 0 {
mb_b
} else if (0..16).contains(&xn) && (0..16).contains(&yn) {
Some(mb_addr)
} else {
None
};
let addr = mb_n?;
let xw = (xn + 16) % 16;
let yw = (yn + 16) % 16;
(addr, xw, yw)
};
let blk = (2 * (yw / 8) + (xw / 8)) as usize;
Some((addr, blk))
}
fn intra_mxm_pred_mode_for_neighbour_4x4(
grid: &MbGrid,
dc_pred_flag: bool,
neighbour: Option<(u32, usize)>,
constrained_intra_pred: bool,
current_slice_id: i32,
) -> u8 {
if dc_pred_flag {
return 2;
}
let Some((addr, blk)) = neighbour else {
return 2;
};
let Some(info) =
intra_neighbour_mb_info(grid, Some(addr), constrained_intra_pred, current_slice_id)
else {
return 2;
};
if mb_is_intra_4x4(info) {
info.intra_4x4_pred_modes[blk]
} else if mb_is_intra_8x8(info) {
info.intra_8x8_pred_modes[blk >> 2]
} else {
2
}
}
fn intra_mxm_pred_mode_for_neighbour_8x8(
grid: &MbGrid,
dc_pred_flag: bool,
neighbour: Option<(u32, usize)>,
constrained_intra_pred: bool,
n_for_4x4: usize,
current_slice_id: i32,
) -> u8 {
if dc_pred_flag {
return 2;
}
let Some((addr, blk)) = neighbour else {
return 2;
};
let Some(info) =
intra_neighbour_mb_info(grid, Some(addr), constrained_intra_pred, current_slice_id)
else {
return 2;
};
if mb_is_intra_8x8(info) {
info.intra_8x8_pred_modes[blk]
} else if mb_is_intra_4x4(info) {
let sub_idx = blk * 4 + n_for_4x4;
info.intra_4x4_pred_modes[sub_idx]
} else {
2
}
}
fn derive_intra_4x4_pred_mode(
grid: &MbGrid,
mb_addr: u32,
block_idx: usize,
pred: &crate::macroblock_layer::MbPred,
constrained_intra_pred: bool,
current_slice_id: i32,
) -> u8 {
let (bx, by) = LUMA_4X4_XY[block_idx];
let na = neighbour_4x4_addr(grid, mb_addr, bx, by, -1, 0);
let nb = neighbour_4x4_addr(grid, mb_addr, bx, by, 0, -1);
let mb_a = intra_neighbour_mb_info(
grid,
na.map(|(a, _)| a),
constrained_intra_pred,
current_slice_id,
);
let mb_b = intra_neighbour_mb_info(
grid,
nb.map(|(a, _)| a),
constrained_intra_pred,
current_slice_id,
);
let dc_pred_flag = mb_a.is_none() || mb_b.is_none();
let mode_a = intra_mxm_pred_mode_for_neighbour_4x4(
grid,
dc_pred_flag,
na,
constrained_intra_pred,
current_slice_id,
);
let mode_b = intra_mxm_pred_mode_for_neighbour_4x4(
grid,
dc_pred_flag,
nb,
constrained_intra_pred,
current_slice_id,
);
let predicted = mode_a.min(mode_b);
if pred.prev_intra4x4_pred_mode_flag[block_idx] {
predicted
} else {
let rem = pred.rem_intra4x4_pred_mode[block_idx];
if rem < predicted {
rem
} else {
rem + 1
}
}
}
fn derive_intra_8x8_pred_mode(
grid: &MbGrid,
mb_addr: u32,
blk8: usize,
pred: &crate::macroblock_layer::MbPred,
constrained_intra_pred: bool,
current_slice_id: i32,
) -> u8 {
let na = neighbour_8x8_addr(grid, mb_addr, blk8, -1, 0);
let nb = neighbour_8x8_addr(grid, mb_addr, blk8, 0, -1);
let mb_a = intra_neighbour_mb_info(
grid,
na.map(|(a, _)| a),
constrained_intra_pred,
current_slice_id,
);
let mb_b = intra_neighbour_mb_info(
grid,
nb.map(|(a, _)| a),
constrained_intra_pred,
current_slice_id,
);
let dc_pred_flag = mb_a.is_none() || mb_b.is_none();
let mode_a = intra_mxm_pred_mode_for_neighbour_8x8(
grid,
dc_pred_flag,
na,
constrained_intra_pred,
1,
current_slice_id,
);
let mode_b = intra_mxm_pred_mode_for_neighbour_8x8(
grid,
dc_pred_flag,
nb,
constrained_intra_pred,
2,
current_slice_id,
);
let predicted = mode_a.min(mode_b);
if pred.prev_intra8x8_pred_mode_flag[blk8] {
predicted
} else {
let rem = pred.rem_intra8x8_pred_mode[blk8];
if rem < predicted {
rem
} else {
rem + 1
}
}
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_intra_nxn(
luma_plane: usize,
mb: &Macroblock,
qp_y: i32,
bit_depth_y: u32,
mb_px: i32,
mb_py: i32,
mb_addr: u32,
writer: &MbWriter,
sps: &Sps,
pps: &Pps,
pic: &mut Picture,
grid: &mut MbGrid,
current_slice_id: i32,
field_scan: bool,
si_switch_qs: Option<i32>,
) -> Result<(), ReconstructError> {
let pred = mb
.mb_pred
.as_ref()
.ok_or_else(|| ReconstructError::UnsupportedMbType("I_NxN without mb_pred".into()))?;
let sl4 = select_scaling_list_4x4(luma_plane, sps, pps);
let sl8 = select_scaling_list_8x8(2 * luma_plane, sps, pps);
let cbp_luma = (mb.coded_block_pattern & 0x0F) as u8;
let cip = pps.constrained_intra_pred_flag;
let qp_bd_offset_y = qp_bd_offset(sps.bit_depth_luma_minus8);
let qp_prime_y = qp_y + qp_bd_offset_y;
let bypass = transform_bypass_active(sps, qp_prime_y);
if let Some(info) = grid.get_mut(mb_addr) {
info.available = true;
info.is_intra = true;
info.is_i_pcm = false;
info.is_intra_nxn = true;
info.mb_type_raw = mb.mb_type_raw;
info.transform_size_8x8_flag = mb.transform_size_8x8_flag;
info.mb_field_decoding_flag = writer.mb_field;
info.intra_4x4_pred_modes = [0; 16];
info.intra_8x8_pred_modes = [0; 4];
info.slice_id = current_slice_id;
}
if mb.transform_size_8x8_flag {
if si_switch_qs.is_some() {
return Err(ReconstructError::SpSiUnsupported(
"SI macroblock with transform_size_8x8_flag".into(),
));
}
#[allow(clippy::needless_range_loop)] for blk8 in 0..4usize {
let (bx, by) = LUMA_8X8_XY[blk8];
let mode_idx =
derive_intra_8x8_pred_mode(grid, mb_addr, blk8, pred, cip, current_slice_id);
let mode = Intra8x8Mode::from_index(mode_idx).unwrap_or(Intra8x8Mode::Dc);
let raw = gather_samples_8x8(
pic,
grid,
mb_px + bx,
mb_py + by,
blk8,
current_slice_id,
cip,
mb_addr,
bx,
by,
);
require_intra_8x8_mode(mode, &raw.availability)?;
let filtered = filter_samples_8x8(&raw, bit_depth_y);
let mut pred_samples = [0i32; 64];
predict_8x8(mode, &filtered, bit_depth_y, &mut pred_samples);
let coeffs_flat: [i32; 64] = {
let bit = (cbp_luma >> blk8) & 1 == 1;
if bit {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let base_slot = set_before * 4;
let mut scan = [0i32; 64];
for sub in 0..4usize {
let slot = base_slot + sub;
if let Some(coefs) = mb.residual_luma.get(slot) {
for (i, c) in coefs.iter().enumerate().take(16) {
scan[sub * 16 + i] = *c;
}
}
}
inv_scan_8x8(&scan, field_scan)
} else {
[0i32; 64]
}
};
let residual = if bypass {
let mut r = coeffs_flat;
if matches!(mode, Intra8x8Mode::Vertical | Intra8x8Mode::Horizontal) {
intra_bypass_dpcm(&mut r, 8, 8, mode == Intra8x8Mode::Horizontal);
}
r
} else {
inverse_transform_8x8(&coeffs_flat, qp_prime_y, &sl8, bit_depth_y)?
};
for y in 0..8 {
for x in 0..8 {
let idx = y * 8 + x;
let v = clip_sample(pred_samples[idx] + residual[idx], bit_depth_y);
writer.set_luma(pic, bx + x as i32, by + y as i32, v);
}
}
if let Some(info) = grid.get_mut(mb_addr) {
info.intra_8x8_pred_modes[blk8] = mode.as_index();
}
}
} else {
let debug_mb = recon_debug_mb_target();
let debug = (mb_addr == 0 && recon_debug_enabled()) || debug_mb == Some(mb_addr);
if debug {
eprintln!(
"RECON MB{} I_NxN: cbp_luma={:#x} qp_y={} mb_type_raw={} cbp={:#x} entropy={}",
mb_addr,
cbp_luma,
qp_y,
mb.mb_type_raw,
mb.coded_block_pattern,
if pps.entropy_coding_mode_flag {
"CABAC"
} else {
"CAVLC"
}
);
eprintln!(" prev_flags: {:?}", pred.prev_intra4x4_pred_mode_flag);
eprintln!(" rem_modes: {:?}", pred.rem_intra4x4_pred_mode);
eprintln!(" residual_luma.len() = {}", mb.residual_luma.len());
for (i, blk) in mb.residual_luma.iter().enumerate() {
let nz = blk.iter().filter(|&&v| v != 0).count();
if nz > 0 {
eprintln!(" residual_luma[{}] ({} nz): {:?}", i, nz, blk);
} else {
eprintln!(" residual_luma[{}] (all zero)", i);
}
}
}
#[allow(clippy::needless_range_loop)] for block_idx in 0..16usize {
let (bx, by) = LUMA_4X4_XY[block_idx];
let mode_idx =
derive_intra_4x4_pred_mode(grid, mb_addr, block_idx, pred, cip, current_slice_id);
let mode = Intra4x4Mode::from_index(mode_idx).unwrap_or(Intra4x4Mode::Dc);
let samples = gather_samples_4x4(
pic,
grid,
mb_px + bx,
mb_py + by,
block_idx,
current_slice_id,
cip,
mb_addr,
bx,
by,
);
require_intra_4x4_mode(mode, &samples.availability)?;
let mut pred_samples = [0i32; 16];
predict_4x4(mode, &samples, bit_depth_y, &mut pred_samples);
let blk8 = block_idx / 4;
let coeffs_scan = if (cbp_luma >> blk8) & 1 == 1 {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let compact_idx = set_before * 4 + (block_idx % 4);
mb.residual_luma
.get(compact_idx)
.copied()
.unwrap_or([0i32; 16])
} else {
[0i32; 16]
};
let coeffs = inv_scan_4x4(&coeffs_scan, field_scan);
let residual = if let Some(qs_y) = si_switch_qs {
let c = sp_luma_switching(&pred_samples, &coeffs, qs_y);
inverse_transform_4x4(&c, qs_y, &sl4, bit_depth_y)?
} else if bypass {
let mut r = coeffs;
if matches!(mode, Intra4x4Mode::Vertical | Intra4x4Mode::Horizontal) {
intra_bypass_dpcm(&mut r, 4, 4, mode == Intra4x4Mode::Horizontal);
}
r
} else {
inverse_transform_4x4(&coeffs, qp_prime_y, &sl4, bit_depth_y)?
};
if debug && (block_idx == 0 || block_idx == 3) {
eprintln!(
" blk{}: pred_mode={} samples.avail=tl{}t{}tr{}l{}",
block_idx,
mode.as_index(),
samples.availability.top_left,
samples.availability.top,
samples.availability.top_right,
samples.availability.left
);
eprintln!(" top: {:?}, left: {:?}", samples.top, samples.left);
eprintln!(" pred_samples: {:?}", pred_samples);
eprintln!(" coeffs_scan: {:?}", coeffs_scan);
eprintln!(" coeffs(4x4): {:?}", coeffs);
eprintln!(" residual: {:?}", residual);
}
let pred_for_write = if si_switch_qs.is_some() {
[0i32; 256]
} else {
pred_block_to_mb(&pred_samples, bx, by)
};
write_block_luma(writer, pic, &pred_for_write, bx, by, &residual, bit_depth_y);
if let Some(info) = grid.get_mut(mb_addr) {
info.intra_4x4_pred_modes[block_idx] = mode.as_index();
}
}
}
Ok(())
}
fn pred_block_to_mb(src4: &[i32; 16], bx: i32, by: i32) -> [i32; 256] {
let mut out = [0i32; 256];
for yy in 0..4 {
for xx in 0..4 {
let gx = (bx as usize) + xx;
let gy = (by as usize) + yy;
out[gy * 16 + gx] = src4[yy * 4 + xx];
}
}
out
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_chroma_intra(
mb: &Macroblock,
qp_y: i32,
chroma_array_type: u32,
bit_depth_c: u32,
mb_px: i32,
mb_py: i32,
mb_addr: u32,
writer: &MbWriter,
sps: &Sps,
pps: &Pps,
pic: &mut Picture,
grid: &MbGrid,
current_slice_id: i32,
field_scan: bool,
si_switch_qs: Option<i32>,
) -> Result<(), ReconstructError> {
if chroma_array_type == 0 {
return Ok(());
}
if chroma_array_type == 3 {
if !mb.mb_type.is_intra_16x16() {
return Err(ReconstructError::UnsupportedChromaArrayType(
chroma_array_type,
));
}
return reconstruct_chroma_intra_444(
mb,
qp_y,
bit_depth_c,
mb_px,
mb_py,
mb_addr,
writer,
sps,
pps,
pic,
grid,
current_slice_id,
field_scan,
);
}
let ct = if chroma_array_type == 1 {
IpChromaArrayType::Yuv420
} else {
IpChromaArrayType::Yuv422
};
let (mbw_c, mbh_c) = chroma_mb_dims(chroma_array_type);
let c_mb_px = writer.chroma_mb_px();
let c_mb_py = writer.chroma_mb_py();
let _ = mb_px;
let _ = mb_py;
let chroma_mode_idx = mb
.mb_pred
.as_ref()
.map(|p| p.intra_chroma_pred_mode)
.unwrap_or(0);
let chroma_mode = IntraChromaMode::from_index(chroma_mode_idx).unwrap_or(IntraChromaMode::Dc);
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let qp_bd_offset_c = qp_bd_offset(sps.bit_depth_chroma_minus8);
let qp_cb = qp_y_to_qp_c_with_bd_offset(qp_y, cb_offset, qp_bd_offset_c) + qp_bd_offset_c;
let qp_cr = qp_y_to_qp_c_with_bd_offset(qp_y, cr_offset, qp_bd_offset_c) + qp_bd_offset_c;
let qp_prime_y = qp_y + qp_bd_offset(sps.bit_depth_luma_minus8);
let bypass = transform_bypass_active(sps, qp_prime_y);
let cbp_chroma = ((mb.coded_block_pattern >> 4) & 0x03) as u8;
let sl4_cb = select_scaling_list_4x4(1, sps, pps);
let sl4_cr = select_scaling_list_4x4(2, sps, pps);
let (sub_width_c, sub_height_c) = match chroma_array_type {
1 => (2, 2), 2 => (2, 1), _ => (1, 1),
};
for plane in 0..2u8 {
let samples = gather_samples_chroma(
pic,
grid,
c_mb_px,
c_mb_py,
ct,
plane,
current_slice_id,
sub_width_c,
sub_height_c,
pps.constrained_intra_pred_flag,
mb_addr,
);
let out_len = (mbw_c as usize) * (mbh_c as usize);
require_intra_chroma_mode(chroma_mode, &samples.availability)?;
let mut pred_samples = vec![0i32; out_len];
predict_chroma(chroma_mode, &samples, ct, bit_depth_c, &mut pred_samples);
let qp_c = if plane == 0 { qp_cb } else { qp_cr };
let sl4 = if plane == 0 { &sl4_cb } else { &sl4_cr };
if let Some(qs_y) = si_switch_qs {
let offset = if plane == 0 { cb_offset } else { cr_offset };
let qs_c = qp_y_to_qp_c_with_bd_offset(qs_y, offset, qp_bd_offset_c) + qp_bd_offset_c;
sp_reconstruct_chroma_plane(
mb,
plane,
cbp_chroma,
&pred_samples,
true,
qp_c,
qs_c,
sl4,
field_scan,
writer,
pic,
bit_depth_c,
)?;
continue;
}
let dc_block = if plane == 0 {
&mb.residual_chroma_dc_cb
} else {
&mb.residual_chroma_dc_cr
};
let dc_flat: [i32; 8] = {
let mut a = [0i32; 8];
for (i, v) in dc_block.iter().enumerate().take(a.len()) {
a[i] = *v;
}
a
};
let (dc_cb4, dc_cb8): (Option<[i32; 4]>, Option<[i32; 8]>) = if cbp_chroma > 0 {
if chroma_array_type == 1 {
let dc4: [i32; 4] = [dc_flat[0], dc_flat[1], dc_flat[2], dc_flat[3]];
if bypass {
(Some(dc4), None)
} else {
let out = inverse_hadamard_chroma_dc_420(&dc4, qp_c, sl4, bit_depth_c)?;
(Some(out), None)
}
} else if bypass {
let out = [
dc_flat[0], dc_flat[2], dc_flat[1], dc_flat[5], dc_flat[3], dc_flat[6], dc_flat[4], dc_flat[7], ];
(None, Some(out))
} else {
let out = inverse_hadamard_chroma_dc_422(&dc_flat, qp_c, sl4, bit_depth_c)?;
(None, Some(out))
}
} else {
(Some([0i32; 4]), Some([0i32; 8]))
};
let num_c8x8 = if chroma_array_type == 1 { 1 } else { 2 };
let n_ac = 4 * num_c8x8 as usize;
let ac_blocks = if plane == 0 {
&mb.residual_chroma_ac_cb
} else {
&mb.residual_chroma_ac_cr
};
let mut rmb = vec![0i32; out_len];
for blk in 0..n_ac {
let dc_c = if chroma_array_type == 1 {
dc_cb4.unwrap()[blk]
} else {
dc_cb8.unwrap()[blk]
};
let ac_scan = if cbp_chroma == 2 {
ac_blocks.get(blk).copied().unwrap_or([0i32; 16])
} else {
[0i32; 16]
};
let mut coeffs = inv_scan_4x4_ac(&ac_scan, field_scan);
coeffs[0] = dc_c;
let residual = if bypass {
coeffs
} else {
inverse_transform_4x4_dc_preserved(&coeffs, qp_c, sl4, bit_depth_c)?
};
let (bx, by) = chroma_block_xy(chroma_array_type, blk);
for yy in 0..4 {
for xx in 0..4 {
let pidx = ((by as usize + yy) * (mbw_c as usize)) + (bx as usize + xx);
rmb[pidx] = residual[yy * 4 + xx];
}
}
}
if bypass && (chroma_mode_idx == 1 || chroma_mode_idx == 2) {
intra_bypass_dpcm(
&mut rmb,
mbw_c as usize,
mbh_c as usize,
chroma_mode_idx == 1,
);
}
for y in 0..mbh_c as usize {
for x in 0..mbw_c as usize {
let pidx = y * (mbw_c as usize) + x;
let v = clip_sample(pred_samples[pidx] + rmb[pidx], bit_depth_c);
if plane == 0 {
writer.set_cb(pic, x as i32, y as i32, v);
} else {
writer.set_cr(pic, x as i32, y as i32, v);
}
}
}
}
let _ = c_mb_px;
let _ = c_mb_py;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_chroma_intra_444(
mb: &Macroblock,
qp_y: i32,
bit_depth_c: u32,
mb_px: i32,
mb_py: i32,
mb_addr: u32,
writer: &MbWriter,
sps: &Sps,
pps: &Pps,
pic: &mut Picture,
grid: &MbGrid,
current_slice_id: i32,
field_scan: bool,
) -> Result<(), ReconstructError> {
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let qp_bd_offset_c = qp_bd_offset(sps.bit_depth_chroma_minus8);
let qp_cb = qp_y_to_qp_c_with_bd_offset(qp_y, cb_offset, qp_bd_offset_c) + qp_bd_offset_c;
let qp_cr = qp_y_to_qp_c_with_bd_offset(qp_y, cr_offset, qp_bd_offset_c) + qp_bd_offset_c;
let (pred_mode_idx, cbp_luma) = match &mb.mb_type {
MbType::Intra16x16(i) => (i.pred_mode, i.cbp_luma),
_ => unreachable!("reconstruct_chroma_intra_444 requires Intra_16x16"),
};
let mode = Intra16x16Mode::from_index(pred_mode_idx).ok_or_else(|| {
ReconstructError::UnsupportedMbType(format!("4:4:4 Intra_16x16 mode {}", pred_mode_idx))
})?;
let sl4_cb = select_scaling_list_4x4(1, sps, pps);
let sl4_cr = select_scaling_list_4x4(2, sps, pps);
let qp_prime_y = qp_y + qp_bd_offset(sps.bit_depth_luma_minus8);
let bypass = transform_bypass_active(sps, qp_prime_y);
for plane in 0..2u8 {
let samples = gather_samples_16x16_chroma(
pic,
grid,
mb_px,
mb_py,
plane,
current_slice_id,
pps.constrained_intra_pred_flag,
mb_addr,
);
require_intra_16x16_mode(mode, &samples.availability)?;
let mut pred = [0i32; 256];
predict_16x16(mode, &samples, bit_depth_c, &mut pred);
let qp_c = if plane == 0 { qp_cb } else { qp_cr };
let sl4 = if plane == 0 { &sl4_cb } else { &sl4_cr };
let dc_levels = if plane == 0 {
mb.residual_cb_16x16_dc.unwrap_or([0i32; 16])
} else {
mb.residual_cr_16x16_dc.unwrap_or([0i32; 16])
};
let dc_matrix = inv_scan_4x4(&dc_levels, field_scan);
let dc_inv = if bypass {
dc_matrix
} else {
inverse_hadamard_luma_dc_16x16(&dc_matrix, qp_c, sl4, bit_depth_c)?
};
let ac_blocks = if plane == 0 {
&mb.residual_cb_luma_like
} else {
&mb.residual_cr_luma_like
};
let mut rmb = [0i32; 256];
#[allow(clippy::needless_range_loop)] for block_idx in 0..16usize {
let (bx, by) = LUMA_4X4_XY[block_idx];
let mut coeffs = if cbp_luma == 15 {
let ac = ac_blocks.get(block_idx).copied().unwrap_or([0i32; 16]);
inv_scan_4x4_ac(&ac, field_scan)
} else {
[0i32; 16]
};
let dc_row = (by / 4) as usize;
let dc_col = (bx / 4) as usize;
coeffs[0] = dc_inv[dc_row * 4 + dc_col];
let residual = if bypass {
coeffs
} else {
inverse_transform_4x4_dc_preserved(&coeffs, qp_c, sl4, bit_depth_c)?
};
for yy in 0..4usize {
for xx in 0..4usize {
rmb[(by as usize + yy) * 16 + bx as usize + xx] = residual[yy * 4 + xx];
}
}
}
if bypass && pred_mode_idx <= 1 {
intra_bypass_dpcm(&mut rmb, 16, 16, pred_mode_idx == 1);
}
for y in 0..16i32 {
for x in 0..16i32 {
let idx = (y * 16 + x) as usize;
let v = clip_sample(pred[idx] + rmb[idx], bit_depth_c);
if plane == 0 {
writer.set_cb(pic, x, y, v);
} else {
writer.set_cr(pic, x, y, v);
}
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_chroma_intra_nxn_444(
mb: &Macroblock,
qp_y: i32,
bit_depth_c: u32,
mb_px: i32,
mb_py: i32,
mb_addr: u32,
writer: &MbWriter,
sps: &Sps,
pps: &Pps,
pic: &mut Picture,
grid: &MbGrid,
current_slice_id: i32,
field_scan: bool,
) -> Result<(), ReconstructError> {
let cip = pps.constrained_intra_pred_flag;
let cbp_luma = (mb.coded_block_pattern & 0x0F) as u8;
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let qp_bd_offset_c = qp_bd_offset(sps.bit_depth_chroma_minus8);
let qp_cb = qp_y_to_qp_c_with_bd_offset(qp_y, cb_offset, qp_bd_offset_c) + qp_bd_offset_c;
let qp_cr = qp_y_to_qp_c_with_bd_offset(qp_y, cr_offset, qp_bd_offset_c) + qp_bd_offset_c;
let sl4_cb = select_scaling_list_4x4(1, sps, pps);
let sl4_cr = select_scaling_list_4x4(2, sps, pps);
let sl8_cb = select_scaling_list_8x8(2, sps, pps);
let sl8_cr = select_scaling_list_8x8(4, sps, pps);
let (pred_modes_4x4, pred_modes_8x8) = match grid.get(mb_addr) {
Some(info) => (info.intra_4x4_pred_modes, info.intra_8x8_pred_modes),
None => ([0u8; 16], [0u8; 4]),
};
let qp_prime_y = qp_y + qp_bd_offset(sps.bit_depth_luma_minus8);
let bypass = transform_bypass_active(sps, qp_prime_y);
for plane in 0..2u8 {
let qp_c = if plane == 0 { qp_cb } else { qp_cr };
let ac_blocks = if plane == 0 {
&mb.residual_cb_luma_like
} else {
&mb.residual_cr_luma_like
};
if mb.transform_size_8x8_flag {
let sl8 = if plane == 0 { &sl8_cb } else { &sl8_cr };
#[allow(clippy::needless_range_loop)]
for blk8 in 0..4usize {
let (bx, by) = LUMA_8X8_XY[blk8];
let mode =
Intra8x8Mode::from_index(pred_modes_8x8[blk8]).unwrap_or(Intra8x8Mode::Dc);
let raw = gather_samples_8x8_chroma(
pic,
grid,
mb_px + bx,
mb_py + by,
blk8,
plane,
current_slice_id,
cip,
mb_addr,
bx,
by,
);
require_intra_8x8_mode(mode, &raw.availability)?;
let filtered = filter_samples_8x8(&raw, bit_depth_c);
let mut pred_samples = [0i32; 64];
predict_8x8(mode, &filtered, bit_depth_c, &mut pred_samples);
let coeffs_flat: [i32; 64] = if (cbp_luma >> blk8) & 1 == 1 {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let base_slot = set_before * 4;
let mut scan = [0i32; 64];
for sub in 0..4usize {
if let Some(coefs) = ac_blocks.get(base_slot + sub) {
for (i, c) in coefs.iter().enumerate().take(16) {
scan[sub * 16 + i] = *c;
}
}
}
inv_scan_8x8(&scan, field_scan)
} else {
[0i32; 64]
};
let residual = if bypass {
let mut r = coeffs_flat;
if matches!(mode, Intra8x8Mode::Vertical | Intra8x8Mode::Horizontal) {
intra_bypass_dpcm(&mut r, 8, 8, mode == Intra8x8Mode::Horizontal);
}
r
} else {
inverse_transform_8x8(&coeffs_flat, qp_c, sl8, bit_depth_c)?
};
for y in 0..8i32 {
for x in 0..8i32 {
let idx = (y * 8 + x) as usize;
let v = clip_sample(pred_samples[idx] + residual[idx], bit_depth_c);
if plane == 0 {
writer.set_cb(pic, bx + x, by + y, v);
} else {
writer.set_cr(pic, bx + x, by + y, v);
}
}
}
}
} else {
let sl4 = if plane == 0 { &sl4_cb } else { &sl4_cr };
#[allow(clippy::needless_range_loop)]
for block_idx in 0..16usize {
let (bx, by) = LUMA_4X4_XY[block_idx];
let mode =
Intra4x4Mode::from_index(pred_modes_4x4[block_idx]).unwrap_or(Intra4x4Mode::Dc);
let samples = gather_samples_4x4_chroma(
pic,
grid,
mb_px + bx,
mb_py + by,
block_idx,
plane,
current_slice_id,
cip,
mb_addr,
bx,
by,
);
require_intra_4x4_mode(mode, &samples.availability)?;
let mut pred_samples = [0i32; 16];
predict_4x4(mode, &samples, bit_depth_c, &mut pred_samples);
let blk8 = block_idx / 4;
let coeffs_scan = if (cbp_luma >> blk8) & 1 == 1 {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let compact_idx = set_before * 4 + (block_idx % 4);
ac_blocks.get(compact_idx).copied().unwrap_or([0i32; 16])
} else {
[0i32; 16]
};
let coeffs = inv_scan_4x4(&coeffs_scan, field_scan);
let residual = if bypass {
let mut r = coeffs;
if matches!(mode, Intra4x4Mode::Vertical | Intra4x4Mode::Horizontal) {
intra_bypass_dpcm(&mut r, 4, 4, mode == Intra4x4Mode::Horizontal);
}
r
} else {
inverse_transform_4x4(&coeffs, qp_c, sl4, bit_depth_c)?
};
for yy in 0..4i32 {
for xx in 0..4i32 {
let p = pred_samples[(yy * 4 + xx) as usize];
let v = clip_sample(p + residual[(yy * 4 + xx) as usize], bit_depth_c);
if plane == 0 {
writer.set_cb(pic, bx + xx, by + yy, v);
} else {
writer.set_cr(pic, bx + xx, by + yy, v);
}
}
}
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn gather_samples_16x16_chroma(
pic: &Picture,
grid: &MbGrid,
mb_px: i32,
mb_py: i32,
plane: u8,
current_slice_id: i32,
constrained_intra_pred: bool,
mb_addr: u32,
) -> Samples16x16 {
if grid.mbaff_frame_flag {
let p = plane + 1;
let (top, top_avail) = mbaff_gather_edge::<16>(
pic,
grid,
mb_addr,
p,
0,
-1,
1,
0,
current_slice_id,
constrained_intra_pred,
);
let (left, left_avail) = mbaff_gather_edge::<16>(
pic,
grid,
mb_addr,
p,
-1,
0,
0,
1,
current_slice_id,
constrained_intra_pred,
);
let tl = mbaff_neigh_sample(
pic,
grid,
mb_addr,
p,
-1,
-1,
current_slice_id,
constrained_intra_pred,
);
return Samples16x16 {
top_left: tl.unwrap_or(0),
top,
left,
availability: Neighbour4x4Availability {
top_left: tl.is_some(),
top: top_avail,
top_right: false,
left: left_avail,
},
};
}
let left_avail = mb_px > 0
&& same_slice_at(grid, mb_px - 1, mb_py, current_slice_id)
&& cip_ok_at(grid, mb_px - 1, mb_py, constrained_intra_pred);
let top_avail = mb_py > 0
&& same_slice_at(grid, mb_px, mb_py - 1, current_slice_id)
&& cip_ok_at(grid, mb_px, mb_py - 1, constrained_intra_pred);
let tl_avail = mb_px > 0
&& mb_py > 0
&& same_slice_at(grid, mb_px - 1, mb_py - 1, current_slice_id)
&& cip_ok_at(grid, mb_px - 1, mb_py - 1, constrained_intra_pred);
let read = |px: i32, py: i32| -> i32 {
if plane == 0 {
pic.cb_at(px, py)
} else {
pic.cr_at(px, py)
}
};
let top_left = if tl_avail {
read(mb_px - 1, mb_py - 1)
} else {
0
};
let mut top = [0i32; 16];
for x in 0..16 {
top[x as usize] = if top_avail {
read(mb_px + x, mb_py - 1)
} else {
0
};
}
let mut left = [0i32; 16];
for y in 0..16 {
left[y as usize] = if left_avail {
read(mb_px - 1, mb_py + y)
} else {
0
};
}
Samples16x16 {
top_left,
top,
left,
availability: Neighbour4x4Availability {
top_left: tl_avail,
top: top_avail,
top_right: false,
left: left_avail,
},
}
}
fn chroma_block_xy(chroma_array_type: u32, blk: usize) -> (i32, i32) {
match chroma_array_type {
1 => {
const XY: [(i32, i32); 4] = [(0, 0), (4, 0), (0, 4), (4, 4)];
XY[blk]
}
2 => {
const XY: [(i32, i32); 8] = [
(0, 0),
(4, 0),
(0, 4),
(4, 4),
(0, 8),
(4, 8),
(0, 12),
(4, 12),
];
XY[blk]
}
_ => (0, 0),
}
}
fn gather_samples_16x16(
pic: &Picture,
grid: &MbGrid,
mb_px: i32,
mb_py: i32,
current_slice_id: i32,
constrained_intra_pred: bool,
mb_addr: u32,
) -> Samples16x16 {
if grid.mbaff_frame_flag {
let (top, top_avail) = mbaff_gather_edge::<16>(
pic,
grid,
mb_addr,
0,
0,
-1,
1,
0,
current_slice_id,
constrained_intra_pred,
);
let (left, left_avail) = mbaff_gather_edge::<16>(
pic,
grid,
mb_addr,
0,
-1,
0,
0,
1,
current_slice_id,
constrained_intra_pred,
);
let tl = mbaff_neigh_sample(
pic,
grid,
mb_addr,
0,
-1,
-1,
current_slice_id,
constrained_intra_pred,
);
return Samples16x16 {
top_left: tl.unwrap_or(0),
top,
left,
availability: Neighbour4x4Availability {
top_left: tl.is_some(),
top: top_avail,
top_right: false,
left: left_avail,
},
};
}
let left_avail = mb_px > 0
&& same_slice_at(grid, mb_px - 1, mb_py, current_slice_id)
&& cip_ok_at(grid, mb_px - 1, mb_py, constrained_intra_pred);
let top_avail = mb_py > 0
&& same_slice_at(grid, mb_px, mb_py - 1, current_slice_id)
&& cip_ok_at(grid, mb_px, mb_py - 1, constrained_intra_pred);
let tl_avail = mb_px > 0
&& mb_py > 0
&& same_slice_at(grid, mb_px - 1, mb_py - 1, current_slice_id)
&& cip_ok_at(grid, mb_px - 1, mb_py - 1, constrained_intra_pred);
let top_left = if tl_avail {
pic.luma_at(mb_px - 1, mb_py - 1)
} else {
0
};
let mut top = [0i32; 16];
for x in 0..16 {
top[x as usize] = if top_avail {
pic.luma_at(mb_px + x, mb_py - 1)
} else {
0
};
}
let mut left = [0i32; 16];
for y in 0..16 {
left[y as usize] = if left_avail {
pic.luma_at(mb_px - 1, mb_py + y)
} else {
0
};
}
Samples16x16 {
top_left,
top,
left,
availability: Neighbour4x4Availability {
top_left: tl_avail,
top: top_avail,
top_right: false, left: left_avail,
},
}
}
#[allow(clippy::too_many_arguments)]
fn gather_samples_4x4(
pic: &Picture,
grid: &MbGrid,
bx: i32,
by: i32,
block_idx: usize,
current_slice_id: i32,
constrained_intra_pred: bool,
mb_addr: u32,
lbx: i32,
lby: i32,
) -> Samples4x4 {
if grid.mbaff_frame_flag {
let tr_scan_ok = !matches!(block_idx, 3 | 7 | 11 | 13 | 15);
let (top, top_avail) = mbaff_gather_edge::<4>(
pic,
grid,
mb_addr,
0,
lbx,
lby - 1,
1,
0,
current_slice_id,
constrained_intra_pred,
);
let (top_right, tr_edge_avail) = if tr_scan_ok {
mbaff_gather_edge::<4>(
pic,
grid,
mb_addr,
0,
lbx + 4,
lby - 1,
1,
0,
current_slice_id,
constrained_intra_pred,
)
} else {
([0i32; 4], false)
};
let (left, left_avail) = mbaff_gather_edge::<4>(
pic,
grid,
mb_addr,
0,
lbx - 1,
lby,
0,
1,
current_slice_id,
constrained_intra_pred,
);
let tl = mbaff_neigh_sample(
pic,
grid,
mb_addr,
0,
lbx - 1,
lby - 1,
current_slice_id,
constrained_intra_pred,
);
return Samples4x4 {
top_left: tl.unwrap_or(0),
top,
top_right,
left,
availability: Neighbour4x4Availability {
top_left: tl.is_some(),
top: top_avail,
top_right: tr_scan_ok && tr_edge_avail,
left: left_avail,
},
};
}
let left_avail = bx > 0
&& same_slice_at(grid, bx - 1, by, current_slice_id)
&& cip_ok_at(grid, bx - 1, by, constrained_intra_pred);
let top_avail = by > 0
&& same_slice_at(grid, bx, by - 1, current_slice_id)
&& cip_ok_at(grid, bx, by - 1, constrained_intra_pred);
let tl_avail = bx > 0
&& by > 0
&& same_slice_at(grid, bx - 1, by - 1, current_slice_id)
&& cip_ok_at(grid, bx - 1, by - 1, constrained_intra_pred);
let tr_scan_ok = !matches!(block_idx, 3 | 7 | 11 | 13 | 15);
let tr_avail = by > 0
&& tr_scan_ok
&& (bx + 4 < pic.width_in_samples as i32)
&& same_slice_at(grid, bx + 4, by - 1, current_slice_id)
&& cip_ok_at(grid, bx + 4, by - 1, constrained_intra_pred);
let top_left = if tl_avail {
pic.luma_at(bx - 1, by - 1)
} else {
0
};
let mut top = [0i32; 4];
let mut top_right = [0i32; 4];
for x in 0..4 {
top[x as usize] = if top_avail {
pic.luma_at(bx + x, by - 1)
} else {
0
};
top_right[x as usize] = if tr_avail {
pic.luma_at(bx + 4 + x, by - 1)
} else {
0
};
}
let mut left = [0i32; 4];
for y in 0..4 {
left[y as usize] = if left_avail {
pic.luma_at(bx - 1, by + y)
} else {
0
};
}
Samples4x4 {
top_left,
top,
top_right,
left,
availability: Neighbour4x4Availability {
top_left: tl_avail,
top: top_avail,
top_right: tr_avail,
left: left_avail,
},
}
}
fn same_slice_at(grid: &MbGrid, x: i32, y: i32, current_slice_id: i32) -> bool {
if current_slice_id < 0 {
return true;
}
if x < 0 || y < 0 {
return true;
}
let mb_x = (x >> 4) as u32;
let mb_y = (y >> 4) as u32;
if mb_x >= grid.width_in_mbs || mb_y >= grid.height_in_mbs {
return true;
}
let addr = mb_y * grid.width_in_mbs + mb_x;
match grid.get(addr) {
Some(info) => info.slice_id >= 0 && info.slice_id == current_slice_id,
None => true,
}
}
fn cip_ok_at(grid: &MbGrid, x: i32, y: i32, constrained_intra_pred: bool) -> bool {
if !constrained_intra_pred {
return true;
}
if x < 0 || y < 0 {
return true;
}
let mb_x = (x >> 4) as u32;
let mb_y = (y >> 4) as u32;
if mb_x >= grid.width_in_mbs || mb_y >= grid.height_in_mbs {
return true;
}
let addr = mb_y * grid.width_in_mbs + mb_x;
match grid.get(addr) {
Some(info) if info.available && !info.is_intra => false,
_ => true,
}
}
#[allow(clippy::too_many_arguments)]
fn gather_samples_8x8(
pic: &Picture,
grid: &MbGrid,
bx: i32,
by: i32,
blk8: usize,
current_slice_id: i32,
constrained_intra_pred: bool,
mb_addr: u32,
lbx: i32,
lby: i32,
) -> Samples8x8 {
if grid.mbaff_frame_flag {
let tr_scan_ok = blk8 != 3;
let (top, top_avail) = mbaff_gather_edge::<8>(
pic,
grid,
mb_addr,
0,
lbx,
lby - 1,
1,
0,
current_slice_id,
constrained_intra_pred,
);
let (top_right, tr_edge_avail) = if tr_scan_ok {
mbaff_gather_edge::<8>(
pic,
grid,
mb_addr,
0,
lbx + 8,
lby - 1,
1,
0,
current_slice_id,
constrained_intra_pred,
)
} else {
([0i32; 8], false)
};
let (left, left_avail) = mbaff_gather_edge::<8>(
pic,
grid,
mb_addr,
0,
lbx - 1,
lby,
0,
1,
current_slice_id,
constrained_intra_pred,
);
let tl = mbaff_neigh_sample(
pic,
grid,
mb_addr,
0,
lbx - 1,
lby - 1,
current_slice_id,
constrained_intra_pred,
);
return Samples8x8 {
top_left: tl.unwrap_or(0),
top,
top_right,
left,
availability: Neighbour4x4Availability {
top_left: tl.is_some(),
top: top_avail,
top_right: tr_scan_ok && tr_edge_avail,
left: left_avail,
},
};
}
let left_avail = bx > 0
&& same_slice_at(grid, bx - 1, by, current_slice_id)
&& cip_ok_at(grid, bx - 1, by, constrained_intra_pred);
let top_avail = by > 0
&& same_slice_at(grid, bx, by - 1, current_slice_id)
&& cip_ok_at(grid, bx, by - 1, constrained_intra_pred);
let tl_avail = bx > 0
&& by > 0
&& same_slice_at(grid, bx - 1, by - 1, current_slice_id)
&& cip_ok_at(grid, bx - 1, by - 1, constrained_intra_pred);
let tr_scan_ok = blk8 != 3;
let tr_avail = by > 0
&& tr_scan_ok
&& (bx + 8 < pic.width_in_samples as i32)
&& same_slice_at(grid, bx + 8, by - 1, current_slice_id)
&& cip_ok_at(grid, bx + 8, by - 1, constrained_intra_pred);
let top_left = if tl_avail {
pic.luma_at(bx - 1, by - 1)
} else {
0
};
let mut top = [0i32; 8];
let mut top_right = [0i32; 8];
for x in 0..8 {
top[x as usize] = if top_avail {
pic.luma_at(bx + x, by - 1)
} else {
0
};
top_right[x as usize] = if tr_avail {
pic.luma_at(bx + 8 + x, by - 1)
} else {
0
};
}
let mut left = [0i32; 8];
for y in 0..8 {
left[y as usize] = if left_avail {
pic.luma_at(bx - 1, by + y)
} else {
0
};
}
Samples8x8 {
top_left,
top,
top_right,
left,
availability: Neighbour4x4Availability {
top_left: tl_avail,
top: top_avail,
top_right: tr_avail,
left: left_avail,
},
}
}
#[allow(clippy::too_many_arguments)]
fn gather_samples_4x4_chroma(
pic: &Picture,
grid: &MbGrid,
bx: i32,
by: i32,
block_idx: usize,
plane: u8,
current_slice_id: i32,
constrained_intra_pred: bool,
mb_addr: u32,
lbx: i32,
lby: i32,
) -> Samples4x4 {
if grid.mbaff_frame_flag {
let p = plane + 1;
let tr_scan_ok = !matches!(block_idx, 3 | 7 | 11 | 13 | 15);
let (top, top_avail) = mbaff_gather_edge::<4>(
pic,
grid,
mb_addr,
p,
lbx,
lby - 1,
1,
0,
current_slice_id,
constrained_intra_pred,
);
let (top_right, tr_edge_avail) = if tr_scan_ok {
mbaff_gather_edge::<4>(
pic,
grid,
mb_addr,
p,
lbx + 4,
lby - 1,
1,
0,
current_slice_id,
constrained_intra_pred,
)
} else {
([0i32; 4], false)
};
let (left, left_avail) = mbaff_gather_edge::<4>(
pic,
grid,
mb_addr,
p,
lbx - 1,
lby,
0,
1,
current_slice_id,
constrained_intra_pred,
);
let tl = mbaff_neigh_sample(
pic,
grid,
mb_addr,
p,
lbx - 1,
lby - 1,
current_slice_id,
constrained_intra_pred,
);
return Samples4x4 {
top_left: tl.unwrap_or(0),
top,
top_right,
left,
availability: Neighbour4x4Availability {
top_left: tl.is_some(),
top: top_avail,
top_right: tr_scan_ok && tr_edge_avail,
left: left_avail,
},
};
}
let left_avail = bx > 0
&& same_slice_at(grid, bx - 1, by, current_slice_id)
&& cip_ok_at(grid, bx - 1, by, constrained_intra_pred);
let top_avail = by > 0
&& same_slice_at(grid, bx, by - 1, current_slice_id)
&& cip_ok_at(grid, bx, by - 1, constrained_intra_pred);
let tl_avail = bx > 0
&& by > 0
&& same_slice_at(grid, bx - 1, by - 1, current_slice_id)
&& cip_ok_at(grid, bx - 1, by - 1, constrained_intra_pred);
let tr_scan_ok = !matches!(block_idx, 3 | 7 | 11 | 13 | 15);
let tr_avail = by > 0
&& tr_scan_ok
&& (bx + 4 < pic.width_in_samples as i32)
&& same_slice_at(grid, bx + 4, by - 1, current_slice_id)
&& cip_ok_at(grid, bx + 4, by - 1, constrained_intra_pred);
let read = |px: i32, py: i32| -> i32 {
if plane == 0 {
pic.cb_at(px, py)
} else {
pic.cr_at(px, py)
}
};
let top_left = if tl_avail { read(bx - 1, by - 1) } else { 0 };
let mut top = [0i32; 4];
let mut top_right = [0i32; 4];
for x in 0..4 {
top[x as usize] = if top_avail { read(bx + x, by - 1) } else { 0 };
top_right[x as usize] = if tr_avail {
read(bx + 4 + x, by - 1)
} else {
0
};
}
let mut left = [0i32; 4];
for y in 0..4 {
left[y as usize] = if left_avail { read(bx - 1, by + y) } else { 0 };
}
Samples4x4 {
top_left,
top,
top_right,
left,
availability: Neighbour4x4Availability {
top_left: tl_avail,
top: top_avail,
top_right: tr_avail,
left: left_avail,
},
}
}
#[allow(clippy::too_many_arguments)]
fn gather_samples_8x8_chroma(
pic: &Picture,
grid: &MbGrid,
bx: i32,
by: i32,
blk8: usize,
plane: u8,
current_slice_id: i32,
constrained_intra_pred: bool,
mb_addr: u32,
lbx: i32,
lby: i32,
) -> Samples8x8 {
if grid.mbaff_frame_flag {
let p = plane + 1;
let tr_scan_ok = blk8 != 3;
let (top, top_avail) = mbaff_gather_edge::<8>(
pic,
grid,
mb_addr,
p,
lbx,
lby - 1,
1,
0,
current_slice_id,
constrained_intra_pred,
);
let (top_right, tr_edge_avail) = if tr_scan_ok {
mbaff_gather_edge::<8>(
pic,
grid,
mb_addr,
p,
lbx + 8,
lby - 1,
1,
0,
current_slice_id,
constrained_intra_pred,
)
} else {
([0i32; 8], false)
};
let (left, left_avail) = mbaff_gather_edge::<8>(
pic,
grid,
mb_addr,
p,
lbx - 1,
lby,
0,
1,
current_slice_id,
constrained_intra_pred,
);
let tl = mbaff_neigh_sample(
pic,
grid,
mb_addr,
p,
lbx - 1,
lby - 1,
current_slice_id,
constrained_intra_pred,
);
return Samples8x8 {
top_left: tl.unwrap_or(0),
top,
top_right,
left,
availability: Neighbour4x4Availability {
top_left: tl.is_some(),
top: top_avail,
top_right: tr_scan_ok && tr_edge_avail,
left: left_avail,
},
};
}
let left_avail = bx > 0
&& same_slice_at(grid, bx - 1, by, current_slice_id)
&& cip_ok_at(grid, bx - 1, by, constrained_intra_pred);
let top_avail = by > 0
&& same_slice_at(grid, bx, by - 1, current_slice_id)
&& cip_ok_at(grid, bx, by - 1, constrained_intra_pred);
let tl_avail = bx > 0
&& by > 0
&& same_slice_at(grid, bx - 1, by - 1, current_slice_id)
&& cip_ok_at(grid, bx - 1, by - 1, constrained_intra_pred);
let tr_scan_ok = blk8 != 3;
let tr_avail = by > 0
&& tr_scan_ok
&& (bx + 8 < pic.width_in_samples as i32)
&& same_slice_at(grid, bx + 8, by - 1, current_slice_id)
&& cip_ok_at(grid, bx + 8, by - 1, constrained_intra_pred);
let read = |px: i32, py: i32| -> i32 {
if plane == 0 {
pic.cb_at(px, py)
} else {
pic.cr_at(px, py)
}
};
let top_left = if tl_avail { read(bx - 1, by - 1) } else { 0 };
let mut top = [0i32; 8];
let mut top_right = [0i32; 8];
for x in 0..8 {
top[x as usize] = if top_avail { read(bx + x, by - 1) } else { 0 };
top_right[x as usize] = if tr_avail {
read(bx + 8 + x, by - 1)
} else {
0
};
}
let mut left = [0i32; 8];
for y in 0..8 {
left[y as usize] = if left_avail { read(bx - 1, by + y) } else { 0 };
}
Samples8x8 {
top_left,
top,
top_right,
left,
availability: Neighbour4x4Availability {
top_left: tl_avail,
top: top_avail,
top_right: tr_avail,
left: left_avail,
},
}
}
fn gather_samples_chroma(
pic: &Picture,
grid: &MbGrid,
c_mb_px: i32,
c_mb_py: i32,
ct: IpChromaArrayType,
plane: u8,
current_slice_id: i32,
sub_width_c: i32,
sub_height_c: i32,
constrained_intra_pred: bool,
mb_addr: u32,
) -> SamplesChroma {
let w = ct.width();
let h = ct.height();
if grid.mbaff_frame_flag {
let p = plane + 1;
let mut top = vec![0i32; w];
let mut top_avail = true;
for (x, slot) in top.iter_mut().enumerate() {
match mbaff_neigh_sample(
pic,
grid,
mb_addr,
p,
x as i32,
-1,
current_slice_id,
constrained_intra_pred,
) {
Some(v) => *slot = v,
None => top_avail = false,
}
}
let mut left = vec![0i32; h];
let mut left_avail = true;
for (y, slot) in left.iter_mut().enumerate() {
match mbaff_neigh_sample(
pic,
grid,
mb_addr,
p,
-1,
y as i32,
current_slice_id,
constrained_intra_pred,
) {
Some(v) => *slot = v,
None => left_avail = false,
}
}
let tl = mbaff_neigh_sample(
pic,
grid,
mb_addr,
p,
-1,
-1,
current_slice_id,
constrained_intra_pred,
);
return SamplesChroma {
top_left: tl.unwrap_or(0),
top,
left,
availability: Neighbour4x4Availability {
top_left: tl.is_some(),
top: top_avail,
top_right: false,
left: left_avail,
},
};
}
let to_lx = |cx: i32| cx * sub_width_c;
let to_ly = |cy: i32| cy * sub_height_c;
let left_avail = c_mb_px > 0
&& same_slice_at(grid, to_lx(c_mb_px - 1), to_ly(c_mb_py), current_slice_id)
&& cip_ok_at(
grid,
to_lx(c_mb_px - 1),
to_ly(c_mb_py),
constrained_intra_pred,
);
let top_avail = c_mb_py > 0
&& same_slice_at(grid, to_lx(c_mb_px), to_ly(c_mb_py - 1), current_slice_id)
&& cip_ok_at(
grid,
to_lx(c_mb_px),
to_ly(c_mb_py - 1),
constrained_intra_pred,
);
let tl_avail = c_mb_px > 0
&& c_mb_py > 0
&& same_slice_at(
grid,
to_lx(c_mb_px - 1),
to_ly(c_mb_py - 1),
current_slice_id,
)
&& cip_ok_at(
grid,
to_lx(c_mb_px - 1),
to_ly(c_mb_py - 1),
constrained_intra_pred,
);
let fetch = |x: i32, y: i32| -> i32 {
if plane == 0 {
pic.cb_at(x, y)
} else {
pic.cr_at(x, y)
}
};
let top_left = if tl_avail {
fetch(c_mb_px - 1, c_mb_py - 1)
} else {
0
};
let mut top = vec![0i32; w];
for (x, slot) in top.iter_mut().enumerate() {
*slot = if top_avail {
fetch(c_mb_px + x as i32, c_mb_py - 1)
} else {
0
};
}
let mut left = vec![0i32; h];
for (y, slot) in left.iter_mut().enumerate() {
*slot = if left_avail {
fetch(c_mb_px - 1, c_mb_py + y as i32)
} else {
0
};
}
SamplesChroma {
top_left,
top,
left,
availability: Neighbour4x4Availability {
top_left: tl_avail,
top: top_avail,
top_right: false,
left: left_avail,
},
}
}
fn write_block_luma(
writer: &MbWriter,
pic: &mut Picture,
pred_mb: &[i32; 256],
bx: i32,
by: i32,
residual_4x4: &[i32; 16],
bit_depth: u32,
) {
for yy in 0..4 {
for xx in 0..4 {
let pred_idx = ((by as usize) + yy) * 16 + (bx as usize) + xx;
let res = residual_4x4[yy * 4 + xx];
let v = clip_sample(pred_mb[pred_idx] + res, bit_depth);
writer.set_luma(pic, bx + xx as i32, by + yy as i32, v);
}
}
}
#[inline]
fn clip_sample(v: i32, bit_depth: u32) -> i32 {
let hi = (1i32 << bit_depth) - 1;
v.clamp(0, hi)
}
#[inline]
fn transform_bypass_active(sps: &Sps, qp_prime_y: i32) -> bool {
sps.qpprime_y_zero_transform_bypass_flag && qp_prime_y == 0
}
fn chroma_mb_dims(chroma_array_type: u32) -> (u32, u32) {
match chroma_array_type {
1 => (8, 8),
2 => (8, 16),
3 => (16, 16),
_ => (0, 0),
}
}
#[inline]
fn luma_scaling_plane(sps: &Sps, header: &SliceHeader) -> usize {
if sps.separate_colour_plane_flag {
(header.colour_plane_id as usize).min(2)
} else {
0
}
}
fn chroma_subsample(chroma_array_type: u32) -> (i32, i32) {
match chroma_array_type {
1 => (2, 2),
2 => (2, 1),
3 => (1, 1),
_ => (1, 1),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PartMode {
L0Only,
L1Only,
BiPred,
Direct,
}
#[derive(Debug, Clone, Copy)]
struct InterPartition {
x: u8,
y: u8,
w: u8,
h: u8,
mode: PartMode,
shape: MvpredShape,
ref_idx_l0: i8,
ref_idx_l1: i8,
mvd_l0: (i32, i32),
mvd_l1: (i32, i32),
is_skip: bool,
precomputed_mv: Option<(Mv, Mv)>,
}
impl Default for InterPartition {
fn default() -> Self {
Self {
x: 0,
y: 0,
w: 16,
h: 16,
mode: PartMode::L0Only,
shape: MvpredShape::Default,
ref_idx_l0: -1,
ref_idx_l1: -1,
mvd_l0: (0, 0),
mvd_l1: (0, 0),
is_skip: false,
precomputed_mv: None,
}
}
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_mb_inter<R: RefPicProvider>(
mb: &Macroblock,
mb_addr: u32,
qp_y: i32,
chroma_array_type: u32,
bit_depth_y: u32,
bit_depth_c: u32,
slice_header: &SliceHeader,
sps: &Sps,
pps: &Pps,
ref_pics: &R,
pic: &mut Picture,
grid: &mut MbGrid,
mbaff_frame_flag: bool,
mb_field_decoding_flag: bool,
current_slice_id: i32,
sp_si: Option<SpSiCtx>,
) -> Result<(), ReconstructError> {
let (mb_px, mb_py) = mb_sample_origin(grid, mb_addr, mbaff_frame_flag, mb_field_decoding_flag);
let writer = MbWriter::new(
grid,
mb_addr,
mb_px,
mb_py,
mbaff_frame_flag,
mb_field_decoding_flag,
chroma_array_type,
);
if let Some(info) = grid.get_mut(mb_addr) {
info.slice_id = current_slice_id;
info.mb_field_decoding_flag = mb_field_decoding_flag;
}
let partitions = derive_inter_partitions(
mb,
slice_header,
sps,
ref_pics,
pic,
mb_addr,
grid,
current_slice_id,
)?;
let inter_debug_mb = recon_debug_mb_target();
let inter_debug = (mb_addr == 0 && recon_debug_enabled()) || inter_debug_mb == Some(mb_addr);
if inter_debug {
eprintln!(
"RECON_INTER MB#{} mb_type={:?} mb_type_raw={} slice_type={:?} \
cbp={:#x} transform8x8={} qp_y={} partitions={}",
mb_addr,
mb.mb_type,
mb.mb_type_raw,
slice_header.slice_type,
mb.coded_block_pattern,
mb.transform_size_8x8_flag,
qp_y,
partitions.len(),
);
for (i, p) in partitions.iter().enumerate() {
eprintln!(
" part[{}] x={} y={} w={} h={} mode={:?} shape={:?} \
ref_l0={} ref_l1={} mvd_l0={:?} mvd_l1={:?}",
i,
p.x,
p.y,
p.w,
p.h,
p.mode,
p.shape,
p.ref_idx_l0,
p.ref_idx_l1,
p.mvd_l0,
p.mvd_l1,
);
}
}
let mut pred_luma = [0i32; 256];
let (mbw_c, mbh_c) = chroma_mb_dims(chroma_array_type);
let mut pred_cb = vec![0i32; (mbw_c as usize) * (mbh_c as usize)];
let mut pred_cr = vec![0i32; (mbw_c as usize) * (mbh_c as usize)];
if let Some(info) = grid.get_mut(mb_addr) {
info.slice_id = current_slice_id;
info.mb_field_decoding_flag = mb_field_decoding_flag;
}
let field_parity: Option<u8> = if mbaff_frame_flag && mb_field_decoding_flag {
Some((mb_addr % 2) as u8)
} else {
None
};
for part in &partitions {
process_partition(
part,
mb_addr,
mb_px,
mb_py,
chroma_array_type,
bit_depth_y,
bit_depth_c,
slice_header,
pps,
ref_pics,
grid,
pic,
&mut pred_luma,
&mut pred_cb,
&mut pred_cr,
inter_debug,
current_slice_id,
field_parity,
)?;
}
if let Some(ctx) = sp_si {
return sp_reconstruct_inter_residual(
mb,
ctx,
qp_y,
bit_depth_y,
bit_depth_c,
sps,
pps,
&pred_luma,
&pred_cb,
&pred_cr,
&writer,
pic,
(mbaff_frame_flag && mb_field_decoding_flag) || slice_header.field_pic_flag,
);
}
let cbp_luma = (mb.coded_block_pattern & 0x0F) as u8;
let cbp_chroma = ((mb.coded_block_pattern >> 4) & 0x03) as u8;
let luma_plane = luma_scaling_plane(sps, slice_header);
let sl4 = select_scaling_list_4x4(luma_plane + 3, sps, pps);
let sl8 = select_scaling_list_8x8(2 * luma_plane + 1, sps, pps);
let qp_bd_offset_y = qp_bd_offset(sps.bit_depth_luma_minus8);
let qp_prime_y = qp_y + qp_bd_offset_y;
let bypass = transform_bypass_active(sps, qp_prime_y);
let field_scan = (mbaff_frame_flag && mb_field_decoding_flag) || slice_header.field_pic_flag;
if mb.transform_size_8x8_flag {
#[allow(clippy::needless_range_loop)] for blk8 in 0..4usize {
let (bx, by) = LUMA_8X8_XY[blk8];
let has_res = (cbp_luma >> blk8) & 1 == 1;
let coeffs = if has_res {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let base_slot = set_before * 4;
let mut scan = [0i32; 64];
for sub in 0..4usize {
let slot = base_slot + sub;
if let Some(c) = mb.residual_luma.get(slot) {
for (i, v) in c.iter().enumerate().take(16) {
scan[sub * 16 + i] = *v;
}
}
}
inv_scan_8x8(&scan, field_scan)
} else {
[0i32; 64]
};
let residual = if bypass {
coeffs
} else {
inverse_transform_8x8(&coeffs, qp_prime_y, &sl8, bit_depth_y)?
};
for y in 0..8 {
for x in 0..8 {
let v =
pred_luma[(by as usize + y) * 16 + (bx as usize + x)] + residual[y * 8 + x];
writer.set_luma(
pic,
bx + x as i32,
by + y as i32,
clip_sample(v, bit_depth_y),
);
}
}
}
} else {
#[allow(clippy::needless_range_loop)] for blk4 in 0..16usize {
let (bx, by) = LUMA_4X4_XY[blk4];
let blk8 = blk4 / 4;
let has_res = (cbp_luma >> blk8) & 1 == 1;
let coeffs_scan = if has_res {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let compact_idx = set_before * 4 + (blk4 % 4);
mb.residual_luma
.get(compact_idx)
.copied()
.unwrap_or([0i32; 16])
} else {
[0i32; 16]
};
let coeffs = inv_scan_4x4(&coeffs_scan, field_scan);
let residual = if bypass {
coeffs
} else {
inverse_transform_4x4(&coeffs, qp_prime_y, &sl4, bit_depth_y)?
};
if inter_debug {
eprintln!(
" RES blk4={blk4} has_res={has_res} qp'={qp_prime_y} res={:?}",
residual
);
}
for yy in 0..4 {
for xx in 0..4 {
let v = pred_luma[(by as usize + yy) * 16 + (bx as usize + xx)]
+ residual[yy * 4 + xx];
writer.set_luma(
pic,
bx + xx as i32,
by + yy as i32,
clip_sample(v, bit_depth_y),
);
}
}
}
}
if chroma_array_type == 1 || chroma_array_type == 2 {
reconstruct_inter_chroma_residual(
mb,
qp_y,
chroma_array_type,
bit_depth_c,
mb_px,
mb_py,
&writer,
sps,
pps,
cbp_chroma,
&pred_cb,
&pred_cr,
pic,
field_scan,
)?;
} else if chroma_array_type == 3 {
reconstruct_inter_chroma_residual_444(
mb,
qp_y,
bit_depth_c,
&writer,
sps,
pps,
cbp_luma,
&pred_cb,
&pred_cr,
pic,
field_scan,
)?;
}
Ok(())
}
fn weighted_explicit_active(slice_header: &SliceHeader, pps: &Pps) -> bool {
if slice_header.pred_weight_table.is_none() {
return false;
}
match slice_header.slice_type {
SliceType::P | SliceType::SP => pps.weighted_pred_flag,
SliceType::B => pps.weighted_bipred_idc == 1,
_ => false,
}
}
fn weighted_implicit_active(slice_header: &SliceHeader, pps: &Pps) -> bool {
matches!(slice_header.slice_type, SliceType::B) && pps.weighted_bipred_idc == 2
}
fn implicit_bipred_weights(
curr_poc: i32,
poc_l0: i32,
poc_l1: i32,
long_term_either: bool,
) -> (i32, i32, u32) {
let log2_wd: u32 = 5;
let td = clip3_i32(-128, 127, poc_l1 - poc_l0);
if td == 0 || long_term_either {
return (32, 32, log2_wd);
}
let tb = clip3_i32(-128, 127, curr_poc - poc_l0);
let tx = (16384 + (td / 2).abs()) / td;
let dist_scale_factor = clip3_i32(-1024, 1023, (tb * tx + 32) >> 6);
let dsf_shift2 = dist_scale_factor >> 2;
if !(-64..=128).contains(&dsf_shift2) {
return (32, 32, log2_wd);
}
let w1 = dsf_shift2;
let w0 = 64 - w1;
(w0, w1, log2_wd)
}
#[inline]
fn clip3_i32(x: i32, y: i32, z: i32) -> i32 {
if z < x {
x
} else if z > y {
y
} else {
z
}
}
fn luma_weight_entry(pwt: &PredWeightTable, list: u8, ref_idx: i8, log2_wd: u32) -> WeightedEntry {
if ref_idx < 0 {
return WeightedEntry::default();
}
let idx = ref_idx as usize;
let table = match list {
0 => &pwt.luma_weights_l0,
_ => &pwt.luma_weights_l1,
};
match table.get(idx).copied().flatten() {
Some((w, o)) => WeightedEntry {
weight: w,
offset: o,
},
None => WeightedEntry {
weight: 1i32 << log2_wd,
offset: 0,
},
}
}
fn chroma_weight_entry(
pwt: &PredWeightTable,
list: u8,
i_cb_cr: usize,
ref_idx: i8,
log2_wd: u32,
) -> WeightedEntry {
if ref_idx < 0 {
return WeightedEntry::default();
}
let idx = ref_idx as usize;
let table = match list {
0 => &pwt.chroma_weights_l0,
_ => &pwt.chroma_weights_l1,
};
match table.get(idx).copied().flatten() {
Some(pair) => {
let (w, o) = pair[i_cb_cr];
WeightedEntry {
weight: w,
offset: o,
}
}
None => WeightedEntry {
weight: 1i32 << log2_wd,
offset: 0,
},
}
}
#[allow(clippy::too_many_arguments)]
fn process_partition<R: RefPicProvider>(
part: &InterPartition,
mb_addr: u32,
mb_px: i32,
mb_py: i32,
chroma_array_type: u32,
bit_depth_y: u32,
bit_depth_c: u32,
slice_header: &SliceHeader,
pps: &Pps,
ref_pics: &R,
grid: &mut MbGrid,
pic: &Picture,
pred_luma: &mut [i32; 256],
pred_cb: &mut [i32],
pred_cr: &mut [i32],
inter_debug: bool,
current_slice_id: i32,
field_parity: Option<u8>,
) -> Result<(), ReconstructError> {
let _ = pic; let (mv_l0, mv_l1) = derive_partition_mvs(part, mb_addr, grid, current_slice_id);
if inter_debug {
eprintln!(
" derived mv_l0={:?} mv_l1={:?} for part@({},{} {}x{})",
(mv_l0.x, mv_l0.y),
(mv_l1.x, mv_l1.y),
part.x,
part.y,
part.w,
part.h
);
}
if let Some(info) = grid.get_mut(mb_addr) {
let x0 = part.x as usize / 4;
let y0 = part.y as usize / 4;
let nx = part.w as usize / 4;
let ny = part.h as usize / 4;
for dy in 0..ny {
for dx in 0..nx {
let bx = x0 + dx;
let by = y0 + dy;
let blk4 = blk4_raster_index(bx as u8, by as u8);
if part.mode != PartMode::L1Only {
info.mv_l0[blk4 as usize] = (mv_l0.x as i16, mv_l0.y as i16);
}
if matches!(
part.mode,
PartMode::L1Only | PartMode::BiPred | PartMode::Direct
) {
info.mv_l1[blk4 as usize] = (mv_l1.x as i16, mv_l1.y as i16);
}
}
}
let q_x0 = part.x as usize / 8;
let q_y0 = part.y as usize / 8;
let q_nx = (part.w as usize).max(8) / 8;
let q_ny = (part.h as usize).max(8) / 8;
for qdy in 0..q_ny {
for qdx in 0..q_nx {
let qx = q_x0 + qdx;
let qy = q_y0 + qdy;
if qx < 2 && qy < 2 {
let q_idx = qy * 2 + qx;
let ref_key = |list: u8, ref_idx: i8| -> i32 {
let idx = ref_idx.max(0) as u32;
match field_parity {
Some(par) => {
let sel = if idx % 2 == 0 { par } else { 1 - par };
ref_pics
.ref_pic_poc(list, idx / 2)
.map(|p| p.wrapping_mul(4).wrapping_add(1 + sel as i32))
.unwrap_or(i32::MIN)
}
None => {
let par_term = if slice_header.field_pic_flag {
ref_pics
.ref_field_parity(list, idx)
.map(|p| 1 + p as i32)
.unwrap_or(0)
} else {
0
};
ref_pics
.ref_pic_poc(list, idx)
.map(|p| p.wrapping_mul(4).wrapping_add(par_term))
.unwrap_or(i32::MIN)
}
}
};
if part.mode != PartMode::L1Only {
info.ref_idx_l0[q_idx] = part.ref_idx_l0;
if part.ref_idx_l0 >= 0 {
info.ref_poc_l0[q_idx] = ref_key(0, part.ref_idx_l0);
}
}
if matches!(
part.mode,
PartMode::L1Only | PartMode::BiPred | PartMode::Direct
) {
info.ref_idx_l1[q_idx] = part.ref_idx_l1;
if part.ref_idx_l1 >= 0 {
info.ref_poc_l1[q_idx] = ref_key(1, part.ref_idx_l1);
}
}
}
}
}
}
let w = part.w as u32;
let h = part.h as u32;
let mb_py_mc = match field_parity {
Some(parity) => (mb_py - parity as i32) / 2,
None => mb_py,
};
let part_abs_x = mb_px + part.x as i32;
let part_abs_y = mb_py_mc + part.y as i32;
let resolve_ref = |list: u8, ref_idx: i8| -> Result<(&Picture, Option<u8>), ReconstructError> {
let idx = ref_idx.max(0) as u32;
match field_parity {
Some(par) => {
let rp = ref_pics
.ref_pic(list, idx / 2)
.ok_or(ReconstructError::MissingRefPic { list, idx: idx / 2 })?;
let sel = if idx % 2 == 0 { par } else { 1 - par };
Ok((rp, Some(sel)))
}
None => Ok((
ref_pics
.ref_pic(list, idx)
.ok_or(ReconstructError::MissingRefPic { list, idx })?,
None,
)),
}
};
let mut l0_buf = vec![0i32; (w as usize) * (h as usize)];
let mut l1_buf = vec![0i32; (w as usize) * (h as usize)];
let has_l0 = matches!(
part.mode,
PartMode::L0Only | PartMode::BiPred | PartMode::Direct
) && part.ref_idx_l0 >= 0;
let has_l1 = matches!(
part.mode,
PartMode::L1Only | PartMode::BiPred | PartMode::Direct
) && part.ref_idx_l1 >= 0;
if has_l0 {
let (rp, fld) = resolve_ref(0, part.ref_idx_l0)?;
if inter_debug {
eprintln!(
" L0 ref idx={} pic {}x{} poc={} fld={:?} ysum0={}",
part.ref_idx_l0,
rp.width_in_samples,
rp.height_in_samples,
rp.pic_order_cnt,
fld,
rp.luma.iter().take(64).map(|&v| v as u32).sum::<u32>()
);
}
mc_luma_partition(
rp,
part_abs_x,
part_abs_y,
mv_l0,
w,
h,
bit_depth_y,
&mut l0_buf,
fld,
)?;
}
if has_l1 {
let (rp, fld) = resolve_ref(1, part.ref_idx_l1)?;
if inter_debug {
eprintln!(
" L1 ref idx={} pic {}x{} poc={} fld={:?} ysum0={}",
part.ref_idx_l1,
rp.width_in_samples,
rp.height_in_samples,
rp.pic_order_cnt,
fld,
rp.luma.iter().take(64).map(|&v| v as u32).sum::<u32>()
);
}
mc_luma_partition(
rp,
part_abs_x,
part_abs_y,
mv_l1,
w,
h,
bit_depth_y,
&mut l1_buf,
fld,
)?;
if inter_debug {
eprintln!(
" L1 buf abs=({part_abs_x},{part_abs_y}) mv={:?} row sums: {:?}",
(mv_l1.x, mv_l1.y),
(0..h as usize)
.map(|j| l1_buf[j * w as usize..(j + 1) * w as usize]
.iter()
.sum::<i32>()
/ w as i32)
.collect::<Vec<_>>()
);
}
}
let use_explicit = weighted_explicit_active(slice_header, pps);
let bi_mode = if has_l0 && has_l1 {
Some(BiPredMode::Bipred)
} else if has_l0 {
Some(BiPredMode::L0Only)
} else if has_l1 {
Some(BiPredMode::L1Only)
} else {
None
};
let use_implicit_bipred =
weighted_implicit_active(slice_header, pps) && matches!(bi_mode, Some(BiPredMode::Bipred));
let implicit_weights = if use_implicit_bipred {
let curr_poc = pic.pic_order_cnt;
let (rp0, _) = resolve_ref(0, part.ref_idx_l0)?;
let (rp1, _) = resolve_ref(1, part.ref_idx_l1)?;
let long_term_either = false;
Some(implicit_bipred_weights(
curr_poc,
rp0.pic_order_cnt,
rp1.pic_order_cnt,
long_term_either,
))
} else {
None
};
for py in 0..h as usize {
for px in 0..w as usize {
let dst_idx = (part.y as usize + py) * 16 + (part.x as usize + px);
let v = match bi_mode {
Some(_) if use_explicit => {
0
}
Some(BiPredMode::Bipred) if use_implicit_bipred => {
0
}
Some(BiPredMode::Bipred) => {
(l0_buf[py * w as usize + px] + l1_buf[py * w as usize + px] + 1) >> 1
}
Some(BiPredMode::L0Only) => l0_buf[py * w as usize + px],
Some(BiPredMode::L1Only) => l1_buf[py * w as usize + px],
None => {
0
}
};
pred_luma[dst_idx] = v;
}
}
if let Some((w0, w1, log2_wd)) = implicit_weights {
let mut scratch = vec![0i32; (w as usize) * (h as usize)];
weighted_pred_explicit(
Some(l0_buf.as_slice()),
Some(l1_buf.as_slice()),
w as usize,
w,
h,
BiPredMode::Bipred,
WeightedEntry {
weight: w0,
offset: 0,
},
WeightedEntry {
weight: w1,
offset: 0,
},
log2_wd,
bit_depth_y,
&mut scratch,
w as usize,
);
for py in 0..h as usize {
for px in 0..w as usize {
let dst_idx = (part.y as usize + py) * 16 + (part.x as usize + px);
pred_luma[dst_idx] = scratch[py * w as usize + px];
}
}
}
let wp_ref_l0 = if field_parity.is_some() && part.ref_idx_l0 >= 0 {
part.ref_idx_l0 / 2
} else {
part.ref_idx_l0
};
let wp_ref_l1 = if field_parity.is_some() && part.ref_idx_l1 >= 0 {
part.ref_idx_l1 / 2
} else {
part.ref_idx_l1
};
if use_explicit {
if let (Some(mode), Some(pwt)) = (bi_mode, slice_header.pred_weight_table.as_ref()) {
let log2_wd = pwt.luma_log2_weight_denom;
let w_l0 = luma_weight_entry(pwt, 0, wp_ref_l0, log2_wd);
let w_l1 = luma_weight_entry(pwt, 1, wp_ref_l1, log2_wd);
let mut scratch = vec![0i32; (w as usize) * (h as usize)];
let l0_opt = if matches!(mode, BiPredMode::L0Only | BiPredMode::Bipred) {
Some(l0_buf.as_slice())
} else {
None
};
let l1_opt = if matches!(mode, BiPredMode::L1Only | BiPredMode::Bipred) {
Some(l1_buf.as_slice())
} else {
None
};
weighted_pred_explicit(
l0_opt,
l1_opt,
w as usize,
w,
h,
mode,
w_l0,
w_l1,
log2_wd,
bit_depth_y,
&mut scratch,
w as usize,
);
for py in 0..h as usize {
for px in 0..w as usize {
let dst_idx = (part.y as usize + py) * 16 + (part.x as usize + px);
pred_luma[dst_idx] = scratch[py * w as usize + px];
}
}
}
}
if chroma_array_type == 1 || chroma_array_type == 2 {
let (mbw_c, mbh_c) = chroma_mb_dims(chroma_array_type);
let c_mb_px = mb_px / 2; let c_mb_py = if chroma_array_type == 1 {
mb_py_mc / 2
} else {
mb_py_mc
};
let c_part_x = part.x as i32 / 2;
let c_part_y = if chroma_array_type == 1 {
part.y as i32 / 2
} else {
part.y as i32
};
let c_w = part.w as u32 / 2;
let c_h = if chroma_array_type == 1 {
part.h as u32 / 2
} else {
part.h as u32
};
let mbw_c_use = mbw_c;
let _ = mbh_c;
let mut l0_cb = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut l0_cr = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut l1_cb = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut l1_cr = vec![0i32; (c_w as usize) * (c_h as usize)];
let paff_parity: Option<u8> = if slice_header.field_pic_flag {
Some(u8::from(slice_header.bottom_field_flag))
} else {
None
};
let chroma_mv = |mv: Mv, list: u8, ref_idx: i8, ref_field: Option<u8>| -> Mv {
if chroma_array_type == 1 {
let cur = field_parity.or(paff_parity);
let refp = ref_field.or_else(|| {
if paff_parity.is_some() {
ref_pics.ref_field_parity(list, ref_idx.max(0) as u32)
} else {
None
}
});
if let (Some(cur_par), Some(ref_par)) = (cur, refp) {
if ref_par != cur_par {
let d = if ref_par == 0 { 2 } else { -2 };
return Mv::new(mv.x, mv.y + d);
}
}
}
mv
};
if has_l0 {
let (rp, fld) = resolve_ref(0, part.ref_idx_l0)?;
mc_chroma_partition(
rp,
c_mb_px + c_part_x,
c_mb_py + c_part_y,
chroma_mv(mv_l0, 0, part.ref_idx_l0, fld),
c_w,
c_h,
chroma_array_type,
bit_depth_c,
&mut l0_cb,
&mut l0_cr,
fld,
)?;
}
if has_l1 {
let (rp, fld) = resolve_ref(1, part.ref_idx_l1)?;
mc_chroma_partition(
rp,
c_mb_px + c_part_x,
c_mb_py + c_part_y,
chroma_mv(mv_l1, 1, part.ref_idx_l1, fld),
c_w,
c_h,
chroma_array_type,
bit_depth_c,
&mut l1_cb,
&mut l1_cr,
fld,
)?;
}
if let Some((w0, w1, log2_wd)) = implicit_weights {
let mut scratch_cb = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut scratch_cr = vec![0i32; (c_w as usize) * (c_h as usize)];
let w_entry_l0 = WeightedEntry {
weight: w0,
offset: 0,
};
let w_entry_l1 = WeightedEntry {
weight: w1,
offset: 0,
};
weighted_pred_explicit(
Some(l0_cb.as_slice()),
Some(l1_cb.as_slice()),
c_w as usize,
c_w,
c_h,
BiPredMode::Bipred,
w_entry_l0,
w_entry_l1,
log2_wd,
bit_depth_c,
&mut scratch_cb,
c_w as usize,
);
weighted_pred_explicit(
Some(l0_cr.as_slice()),
Some(l1_cr.as_slice()),
c_w as usize,
c_w,
c_h,
BiPredMode::Bipred,
w_entry_l0,
w_entry_l1,
log2_wd,
bit_depth_c,
&mut scratch_cr,
c_w as usize,
);
for py in 0..c_h as usize {
for px in 0..c_w as usize {
let dst_idx =
(c_part_y as usize + py) * (mbw_c_use as usize) + (c_part_x as usize + px);
let idx = py * c_w as usize + px;
pred_cb[dst_idx] = scratch_cb[idx];
pred_cr[dst_idx] = scratch_cr[idx];
}
}
} else if use_explicit {
if let (Some(mode), Some(pwt)) = (bi_mode, slice_header.pred_weight_table.as_ref()) {
let log2_wd_c = pwt.chroma_log2_weight_denom;
let (w_cb_l0, w_cb_l1) = (
chroma_weight_entry(pwt, 0, 0, wp_ref_l0, log2_wd_c),
chroma_weight_entry(pwt, 1, 0, wp_ref_l1, log2_wd_c),
);
let (w_cr_l0, w_cr_l1) = (
chroma_weight_entry(pwt, 0, 1, wp_ref_l0, log2_wd_c),
chroma_weight_entry(pwt, 1, 1, wp_ref_l1, log2_wd_c),
);
let mut scratch_cb = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut scratch_cr = vec![0i32; (c_w as usize) * (c_h as usize)];
let cb_l0 = if matches!(mode, BiPredMode::L0Only | BiPredMode::Bipred) {
Some(l0_cb.as_slice())
} else {
None
};
let cb_l1 = if matches!(mode, BiPredMode::L1Only | BiPredMode::Bipred) {
Some(l1_cb.as_slice())
} else {
None
};
let cr_l0 = if matches!(mode, BiPredMode::L0Only | BiPredMode::Bipred) {
Some(l0_cr.as_slice())
} else {
None
};
let cr_l1 = if matches!(mode, BiPredMode::L1Only | BiPredMode::Bipred) {
Some(l1_cr.as_slice())
} else {
None
};
weighted_pred_explicit(
cb_l0,
cb_l1,
c_w as usize,
c_w,
c_h,
mode,
w_cb_l0,
w_cb_l1,
log2_wd_c,
bit_depth_c,
&mut scratch_cb,
c_w as usize,
);
weighted_pred_explicit(
cr_l0,
cr_l1,
c_w as usize,
c_w,
c_h,
mode,
w_cr_l0,
w_cr_l1,
log2_wd_c,
bit_depth_c,
&mut scratch_cr,
c_w as usize,
);
for py in 0..c_h as usize {
for px in 0..c_w as usize {
let dst_idx = (c_part_y as usize + py) * (mbw_c_use as usize)
+ (c_part_x as usize + px);
let idx = py * c_w as usize + px;
pred_cb[dst_idx] = scratch_cb[idx];
pred_cr[dst_idx] = scratch_cr[idx];
}
}
} else {
for py in 0..c_h as usize {
for px in 0..c_w as usize {
let dst_idx = (c_part_y as usize + py) * (mbw_c_use as usize)
+ (c_part_x as usize + px);
let idx = py * c_w as usize + px;
let (vb, vr) = if has_l0 && has_l1 {
(
(l0_cb[idx] + l1_cb[idx] + 1) >> 1,
(l0_cr[idx] + l1_cr[idx] + 1) >> 1,
)
} else if has_l0 {
(l0_cb[idx], l0_cr[idx])
} else if has_l1 {
(l1_cb[idx], l1_cr[idx])
} else {
(0, 0)
};
pred_cb[dst_idx] = vb;
pred_cr[dst_idx] = vr;
}
}
}
} else {
for py in 0..c_h as usize {
for px in 0..c_w as usize {
let dst_idx =
(c_part_y as usize + py) * (mbw_c_use as usize) + (c_part_x as usize + px);
let idx = py * c_w as usize + px;
let (vb, vr) = if has_l0 && has_l1 {
(
(l0_cb[idx] + l1_cb[idx] + 1) >> 1,
(l0_cr[idx] + l1_cr[idx] + 1) >> 1,
)
} else if has_l0 {
(l0_cb[idx], l0_cr[idx])
} else if has_l1 {
(l1_cb[idx], l1_cr[idx])
} else {
(0, 0)
};
pred_cb[dst_idx] = vb;
pred_cr[dst_idx] = vr;
}
}
}
} else if chroma_array_type == 3 {
let mbw_c_use = 16u32; let c_w = part.w as u32;
let c_h = part.h as u32;
let c_part_x = part.x as i32;
let c_part_y = part.y as i32;
let mut l0_cb = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut l0_cr = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut l1_cb = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut l1_cr = vec![0i32; (c_w as usize) * (c_h as usize)];
if has_l0 {
let (rp, fld) = resolve_ref(0, part.ref_idx_l0)?;
mc_chroma_partition_444(
rp,
part_abs_x,
part_abs_y,
mv_l0,
c_w,
c_h,
bit_depth_c,
&mut l0_cb,
&mut l0_cr,
fld,
)?;
}
if has_l1 {
let (rp, fld) = resolve_ref(1, part.ref_idx_l1)?;
mc_chroma_partition_444(
rp,
part_abs_x,
part_abs_y,
mv_l1,
c_w,
c_h,
bit_depth_c,
&mut l1_cb,
&mut l1_cr,
fld,
)?;
}
if let Some((w0, w1, log2_wd)) = implicit_weights {
let mut scratch_cb = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut scratch_cr = vec![0i32; (c_w as usize) * (c_h as usize)];
let w_entry_l0 = WeightedEntry {
weight: w0,
offset: 0,
};
let w_entry_l1 = WeightedEntry {
weight: w1,
offset: 0,
};
weighted_pred_explicit(
Some(l0_cb.as_slice()),
Some(l1_cb.as_slice()),
c_w as usize,
c_w,
c_h,
BiPredMode::Bipred,
w_entry_l0,
w_entry_l1,
log2_wd,
bit_depth_c,
&mut scratch_cb,
c_w as usize,
);
weighted_pred_explicit(
Some(l0_cr.as_slice()),
Some(l1_cr.as_slice()),
c_w as usize,
c_w,
c_h,
BiPredMode::Bipred,
w_entry_l0,
w_entry_l1,
log2_wd,
bit_depth_c,
&mut scratch_cr,
c_w as usize,
);
for py in 0..c_h as usize {
for px in 0..c_w as usize {
let dst_idx =
(c_part_y as usize + py) * (mbw_c_use as usize) + (c_part_x as usize + px);
let idx = py * c_w as usize + px;
pred_cb[dst_idx] = scratch_cb[idx];
pred_cr[dst_idx] = scratch_cr[idx];
}
}
} else if use_explicit {
if let (Some(mode), Some(pwt)) = (bi_mode, slice_header.pred_weight_table.as_ref()) {
let log2_wd_c = pwt.chroma_log2_weight_denom;
let (w_cb_l0, w_cb_l1) = (
chroma_weight_entry(pwt, 0, 0, wp_ref_l0, log2_wd_c),
chroma_weight_entry(pwt, 1, 0, wp_ref_l1, log2_wd_c),
);
let (w_cr_l0, w_cr_l1) = (
chroma_weight_entry(pwt, 0, 1, wp_ref_l0, log2_wd_c),
chroma_weight_entry(pwt, 1, 1, wp_ref_l1, log2_wd_c),
);
let mut scratch_cb = vec![0i32; (c_w as usize) * (c_h as usize)];
let mut scratch_cr = vec![0i32; (c_w as usize) * (c_h as usize)];
let cb_l0 = if matches!(mode, BiPredMode::L0Only | BiPredMode::Bipred) {
Some(l0_cb.as_slice())
} else {
None
};
let cb_l1 = if matches!(mode, BiPredMode::L1Only | BiPredMode::Bipred) {
Some(l1_cb.as_slice())
} else {
None
};
let cr_l0 = if matches!(mode, BiPredMode::L0Only | BiPredMode::Bipred) {
Some(l0_cr.as_slice())
} else {
None
};
let cr_l1 = if matches!(mode, BiPredMode::L1Only | BiPredMode::Bipred) {
Some(l1_cr.as_slice())
} else {
None
};
weighted_pred_explicit(
cb_l0,
cb_l1,
c_w as usize,
c_w,
c_h,
mode,
w_cb_l0,
w_cb_l1,
log2_wd_c,
bit_depth_c,
&mut scratch_cb,
c_w as usize,
);
weighted_pred_explicit(
cr_l0,
cr_l1,
c_w as usize,
c_w,
c_h,
mode,
w_cr_l0,
w_cr_l1,
log2_wd_c,
bit_depth_c,
&mut scratch_cr,
c_w as usize,
);
for py in 0..c_h as usize {
for px in 0..c_w as usize {
let dst_idx = (c_part_y as usize + py) * (mbw_c_use as usize)
+ (c_part_x as usize + px);
let idx = py * c_w as usize + px;
pred_cb[dst_idx] = scratch_cb[idx];
pred_cr[dst_idx] = scratch_cr[idx];
}
}
} else {
for py in 0..c_h as usize {
for px in 0..c_w as usize {
let dst_idx = (c_part_y as usize + py) * (mbw_c_use as usize)
+ (c_part_x as usize + px);
let idx = py * c_w as usize + px;
let (vb, vr) = if has_l0 && has_l1 {
(
(l0_cb[idx] + l1_cb[idx] + 1) >> 1,
(l0_cr[idx] + l1_cr[idx] + 1) >> 1,
)
} else if has_l0 {
(l0_cb[idx], l0_cr[idx])
} else if has_l1 {
(l1_cb[idx], l1_cr[idx])
} else {
(0, 0)
};
pred_cb[dst_idx] = vb;
pred_cr[dst_idx] = vr;
}
}
}
} else {
for py in 0..c_h as usize {
for px in 0..c_w as usize {
let dst_idx =
(c_part_y as usize + py) * (mbw_c_use as usize) + (c_part_x as usize + px);
let idx = py * c_w as usize + px;
let (vb, vr) = if has_l0 && has_l1 {
(
(l0_cb[idx] + l1_cb[idx] + 1) >> 1,
(l0_cr[idx] + l1_cr[idx] + 1) >> 1,
)
} else if has_l0 {
(l0_cb[idx], l0_cr[idx])
} else if has_l1 {
(l1_cb[idx], l1_cr[idx])
} else {
(0, 0)
};
pred_cb[dst_idx] = vb;
pred_cr[dst_idx] = vr;
}
}
}
}
Ok(())
}
fn mc_luma_partition(
ref_pic: &Picture,
part_abs_x: i32,
part_abs_y: i32,
mv: Mv,
w: u32,
h: u32,
bit_depth: u32,
dst: &mut [i32],
field: Option<u8>,
) -> Result<(), ReconstructError> {
if ref_pic.width_in_samples == 0 || ref_pic.height_in_samples == 0 {
return Err(ReconstructError::InvalidRefDims {
width: ref_pic.width_in_samples,
height: ref_pic.height_in_samples,
});
}
if ref_pic.non_existing {
return Err(ReconstructError::NonExistingRef);
}
let mv_x = mv.x;
let mv_y = mv.y;
let int_x = part_abs_x + (mv_x >> 2);
let int_y = part_abs_y + (mv_y >> 2);
let x_frac = (mv_x & 3) as u8;
let y_frac = (mv_y & 3) as u8;
let stride = ref_pic.width_in_samples as usize;
let (src, src_stride, src_h): (&[i32], usize, usize) = match field {
Some(parity) => (
&ref_pic.luma[(parity as usize) * stride..],
stride * 2,
(ref_pic.height_in_samples as usize) / 2,
),
None => (
&ref_pic.luma[..],
stride,
ref_pic.height_in_samples as usize,
),
};
if src_h == 0 {
return Err(ReconstructError::InvalidRefDims {
width: ref_pic.width_in_samples,
height: 0,
});
}
interpolate_luma(
src, src_stride, stride, src_h, int_x, int_y, x_frac, y_frac, w, h, bit_depth, dst,
w as usize,
)
.map_err(|_| ReconstructError::IntraPredOutOfBounds)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn mc_chroma_partition(
ref_pic: &Picture,
part_abs_x: i32,
part_abs_y: i32,
mv: Mv,
w: u32,
h: u32,
chroma_array_type: u32,
bit_depth: u32,
dst_cb: &mut [i32],
dst_cr: &mut [i32],
field: Option<u8>,
) -> Result<(), ReconstructError> {
let (mv_cx, mv_cy) = match chroma_array_type {
1 => (mv.x, mv.y),
2 => (mv.x, mv.y * 2), _ => (mv.x, mv.y),
};
let int_x = part_abs_x + (mv_cx >> 3);
let int_y = part_abs_y + (mv_cy >> 3);
let x_frac = (mv_cx & 7) as u8;
let y_frac = (mv_cy & 7) as u8;
let cw = ref_pic.chroma_width() as usize;
let ch = ref_pic.chroma_height() as usize;
if cw == 0 || ch == 0 {
return Err(ReconstructError::InvalidRefDims {
width: cw as u32,
height: ch as u32,
});
}
let (src_stride, src_h, row_off): (usize, usize, usize) = match field {
Some(parity) => (cw * 2, ch / 2, (parity as usize) * cw),
None => (cw, ch, 0),
};
if src_h == 0 {
return Err(ReconstructError::InvalidRefDims {
width: cw as u32,
height: 0,
});
}
interpolate_chroma(
&ref_pic.cb[row_off..],
src_stride,
cw,
src_h,
int_x,
int_y,
x_frac,
y_frac,
w,
h,
bit_depth,
dst_cb,
w as usize,
)
.map_err(|_| ReconstructError::IntraPredOutOfBounds)?;
interpolate_chroma(
&ref_pic.cr[row_off..],
src_stride,
cw,
src_h,
int_x,
int_y,
x_frac,
y_frac,
w,
h,
bit_depth,
dst_cr,
w as usize,
)
.map_err(|_| ReconstructError::IntraPredOutOfBounds)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn mc_chroma_partition_444(
ref_pic: &Picture,
part_abs_x: i32,
part_abs_y: i32,
mv: Mv,
w: u32,
h: u32,
bit_depth: u32,
dst_cb: &mut [i32],
dst_cr: &mut [i32],
field: Option<u8>,
) -> Result<(), ReconstructError> {
let int_x = part_abs_x + (mv.x >> 2);
let int_y = part_abs_y + (mv.y >> 2);
let x_frac = (mv.x & 3) as u8;
let y_frac = (mv.y & 3) as u8;
let cw = ref_pic.chroma_width() as usize;
let ch = ref_pic.chroma_height() as usize;
if cw == 0 || ch == 0 {
return Err(ReconstructError::InvalidRefDims {
width: cw as u32,
height: ch as u32,
});
}
let (src_stride, src_h, row_off): (usize, usize, usize) = match field {
Some(parity) => (cw * 2, ch / 2, (parity as usize) * cw),
None => (cw, ch, 0),
};
if src_h == 0 {
return Err(ReconstructError::InvalidRefDims {
width: cw as u32,
height: 0,
});
}
interpolate_luma(
&ref_pic.cb[row_off..],
src_stride,
cw,
src_h,
int_x,
int_y,
x_frac,
y_frac,
w,
h,
bit_depth,
dst_cb,
w as usize,
)
.map_err(|_| ReconstructError::IntraPredOutOfBounds)?;
interpolate_luma(
&ref_pic.cr[row_off..],
src_stride,
cw,
src_h,
int_x,
int_y,
x_frac,
y_frac,
w,
h,
bit_depth,
dst_cr,
w as usize,
)
.map_err(|_| ReconstructError::IntraPredOutOfBounds)?;
Ok(())
}
fn derive_partition_mvs(
part: &InterPartition,
mb_addr: u32,
grid: &MbGrid,
current_slice_id: i32,
) -> (Mv, Mv) {
if let Some((mv_l0, mv_l1)) = part.precomputed_mv {
return (mv_l0, mv_l1);
}
let (neigh_a_l0, neigh_b_l0, neigh_c_l0, neigh_d_l0) =
neighbour_mvs_for_list(part, mb_addr, grid, 0, current_slice_id);
let (neigh_a_l1, neigh_b_l1, neigh_c_l1, neigh_d_l1) =
neighbour_mvs_for_list(part, mb_addr, grid, 1, current_slice_id);
let (mv_l0, mv_l1) = match part.mode {
PartMode::Direct => {
let mvp_l0 = derive_mvpred_with_d(
&MvpredInputs {
neighbour_a: neigh_a_l0,
neighbour_b: neigh_b_l0,
neighbour_c: neigh_c_l0,
current_ref_idx: part.ref_idx_l0.max(0) as i32,
shape: MvpredShape::Default,
},
neigh_d_l0,
);
let mvp_l1 = derive_mvpred_with_d(
&MvpredInputs {
neighbour_a: neigh_a_l1,
neighbour_b: neigh_b_l1,
neighbour_c: neigh_c_l1,
current_ref_idx: part.ref_idx_l1.max(0) as i32,
shape: MvpredShape::Default,
},
neigh_d_l1,
);
(mvp_l0, mvp_l1)
}
_ => {
let mvp_l0 =
if part.is_skip && part.mode == PartMode::L0Only && part.w == 16 && part.h == 16 {
let (_, mv) =
derive_p_skip_mv_with_d(neigh_a_l0, neigh_b_l0, neigh_c_l0, neigh_d_l0);
mv
} else if part.mode != PartMode::L1Only && part.ref_idx_l0 >= 0 {
derive_mvpred_with_d(
&MvpredInputs {
neighbour_a: neigh_a_l0,
neighbour_b: neigh_b_l0,
neighbour_c: neigh_c_l0,
current_ref_idx: part.ref_idx_l0 as i32,
shape: part.shape,
},
neigh_d_l0,
)
} else {
Mv::ZERO
};
let mvp_l1 = if part.mode != PartMode::L0Only && part.ref_idx_l1 >= 0 {
derive_mvpred_with_d(
&MvpredInputs {
neighbour_a: neigh_a_l1,
neighbour_b: neigh_b_l1,
neighbour_c: neigh_c_l1,
current_ref_idx: part.ref_idx_l1 as i32,
shape: part.shape,
},
neigh_d_l1,
)
} else {
Mv::ZERO
};
(
Mv::new(mvp_l0.x + part.mvd_l0.0, mvp_l0.y + part.mvd_l0.1),
Mv::new(mvp_l1.x + part.mvd_l1.0, mvp_l1.y + part.mvd_l1.1),
)
}
};
(mv_l0, mv_l1)
}
#[inline]
fn blk4_raster_index(bx_b: u8, by_b: u8) -> u8 {
let q = 2 * (by_b / 2) + (bx_b / 2);
let lo = 2 * (by_b % 2) + (bx_b % 2);
4 * q + lo
}
fn neighbour_mvs_for_list(
part: &InterPartition,
mb_addr: u32,
grid: &MbGrid,
list: u8,
current_slice_id: i32,
) -> (NeighbourMv, NeighbourMv, NeighbourMv, NeighbourMv) {
let px = part.x as i32 / 4;
let py = part.y as i32 / 4;
let width = grid.width_in_mbs as i32;
let (mb_xx, mb_yy) = grid.mb_xy(mb_addr);
let mb_xx = mb_xx as i32;
let mb_yy = mb_yy as i32;
let a = neighbour_from_block(
grid,
mb_addr,
mb_xx,
mb_yy,
width,
4 * px - 1,
4 * py,
list,
current_slice_id,
);
let b = neighbour_from_block(
grid,
mb_addr,
mb_xx,
mb_yy,
width,
4 * px,
4 * py - 1,
list,
current_slice_id,
);
let c = neighbour_from_block(
grid,
mb_addr,
mb_xx,
mb_yy,
width,
4 * px + part.w as i32,
4 * py - 1,
list,
current_slice_id,
);
let d = neighbour_from_block(
grid,
mb_addr,
mb_xx,
mb_yy,
width,
4 * px - 1,
4 * py - 1,
list,
current_slice_id,
);
(a, b, c, d)
}
fn neighbour_from_block(
grid: &MbGrid,
curr_mb_addr: u32,
mb_xx: i32,
mb_yy: i32,
width: i32,
xn: i32,
yn: i32,
list: u8,
current_slice_id: i32,
) -> NeighbourMv {
let (addr, bxw, byw) = if grid.mbaff_frame_flag {
match mbaff_neigh_loc_luma(grid, curr_mb_addr, xn, yn) {
Some((addr, xw, yw)) => (addr, xw / 4, yw / 4),
None => return NeighbourMv::UNAVAILABLE,
}
} else {
let mut nx = mb_xx;
let mut ny = mb_yy;
let mut bxw = xn.div_euclid(4);
let mut byw = yn.div_euclid(4);
if bxw < 0 {
nx -= 1;
bxw += 4;
} else if bxw >= 4 {
nx += 1;
bxw -= 4;
}
if byw < 0 {
ny -= 1;
byw += 4;
} else if byw >= 4 {
ny += 1;
byw -= 4;
}
if nx < 0 || ny < 0 || nx >= width {
return NeighbourMv::UNAVAILABLE;
}
((ny as u32) * (width as u32) + (nx as u32), bxw, byw)
};
let Some(info) = grid.get(addr) else {
return NeighbourMv::UNAVAILABLE;
};
let is_same_mb = addr == curr_mb_addr;
if !is_same_mb && !info.available {
return NeighbourMv::UNAVAILABLE;
}
if !is_same_mb
&& current_slice_id >= 0
&& info.slice_id >= 0
&& info.slice_id != current_slice_id
{
return NeighbourMv::UNAVAILABLE;
}
if info.is_intra {
return NeighbourMv::intra_but_mb_available();
}
let blk4 = blk4_raster_index(bxw as u8, byw as u8) as usize;
let q_idx = blk4 / 4;
let (mv, ref_idx) = if list == 0 {
(info.mv_l0[blk4], info.ref_idx_l0[q_idx])
} else {
(info.mv_l1[blk4], info.ref_idx_l1[q_idx])
};
if ref_idx < 0 {
let other_ref_idx = if list == 0 {
info.ref_idx_l1[q_idx]
} else {
info.ref_idx_l0[q_idx]
};
if is_same_mb && other_ref_idx < 0 {
return NeighbourMv::partition_not_yet_decoded_same_mb();
}
return NeighbourMv::intra_but_mb_available();
}
let mut mv_y = mv.1 as i32;
let mut ref_idx = ref_idx as i32;
if grid.mbaff_frame_flag {
let curr_field = grid
.get(curr_mb_addr)
.map(|i| i.mb_field_decoding_flag)
.unwrap_or(false);
let n_field = info.mb_field_decoding_flag;
if curr_field && !n_field {
mv_y /= 2; ref_idx *= 2; } else if !curr_field && n_field {
mv_y *= 2; ref_idx /= 2; }
}
NeighbourMv {
available: true,
mb_available: true,
partition_available: true,
ref_idx,
mv: Mv::new(mv.0 as i32, mv_y),
}
}
fn derive_inter_partitions<R: RefPicProvider>(
mb: &Macroblock,
slice_header: &SliceHeader,
sps: &Sps,
ref_pics: &R,
pic: &Picture,
mb_addr: u32,
grid: &MbGrid,
current_slice_id: i32,
) -> Result<Vec<InterPartition>, ReconstructError> {
use MbType::*;
let pred = mb.mb_pred.as_ref();
match &mb.mb_type {
PSkip => {
Ok(vec![InterPartition {
x: 0,
y: 0,
w: 16,
h: 16,
mode: PartMode::L0Only,
shape: MvpredShape::Default,
ref_idx_l0: 0,
ref_idx_l1: -1,
mvd_l0: (0, 0),
mvd_l1: (0, 0),
is_skip: true,
precomputed_mv: None,
}])
}
PL016x16 => {
let p = pred.ok_or_else(|| {
ReconstructError::UnsupportedInterMbType("P_L0_16x16 without mb_pred".into())
})?;
let r0 = *p.ref_idx_l0.first().unwrap_or(&0) as i8;
let mvd = p.mvd_l0.first().copied().unwrap_or([0, 0]);
Ok(vec![InterPartition {
x: 0,
y: 0,
w: 16,
h: 16,
mode: PartMode::L0Only,
shape: MvpredShape::Default,
ref_idx_l0: r0,
ref_idx_l1: -1,
mvd_l0: (mvd[0], mvd[1]),
mvd_l1: (0, 0),
is_skip: false,
precomputed_mv: None,
}])
}
PL0L016x8 => {
let p = pred.ok_or_else(|| {
ReconstructError::UnsupportedInterMbType("P_L0_L0_16x8 without mb_pred".into())
})?;
let r0 = *p.ref_idx_l0.first().unwrap_or(&0) as i8;
let r1 = *p.ref_idx_l0.get(1).unwrap_or(&0) as i8;
let mvd0 = p.mvd_l0.first().copied().unwrap_or([0, 0]);
let mvd1 = p.mvd_l0.get(1).copied().unwrap_or([0, 0]);
Ok(vec![
InterPartition {
x: 0,
y: 0,
w: 16,
h: 8,
mode: PartMode::L0Only,
shape: MvpredShape::Partition16x8Top,
ref_idx_l0: r0,
ref_idx_l1: -1,
mvd_l0: (mvd0[0], mvd0[1]),
mvd_l1: (0, 0),
is_skip: false,
precomputed_mv: None,
},
InterPartition {
x: 0,
y: 8,
w: 16,
h: 8,
mode: PartMode::L0Only,
shape: MvpredShape::Partition16x8Bottom,
ref_idx_l0: r1,
ref_idx_l1: -1,
mvd_l0: (mvd1[0], mvd1[1]),
mvd_l1: (0, 0),
is_skip: false,
precomputed_mv: None,
},
])
}
PL0L08x16 => {
let p = pred.ok_or_else(|| {
ReconstructError::UnsupportedInterMbType("P_L0_L0_8x16 without mb_pred".into())
})?;
let r0 = *p.ref_idx_l0.first().unwrap_or(&0) as i8;
let r1 = *p.ref_idx_l0.get(1).unwrap_or(&0) as i8;
let mvd0 = p.mvd_l0.first().copied().unwrap_or([0, 0]);
let mvd1 = p.mvd_l0.get(1).copied().unwrap_or([0, 0]);
Ok(vec![
InterPartition {
x: 0,
y: 0,
w: 8,
h: 16,
mode: PartMode::L0Only,
shape: MvpredShape::Partition8x16Left,
ref_idx_l0: r0,
ref_idx_l1: -1,
mvd_l0: (mvd0[0], mvd0[1]),
mvd_l1: (0, 0),
is_skip: false,
precomputed_mv: None,
},
InterPartition {
x: 8,
y: 0,
w: 8,
h: 16,
mode: PartMode::L0Only,
shape: MvpredShape::Partition8x16Right,
ref_idx_l0: r1,
ref_idx_l1: -1,
mvd_l0: (mvd1[0], mvd1[1]),
mvd_l1: (0, 0),
is_skip: false,
precomputed_mv: None,
},
])
}
P8x8 | P8x8Ref0 => {
let sm = mb.sub_mb_pred.as_ref().ok_or_else(|| {
ReconstructError::UnsupportedInterMbType("P_8x8 without sub_mb_pred".into())
})?;
let force_ref0 = matches!(mb.mb_type, P8x8Ref0);
let mut parts = Vec::new();
for mb_part in 0..4usize {
let (part_x, part_y) = match mb_part {
0 => (0u8, 0u8),
1 => (8, 0),
2 => (0, 8),
_ => (8, 8),
};
let sub_type = sm.sub_mb_type[mb_part];
let r0 = if force_ref0 {
0i8
} else {
sm.ref_idx_l0[mb_part] as i8
};
let sub_parts = sub_mb_partitions(sub_type);
for (sub_idx, (sx, sy, sw, sh)) in sub_parts.iter().enumerate() {
let mvd = sm.mvd_l0[mb_part].get(sub_idx).copied().unwrap_or([0, 0]);
parts.push(InterPartition {
x: part_x + sx,
y: part_y + sy,
w: *sw,
h: *sh,
mode: match sub_type {
SubMbType::BDirect8x8 => PartMode::Direct,
_ => PartMode::L0Only,
},
shape: MvpredShape::Default,
ref_idx_l0: r0,
ref_idx_l1: -1,
mvd_l0: (mvd[0], mvd[1]),
mvd_l1: (0, 0),
is_skip: false,
precomputed_mv: None,
});
}
}
Ok(parts)
}
BSkip | BDirect16x16 => {
let is_skip = matches!(mb.mb_type, BSkip);
if !slice_header.direct_spatial_mv_pred_flag {
Ok(build_temporal_direct_partitions(
sps, ref_pics, pic, grid, mb_addr, is_skip,
))
} else {
Ok(build_spatial_direct_partitions(
sps,
ref_pics,
pic,
mb_addr,
grid,
current_slice_id,
0,
0,
16,
is_skip,
))
}
}
BL016x16 | BL116x16 | BBi16x16 => {
let p = pred.ok_or_else(|| {
ReconstructError::UnsupportedInterMbType("B_*_16x16 without mb_pred".into())
})?;
let (mode, r0, r1) = b_dir_refs_16x16(&mb.mb_type, p);
let mvd0 = p.mvd_l0.first().copied().unwrap_or([0, 0]);
let mvd1 = p.mvd_l1.first().copied().unwrap_or([0, 0]);
Ok(vec![InterPartition {
x: 0,
y: 0,
w: 16,
h: 16,
mode,
shape: MvpredShape::Default,
ref_idx_l0: r0,
ref_idx_l1: r1,
mvd_l0: (mvd0[0], mvd0[1]),
mvd_l1: (mvd1[0], mvd1[1]),
is_skip: false,
precomputed_mv: None,
}])
}
BL0L016x8 | BL1L116x8 | BL0L116x8 | BL1L016x8 | BL0Bi16x8 | BL1Bi16x8 | BBiL016x8
| BBiL116x8 | BBiBi16x8 => {
let p = pred.ok_or_else(|| {
ReconstructError::UnsupportedInterMbType("B_*_16x8 without mb_pred".into())
})?;
let (m0, m1) = b_dir_for_16x8(&mb.mb_type);
let (r0_top, r1_top) = ref_idx_for_part(p, 0, m0);
let (r0_bot, r1_bot) = ref_idx_for_part(p, 1, m1);
let mvd_l0_top = p.mvd_l0.first().copied().unwrap_or([0, 0]);
let mvd_l1_top = p.mvd_l1.first().copied().unwrap_or([0, 0]);
let mvd_l0_bot = p.mvd_l0.get(1).copied().unwrap_or([0, 0]);
let mvd_l1_bot = p.mvd_l1.get(1).copied().unwrap_or([0, 0]);
Ok(vec![
InterPartition {
x: 0,
y: 0,
w: 16,
h: 8,
mode: m0,
shape: MvpredShape::Partition16x8Top,
ref_idx_l0: r0_top,
ref_idx_l1: r1_top,
mvd_l0: (mvd_l0_top[0], mvd_l0_top[1]),
mvd_l1: (mvd_l1_top[0], mvd_l1_top[1]),
is_skip: false,
precomputed_mv: None,
},
InterPartition {
x: 0,
y: 8,
w: 16,
h: 8,
mode: m1,
shape: MvpredShape::Partition16x8Bottom,
ref_idx_l0: r0_bot,
ref_idx_l1: r1_bot,
mvd_l0: (mvd_l0_bot[0], mvd_l0_bot[1]),
mvd_l1: (mvd_l1_bot[0], mvd_l1_bot[1]),
is_skip: false,
precomputed_mv: None,
},
])
}
BL0L08x16 | BL1L18x16 | BL0L18x16 | BL1L08x16 | BL0Bi8x16 | BL1Bi8x16 | BBiL08x16
| BBiL18x16 | BBiBi8x16 => {
let p = pred.ok_or_else(|| {
ReconstructError::UnsupportedInterMbType("B_*_8x16 without mb_pred".into())
})?;
let (m0, m1) = b_dir_for_8x16(&mb.mb_type);
let (r0_l, r1_l) = ref_idx_for_part(p, 0, m0);
let (r0_r, r1_r) = ref_idx_for_part(p, 1, m1);
let mvd_l0_l = p.mvd_l0.first().copied().unwrap_or([0, 0]);
let mvd_l1_l = p.mvd_l1.first().copied().unwrap_or([0, 0]);
let mvd_l0_r = p.mvd_l0.get(1).copied().unwrap_or([0, 0]);
let mvd_l1_r = p.mvd_l1.get(1).copied().unwrap_or([0, 0]);
Ok(vec![
InterPartition {
x: 0,
y: 0,
w: 8,
h: 16,
mode: m0,
shape: MvpredShape::Partition8x16Left,
ref_idx_l0: r0_l,
ref_idx_l1: r1_l,
mvd_l0: (mvd_l0_l[0], mvd_l0_l[1]),
mvd_l1: (mvd_l1_l[0], mvd_l1_l[1]),
is_skip: false,
precomputed_mv: None,
},
InterPartition {
x: 8,
y: 0,
w: 8,
h: 16,
mode: m1,
shape: MvpredShape::Partition8x16Right,
ref_idx_l0: r0_r,
ref_idx_l1: r1_r,
mvd_l0: (mvd_l0_r[0], mvd_l0_r[1]),
mvd_l1: (mvd_l1_r[0], mvd_l1_r[1]),
is_skip: false,
precomputed_mv: None,
},
])
}
B8x8 => {
let sm = mb.sub_mb_pred.as_ref().ok_or_else(|| {
ReconstructError::UnsupportedInterMbType("B_8x8 without sub_mb_pred".into())
})?;
let mut parts = Vec::new();
for mb_part in 0..4usize {
let (part_x, part_y) = match mb_part {
0 => (0u8, 0u8),
1 => (8, 0),
2 => (0, 8),
_ => (8, 8),
};
let sub_type = sm.sub_mb_type[mb_part];
if matches!(sub_type, SubMbType::BDirect8x8) {
if !slice_header.direct_spatial_mv_pred_flag {
parts.extend(build_temporal_direct_sub_partitions(
sps, ref_pics, pic, grid, mb_addr, part_x, part_y,
));
} else {
parts.extend(build_spatial_direct_partitions(
sps,
ref_pics,
pic,
mb_addr,
grid,
current_slice_id,
part_x,
part_y,
8,
false,
));
}
continue;
}
let (mode, r0, r1) = b_sub_mode(
sub_type,
sm.ref_idx_l0[mb_part] as i8,
sm.ref_idx_l1[mb_part] as i8,
);
let sub_parts = sub_mb_partitions(sub_type);
for (sub_idx, (sx, sy, sw, sh)) in sub_parts.iter().enumerate() {
let mvd0 = sm.mvd_l0[mb_part].get(sub_idx).copied().unwrap_or([0, 0]);
let mvd1 = sm.mvd_l1[mb_part].get(sub_idx).copied().unwrap_or([0, 0]);
parts.push(InterPartition {
x: part_x + sx,
y: part_y + sy,
w: *sw,
h: *sh,
mode,
shape: MvpredShape::Default,
ref_idx_l0: r0,
ref_idx_l1: r1,
mvd_l0: (mvd0[0], mvd0[1]),
mvd_l1: (mvd1[0], mvd1[1]),
is_skip: false,
precomputed_mv: None,
});
}
}
Ok(parts)
}
other => Err(ReconstructError::UnsupportedInterMbType(format!(
"{:?}",
other
))),
}
}
fn sub_mb_partitions(t: SubMbType) -> Vec<(u8, u8, u8, u8)> {
use SubMbType::*;
match t {
PL08x8 | BDirect8x8 | BL08x8 | BL18x8 | BBi8x8 => vec![(0, 0, 8, 8)],
PL08x4 | BL08x4 | BL18x4 | BBi8x4 => vec![(0, 0, 8, 4), (0, 4, 8, 4)],
PL04x8 | BL04x8 | BL14x8 | BBi4x8 => vec![(0, 0, 4, 8), (4, 0, 4, 8)],
PL04x4 | BL04x4 | BL14x4 | BBi4x4 => {
vec![(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)]
}
Reserved(_) => vec![(0, 0, 8, 8)],
}
}
fn derive_temporal_direct_mvs_for_block<R: RefPicProvider>(
ref_pics: &R,
pic: &Picture,
grid: &MbGrid,
mb_addr: u32,
luma4x4_blk_idx: usize,
) -> Option<(i8, Mv, Mv)> {
use crate::picture::PicCodingStruct as Pcs;
#[derive(Clone, Copy, PartialEq, Eq)]
enum VertMvScale {
OneToOne,
FrmToFld,
FldToFrm,
}
let curr_struct = pic.coding_struct;
let w = grid.width_in_mbs;
if w == 0 {
return None;
}
let curr_mb_field = curr_struct == Pcs::Afrm
&& grid
.get(mb_addr)
.map(|i| i.mb_field_decoding_flag)
.unwrap_or(false);
let curr_mb_bottom_parity = (mb_addr % 2) as u8; let curr_field_parity = u8::from(pic.is_bottom_field);
let (x_col, y_col) = LUMA_4X4_XY[luma4x4_blk_idx & 15];
let (x_col, y_col) = (x_col as u32, y_col as u32);
let (col_pic, col_struct): (&Picture, Pcs) = if curr_struct == Pcs::Fld {
let c = ref_pics.ref_pic(1, 0)?;
if c.view_of_frame_parity.is_some() {
(c, c.coding_struct)
} else {
(c, Pcs::Fld)
}
} else if ref_pics.ref_pair_field(1, 0, false).is_some() {
let bottom = if curr_mb_field {
curr_mb_bottom_parity != 0
} else {
let (t_foc, b_foc) = ref_pics.ref_entry_unit_focs(1, 0)?;
let top_abs = (t_foc - pic.pic_order_cnt).abs();
let bottom_abs = (b_foc - pic.pic_order_cnt).abs();
top_abs >= bottom_abs
};
(ref_pics.ref_pair_field(1, 0, bottom)?, Pcs::Fld)
} else {
let c = ref_pics.ref_pic(1, 0)?;
(c, c.coding_struct)
};
let col_mb_field = |addr: u32| -> bool {
col_pic
.mb_field_flags
.get(addr as usize)
.copied()
.unwrap_or(false)
};
let curr = mb_addr;
let (mb_addr_col, y_m, vert): (u32, u32, VertMvScale) = match (curr_struct, col_struct) {
(Pcs::Fld, Pcs::Fld) => (curr, y_col, VertMvScale::OneToOne),
(Pcs::Fld, Pcs::Frm) => (
2 * w * (curr / w) + (curr % w) + w * (y_col / 8),
(2 * y_col) % 16,
VertMvScale::FrmToFld,
),
(Pcs::Fld, Pcs::Afrm) => {
if !col_mb_field(2 * curr) {
(
2 * curr + (y_col / 8),
(2 * y_col) % 16,
VertMvScale::FrmToFld,
)
} else {
(
2 * curr + u32::from(curr_field_parity),
y_col,
VertMvScale::OneToOne,
)
}
}
(Pcs::Frm, Pcs::Fld) => (
w * (curr / (2 * w)) + (curr % w),
8 * ((curr / w) % 2) + 4 * (y_col / 8),
VertMvScale::FldToFrm,
),
(Pcs::Frm, _) => (curr, y_col, VertMvScale::OneToOne),
(Pcs::Afrm, Pcs::Fld) => {
let a = curr / 2;
if !curr_mb_field {
(a, 8 * (curr % 2) + 4 * (y_col / 8), VertMvScale::FldToFrm)
} else {
(a, y_col, VertMvScale::OneToOne)
}
}
(Pcs::Afrm, _) => {
let col_field_x = col_mb_field(curr);
match (curr_mb_field, col_field_x) {
(false, false) | (true, true) => (curr, y_col, VertMvScale::OneToOne),
(false, true) => {
let (t_foc, b_foc) = ref_pics
.ref_entry_unit_focs(1, 0)
.unwrap_or((col_pic.top_field_order_cnt, col_pic.bottom_field_order_cnt));
let top_abs = (t_foc - pic.pic_order_cnt).abs();
let bottom_abs = (b_foc - pic.pic_order_cnt).abs();
(
2 * (curr / 2) + u32::from(top_abs >= bottom_abs),
8 * (curr % 2) + 4 * (y_col / 8),
VertMvScale::FldToFrm,
)
}
(true, false) => (
2 * (curr / 2) + (y_col / 8),
(2 * y_col) % 16,
VertMvScale::FrmToFld,
),
}
}
};
let col_blk4 =
(8 * (y_m / 8) + 4 * (x_col / 8) + 2 * ((y_m % 8) / 4) + ((x_col % 8) / 4)) as usize;
let l0 = col_pic.colocated_l0(mb_addr_col, col_blk4);
let l1 = col_pic.colocated_l1(mb_addr_col, col_blk4);
let is_intra =
l0.as_ref().map(|t| t.2).unwrap_or(false) || l1.as_ref().map(|t| t.2).unwrap_or(false);
let (mv_col_i16, ref_idx_col, used_l1) = if is_intra {
((0i16, 0i16), -1i8, false)
} else {
match (l0, l1) {
(Some((mv, rf, _)), _) if rf >= 0 => (mv, rf, false),
(_, Some((mv, rf, _))) if rf >= 0 => (mv, rf, true),
_ => ((0, 0), -1, false),
}
};
let mut mv_col = Mv::new(mv_col_i16.0 as i32, mv_col_i16.1 as i32);
match vert {
VertMvScale::FrmToFld => mv_col.y /= 2,
VertMvScale::FldToFrm => mv_col.y *= 2,
VertMvScale::OneToOne => {}
}
let curr_parities = ref_pics.ref_list_0_parities();
let curr_units = ref_pics.ref_list_0_unit_keys();
let curr_list0_lt = ref_pics.ref_list_0_longterm();
let curr_list0_pocs = ref_pics.ref_list_0_pocs();
let ref_pic_col: Option<(u32, Option<u8>)> = if ref_idx_col < 0 {
None
} else {
let i = ref_idx_col as usize;
let (keys, parities, units) = if used_l1 {
(
&col_pic.ref_list_1_keys,
&col_pic.ref_list_1_parities,
&col_pic.ref_list_1_unit_keys,
)
} else {
(
&col_pic.ref_list_0_keys,
&col_pic.ref_list_0_parities,
&col_pic.ref_list_0_unit_keys,
)
};
let col_mb_is_field = match col_struct {
Pcs::Fld => false, Pcs::Frm => false,
Pcs::Afrm => col_mb_field(mb_addr_col),
};
if col_struct == Pcs::Afrm && col_mb_is_field {
let unit = units.get(i >> 1).copied();
let col_parity = (mb_addr_col % 2) as u8;
let parity = if i % 2 == 0 {
col_parity
} else {
1 - col_parity
};
unit.map(|u| (u, Some(parity)))
} else {
match (keys.get(i), units.get(i)) {
(Some(_), Some(u)) => {
let parity = parities.get(i).copied().flatten();
Some((*u, parity))
}
_ => None,
}
}
};
let ref_idx_l0: i32 = match ref_pic_col {
None => 0,
Some((unit, parity)) => {
let found: Option<i32> = match vert {
VertMvScale::OneToOne => {
if curr_struct == Pcs::Fld {
(0..curr_units.len()).find_map(|k| {
(curr_units[k] == unit
&& curr_parities.get(k).copied().flatten() == parity)
.then_some(k as i32)
})
} else if curr_mb_field {
let p = parity.unwrap_or(curr_mb_bottom_parity);
(0..curr_units.len())
.find(|&k| curr_units[k] == unit)
.map(|k| ((k as i32) << 1) + i32::from(p != curr_mb_bottom_parity))
} else {
(0..curr_units.len())
.find(|&k| curr_units[k] == unit)
.map(|k| k as i32)
}
}
VertMvScale::FrmToFld => {
if curr_struct == Pcs::Fld {
(0..curr_units.len()).find_map(|k| {
(curr_units[k] == unit
&& curr_parities.get(k).copied().flatten()
== Some(curr_field_parity))
.then_some(k as i32)
})
} else {
(0..curr_units.len())
.find(|&k| curr_units[k] == unit)
.map(|k| (k as i32) << 1)
}
}
VertMvScale::FldToFrm => (0..curr_units.len())
.find(|&k| curr_units[k] == unit)
.map(|k| k as i32),
};
found.unwrap_or(0)
}
};
let ref_idx_l0 = if curr_units.is_empty() && ref_idx_col >= 0 && !curr_list0_pocs.is_empty() {
let col_list = if used_l1 {
&col_pic.ref_list_1_pocs
} else {
&col_pic.ref_list_0_pocs
};
match col_list.get(ref_idx_col as usize).copied() {
Some(ref_poc) => curr_list0_pocs
.iter()
.position(|&p| p == ref_poc)
.map(|k| k as i32)
.unwrap_or(0),
None => 0,
}
} else {
ref_idx_l0
};
let (curr_poc, poc_pic1, poc_pic0, pic0_long_term) = if curr_mb_field {
let curr_p = if curr_mb_bottom_parity == 0 {
pic.top_field_order_cnt
} else {
pic.bottom_field_order_cnt
};
let (t1, b1) = ref_pics.ref_entry_unit_focs(1, 0)?;
let poc1 = if curr_mb_bottom_parity == 0 { t1 } else { b1 };
let unit0 = (ref_idx_l0 >> 1) as u32;
let (t0, b0) = ref_pics.ref_entry_unit_focs(0, unit0)?;
let same_parity = ref_idx_l0 % 2 == 0;
let pic0_parity_bottom = if same_parity {
curr_mb_bottom_parity != 0
} else {
curr_mb_bottom_parity == 0
};
let poc0 = if pic0_parity_bottom { b0 } else { t0 };
let lt = curr_list0_lt.get(unit0 as usize).copied().unwrap_or(false);
(curr_p, poc1, poc0, lt)
} else {
let curr_p = pic.pic_order_cnt;
let poc1 = ref_pics.ref_pic_poc(1, 0).unwrap_or(col_pic.pic_order_cnt);
let poc0 = ref_pics.ref_pic_poc(0, ref_idx_l0 as u32).unwrap_or(curr_p);
let lt = curr_list0_lt
.get(ref_idx_l0 as usize)
.copied()
.unwrap_or(false);
(curr_p, poc1, poc0, lt)
};
if pic0_long_term || poc_pic1 == poc_pic0 {
return Some((ref_idx_l0 as i8, mv_col, Mv::ZERO));
}
let tb = clip3_i32(-128, 127, curr_poc - poc_pic0);
let td = clip3_i32(-128, 127, poc_pic1 - poc_pic0);
if td == 0 {
return Some((ref_idx_l0 as i8, mv_col, Mv::ZERO));
}
let tx = (16384 + (td / 2).abs()) / td;
let dsf = clip3_i32(-1024, 1023, (tb * tx + 32) >> 6);
let mvx_l0 = ((dsf * mv_col.x) + 128) >> 8;
let mvy_l0 = ((dsf * mv_col.y) + 128) >> 8;
let mv_l0 = Mv::new(mvx_l0, mvy_l0);
let mv_l1 = Mv::new(mv_l0.x - mv_col.x, mv_l0.y - mv_col.y);
Some((ref_idx_l0 as i8, mv_l0, mv_l1))
}
fn build_temporal_direct_partitions<R: RefPicProvider>(
sps: &Sps,
ref_pics: &R,
pic: &Picture,
grid: &MbGrid,
mb_addr: u32,
is_skip: bool,
) -> Vec<InterPartition> {
let use_8x8 = sps.direct_8x8_inference_flag;
let block_size: u8 = if use_8x8 { 8 } else { 4 };
let step = block_size as usize;
let n_per_row = 16usize / step;
let mut partitions = Vec::with_capacity(n_per_row * n_per_row);
for by in 0..n_per_row {
for bx in 0..n_per_row {
let x = (bx * step) as u8;
let y = (by * step) as u8;
let col_blk4 = if use_8x8 {
let mb_part_idx = 2 * by + bx;
5 * mb_part_idx
} else {
blk4_raster_index(bx as u8, by as u8) as usize
};
let (ref_idx_l0, mv_l0, mv_l1) =
derive_temporal_direct_mvs_for_block(ref_pics, pic, grid, mb_addr, col_blk4)
.unwrap_or((0, Mv::ZERO, Mv::ZERO));
partitions.push(InterPartition {
x,
y,
w: block_size,
h: block_size,
mode: PartMode::Direct,
shape: MvpredShape::Default,
ref_idx_l0,
ref_idx_l1: 0,
mvd_l0: (0, 0),
mvd_l1: (0, 0),
is_skip,
precomputed_mv: Some((mv_l0, mv_l1)),
});
}
}
partitions
}
fn build_temporal_direct_sub_partitions<R: RefPicProvider>(
sps: &Sps,
ref_pics: &R,
pic: &Picture,
grid: &MbGrid,
mb_addr: u32,
part_x: u8,
part_y: u8,
) -> Vec<InterPartition> {
let use_8x8 = sps.direct_8x8_inference_flag;
let block_size: u8 = if use_8x8 { 8 } else { 4 };
let step = block_size as usize;
let n_per_row = 8usize / step;
let mb_part_idx = 2 * ((part_y / 8) as usize) + ((part_x / 8) as usize);
let mut partitions = Vec::with_capacity(n_per_row * n_per_row);
for sy in 0..n_per_row {
for sx in 0..n_per_row {
let x = part_x + (sx * step) as u8;
let y = part_y + (sy * step) as u8;
let col_blk4 = if use_8x8 {
5 * mb_part_idx
} else {
let sub_mb_part_idx = 2 * sy + sx;
4 * mb_part_idx + sub_mb_part_idx
};
let (ref_idx_l0, mv_l0, mv_l1) =
derive_temporal_direct_mvs_for_block(ref_pics, pic, grid, mb_addr, col_blk4)
.unwrap_or((0, Mv::ZERO, Mv::ZERO));
partitions.push(InterPartition {
x,
y,
w: block_size,
h: block_size,
mode: PartMode::Direct,
shape: MvpredShape::Default,
ref_idx_l0,
ref_idx_l1: 0,
mvd_l0: (0, 0),
mvd_l1: (0, 0),
is_skip: false,
precomputed_mv: Some((mv_l0, mv_l1)),
});
}
}
partitions
}
fn build_spatial_direct_partitions<R: RefPicProvider>(
sps: &Sps,
ref_pics: &R,
_pic: &Picture,
mb_addr: u32,
grid: &MbGrid,
current_slice_id: i32,
region_x: u8,
region_y: u8,
region_size: u8,
is_skip: bool,
) -> Vec<InterPartition> {
let neighbour_probe = InterPartition {
x: 0,
y: 0,
w: 16,
h: 16,
mode: PartMode::Direct,
shape: MvpredShape::Default,
ref_idx_l0: 0,
ref_idx_l1: 0,
mvd_l0: (0, 0),
mvd_l1: (0, 0),
is_skip,
precomputed_mv: None,
};
let (a_l0, b_l0, c_l0, d_l0) =
neighbour_mvs_for_list(&neighbour_probe, mb_addr, grid, 0, current_slice_id);
let (a_l1, b_l1, c_l1, d_l1) =
neighbour_mvs_for_list(&neighbour_probe, mb_addr, grid, 1, current_slice_id);
fn min_positive(x: i32, y: i32) -> i32 {
if x >= 0 && y >= 0 {
x.min(y)
} else {
x.max(y)
}
}
let c_l0_eff = if !c_l0.partition_available && d_l0.partition_available {
d_l0
} else {
c_l0
};
let c_l1_eff = if !c_l1.partition_available && d_l1.partition_available {
d_l1
} else {
c_l1
};
let ref_l0_minp = min_positive(a_l0.ref_idx, min_positive(b_l0.ref_idx, c_l0_eff.ref_idx));
let ref_l1_minp = min_positive(a_l1.ref_idx, min_positive(b_l1.ref_idx, c_l1_eff.ref_idx));
let direct_zero_both = ref_l0_minp < 0 && ref_l1_minp < 0;
let ref_idx_l0_final: i8 = if direct_zero_both {
0
} else if ref_l0_minp < 0 {
-1
} else {
ref_l0_minp as i8
};
let ref_idx_l1_final: i8 = if direct_zero_both {
0
} else if ref_l1_minp < 0 {
-1
} else {
ref_l1_minp as i8
};
let _ = derive_b_spatial_direct_with_d(a_l0, a_l1, b_l0, b_l1, c_l0, c_l1, d_l0, d_l1);
let col_pic_is_short_term = !ref_pics
.ref_list_1_longterm()
.first()
.copied()
.unwrap_or(false);
let col_pic = ref_pics.ref_pic(1, 0);
let use_8x8 = sps.direct_8x8_inference_flag;
let block_size: u8 = if use_8x8 { 8 } else { 4 };
let step = block_size.min(region_size) as usize;
let block_size = step as u8;
let n_per_row = (region_size as usize) / step;
let mut partitions = Vec::with_capacity(n_per_row * n_per_row);
for by in 0..n_per_row {
for bx in 0..n_per_row {
let x = region_x + (bx * step) as u8;
let y = region_y + (by * step) as u8;
let bx4 = x / 4;
let by4 = y / 4;
let col_blk4: usize = if use_8x8 {
let mb_part_idx = 2 * ((y / 8) as usize) + ((x / 8) as usize);
5 * mb_part_idx
} else {
blk4_raster_index(bx4, by4) as usize
};
let col_zero_flag = col_pic_is_short_term
&& col_pic.is_some_and(|cp| is_colocated_zero_mv(cp, mb_addr, col_blk4));
let mv_l0 = if direct_zero_both
|| ref_idx_l0_final < 0
|| (ref_idx_l0_final == 0 && col_zero_flag)
{
Mv::ZERO
} else {
derive_median_mvpred(a_l0, b_l0, c_l0_eff, ref_idx_l0_final as i32)
};
let mv_l1 = if direct_zero_both
|| ref_idx_l1_final < 0
|| (ref_idx_l1_final == 0 && col_zero_flag)
{
Mv::ZERO
} else {
derive_median_mvpred(a_l1, b_l1, c_l1_eff, ref_idx_l1_final as i32)
};
let mode = match (ref_idx_l0_final >= 0, ref_idx_l1_final >= 0) {
(true, true) => PartMode::Direct,
(true, false) => PartMode::L0Only,
(false, true) => PartMode::L1Only,
(false, false) => PartMode::Direct, };
partitions.push(InterPartition {
x,
y,
w: block_size,
h: block_size,
mode,
shape: MvpredShape::Default,
ref_idx_l0: ref_idx_l0_final,
ref_idx_l1: ref_idx_l1_final,
mvd_l0: (0, 0),
mvd_l1: (0, 0),
is_skip,
precomputed_mv: Some((mv_l0, mv_l1)),
});
}
}
partitions
}
fn is_colocated_zero_mv(col_pic: &Picture, mb_addr: u32, col_blk4: usize) -> bool {
let l0 = col_pic.colocated_l0(mb_addr, col_blk4);
let l1 = col_pic.colocated_l1(mb_addr, col_blk4);
let is_intra =
l0.as_ref().map(|t| t.2).unwrap_or(false) || l1.as_ref().map(|t| t.2).unwrap_or(false);
if is_intra {
return false;
}
let (mv, ref_idx) = match (l0, l1) {
(Some((mv, rf, _)), _) if rf >= 0 => (mv, rf),
(_, Some((mv, rf, _))) if rf >= 0 => (mv, rf),
_ => return false,
};
ref_idx == 0 && mv.0.abs() <= 1 && mv.1.abs() <= 1
}
fn b_dir_refs_16x16(ty: &MbType, p: &crate::macroblock_layer::MbPred) -> (PartMode, i8, i8) {
match ty {
MbType::BL016x16 => (
PartMode::L0Only,
*p.ref_idx_l0.first().unwrap_or(&0) as i8,
-1,
),
MbType::BL116x16 => (
PartMode::L1Only,
-1,
*p.ref_idx_l1.first().unwrap_or(&0) as i8,
),
MbType::BBi16x16 => (
PartMode::BiPred,
*p.ref_idx_l0.first().unwrap_or(&0) as i8,
*p.ref_idx_l1.first().unwrap_or(&0) as i8,
),
_ => (PartMode::L0Only, 0, -1),
}
}
fn b_dir_for_16x8(ty: &MbType) -> (PartMode, PartMode) {
use MbType::*;
match ty {
BL0L016x8 => (PartMode::L0Only, PartMode::L0Only),
BL1L116x8 => (PartMode::L1Only, PartMode::L1Only),
BL0L116x8 => (PartMode::L0Only, PartMode::L1Only),
BL1L016x8 => (PartMode::L1Only, PartMode::L0Only),
BL0Bi16x8 => (PartMode::L0Only, PartMode::BiPred),
BL1Bi16x8 => (PartMode::L1Only, PartMode::BiPred),
BBiL016x8 => (PartMode::BiPred, PartMode::L0Only),
BBiL116x8 => (PartMode::BiPred, PartMode::L1Only),
BBiBi16x8 => (PartMode::BiPred, PartMode::BiPred),
_ => (PartMode::L0Only, PartMode::L0Only),
}
}
fn b_dir_for_8x16(ty: &MbType) -> (PartMode, PartMode) {
use MbType::*;
match ty {
BL0L08x16 => (PartMode::L0Only, PartMode::L0Only),
BL1L18x16 => (PartMode::L1Only, PartMode::L1Only),
BL0L18x16 => (PartMode::L0Only, PartMode::L1Only),
BL1L08x16 => (PartMode::L1Only, PartMode::L0Only),
BL0Bi8x16 => (PartMode::L0Only, PartMode::BiPred),
BL1Bi8x16 => (PartMode::L1Only, PartMode::BiPred),
BBiL08x16 => (PartMode::BiPred, PartMode::L0Only),
BBiL18x16 => (PartMode::BiPred, PartMode::L1Only),
BBiBi8x16 => (PartMode::BiPred, PartMode::BiPred),
_ => (PartMode::L0Only, PartMode::L0Only),
}
}
fn ref_idx_for_part(p: &crate::macroblock_layer::MbPred, idx: usize, mode: PartMode) -> (i8, i8) {
let r0 = p.ref_idx_l0.get(idx).copied().unwrap_or(0) as i8;
let r1 = p.ref_idx_l1.get(idx).copied().unwrap_or(0) as i8;
match mode {
PartMode::L0Only => (r0, -1),
PartMode::L1Only => (-1, r1),
PartMode::BiPred => (r0, r1),
PartMode::Direct => (r0, r1),
}
}
fn b_sub_mode(t: SubMbType, r0: i8, r1: i8) -> (PartMode, i8, i8) {
use SubMbType::*;
match t {
BDirect8x8 => (PartMode::Direct, 0, 0),
BL08x8 | BL08x4 | BL04x8 | BL04x4 => (PartMode::L0Only, r0, -1),
BL18x8 | BL18x4 | BL14x8 | BL14x4 => (PartMode::L1Only, -1, r1),
BBi8x8 | BBi8x4 | BBi4x8 | BBi4x4 => (PartMode::BiPred, r0, r1),
_ => (PartMode::L0Only, r0, -1),
}
}
#[allow(clippy::too_many_arguments)]
fn sp_reconstruct_inter_residual(
mb: &Macroblock,
ctx: SpSiCtx,
qp_y: i32,
bit_depth_y: u32,
bit_depth_c: u32,
sps: &Sps,
pps: &Pps,
pred_luma: &[i32; 256],
pred_cb: &[i32],
pred_cr: &[i32],
writer: &MbWriter,
pic: &mut Picture,
field_scan: bool,
) -> Result<(), ReconstructError> {
if mb.transform_size_8x8_flag {
return Err(ReconstructError::SpSiUnsupported(
"SP inter macroblock with transform_size_8x8_flag".into(),
));
}
let cbp_luma = (mb.coded_block_pattern & 0x0F) as u8;
let cbp_chroma = ((mb.coded_block_pattern >> 4) & 0x03) as u8;
let sl4 = select_scaling_list_4x4(3, sps, pps);
let qs_y = ctx.qs_y;
#[allow(clippy::needless_range_loop)] for blk4 in 0..16usize {
let (bx, by) = LUMA_4X4_XY[blk4];
let blk8 = blk4 / 4;
let coeffs_scan = if (cbp_luma >> blk8) & 1 == 1 {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let compact_idx = set_before * 4 + (blk4 % 4);
mb.residual_luma
.get(compact_idx)
.copied()
.unwrap_or([0i32; 16])
} else {
[0i32; 16]
};
let cr = inv_scan_4x4(&coeffs_scan, field_scan);
let mut p = [0i32; 16];
for yy in 0..4 {
for xx in 0..4 {
p[yy * 4 + xx] = pred_luma[(by as usize + yy) * 16 + (bx as usize + xx)];
}
}
let c = if ctx.switching {
sp_luma_switching(&p, &cr, qs_y)
} else {
sp_luma_non_switching(&p, &cr, qp_y, qs_y, &sl4)
};
let r = inverse_transform_4x4(&c, qs_y, &sl4, bit_depth_y)?;
for yy in 0..4 {
for xx in 0..4 {
writer.set_luma(
pic,
bx + xx as i32,
by + yy as i32,
clip_sample(r[yy * 4 + xx], bit_depth_y),
);
}
}
}
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let sl4_cb = select_scaling_list_4x4(4, sps, pps);
let sl4_cr = select_scaling_list_4x4(5, sps, pps);
for plane in 0..2u8 {
let offset = if plane == 0 { cb_offset } else { cr_offset };
let qp_c = qp_y_to_qp_c_with_bd_offset(qp_y, offset, 0);
let qs_c = qp_y_to_qp_c_with_bd_offset(qs_y, offset, 0);
let sl = if plane == 0 { &sl4_cb } else { &sl4_cr };
let pred = if plane == 0 { pred_cb } else { pred_cr };
sp_reconstruct_chroma_plane(
mb,
plane,
cbp_chroma,
pred,
ctx.switching,
qp_c,
qs_c,
sl,
field_scan,
writer,
pic,
bit_depth_c,
)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_inter_chroma_residual(
mb: &Macroblock,
qp_y: i32,
chroma_array_type: u32,
bit_depth_c: u32,
mb_px: i32,
mb_py: i32,
writer: &MbWriter,
sps: &Sps,
pps: &Pps,
cbp_chroma: u8,
pred_cb: &[i32],
pred_cr: &[i32],
pic: &mut Picture,
field_scan: bool,
) -> Result<(), ReconstructError> {
let (mbw_c, _mbh_c) = chroma_mb_dims(chroma_array_type);
let c_mb_px = writer.chroma_mb_px();
let c_mb_py = writer.chroma_mb_py();
let _ = mb_px;
let _ = mb_py;
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let qp_bd_offset_c = qp_bd_offset(sps.bit_depth_chroma_minus8);
let qp_cb = qp_y_to_qp_c_with_bd_offset(qp_y, cb_offset, qp_bd_offset_c) + qp_bd_offset_c;
let qp_cr = qp_y_to_qp_c_with_bd_offset(qp_y, cr_offset, qp_bd_offset_c) + qp_bd_offset_c;
let qp_prime_y = qp_y + qp_bd_offset(sps.bit_depth_luma_minus8);
let bypass = transform_bypass_active(sps, qp_prime_y);
let sl4_cb = select_scaling_list_4x4(4, sps, pps);
let sl4_cr = select_scaling_list_4x4(5, sps, pps);
let num_c8x8 = if chroma_array_type == 1 { 1 } else { 2 };
let n_ac = 4 * num_c8x8 as usize;
for plane in 0..2u8 {
let qp_c = if plane == 0 { qp_cb } else { qp_cr };
let sl4 = if plane == 0 { &sl4_cb } else { &sl4_cr };
let dc_block = if plane == 0 {
&mb.residual_chroma_dc_cb
} else {
&mb.residual_chroma_dc_cr
};
let dc_flat: [i32; 8] = {
let mut a = [0i32; 8];
for (i, v) in dc_block.iter().enumerate().take(a.len()) {
a[i] = *v;
}
a
};
let (dc4, dc8): (Option<[i32; 4]>, Option<[i32; 8]>) = if cbp_chroma > 0 {
if chroma_array_type == 1 {
let dc4: [i32; 4] = [dc_flat[0], dc_flat[1], dc_flat[2], dc_flat[3]];
if bypass {
(Some(dc4), None)
} else {
let out = inverse_hadamard_chroma_dc_420(&dc4, qp_c, sl4, bit_depth_c)?;
(Some(out), None)
}
} else if bypass {
let out = [
dc_flat[0], dc_flat[2], dc_flat[1], dc_flat[5], dc_flat[3], dc_flat[6], dc_flat[4], dc_flat[7], ];
(None, Some(out))
} else {
let out = inverse_hadamard_chroma_dc_422(&dc_flat, qp_c, sl4, bit_depth_c)?;
(None, Some(out))
}
} else {
(Some([0i32; 4]), Some([0i32; 8]))
};
let ac_blocks = if plane == 0 {
&mb.residual_chroma_ac_cb
} else {
&mb.residual_chroma_ac_cr
};
for blk in 0..n_ac {
let dc_c = if chroma_array_type == 1 {
dc4.unwrap()[blk]
} else {
dc8.unwrap()[blk]
};
let ac_scan = if cbp_chroma == 2 {
ac_blocks.get(blk).copied().unwrap_or([0i32; 16])
} else {
[0i32; 16]
};
let mut coeffs = inv_scan_4x4_ac(&ac_scan, field_scan);
coeffs[0] = dc_c;
let residual = if bypass {
coeffs
} else {
inverse_transform_4x4_dc_preserved(&coeffs, qp_c, sl4, bit_depth_c)?
};
let (bx, by) = chroma_block_xy(chroma_array_type, blk);
for yy in 0..4 {
for xx in 0..4 {
let pidx = ((by as usize + yy) * (mbw_c as usize)) + (bx as usize + xx);
let pred_v = if plane == 0 {
pred_cb[pidx]
} else {
pred_cr[pidx]
};
let v = clip_sample(pred_v + residual[yy * 4 + xx], bit_depth_c);
if plane == 0 {
writer.set_cb(pic, bx + xx as i32, by + yy as i32, v);
} else {
writer.set_cr(pic, bx + xx as i32, by + yy as i32, v);
}
}
}
}
}
let _ = c_mb_px;
let _ = c_mb_py;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_inter_chroma_residual_444(
mb: &Macroblock,
qp_y: i32,
bit_depth_c: u32,
writer: &MbWriter,
sps: &Sps,
pps: &Pps,
cbp_luma: u8,
pred_cb: &[i32],
pred_cr: &[i32],
pic: &mut Picture,
field_scan: bool,
) -> Result<(), ReconstructError> {
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let qp_bd_offset_c = qp_bd_offset(sps.bit_depth_chroma_minus8);
let qp_cb = qp_y_to_qp_c_with_bd_offset(qp_y, cb_offset, qp_bd_offset_c) + qp_bd_offset_c;
let qp_cr = qp_y_to_qp_c_with_bd_offset(qp_y, cr_offset, qp_bd_offset_c) + qp_bd_offset_c;
let sl4_cb = select_scaling_list_4x4(4, sps, pps);
let sl4_cr = select_scaling_list_4x4(5, sps, pps);
let sl8_cb = select_scaling_list_8x8(3, sps, pps);
let sl8_cr = select_scaling_list_8x8(5, sps, pps);
let qp_prime_y = qp_y + qp_bd_offset(sps.bit_depth_luma_minus8);
let bypass = transform_bypass_active(sps, qp_prime_y);
for plane in 0..2u8 {
let qp_c = if plane == 0 { qp_cb } else { qp_cr };
let ac_blocks = if plane == 0 {
&mb.residual_cb_luma_like
} else {
&mb.residual_cr_luma_like
};
if mb.transform_size_8x8_flag {
let sl8 = if plane == 0 { &sl8_cb } else { &sl8_cr };
#[allow(clippy::needless_range_loop)] for blk8 in 0..4usize {
let (bx, by) = LUMA_8X8_XY[blk8];
let coeffs_flat: [i32; 64] = if (cbp_luma >> blk8) & 1 == 1 {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let base_slot = set_before * 4;
let mut scan = [0i32; 64];
for sub in 0..4usize {
if let Some(coefs) = ac_blocks.get(base_slot + sub) {
for (i, c) in coefs.iter().enumerate().take(16) {
scan[sub * 16 + i] = *c;
}
}
}
inv_scan_8x8(&scan, field_scan)
} else {
[0i32; 64]
};
let residual = if bypass {
coeffs_flat
} else {
inverse_transform_8x8(&coeffs_flat, qp_c, sl8, bit_depth_c)?
};
for y in 0..8i32 {
for x in 0..8i32 {
let pidx = ((by + y) as usize) * 16 + (bx + x) as usize;
let pred_v = if plane == 0 {
pred_cb[pidx]
} else {
pred_cr[pidx]
};
let v = clip_sample(pred_v + residual[(y * 8 + x) as usize], bit_depth_c);
if plane == 0 {
writer.set_cb(pic, bx + x, by + y, v);
} else {
writer.set_cr(pic, bx + x, by + y, v);
}
}
}
}
} else {
let sl4 = if plane == 0 { &sl4_cb } else { &sl4_cr };
#[allow(clippy::needless_range_loop)] for block_idx in 0..16usize {
let (bx, by) = LUMA_4X4_XY[block_idx];
let blk8 = block_idx / 4;
let coeffs_scan = if (cbp_luma >> blk8) & 1 == 1 {
let set_before = (cbp_luma & ((1u8 << blk8) - 1)).count_ones() as usize;
let compact_idx = set_before * 4 + (block_idx % 4);
ac_blocks.get(compact_idx).copied().unwrap_or([0i32; 16])
} else {
[0i32; 16]
};
let coeffs = inv_scan_4x4(&coeffs_scan, field_scan);
let residual = if bypass {
coeffs
} else {
inverse_transform_4x4(&coeffs, qp_c, sl4, bit_depth_c)?
};
for yy in 0..4i32 {
for xx in 0..4i32 {
let pidx = ((by + yy) as usize) * 16 + (bx + xx) as usize;
let pred_v = if plane == 0 {
pred_cb[pidx]
} else {
pred_cr[pidx]
};
let v = clip_sample(pred_v + residual[(yy * 4 + xx) as usize], bit_depth_c);
if plane == 0 {
writer.set_cb(pic, bx + xx, by + yy, v);
} else {
writer.set_cr(pic, bx + xx, by + yy, v);
}
}
}
}
}
}
Ok(())
}
#[allow(dead_code)]
fn _mb_info_marker(_: MbInfo) {}
fn compute_luma_nonzero_mask(mb: &Macroblock) -> u16 {
if mb.mb_type.is_i_pcm() {
return 0xFFFF;
}
let cbp_luma = (mb.coded_block_pattern & 0x0F) as u8;
if let MbType::Intra16x16(cfg) = &mb.mb_type {
let mut mask = 0u16;
if cfg.cbp_luma == 15 {
for (blk_idx, block) in mb.residual_luma.iter().enumerate().take(16) {
let any_nz = block.iter().take(15).any(|c| *c != 0);
if any_nz {
mask |= 1 << blk_idx;
}
}
}
return mask;
}
if mb.transform_size_8x8_flag {
let mut mask = 0u16;
for quad in 0..4usize {
if (cbp_luma >> quad) & 1 == 0 {
continue;
}
let set_before = (cbp_luma & ((1u8 << quad) - 1)).count_ones() as usize;
let base_slot = set_before * 4;
let mut any_nz = false;
for sub in 0..4usize {
let slot = base_slot + sub;
if let Some(c) = mb.residual_luma.get(slot) {
if c.iter().any(|v| *v != 0) {
any_nz = true;
break;
}
}
}
if any_nz {
for z in (quad * 4)..(quad * 4 + 4) {
mask |= 1 << z;
}
}
}
return mask;
}
let mut mask = 0u16;
for quad in 0..4usize {
if (cbp_luma >> quad) & 1 == 0 {
continue;
}
let set_before = (cbp_luma & ((1u8 << quad) - 1)).count_ones() as usize;
let base_slot = set_before * 4;
for sub in 0..4usize {
let slot = base_slot + sub;
if let Some(c) = mb.residual_luma.get(slot) {
if c.iter().any(|v| *v != 0) {
let z = quad * 4 + sub;
mask |= 1 << z;
}
}
}
}
mask
}
fn compute_chroma_nonzero_mask(mb: &Macroblock, chroma_array_type: u32) -> u16 {
if mb.mb_type.is_i_pcm() {
return 0xFFFF;
}
if chroma_array_type != 1 && chroma_array_type != 2 {
return 0;
}
let cbp_chroma = ((mb.coded_block_pattern >> 4) & 0x03) as u8;
if cbp_chroma < 2 {
return 0;
}
let num_chroma_blocks = if chroma_array_type == 1 { 4 } else { 8 };
let mut mask = 0u16;
for (plane, ac) in [&mb.residual_chroma_ac_cb, &mb.residual_chroma_ac_cr]
.iter()
.enumerate()
{
let shift = if plane == 0 { 0 } else { 8 };
for (blk, coeffs) in ac.iter().enumerate().take(num_chroma_blocks) {
let any_nz = coeffs.iter().any(|v| *v != 0);
if any_nz {
mask |= 1u16 << (shift + blk as u32);
}
}
}
mask
}
#[allow(clippy::too_many_arguments)]
fn deblock_picture(
pic: &mut Picture,
grid: &MbGrid,
alpha_off: i32,
beta_off: i32,
bit_depth_y: u32,
bit_depth_c: u32,
pps: &Pps,
mbaff_frame_flag: bool,
field_pic: bool,
mb_field_flags: &[bool],
) {
if mbaff_frame_flag {
deblock_mbaff_frame(
pic,
grid,
alpha_off,
beta_off,
bit_depth_y,
bit_depth_c,
pps,
mb_field_flags,
);
if pic.chroma_array_type == 3 {
deblock_plane_chroma_444(
pic,
grid,
alpha_off,
beta_off,
bit_depth_c,
pps,
mbaff_frame_flag,
field_pic,
mb_field_flags,
);
}
return;
}
deblock_plane_luma(
pic,
grid,
alpha_off,
beta_off,
bit_depth_y,
mbaff_frame_flag,
field_pic,
mb_field_flags,
);
if pic.chroma_array_type == 1 || pic.chroma_array_type == 2 {
deblock_plane_chroma(
pic,
grid,
alpha_off,
beta_off,
bit_depth_c,
pps,
mbaff_frame_flag,
field_pic,
mb_field_flags,
);
} else if pic.chroma_array_type == 3 {
deblock_plane_chroma_444(
pic,
grid,
alpha_off,
beta_off,
bit_depth_c,
pps,
mbaff_frame_flag,
field_pic,
mb_field_flags,
);
}
let _ = pps;
}
fn deblock_scan_pos(flat: i32, mb_w: i32, mbaff_frame_flag: bool) -> (i32, i32) {
if !mbaff_frame_flag {
(flat % mb_w, flat / mb_w)
} else {
let pair_row = flat / (2 * mb_w);
let rem = flat % (2 * mb_w);
(rem / 2, pair_row * 2 + rem % 2)
}
}
fn pixel_to_mb_addr(
grid: &MbGrid,
x: i32,
y: i32,
mbaff_frame_flag: bool,
mb_field_flags: &[bool],
) -> Option<u32> {
if x < 0 || y < 0 {
return None;
}
let w = grid.width_in_mbs as i32;
let mb_x = x / 16;
if mb_x >= w {
return None;
}
if !mbaff_frame_flag {
let mb_y = y / 16;
if mb_y >= grid.height_in_mbs as i32 {
return None;
}
return Some((mb_y as u32) * grid.width_in_mbs + mb_x as u32);
}
let pair_row = y / 32;
if pair_row * 2 >= grid.height_in_mbs as i32 {
return None;
}
let pair_top_addr = 2 * (pair_row as u32 * grid.width_in_mbs + mb_x as u32);
let bot_addr = pair_top_addr + 1; let pair_field = mb_field_flags
.get(pair_top_addr as usize)
.copied()
.unwrap_or(false);
let rel_y = y - pair_row * 32; let is_bot = if pair_field {
(rel_y & 1) == 1
} else {
rel_y >= 16
};
if is_bot {
Some(bot_addr)
} else {
Some(pair_top_addr)
}
}
fn bs_pair_flags(
p_addr: u32,
q_addr: u32,
mbaff_frame_flag: bool,
field_pic: bool,
mb_field_flags: &[bool],
) -> (bool, bool, bool) {
if !mbaff_frame_flag {
return (field_pic, !field_pic, false);
}
let p_field = mb_field_flags
.get(p_addr as usize)
.copied()
.unwrap_or(false);
let q_field = mb_field_flags
.get(q_addr as usize)
.copied()
.unwrap_or(false);
let mixed = (p_addr / 2 != q_addr / 2) && (p_field != q_field);
(true, !p_field && !q_field, mixed)
}
fn in_mb_luma_coords(
grid: &MbGrid,
addr: u32,
mb_field_flags: &[bool],
x: i32,
y: i32,
) -> (u32, u32) {
let field = mb_field_flags.get(addr as usize).copied().unwrap_or(false);
let (xo, yo) = mb_sample_origin(grid, addr, true, field);
let dy = if field { 2 } else { 1 };
(
(x - xo).clamp(0, 15) as u32,
((y - yo) / dy).clamp(0, 15) as u32,
)
}
fn chroma_pixel_to_mb_addr(
grid: &MbGrid,
cx: i32,
cy: i32,
sub_w: i32,
sub_h: i32,
mb_field_flags: &[bool],
) -> Option<u32> {
if cx < 0 || cy < 0 {
return None;
}
let mb_x = cx / (16 / sub_w);
if mb_x >= grid.width_in_mbs as i32 {
return None;
}
let pair_ch = 32 / sub_h;
let pair_row = cy / pair_ch;
if pair_row * 2 >= grid.height_in_mbs as i32 {
return None;
}
let pair_top_addr = 2 * (pair_row as u32 * grid.width_in_mbs + mb_x as u32);
let pair_field = mb_field_flags
.get(pair_top_addr as usize)
.copied()
.unwrap_or(false);
let rel = cy - pair_row * pair_ch;
let is_bot = if pair_field {
(rel & 1) == 1
} else {
rel >= pair_ch / 2
};
Some(pair_top_addr + is_bot as u32)
}
fn in_mb_chroma_coords(
grid: &MbGrid,
addr: u32,
mb_field_flags: &[bool],
cx: i32,
cy: i32,
sub_w: i32,
sub_h: i32,
) -> (u32, u32) {
let field = mb_field_flags.get(addr as usize).copied().unwrap_or(false);
let (xo, yo) = mb_sample_origin(grid, addr, true, field);
let xo_c = xo / sub_w;
let yo_c = (yo + sub_h - 1) / sub_h;
let dy = if field { 2 } else { 1 };
let max_x = 16 / sub_w - 1;
let max_y = 16 / sub_h - 1;
(
(cx - xo_c).clamp(0, max_x) as u32,
((cy - yo_c) / dy).clamp(0, max_y) as u32,
)
}
#[allow(clippy::too_many_arguments)]
fn mbaff_edge_bs(
grid: &MbGrid,
p_addr: u32,
q_addr: u32,
p_in: (u32, u32),
q_in: (u32, u32),
vertical_edge: bool,
mb_field_flags: &[bool],
) -> Option<(u8, i32, i32)> {
let (p_info, q_info) = match (grid.get(p_addr), grid.get(q_addr)) {
(Some(p), Some(q)) if p.available && q.available => (p, q),
_ => return None,
};
let is_mb_edge = p_addr != q_addr;
let field_units = mb_field_flags
.get(p_addr as usize)
.copied()
.unwrap_or(false)
&& mb_field_flags
.get(q_addr as usize)
.copied()
.unwrap_or(false);
let diff_ref_mv = !p_info.is_intra
&& !q_info.is_intra
&& different_ref_or_mv_luma(p_info, q_info, p_in.0, p_in.1, q_in.0, q_in.1, field_units);
let p_blk4_z = blk4_raster_index((p_in.0 / 4) as u8, (p_in.1 / 4) as u8) as usize;
let q_blk4_z = blk4_raster_index((q_in.0 / 4) as u8, (q_in.1 / 4) as u8) as usize;
let p_has_nz = (p_info.luma_nonzero_4x4 >> p_blk4_z) & 1 == 1;
let q_has_nz = (q_info.luma_nonzero_4x4 >> q_blk4_z) & 1 == 1;
let (mbaff_or_field, both_in_frame_mbs, mixed_mode_edge) =
bs_pair_flags(p_addr, q_addr, true, false, mb_field_flags);
let bs = derive_boundary_strength(BsInputs {
p_is_intra: p_info.is_intra,
q_is_intra: q_info.is_intra,
is_mb_edge,
is_sp_or_si: p_info.in_sp_si_slice || q_info.in_sp_si_slice,
either_has_nonzero_coeffs: p_has_nz || q_has_nz,
different_ref_or_mv: diff_ref_mv,
mixed_mode_edge,
vertical_edge,
mbaff_or_field,
both_in_frame_mbs,
});
Some((bs, p_info.qp_y, q_info.qp_y))
}
#[allow(clippy::too_many_arguments)]
fn filter_luma_set_row(
pic: &mut Picture,
edge_x: i32,
y: i32,
bs: u8,
p_qp: i32,
q_qp: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
if y < 0 || y >= pic.height_in_samples as i32 {
return;
}
let params = FilterParams {
bs,
qp_avg: (p_qp + q_qp + 1) >> 1,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
let mut s = [0i32; 8];
for (i, x) in (edge_x - 4..edge_x + 4).enumerate() {
s[i] = pic.luma_at(x, y);
}
let p3 = s[0];
let mut p2 = s[1];
let mut p1 = s[2];
let mut p0 = s[3];
let mut q0 = s[4];
let mut q1 = s[5];
let mut q2 = s[6];
let q3 = s[7];
filter_edge(
Plane::Luma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
pic.set_luma(edge_x - 3, y, p2);
pic.set_luma(edge_x - 2, y, p1);
pic.set_luma(edge_x - 1, y, p0);
pic.set_luma(edge_x, y, q0);
pic.set_luma(edge_x + 1, y, q1);
pic.set_luma(edge_x + 2, y, q2);
}
#[allow(clippy::too_many_arguments)]
fn filter_luma_set_col(
pic: &mut Picture,
x: i32,
q_y0: i32,
stride: i32,
bs: u8,
p_qp: i32,
q_qp: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
if x < 0 || x >= pic.width_in_samples as i32 {
return;
}
let params = FilterParams {
bs,
qp_avg: (p_qp + q_qp + 1) >> 1,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
let p3 = pic.luma_at(x, q_y0 - 4 * stride);
let mut p2 = pic.luma_at(x, q_y0 - 3 * stride);
let mut p1 = pic.luma_at(x, q_y0 - 2 * stride);
let mut p0 = pic.luma_at(x, q_y0 - stride);
let mut q0 = pic.luma_at(x, q_y0);
let mut q1 = pic.luma_at(x, q_y0 + stride);
let mut q2 = pic.luma_at(x, q_y0 + 2 * stride);
let q3 = pic.luma_at(x, q_y0 + 3 * stride);
filter_edge(
Plane::Luma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
pic.set_luma(x, q_y0 - 3 * stride, p2);
pic.set_luma(x, q_y0 - 2 * stride, p1);
pic.set_luma(x, q_y0 - stride, p0);
pic.set_luma(x, q_y0, q0);
pic.set_luma(x, q_y0 + stride, q1);
pic.set_luma(x, q_y0 + 2 * stride, q2);
}
#[allow(clippy::too_many_arguments)]
fn filter_chroma_set_row(
pic: &mut Picture,
plane: u8,
edge_x: i32,
y: i32,
bs: u8,
qp_avg: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
if y < 0 || y >= pic.chroma_height() as i32 {
return;
}
let params = FilterParams {
bs,
qp_avg,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
let fetch = |p: &Picture, x: i32, y: i32| -> i32 {
if plane == 0 {
p.cb_at(x, y)
} else {
p.cr_at(x, y)
}
};
let mut s = [0i32; 8];
for (i, x) in (edge_x - 4..edge_x + 4).enumerate() {
s[i] = fetch(pic, x, y);
}
let p3 = s[0];
let mut p2 = s[1];
let mut p1 = s[2];
let mut p0 = s[3];
let mut q0 = s[4];
let mut q1 = s[5];
let mut q2 = s[6];
let q3 = s[7];
filter_edge(
Plane::Chroma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
if plane == 0 {
pic.set_cb(edge_x - 2, y, p1);
pic.set_cb(edge_x - 1, y, p0);
pic.set_cb(edge_x, y, q0);
pic.set_cb(edge_x + 1, y, q1);
} else {
pic.set_cr(edge_x - 2, y, p1);
pic.set_cr(edge_x - 1, y, p0);
pic.set_cr(edge_x, y, q0);
pic.set_cr(edge_x + 1, y, q1);
}
}
#[allow(clippy::too_many_arguments)]
fn filter_chroma_set_col(
pic: &mut Picture,
plane: u8,
x: i32,
q_y0: i32,
stride: i32,
bs: u8,
qp_avg: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
if x < 0 || x >= pic.chroma_width() as i32 {
return;
}
let params = FilterParams {
bs,
qp_avg,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
let fetch = |p: &Picture, x: i32, y: i32| -> i32 {
if plane == 0 {
p.cb_at(x, y)
} else {
p.cr_at(x, y)
}
};
let p3 = fetch(pic, x, q_y0 - 4 * stride);
let mut p2 = fetch(pic, x, q_y0 - 3 * stride);
let mut p1 = fetch(pic, x, q_y0 - 2 * stride);
let mut p0 = fetch(pic, x, q_y0 - stride);
let mut q0 = fetch(pic, x, q_y0);
let mut q1 = fetch(pic, x, q_y0 + stride);
let mut q2 = fetch(pic, x, q_y0 + 2 * stride);
let q3 = fetch(pic, x, q_y0 + 3 * stride);
filter_edge(
Plane::Chroma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
if plane == 0 {
pic.set_cb(x, q_y0 - 2 * stride, p1);
pic.set_cb(x, q_y0 - stride, p0);
pic.set_cb(x, q_y0, q0);
pic.set_cb(x, q_y0 + stride, q1);
} else {
pic.set_cr(x, q_y0 - 2 * stride, p1);
pic.set_cr(x, q_y0 - stride, p0);
pic.set_cr(x, q_y0, q0);
pic.set_cr(x, q_y0 + stride, q1);
}
}
#[allow(clippy::too_many_arguments)]
fn deblock_mbaff_frame(
pic: &mut Picture,
grid: &MbGrid,
alpha_off: i32,
beta_off: i32,
bit_depth_y: u32,
bit_depth_c: u32,
pps: &Pps,
mb_field_flags: &[bool],
) {
let mb_w = grid.width_in_mbs;
let pic_size = grid.width_in_mbs * grid.height_in_mbs;
let chroma = matches!(pic.chroma_array_type, 1 | 2);
let (sub_w, sub_h) = chroma_subsample(pic.chroma_array_type);
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let qp_bd_offset_c = qp_bd_offset(bit_depth_c.saturating_sub(8));
for addr in 0..pic_size {
let Some(info) = grid.get(addr) else { continue };
if !info.available {
continue;
}
let field = mb_field_flags.get(addr as usize).copied().unwrap_or(false);
let dy = if field { 2 } else { 1 };
let (x_i, y_i) = mb_sample_origin(grid, addr, true, field);
let pair_idx = addr >> 1;
let pair_col0 = pair_idx % mb_w == 0;
let pair_row0 = pair_idx < mb_w;
let is_top = addr % 2 == 0;
let t8 = info.transform_size_8x8_flag;
let filter_left = !pair_col0;
let filter_top = if pair_row0 { !field && !is_top } else { true };
let special_top = filter_top
&& is_top
&& !field
&& !pair_row0
&& mb_field_flags
.get((addr - 2 * mb_w + 1) as usize)
.copied()
.unwrap_or(false);
for xe in [0i32, 4, 8, 12] {
if xe == 0 && !filter_left {
continue;
}
if (xe == 4 || xe == 12) && t8 {
continue;
}
let qx = x_i + xe;
for k in 0..16i32 {
let y = y_i + dy * k;
let (p_addr, p_in) = if xe == 0 {
let Some(pa) = pixel_to_mb_addr(grid, qx - 1, y, true, mb_field_flags) else {
continue;
};
(pa, in_mb_luma_coords(grid, pa, mb_field_flags, qx - 1, y))
} else {
(addr, ((xe - 1) as u32, k as u32))
};
let q_in = (xe as u32, k as u32);
let Some((bs, p_qp, q_qp)) =
mbaff_edge_bs(grid, p_addr, addr, p_in, q_in, true, mb_field_flags)
else {
continue;
};
if bs == 0 {
continue;
}
filter_luma_set_row(pic, qx, y, bs, p_qp, q_qp, alpha_off, beta_off, bit_depth_y);
}
}
if filter_top {
if special_top {
for ye in [0i32, 1] {
let base_q = y_i + 2 * ye - (ye % 2);
for k in 0..16i32 {
let x = x_i + k;
let py0 = base_q - 2;
let Some(pa) = pixel_to_mb_addr(grid, x, py0, true, mb_field_flags) else {
continue;
};
let p_in = in_mb_luma_coords(grid, pa, mb_field_flags, x, py0);
let q_in = (k as u32, (base_q - y_i).clamp(0, 15) as u32);
let Some((bs, p_qp, q_qp)) =
mbaff_edge_bs(grid, pa, addr, p_in, q_in, false, mb_field_flags)
else {
continue;
};
if bs == 0 {
continue;
}
filter_luma_set_col(
pic,
x,
base_q,
2,
bs,
p_qp,
q_qp,
alpha_off,
beta_off,
bit_depth_y,
);
}
}
} else {
for k in 0..16i32 {
let x = x_i + k;
let py0 = y_i - dy;
let Some(pa) = pixel_to_mb_addr(grid, x, py0, true, mb_field_flags) else {
continue;
};
let p_in = in_mb_luma_coords(grid, pa, mb_field_flags, x, py0);
let q_in = (k as u32, 0u32);
let Some((bs, p_qp, q_qp)) =
mbaff_edge_bs(grid, pa, addr, p_in, q_in, false, mb_field_flags)
else {
continue;
};
if bs == 0 {
continue;
}
filter_luma_set_col(
pic,
x,
y_i,
dy,
bs,
p_qp,
q_qp,
alpha_off,
beta_off,
bit_depth_y,
);
}
}
}
for ye in [4i32, 8, 12] {
if (ye == 4 || ye == 12) && t8 {
continue;
}
let base_q = y_i + dy * ye;
for k in 0..16i32 {
let x = x_i + k;
let p_in = (k as u32, (ye - 1) as u32);
let q_in = (k as u32, ye as u32);
let Some((bs, p_qp, q_qp)) =
mbaff_edge_bs(grid, addr, addr, p_in, q_in, false, mb_field_flags)
else {
continue;
};
if bs == 0 {
continue;
}
filter_luma_set_col(
pic,
x,
base_q,
dy,
bs,
p_qp,
q_qp,
alpha_off,
beta_off,
bit_depth_y,
);
}
}
if !chroma {
continue;
}
let mbw_c = 16 / sub_w;
let mbh_c = 16 / sub_h;
let x_c = x_i / sub_w;
let y_c = (y_i + sub_h - 1) / sub_h;
for plane in 0..2u8 {
let qp_off = if plane == 0 { cb_offset } else { cr_offset };
let cqp = |p_qp: i32, q_qp: i32| chroma_qp_avg(p_qp, q_qp, qp_off, qp_bd_offset_c);
for xe in [0i32, 4] {
if xe == 0 && !filter_left {
continue;
}
let qx = x_c + xe;
for k in 0..mbh_c {
let y = y_c + dy * k;
let (p_addr, p_in_c) = if xe == 0 {
let Some(pa) =
chroma_pixel_to_mb_addr(grid, qx - 1, y, sub_w, sub_h, mb_field_flags)
else {
continue;
};
(
pa,
in_mb_chroma_coords(grid, pa, mb_field_flags, qx - 1, y, sub_w, sub_h),
)
} else {
(addr, ((xe - 1) as u32, k as u32))
};
let q_in_c = (xe as u32, k as u32);
let p_in = (
(p_in_c.0 * sub_w as u32).min(15),
(p_in_c.1 * sub_h as u32).min(15),
);
let q_in = (
(q_in_c.0 * sub_w as u32).min(15),
(q_in_c.1 * sub_h as u32).min(15),
);
let Some((bs, p_qp, q_qp)) =
mbaff_edge_bs(grid, p_addr, addr, p_in, q_in, true, mb_field_flags)
else {
continue;
};
if bs == 0 {
continue;
}
filter_chroma_set_row(
pic,
plane,
qx,
y,
bs,
cqp(p_qp, q_qp),
alpha_off,
beta_off,
bit_depth_c,
);
}
}
if filter_top {
if special_top {
for ye in [0i32, 1] {
let base_q = y_c + 2 * ye - (ye % 2);
for k in 0..mbw_c {
let x = x_c + k;
let py0 = base_q - 2;
let Some(pa) =
chroma_pixel_to_mb_addr(grid, x, py0, sub_w, sub_h, mb_field_flags)
else {
continue;
};
let p_in_c =
in_mb_chroma_coords(grid, pa, mb_field_flags, x, py0, sub_w, sub_h);
let p_in = (
(p_in_c.0 * sub_w as u32).min(15),
(p_in_c.1 * sub_h as u32).min(15),
);
let q_in = (
(k * sub_w).clamp(0, 15) as u32,
(((base_q - y_c).clamp(0, mbh_c - 1)) * sub_h).min(15) as u32,
);
let Some((bs, p_qp, q_qp)) =
mbaff_edge_bs(grid, pa, addr, p_in, q_in, false, mb_field_flags)
else {
continue;
};
if bs == 0 {
continue;
}
filter_chroma_set_col(
pic,
plane,
x,
base_q,
2,
bs,
cqp(p_qp, q_qp),
alpha_off,
beta_off,
bit_depth_c,
);
}
}
} else {
for k in 0..mbw_c {
let x = x_c + k;
let py0 = y_c - dy;
let Some(pa) =
chroma_pixel_to_mb_addr(grid, x, py0, sub_w, sub_h, mb_field_flags)
else {
continue;
};
let p_in_c =
in_mb_chroma_coords(grid, pa, mb_field_flags, x, py0, sub_w, sub_h);
let p_in = (
(p_in_c.0 * sub_w as u32).min(15),
(p_in_c.1 * sub_h as u32).min(15),
);
let q_in = ((k * sub_w).clamp(0, 15) as u32, 0u32);
let Some((bs, p_qp, q_qp)) =
mbaff_edge_bs(grid, pa, addr, p_in, q_in, false, mb_field_flags)
else {
continue;
};
if bs == 0 {
continue;
}
filter_chroma_set_col(
pic,
plane,
x,
y_c,
dy,
bs,
cqp(p_qp, q_qp),
alpha_off,
beta_off,
bit_depth_c,
);
}
}
}
let internal_yes: &[i32] = if sub_h == 2 { &[4] } else { &[4, 8, 12] };
for &ye in internal_yes {
let base_q = y_c + dy * ye;
for k in 0..mbw_c {
let x = x_c + k;
let p_in = (
(k * sub_w).clamp(0, 15) as u32,
((ye - 1) * sub_h).min(15) as u32,
);
let q_in = ((k * sub_w).clamp(0, 15) as u32, (ye * sub_h).min(15) as u32);
let Some((bs, p_qp, q_qp)) =
mbaff_edge_bs(grid, addr, addr, p_in, q_in, false, mb_field_flags)
else {
continue;
};
if bs == 0 {
continue;
}
filter_chroma_set_col(
pic,
plane,
x,
base_q,
dy,
bs,
cqp(p_qp, q_qp),
alpha_off,
beta_off,
bit_depth_c,
);
}
}
}
}
}
fn different_ref_or_mv_luma(
p_info: &MbInfo,
q_info: &MbInfo,
p_in_mb_x: u32,
p_in_mb_y: u32,
q_in_mb_x: u32,
q_in_mb_y: u32,
field_units: bool,
) -> bool {
let p_blk4 = blk4_raster_index((p_in_mb_x / 4) as u8, (p_in_mb_y / 4) as u8) as usize;
let q_blk4 = blk4_raster_index((q_in_mb_x / 4) as u8, (q_in_mb_y / 4) as u8) as usize;
let p_blk8 = ((p_in_mb_y / 8) * 2 + (p_in_mb_x / 8)) as usize;
let q_blk8 = ((q_in_mb_y / 8) * 2 + (q_in_mb_x / 8)) as usize;
let p_ref0 = p_info.ref_idx_l0[p_blk8];
let q_ref0 = q_info.ref_idx_l0[q_blk8];
let p_ref1 = p_info.ref_idx_l1[p_blk8];
let q_ref1 = q_info.ref_idx_l1[q_blk8];
let p_has_l0 = p_ref0 >= 0;
let q_has_l0 = q_ref0 >= 0;
let p_has_l1 = p_ref1 >= 0;
let q_has_l1 = q_ref1 >= 0;
let poc_sentinel: i32 = i32::MIN;
let p_pic0 = if p_has_l0 {
p_info.ref_poc_l0[p_blk8]
} else {
poc_sentinel
};
let q_pic0 = if q_has_l0 {
q_info.ref_poc_l0[q_blk8]
} else {
poc_sentinel
};
let p_pic1 = if p_has_l1 {
p_info.ref_poc_l1[p_blk8]
} else {
poc_sentinel
};
let q_pic1 = if q_has_l1 {
q_info.ref_poc_l1[q_blk8]
} else {
poc_sentinel
};
let p_count = (p_has_l0 as u8) + (p_has_l1 as u8);
let q_count = (q_has_l0 as u8) + (q_has_l1 as u8);
if p_count != q_count {
return true;
}
let p_mv0 = p_info.mv_l0[p_blk4];
let q_mv0 = q_info.mv_l0[q_blk4];
let p_mv1 = p_info.mv_l1[p_blk4];
let q_mv1 = q_info.mv_l1[q_blk4];
let bi = p_has_l0 && p_has_l1;
if bi {
let straight_refs_match = p_pic0 == q_pic0 && p_pic1 == q_pic1;
let swapped_refs_match = p_pic0 == q_pic1 && p_pic1 == q_pic0;
if !straight_refs_match && !swapped_refs_match {
return true;
}
let straight_mv_ok = mv_delta_below_4(p_mv0, q_mv0, field_units)
&& mv_delta_below_4(p_mv1, q_mv1, field_units);
let swapped_mv_ok = mv_delta_below_4(p_mv0, q_mv1, field_units)
&& mv_delta_below_4(p_mv1, q_mv0, field_units);
let same_ref = p_pic0 == p_pic1 && q_pic0 == q_pic1;
if same_ref {
return !(straight_mv_ok || swapped_mv_ok);
}
let straight_fails = straight_refs_match && !straight_mv_ok;
let swapped_fails = swapped_refs_match && !swapped_mv_ok;
straight_fails || swapped_fails
} else if p_has_l0 || p_has_l1 {
let (p_pic, p_mv) = if p_has_l0 {
(p_pic0, p_mv0)
} else {
(p_pic1, p_mv1)
};
let (q_pic, q_mv) = if q_has_l0 {
(q_pic0, q_mv0)
} else {
(q_pic1, q_mv1)
};
if p_pic != q_pic {
return true;
}
!mv_delta_below_4(p_mv, q_mv, field_units)
} else {
false
}
}
#[inline]
fn mv_delta_below_4(a: (i16, i16), b: (i16, i16), field_units: bool) -> bool {
let v_thresh = if field_units { 2 } else { 4 };
(a.0 as i32 - b.0 as i32).abs() < 4 && (a.1 as i32 - b.1 as i32).abs() < v_thresh
}
#[allow(clippy::too_many_arguments)]
fn deblock_plane_luma(
pic: &mut Picture,
grid: &MbGrid,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
mbaff_frame_flag: bool,
field_pic: bool,
mb_field_flags: &[bool],
) {
let w = pic.width_in_samples as i32;
let h = pic.height_in_samples as i32;
let mb_w = grid.width_in_mbs as i32;
let mb_h = grid.height_in_mbs as i32;
for mb_scan_y in 0..mb_h {
for mb_scan_x in 0..mb_w {
let (mb_x, mb_y) =
deblock_scan_pos(mb_scan_y * mb_w + mb_scan_x, mb_w, mbaff_frame_flag);
for edge_off in 0..4 {
let edge_x = mb_x * 16 + edge_off * 4;
if edge_x == 0 || edge_x >= w {
continue; }
for seg in 0..4 {
let y0 = mb_y * 16 + seg * 4;
if y0 >= h {
break;
}
let p_addr = match pixel_to_mb_addr(
grid,
edge_x - 1,
y0,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let q_addr = match pixel_to_mb_addr(
grid,
edge_x,
y0,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let (p_info, q_info) = match (grid.get(p_addr), grid.get(q_addr)) {
(Some(p), Some(q)) if p.available && q.available => (p, q),
_ => continue,
};
let is_mb_edge = p_addr != q_addr;
if !is_mb_edge
&& (edge_off == 1 || edge_off == 3)
&& q_info.transform_size_8x8_flag
{
continue;
}
let p_in_mb_x = (edge_x - 1).rem_euclid(16) as u32;
let p_in_mb_y = y0.rem_euclid(16) as u32;
let q_in_mb_x = edge_x.rem_euclid(16) as u32;
let q_in_mb_y = y0.rem_euclid(16) as u32;
let field_units = if mbaff_frame_flag {
mb_field_flags
.get(p_addr as usize)
.copied()
.unwrap_or(false)
&& mb_field_flags
.get(q_addr as usize)
.copied()
.unwrap_or(false)
} else {
field_pic
};
let diff_ref_mv = !p_info.is_intra
&& !q_info.is_intra
&& different_ref_or_mv_luma(
p_info,
q_info,
p_in_mb_x,
p_in_mb_y,
q_in_mb_x,
q_in_mb_y,
field_units,
);
let p_blk4_z =
blk4_raster_index((p_in_mb_x / 4) as u8, (p_in_mb_y / 4) as u8) as usize;
let q_blk4_z =
blk4_raster_index((q_in_mb_x / 4) as u8, (q_in_mb_y / 4) as u8) as usize;
let p_has_nz = (p_info.luma_nonzero_4x4 >> p_blk4_z) & 1 == 1;
let q_has_nz = (q_info.luma_nonzero_4x4 >> q_blk4_z) & 1 == 1;
let (mbaff_or_field, both_in_frame_mbs, mixed_mode_edge) =
bs_pair_flags(p_addr, q_addr, mbaff_frame_flag, field_pic, mb_field_flags);
let bs = derive_boundary_strength(BsInputs {
p_is_intra: p_info.is_intra,
q_is_intra: q_info.is_intra,
is_mb_edge,
is_sp_or_si: p_info.in_sp_si_slice || q_info.in_sp_si_slice,
either_has_nonzero_coeffs: p_has_nz || q_has_nz,
different_ref_or_mv: diff_ref_mv,
mixed_mode_edge,
vertical_edge: true,
mbaff_or_field,
both_in_frame_mbs,
});
if bs == 0 {
continue;
}
filter_vertical_edge_luma(
pic,
edge_x,
y0,
bs,
p_info.qp_y,
q_info.qp_y,
alpha_off,
beta_off,
bit_depth,
);
}
}
for edge_off in 0..4 {
let edge_y = mb_y * 16 + edge_off * 4;
if edge_y == 0 || edge_y >= h {
continue;
}
for seg in 0..4 {
let x0 = mb_x * 16 + seg * 4;
if x0 >= w {
break;
}
let p_addr = match pixel_to_mb_addr(
grid,
x0,
edge_y - 1,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let q_addr = match pixel_to_mb_addr(
grid,
x0,
edge_y,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let (p_info, q_info) = match (grid.get(p_addr), grid.get(q_addr)) {
(Some(p), Some(q)) if p.available && q.available => (p, q),
_ => continue,
};
let is_mb_edge = p_addr != q_addr;
if !is_mb_edge
&& (edge_off == 1 || edge_off == 3)
&& q_info.transform_size_8x8_flag
{
continue;
}
let p_in_mb_x = x0.rem_euclid(16) as u32;
let p_in_mb_y = (edge_y - 1).rem_euclid(16) as u32;
let q_in_mb_x = x0.rem_euclid(16) as u32;
let q_in_mb_y = edge_y.rem_euclid(16) as u32;
let field_units = if mbaff_frame_flag {
mb_field_flags
.get(p_addr as usize)
.copied()
.unwrap_or(false)
&& mb_field_flags
.get(q_addr as usize)
.copied()
.unwrap_or(false)
} else {
field_pic
};
let diff_ref_mv = !p_info.is_intra
&& !q_info.is_intra
&& different_ref_or_mv_luma(
p_info,
q_info,
p_in_mb_x,
p_in_mb_y,
q_in_mb_x,
q_in_mb_y,
field_units,
);
let p_blk4_z =
blk4_raster_index((p_in_mb_x / 4) as u8, (p_in_mb_y / 4) as u8) as usize;
let q_blk4_z =
blk4_raster_index((q_in_mb_x / 4) as u8, (q_in_mb_y / 4) as u8) as usize;
let p_has_nz = (p_info.luma_nonzero_4x4 >> p_blk4_z) & 1 == 1;
let q_has_nz = (q_info.luma_nonzero_4x4 >> q_blk4_z) & 1 == 1;
let (mbaff_or_field, both_in_frame_mbs, mixed_mode_edge) =
bs_pair_flags(p_addr, q_addr, mbaff_frame_flag, field_pic, mb_field_flags);
let bs = derive_boundary_strength(BsInputs {
p_is_intra: p_info.is_intra,
q_is_intra: q_info.is_intra,
is_mb_edge,
is_sp_or_si: p_info.in_sp_si_slice || q_info.in_sp_si_slice,
either_has_nonzero_coeffs: p_has_nz || q_has_nz,
different_ref_or_mv: diff_ref_mv,
mixed_mode_edge,
vertical_edge: false,
mbaff_or_field,
both_in_frame_mbs,
});
if bs == 0 {
continue;
}
filter_horizontal_edge_luma(
pic,
x0,
edge_y,
bs,
p_info.qp_y,
q_info.qp_y,
alpha_off,
beta_off,
bit_depth,
);
}
}
}
}
let _ = alpha_from_index;
let _ = beta_from_index;
let _ = tc0_from;
}
#[allow(clippy::too_many_arguments)]
fn deblock_plane_chroma(
pic: &mut Picture,
grid: &MbGrid,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
pps: &Pps,
mbaff_frame_flag: bool,
field_pic: bool,
mb_field_flags: &[bool],
) {
let _ = pps; let cw = pic.chroma_width() as i32;
let ch = pic.chroma_height() as i32;
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let qp_bd_offset_c = qp_bd_offset(bit_depth.saturating_sub(8));
let (sub_w, sub_h) = chroma_subsample(pic.chroma_array_type);
if sub_w == 0 || pic.chroma_array_type == 0 {
return;
}
let mb_w = grid.width_in_mbs as i32;
let mb_h = grid.height_in_mbs as i32;
let chroma_mb_w = 16 / sub_w.max(1);
let chroma_mb_h = 16 / sub_h.max(1);
for plane in 0..2u8 {
for mb_scan_y in 0..mb_h {
for mb_scan_x in 0..mb_w {
let (mb_x, mb_y) =
deblock_scan_pos(mb_scan_y * mb_w + mb_scan_x, mb_w, mbaff_frame_flag);
for edge_off in (0..chroma_mb_w).step_by(4) {
let edge_x = mb_x * chroma_mb_w + edge_off;
if edge_x == 0 || edge_x >= cw {
continue;
}
let sub_seg_rows = 4usize / sub_h as usize; let sub_seg_count = 4usize / sub_seg_rows; for seg_off in (0..chroma_mb_h).step_by(4) {
for sub in 0..sub_seg_count {
let y0 = mb_y * chroma_mb_h + seg_off + (sub * sub_seg_rows) as i32;
if y0 >= ch {
break;
}
let lp_x = (edge_x - 1) * sub_w;
let lq_x = edge_x * sub_w;
let ly = y0 * sub_h;
let p_addr = match pixel_to_mb_addr(
grid,
lp_x,
ly,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let q_addr = match pixel_to_mb_addr(
grid,
lq_x,
ly,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let (p_info, q_info) = match (grid.get(p_addr), grid.get(q_addr)) {
(Some(p), Some(q)) if p.available && q.available => (p, q),
_ => continue,
};
let is_mb_edge = p_addr != q_addr;
let p_in_mb_x = (lp_x).rem_euclid(16) as u32;
let p_in_mb_y = (ly).rem_euclid(16) as u32;
let q_in_mb_x = (lq_x).rem_euclid(16) as u32;
let q_in_mb_y = (ly).rem_euclid(16) as u32;
let field_units = if mbaff_frame_flag {
mb_field_flags
.get(p_addr as usize)
.copied()
.unwrap_or(false)
&& mb_field_flags
.get(q_addr as usize)
.copied()
.unwrap_or(false)
} else {
field_pic
};
let diff_ref_mv = !p_info.is_intra
&& !q_info.is_intra
&& different_ref_or_mv_luma(
p_info,
q_info,
p_in_mb_x,
p_in_mb_y,
q_in_mb_x,
q_in_mb_y,
field_units,
);
let p_blk4_z =
blk4_raster_index((p_in_mb_x / 4) as u8, (p_in_mb_y / 4) as u8)
as usize;
let q_blk4_z =
blk4_raster_index((q_in_mb_x / 4) as u8, (q_in_mb_y / 4) as u8)
as usize;
let p_has_nz = (p_info.luma_nonzero_4x4 >> p_blk4_z) & 1 == 1;
let q_has_nz = (q_info.luma_nonzero_4x4 >> q_blk4_z) & 1 == 1;
let (mbaff_or_field, both_in_frame_mbs, mixed_mode_edge) =
bs_pair_flags(
p_addr,
q_addr,
mbaff_frame_flag,
field_pic,
mb_field_flags,
);
let bs = derive_boundary_strength(BsInputs {
p_is_intra: p_info.is_intra,
q_is_intra: q_info.is_intra,
is_mb_edge,
is_sp_or_si: p_info.in_sp_si_slice || q_info.in_sp_si_slice,
either_has_nonzero_coeffs: p_has_nz || q_has_nz,
different_ref_or_mv: diff_ref_mv,
mixed_mode_edge,
vertical_edge: true,
mbaff_or_field,
both_in_frame_mbs,
});
if deblock_trace_enabled() {
eprintln!(
"DBL C{} Vx={edge_x} y0={y0} bs={bs} plane={plane} p_addr={p_addr} q_addr={q_addr} p_is_intra={} q_is_intra={} p_nz={} q_nz={} diff_ref_mv={}",
plane, p_info.is_intra, q_info.is_intra, p_has_nz, q_has_nz, diff_ref_mv,
);
}
if bs == 0 {
continue;
}
let qp_avg = chroma_qp_avg(
p_info.qp_y,
q_info.qp_y,
if plane == 0 { cb_offset } else { cr_offset },
qp_bd_offset_c,
);
filter_chroma_vertical_rows(
pic,
plane,
edge_x,
y0,
sub_seg_rows as i32,
bs,
qp_avg,
alpha_off,
beta_off,
bit_depth,
);
}
}
}
let sub_seg_cols = 4usize / sub_w as usize;
let sub_seg_count_h = 4usize / sub_seg_cols;
for edge_off in (0..chroma_mb_h).step_by(4) {
let edge_y = mb_y * chroma_mb_h + edge_off;
if edge_y == 0 || edge_y >= ch {
continue;
}
for seg_off in (0..chroma_mb_w).step_by(4) {
for sub in 0..sub_seg_count_h {
let x0 = mb_x * chroma_mb_w + seg_off + (sub * sub_seg_cols) as i32;
if x0 >= cw {
break;
}
let lx = x0 * sub_w;
let lp_y = (edge_y - 1) * sub_h;
let lq_y = edge_y * sub_h;
let p_addr = match pixel_to_mb_addr(
grid,
lx,
lp_y,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let q_addr = match pixel_to_mb_addr(
grid,
lx,
lq_y,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let (p_info, q_info) = match (grid.get(p_addr), grid.get(q_addr)) {
(Some(p), Some(q)) if p.available && q.available => (p, q),
_ => continue,
};
let is_mb_edge = p_addr != q_addr;
let p_in_mb_x = lx.rem_euclid(16) as u32;
let p_in_mb_y = lp_y.rem_euclid(16) as u32;
let q_in_mb_x = lx.rem_euclid(16) as u32;
let q_in_mb_y = lq_y.rem_euclid(16) as u32;
let field_units = if mbaff_frame_flag {
mb_field_flags
.get(p_addr as usize)
.copied()
.unwrap_or(false)
&& mb_field_flags
.get(q_addr as usize)
.copied()
.unwrap_or(false)
} else {
field_pic
};
let diff_ref_mv = !p_info.is_intra
&& !q_info.is_intra
&& different_ref_or_mv_luma(
p_info,
q_info,
p_in_mb_x,
p_in_mb_y,
q_in_mb_x,
q_in_mb_y,
field_units,
);
let p_blk4_z =
blk4_raster_index((p_in_mb_x / 4) as u8, (p_in_mb_y / 4) as u8)
as usize;
let q_blk4_z =
blk4_raster_index((q_in_mb_x / 4) as u8, (q_in_mb_y / 4) as u8)
as usize;
let p_has_nz = (p_info.luma_nonzero_4x4 >> p_blk4_z) & 1 == 1;
let q_has_nz = (q_info.luma_nonzero_4x4 >> q_blk4_z) & 1 == 1;
let (mbaff_or_field, both_in_frame_mbs, mixed_mode_edge) =
bs_pair_flags(
p_addr,
q_addr,
mbaff_frame_flag,
field_pic,
mb_field_flags,
);
let bs = derive_boundary_strength(BsInputs {
p_is_intra: p_info.is_intra,
q_is_intra: q_info.is_intra,
is_mb_edge,
is_sp_or_si: p_info.in_sp_si_slice || q_info.in_sp_si_slice,
either_has_nonzero_coeffs: p_has_nz || q_has_nz,
different_ref_or_mv: diff_ref_mv,
mixed_mode_edge,
vertical_edge: false,
mbaff_or_field,
both_in_frame_mbs,
});
if bs == 0 {
continue;
}
let qp_avg = chroma_qp_avg(
p_info.qp_y,
q_info.qp_y,
if plane == 0 { cb_offset } else { cr_offset },
qp_bd_offset_c,
);
filter_chroma_horizontal_cols(
pic,
plane,
x0,
edge_y,
sub_seg_cols as i32,
bs,
qp_avg,
alpha_off,
beta_off,
bit_depth,
);
}
}
}
}
}
}
let _ = alpha_off;
let _ = beta_off;
let _ = bit_depth;
}
fn chroma_qp_avg(p_qp_y: i32, q_qp_y: i32, offset: i32, qp_bd_offset_c: i32) -> i32 {
let p_c = qp_y_to_qp_c_with_bd_offset(p_qp_y, offset, qp_bd_offset_c);
let q_c = qp_y_to_qp_c_with_bd_offset(q_qp_y, offset, qp_bd_offset_c);
(p_c + q_c + 1) >> 1
}
#[allow(clippy::too_many_arguments)]
fn deblock_plane_chroma_444(
pic: &mut Picture,
grid: &MbGrid,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
pps: &Pps,
mbaff_frame_flag: bool,
field_pic: bool,
mb_field_flags: &[bool],
) {
let w = pic.width_in_samples as i32;
let h = pic.height_in_samples as i32;
let mb_w = grid.width_in_mbs as i32;
let mb_h = grid.height_in_mbs as i32;
let cb_offset = pps.chroma_qp_index_offset;
let cr_offset = pps
.extension
.as_ref()
.map(|e| e.second_chroma_qp_index_offset)
.unwrap_or(pps.chroma_qp_index_offset);
let qp_bd_offset_c = qp_bd_offset(bit_depth.saturating_sub(8));
let side_qp = |info: &MbInfo| -> i32 {
if info.is_i_pcm {
0
} else {
info.qp_y
}
};
for plane in 0..2u8 {
let offset = if plane == 0 { cb_offset } else { cr_offset };
for mb_scan_y in 0..mb_h {
for mb_scan_x in 0..mb_w {
let (mb_x, mb_y) =
deblock_scan_pos(mb_scan_y * mb_w + mb_scan_x, mb_w, mbaff_frame_flag);
for edge_off in 0..4 {
let edge_x = mb_x * 16 + edge_off * 4;
if edge_x == 0 || edge_x >= w {
continue;
}
for seg in 0..4 {
let y0 = mb_y * 16 + seg * 4;
if y0 >= h {
break;
}
let p_addr = match pixel_to_mb_addr(
grid,
edge_x - 1,
y0,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let q_addr = match pixel_to_mb_addr(
grid,
edge_x,
y0,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let (p_info, q_info) = match (grid.get(p_addr), grid.get(q_addr)) {
(Some(p), Some(q)) if p.available && q.available => (p, q),
_ => continue,
};
let is_mb_edge = p_addr != q_addr;
if !is_mb_edge
&& (edge_off == 1 || edge_off == 3)
&& q_info.transform_size_8x8_flag
{
continue;
}
let p_in_mb_x = (edge_x - 1).rem_euclid(16) as u32;
let p_in_mb_y = y0.rem_euclid(16) as u32;
let q_in_mb_x = edge_x.rem_euclid(16) as u32;
let q_in_mb_y = y0.rem_euclid(16) as u32;
let bs = derive_chroma_444_bs(
p_info,
q_info,
is_mb_edge,
p_in_mb_x,
p_in_mb_y,
q_in_mb_x,
q_in_mb_y,
true,
bs_pair_flags(
p_addr,
q_addr,
mbaff_frame_flag,
field_pic,
mb_field_flags,
),
if mbaff_frame_flag {
mb_field_flags
.get(p_addr as usize)
.copied()
.unwrap_or(false)
&& mb_field_flags
.get(q_addr as usize)
.copied()
.unwrap_or(false)
} else {
field_pic
},
);
if bs == 0 {
continue;
}
let qp_avg =
chroma_qp_avg(side_qp(p_info), side_qp(q_info), offset, qp_bd_offset_c);
filter_vertical_edge_plane_luma_style(
pic, plane, edge_x, y0, bs, qp_avg, alpha_off, beta_off, bit_depth,
);
}
}
for edge_off in 0..4 {
let edge_y = mb_y * 16 + edge_off * 4;
if edge_y == 0 || edge_y >= h {
continue;
}
for seg in 0..4 {
let x0 = mb_x * 16 + seg * 4;
if x0 >= w {
break;
}
let p_addr = match pixel_to_mb_addr(
grid,
x0,
edge_y - 1,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let q_addr = match pixel_to_mb_addr(
grid,
x0,
edge_y,
mbaff_frame_flag,
mb_field_flags,
) {
Some(a) => a,
None => continue,
};
let (p_info, q_info) = match (grid.get(p_addr), grid.get(q_addr)) {
(Some(p), Some(q)) if p.available && q.available => (p, q),
_ => continue,
};
let is_mb_edge = p_addr != q_addr;
if !is_mb_edge
&& (edge_off == 1 || edge_off == 3)
&& q_info.transform_size_8x8_flag
{
continue;
}
let p_in_mb_x = x0.rem_euclid(16) as u32;
let p_in_mb_y = (edge_y - 1).rem_euclid(16) as u32;
let q_in_mb_x = x0.rem_euclid(16) as u32;
let q_in_mb_y = edge_y.rem_euclid(16) as u32;
let bs = derive_chroma_444_bs(
p_info,
q_info,
is_mb_edge,
p_in_mb_x,
p_in_mb_y,
q_in_mb_x,
q_in_mb_y,
false,
bs_pair_flags(
p_addr,
q_addr,
mbaff_frame_flag,
field_pic,
mb_field_flags,
),
if mbaff_frame_flag {
mb_field_flags
.get(p_addr as usize)
.copied()
.unwrap_or(false)
&& mb_field_flags
.get(q_addr as usize)
.copied()
.unwrap_or(false)
} else {
field_pic
},
);
if bs == 0 {
continue;
}
let qp_avg =
chroma_qp_avg(side_qp(p_info), side_qp(q_info), offset, qp_bd_offset_c);
filter_horizontal_edge_plane_luma_style(
pic, plane, x0, edge_y, bs, qp_avg, alpha_off, beta_off, bit_depth,
);
}
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn derive_chroma_444_bs(
p_info: &MbInfo,
q_info: &MbInfo,
is_mb_edge: bool,
p_in_mb_x: u32,
p_in_mb_y: u32,
q_in_mb_x: u32,
q_in_mb_y: u32,
vertical_edge: bool,
pair_flags: (bool, bool, bool),
field_units: bool,
) -> u8 {
let (mbaff_or_field, both_in_frame_mbs, mixed_mode_edge) = pair_flags;
let diff_ref_mv = !p_info.is_intra
&& !q_info.is_intra
&& different_ref_or_mv_luma(
p_info,
q_info,
p_in_mb_x,
p_in_mb_y,
q_in_mb_x,
q_in_mb_y,
field_units,
);
let p_blk4_z = blk4_raster_index((p_in_mb_x / 4) as u8, (p_in_mb_y / 4) as u8) as usize;
let q_blk4_z = blk4_raster_index((q_in_mb_x / 4) as u8, (q_in_mb_y / 4) as u8) as usize;
let p_has_nz = (p_info.luma_nonzero_4x4 >> p_blk4_z) & 1 == 1;
let q_has_nz = (q_info.luma_nonzero_4x4 >> q_blk4_z) & 1 == 1;
derive_boundary_strength(BsInputs {
p_is_intra: p_info.is_intra,
q_is_intra: q_info.is_intra,
is_mb_edge,
is_sp_or_si: p_info.in_sp_si_slice || q_info.in_sp_si_slice,
either_has_nonzero_coeffs: p_has_nz || q_has_nz,
different_ref_or_mv: diff_ref_mv,
mixed_mode_edge,
vertical_edge,
mbaff_or_field,
both_in_frame_mbs,
})
}
#[allow(clippy::too_many_arguments)]
fn filter_vertical_edge_plane_luma_style(
pic: &mut Picture,
plane: u8,
edge_x: i32,
y0: i32,
bs: u8,
qp_avg: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
let params = FilterParams {
bs,
qp_avg,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
let pic_h = pic.height_in_samples as i32;
for dy in 0..4 {
let y = y0 + dy;
if y < 0 || y >= pic_h {
continue;
}
let mut samples = [0i32; 8];
for (i, x) in (edge_x - 4..edge_x + 4).enumerate() {
samples[i] = plane_at(pic, plane, x, y);
}
let p3 = samples[0];
let mut p2 = samples[1];
let mut p1 = samples[2];
let mut p0 = samples[3];
let mut q0 = samples[4];
let mut q1 = samples[5];
let mut q2 = samples[6];
let q3 = samples[7];
filter_edge(
Plane::Luma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
plane_set(pic, plane, edge_x - 3, y, p2);
plane_set(pic, plane, edge_x - 2, y, p1);
plane_set(pic, plane, edge_x - 1, y, p0);
plane_set(pic, plane, edge_x, y, q0);
plane_set(pic, plane, edge_x + 1, y, q1);
plane_set(pic, plane, edge_x + 2, y, q2);
}
}
#[allow(clippy::too_many_arguments)]
fn filter_horizontal_edge_plane_luma_style(
pic: &mut Picture,
plane: u8,
x0: i32,
edge_y: i32,
bs: u8,
qp_avg: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
let params = FilterParams {
bs,
qp_avg,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
let pic_w = pic.width_in_samples as i32;
for dx in 0..4 {
let x = x0 + dx;
if x < 0 || x >= pic_w {
continue;
}
let mut samples = [0i32; 8];
for (i, y) in (edge_y - 4..edge_y + 4).enumerate() {
samples[i] = plane_at(pic, plane, x, y);
}
let p3 = samples[0];
let mut p2 = samples[1];
let mut p1 = samples[2];
let mut p0 = samples[3];
let mut q0 = samples[4];
let mut q1 = samples[5];
let mut q2 = samples[6];
let q3 = samples[7];
filter_edge(
Plane::Luma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
plane_set(pic, plane, x, edge_y - 3, p2);
plane_set(pic, plane, x, edge_y - 2, p1);
plane_set(pic, plane, x, edge_y - 1, p0);
plane_set(pic, plane, x, edge_y, q0);
plane_set(pic, plane, x, edge_y + 1, q1);
plane_set(pic, plane, x, edge_y + 2, q2);
}
}
#[inline]
fn plane_at(pic: &Picture, plane: u8, x: i32, y: i32) -> i32 {
if plane == 0 {
pic.cb_at(x, y)
} else {
pic.cr_at(x, y)
}
}
#[inline]
fn plane_set(pic: &mut Picture, plane: u8, x: i32, y: i32, v: i32) {
if plane == 0 {
pic.set_cb(x, y, v);
} else {
pic.set_cr(x, y, v);
}
}
fn filter_vertical_edge_luma(
pic: &mut Picture,
edge_x: i32,
y0: i32,
bs: u8,
p_qp: i32,
q_qp: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
let qp_avg = (p_qp + q_qp + 1) >> 1;
let params = FilterParams {
bs,
qp_avg,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
let pic_w = pic.width_in_samples as i32;
let pic_h = pic.height_in_samples as i32;
if edge_x >= 4 && edge_x + 4 <= pic_w && y0 >= 0 && y0 + 4 <= pic_h {
let stride = pic_w as usize;
let base_x = (edge_x - 4) as usize;
for dy in 0..4 {
let y = (y0 + dy) as usize;
let row = &mut pic.luma[y * stride + base_x..y * stride + base_x + 8];
let p3 = row[0];
let q3 = row[7];
let mut p2 = row[1];
let mut p1 = row[2];
let mut p0 = row[3];
let mut q0 = row[4];
let mut q1 = row[5];
let mut q2 = row[6];
filter_edge(
Plane::Luma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
row[1] = p2;
row[2] = p1;
row[3] = p0;
row[4] = q0;
row[5] = q1;
row[6] = q2;
}
return;
}
for dy in 0..4 {
let y = y0 + dy;
if y < 0 || y >= pic_h {
continue;
}
let mut samples = [0i32; 8];
for (i, x) in (edge_x - 4..edge_x + 4).enumerate() {
samples[i] = pic.luma_at(x, y);
}
let p3 = samples[0];
let mut p2 = samples[1];
let mut p1 = samples[2];
let mut p0 = samples[3];
let mut q0 = samples[4];
let mut q1 = samples[5];
let mut q2 = samples[6];
let q3 = samples[7];
filter_edge(
Plane::Luma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
pic.set_luma(edge_x - 4, y, samples[0]);
pic.set_luma(edge_x - 3, y, p2);
pic.set_luma(edge_x - 2, y, p1);
pic.set_luma(edge_x - 1, y, p0);
pic.set_luma(edge_x, y, q0);
pic.set_luma(edge_x + 1, y, q1);
pic.set_luma(edge_x + 2, y, q2);
pic.set_luma(edge_x + 3, y, samples[7]);
}
}
fn filter_horizontal_edge_luma(
pic: &mut Picture,
x0: i32,
edge_y: i32,
bs: u8,
p_qp: i32,
q_qp: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
let qp_avg = (p_qp + q_qp + 1) >> 1;
let params = FilterParams {
bs,
qp_avg,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
let pic_w = pic.width_in_samples as i32;
let pic_h = pic.height_in_samples as i32;
if x0 >= 0 && x0 + 4 <= pic_w && edge_y >= 4 && edge_y + 4 <= pic_h {
let stride = pic_w as usize;
for dx in 0..4 {
let x = (x0 + dx) as usize;
let p3_idx = ((edge_y - 4) as usize) * stride + x;
let q3_idx = ((edge_y + 3) as usize) * stride + x;
let p3 = pic.luma[p3_idx];
let q3 = pic.luma[q3_idx];
let mut p2 = pic.luma[((edge_y - 3) as usize) * stride + x];
let mut p1 = pic.luma[((edge_y - 2) as usize) * stride + x];
let mut p0 = pic.luma[((edge_y - 1) as usize) * stride + x];
let mut q0 = pic.luma[((edge_y) as usize) * stride + x];
let mut q1 = pic.luma[((edge_y + 1) as usize) * stride + x];
let mut q2 = pic.luma[((edge_y + 2) as usize) * stride + x];
filter_edge(
Plane::Luma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
pic.luma[((edge_y - 3) as usize) * stride + x] = p2;
pic.luma[((edge_y - 2) as usize) * stride + x] = p1;
pic.luma[((edge_y - 1) as usize) * stride + x] = p0;
pic.luma[((edge_y) as usize) * stride + x] = q0;
pic.luma[((edge_y + 1) as usize) * stride + x] = q1;
pic.luma[((edge_y + 2) as usize) * stride + x] = q2;
}
return;
}
for dx in 0..4 {
let x = x0 + dx;
if x < 0 || x >= pic_w {
continue;
}
let mut samples = [0i32; 8];
for (i, y) in (edge_y - 4..edge_y + 4).enumerate() {
samples[i] = pic.luma_at(x, y);
}
let p3 = samples[0];
let mut p2 = samples[1];
let mut p1 = samples[2];
let mut p0 = samples[3];
let mut q0 = samples[4];
let mut q1 = samples[5];
let mut q2 = samples[6];
let q3 = samples[7];
filter_edge(
Plane::Luma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
pic.set_luma(x, edge_y - 3, p2);
pic.set_luma(x, edge_y - 2, p1);
pic.set_luma(x, edge_y - 1, p0);
pic.set_luma(x, edge_y, q0);
pic.set_luma(x, edge_y + 1, q1);
pic.set_luma(x, edge_y + 2, q2);
}
}
#[allow(clippy::too_many_arguments)]
fn filter_chroma_vertical_rows(
pic: &mut Picture,
plane: u8,
edge_x: i32,
y0: i32,
rows: i32,
bs: u8,
qp_avg: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
let cw = pic.chroma_width() as i32;
let ch = pic.chroma_height() as i32;
let params = FilterParams {
bs,
qp_avg,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
if edge_x >= 4 && edge_x + 4 <= cw && y0 >= 0 && y0 + rows <= ch {
let stride = cw as usize;
let base_x = (edge_x - 4) as usize;
let buf: &mut [i32] = if plane == 0 { &mut pic.cb } else { &mut pic.cr };
for dy in 0..rows {
let y = (y0 + dy) as usize;
let row = &mut buf[y * stride + base_x..y * stride + base_x + 8];
let p3 = row[0];
let q3 = row[7];
let mut p2 = row[1];
let mut p1 = row[2];
let mut p0 = row[3];
let mut q0 = row[4];
let mut q1 = row[5];
let mut q2 = row[6];
filter_edge(
Plane::Chroma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
row[2] = p1;
row[3] = p0;
row[4] = q0;
row[5] = q1;
}
return;
}
for dy in 0..rows {
let y = y0 + dy;
if y < 0 || y >= ch {
continue;
}
let fetch = |x: i32, y: i32| -> i32 {
if plane == 0 {
pic.cb_at(x, y)
} else {
pic.cr_at(x, y)
}
};
let mut s = [0i32; 8];
for (i, x) in (edge_x - 4..edge_x + 4).enumerate() {
let xc = x.clamp(0, cw.max(1) - 1);
s[i] = fetch(xc, y);
}
let p3 = s[0];
let mut p2 = s[1];
let mut p1 = s[2];
let mut p0 = s[3];
let mut q0 = s[4];
let mut q1 = s[5];
let mut q2 = s[6];
let q3 = s[7];
filter_edge(
Plane::Chroma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
if plane == 0 {
pic.set_cb(edge_x - 2, y, p1);
pic.set_cb(edge_x - 1, y, p0);
pic.set_cb(edge_x, y, q0);
pic.set_cb(edge_x + 1, y, q1);
} else {
pic.set_cr(edge_x - 2, y, p1);
pic.set_cr(edge_x - 1, y, p0);
pic.set_cr(edge_x, y, q0);
pic.set_cr(edge_x + 1, y, q1);
}
}
}
#[allow(clippy::too_many_arguments)]
fn filter_chroma_horizontal_cols(
pic: &mut Picture,
plane: u8,
x0: i32,
edge_y: i32,
cols: i32,
bs: u8,
qp_avg: i32,
alpha_off: i32,
beta_off: i32,
bit_depth: u32,
) {
let cw = pic.chroma_width() as i32;
let ch = pic.chroma_height() as i32;
let params = FilterParams {
bs,
qp_avg,
filter_offset_a: alpha_off,
filter_offset_b: beta_off,
bit_depth,
};
if x0 >= 0 && x0 + cols <= cw && edge_y >= 4 && edge_y + 4 <= ch {
let stride = cw as usize;
let buf: &mut [i32] = if plane == 0 { &mut pic.cb } else { &mut pic.cr };
for dx in 0..cols {
let x = (x0 + dx) as usize;
let p3 = buf[((edge_y - 4) as usize) * stride + x];
let q3 = buf[((edge_y + 3) as usize) * stride + x];
let mut p2 = buf[((edge_y - 3) as usize) * stride + x];
let mut p1 = buf[((edge_y - 2) as usize) * stride + x];
let mut p0 = buf[((edge_y - 1) as usize) * stride + x];
let mut q0 = buf[((edge_y) as usize) * stride + x];
let mut q1 = buf[((edge_y + 1) as usize) * stride + x];
let mut q2 = buf[((edge_y + 2) as usize) * stride + x];
filter_edge(
Plane::Chroma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
buf[((edge_y - 2) as usize) * stride + x] = p1;
buf[((edge_y - 1) as usize) * stride + x] = p0;
buf[((edge_y) as usize) * stride + x] = q0;
buf[((edge_y + 1) as usize) * stride + x] = q1;
}
return;
}
for dx in 0..cols {
let x = x0 + dx;
if x < 0 || x >= cw {
continue;
}
let fetch = |x: i32, y: i32| -> i32 {
if plane == 0 {
pic.cb_at(x, y)
} else {
pic.cr_at(x, y)
}
};
let mut s = [0i32; 8];
for (i, y) in (edge_y - 4..edge_y + 4).enumerate() {
let yc = y.clamp(0, ch.max(1) - 1);
s[i] = fetch(x, yc);
}
let p3 = s[0];
let mut p2 = s[1];
let mut p1 = s[2];
let mut p0 = s[3];
let mut q0 = s[4];
let mut q1 = s[5];
let mut q2 = s[6];
let q3 = s[7];
filter_edge(
Plane::Chroma,
EdgeSamples {
p3,
p2: &mut p2,
p1: &mut p1,
p0: &mut p0,
q0: &mut q0,
q1: &mut q1,
q2: &mut q2,
q3,
},
params,
);
if plane == 0 {
pic.set_cb(x, edge_y - 2, p1);
pic.set_cb(x, edge_y - 1, p0);
pic.set_cb(x, edge_y, q0);
pic.set_cb(x, edge_y + 1, q1);
} else {
pic.set_cr(x, edge_y - 2, p1);
pic.set_cr(x, edge_y - 1, p0);
pic.set_cr(x, edge_y, q0);
pic.set_cr(x, edge_y + 1, q1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::macroblock_layer::{
Intra16x16, Macroblock, MbPred, MbType, PcmSamples, SubMbPred, SubMbType,
};
#[test]
fn neighbour_4x4_mbaff_uses_pair_interleaved_addressing() {
let mut grid = MbGrid::new(2, 4);
grid.mbaff_frame_flag = true;
for info in grid.info.iter_mut() {
info.available = true;
info.mb_field_decoding_flag = false; }
let left = neighbour_4x4_addr(&grid, 5, 0, 0, -1, 0);
assert!(
left.is_none(),
"addr 5 is in pair column 0 → no left pair (A unavailable), got {left:?}"
);
let above = neighbour_4x4_addr(&grid, 4, 0, 0, 0, -1);
assert_eq!(
above.map(|(a, _)| a),
Some(1),
"above neighbour of MBAFF addr 4 (top of lower-left pair) is addr 1 (bottom of upper-left pair)"
);
let left2 = neighbour_4x4_addr(&grid, 2, 0, 0, -1, 0);
assert_eq!(
left2.map(|(a, _)| a),
Some(0),
"left neighbour of MBAFF addr 2 is addr 0"
);
}
#[test]
fn next_qp_y_8bit_reduces_to_mod52() {
assert_eq!(next_qp_y(28, 0, 0), 28);
assert_eq!(next_qp_y(28, 5, 0), 33);
assert_eq!(next_qp_y(28, -10, 0), 18);
assert_eq!(next_qp_y(51, 1, 0), 0);
assert_eq!(next_qp_y(0, -1, 0), 51);
for prev in 0..=51 {
for delta in -39..=39 {
let q = next_qp_y(prev, delta, 0);
assert!((0..=51).contains(&q), "8-bit QP_Y {q} out of 0..=51");
}
}
}
#[test]
fn next_qp_y_10bit_eq_8_309() {
assert_eq!(next_qp_y(39, -11, 2), 28);
assert_eq!(next_qp_y(39, 0, 2), 39);
assert_eq!(next_qp_y(0, -12, 2), -12);
assert_eq!(next_qp_y(51, 1, 2), -12);
assert_eq!(next_qp_y(-12, -1, 2), 51);
for prev in -12..=51 {
for delta in -45..=45 {
let q = next_qp_y(prev, delta, 2);
assert!((-12..=51).contains(&q), "10-bit QP_Y {q} out of −12..=51");
}
}
}
#[test]
fn next_qp_y_12bit_eq_8_309() {
assert_eq!(next_qp_y(0, 0, 4), 0);
assert_eq!(next_qp_y(0, -24, 4), -24);
assert_eq!(next_qp_y(51, 1, 4), -24);
assert_eq!(next_qp_y(-24, -1, 4), 51);
for prev in -24..=51 {
for delta in -50..=50 {
let q = next_qp_y(prev, delta, 4);
assert!((-24..=51).contains(&q), "12-bit QP_Y {q} out of −24..=51");
}
}
}
use crate::pps::Pps;
use crate::ref_store::{NoRefs, RefPicStore};
use crate::slice_header::{
DecRefPicMarking, PredWeightTable, RefPicListModification, SliceHeader, SliceType,
};
use crate::sps::{FrameCropping, Sps};
fn make_sps(w_mbs: u32, h_mbs: u32) -> Sps {
Sps {
profile_idc: 66,
constraint_set_flags: 0,
level_idc: 30,
seq_parameter_set_id: 0,
chroma_format_idc: 1,
separate_colour_plane_flag: false,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
qpprime_y_zero_transform_bypass_flag: false,
seq_scaling_matrix_present_flag: false,
seq_scaling_lists: None,
log2_max_frame_num_minus4: 0,
pic_order_cnt_type: 0,
log2_max_pic_order_cnt_lsb_minus4: 0,
delta_pic_order_always_zero_flag: false,
offset_for_non_ref_pic: 0,
offset_for_top_to_bottom_field: 0,
num_ref_frames_in_pic_order_cnt_cycle: 0,
offset_for_ref_frame: Vec::new(),
max_num_ref_frames: 1,
gaps_in_frame_num_value_allowed_flag: false,
pic_width_in_mbs_minus1: w_mbs - 1,
pic_height_in_map_units_minus1: h_mbs - 1,
frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: false,
frame_cropping: None::<FrameCropping>,
vui_parameters_present_flag: false,
vui: None,
}
}
fn make_pps() -> Pps {
Pps {
pic_parameter_set_id: 0,
seq_parameter_set_id: 0,
entropy_coding_mode_flag: false,
bottom_field_pic_order_in_frame_present_flag: false,
num_slice_groups_minus1: 0,
slice_group_map: None,
num_ref_idx_l0_default_active_minus1: 0,
num_ref_idx_l1_default_active_minus1: 0,
weighted_pred_flag: false,
weighted_bipred_idc: 0,
pic_init_qp_minus26: 0,
pic_init_qs_minus26: 0,
chroma_qp_index_offset: 0,
deblocking_filter_control_present_flag: false,
constrained_intra_pred_flag: false,
redundant_pic_cnt_present_flag: false,
extension: None,
}
}
fn make_slice_header() -> SliceHeader {
SliceHeader {
first_mb_in_slice: 0,
slice_type_raw: 2,
slice_type: SliceType::I,
all_slices_same_type: false,
pic_parameter_set_id: 0,
colour_plane_id: 0,
frame_num: 0,
field_pic_flag: false,
bottom_field_flag: false,
idr_pic_id: 0,
pic_order_cnt_lsb: 0,
delta_pic_order_cnt_bottom: 0,
delta_pic_order_cnt: [0, 0],
redundant_pic_cnt: 0,
direct_spatial_mv_pred_flag: false,
num_ref_idx_active_override_flag: false,
num_ref_idx_l0_active_minus1: 0,
num_ref_idx_l1_active_minus1: 0,
ref_pic_list_modification: RefPicListModification::default(),
pred_weight_table: None::<PredWeightTable>,
dec_ref_pic_marking: None::<DecRefPicMarking>,
cabac_init_idc: 0,
slice_qp_delta: 0,
sp_for_switch_flag: false,
slice_qs_delta: 0,
disable_deblocking_filter_idc: 1, slice_alpha_c0_offset_div2: 0,
slice_beta_offset_div2: 0,
slice_group_change_cycle: 0,
}
}
fn make_empty_ipcm_mb() -> Macroblock {
let mut luma = vec![0u32; 256];
for (i, slot) in luma.iter_mut().enumerate() {
*slot = (i as u32) & 0xFF;
}
let cb = vec![100u32; 64];
let cr = vec![200u32; 64];
Macroblock {
mb_type: MbType::IPcm,
mb_type_raw: 25,
mb_pred: None,
sub_mb_pred: None,
pcm_samples: Some(PcmSamples {
luma,
chroma_cb: cb,
chroma_cr: cr,
}),
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: Vec::new(),
residual_chroma_dc_cr: Vec::new(),
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
}
}
fn make_intra16x16_dc_mb(pred_mode: u8) -> Macroblock {
Macroblock {
mb_type: MbType::Intra16x16(Intra16x16 {
pred_mode,
cbp_luma: 0,
cbp_chroma: 0,
}),
mb_type_raw: 1,
mb_pred: Some(MbPred::default()),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: Some([0i32; 16]),
residual_chroma_dc_cb: vec![0i32; 4],
residual_chroma_dc_cr: vec![0i32; 4],
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
}
}
#[test]
fn i_pcm_macroblock_is_copied_into_picture() {
let sps = make_sps(1, 1);
let pps = make_pps();
let sh = make_slice_header();
let mb = make_empty_ipcm_mb();
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
let expected = (y * 16 + x) & 0xFF;
assert_eq!(pic.luma_at(x, y), expected, "mismatch at ({}, {})", x, y);
}
}
for y in 0..8 {
for x in 0..8 {
assert_eq!(pic.cb_at(x, y), 100);
assert_eq!(pic.cr_at(x, y), 200);
}
}
let info = grid.get(0).unwrap();
assert!(info.available);
assert!(info.is_intra);
assert!(info.is_i_pcm);
}
#[test]
fn intra_16x16_dc_zero_residual_falls_back_to_default_dc() {
let sps = make_sps(1, 1);
let pps = make_pps();
let sh = make_slice_header();
let mb = make_intra16x16_dc_mb(2); let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
128,
"mismatch at ({}, {}): expected DC=128",
x,
y
);
}
}
}
#[test]
fn intra_4x4_zero_residual_vertical_pred_from_top() {
let sps = make_sps(1, 2);
let pps = make_pps();
let sh = make_slice_header();
let mut luma = vec![0u32; 256];
for x in 0..16 {
luma[15 * 16 + x] = 50 + x as u32;
}
let cb = vec![128u32; 64];
let cr = vec![128u32; 64];
let mb_top = Macroblock {
mb_type: MbType::IPcm,
mb_type_raw: 25,
mb_pred: None,
sub_mb_pred: None,
pcm_samples: Some(PcmSamples {
luma,
chroma_cb: cb,
chroma_cr: cr,
}),
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: Vec::new(),
residual_chroma_dc_cr: Vec::new(),
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let mut pred = MbPred::default();
for i in 0..16 {
let left_col = matches!(i, 0 | 2 | 8 | 10);
if left_col {
pred.prev_intra4x4_pred_mode_flag[i] = false;
pred.rem_intra4x4_pred_mode[i] = 0;
} else {
pred.prev_intra4x4_pred_mode_flag[i] = true;
pred.rem_intra4x4_pred_mode[i] = 0;
}
}
pred.intra_chroma_pred_mode = 0; let mb_bot = Macroblock {
mb_type: MbType::INxN,
mb_type_raw: 0,
mb_pred: Some(pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: vec![0i32; 4],
residual_chroma_dc_cr: vec![0i32; 4],
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let slice_data = SliceData {
macroblocks: vec![mb_top, mb_bot],
mb_field_decoding_flags: vec![false, false],
last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid).unwrap();
for x in 0..16 {
assert_eq!(pic.luma_at(x, 15), (50 + x));
}
for y in 16..32 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
(50 + x),
"bottom MB at ({}, {}) should replicate the top row",
x,
y
);
}
}
}
#[test]
fn intra_8x8_zero_residual_dc_fallback_produces_dc_prediction() {
let sps = make_sps(1, 1);
let pps = make_pps();
let sh = make_slice_header();
let mut pred = MbPred::default();
for i in 0..4 {
pred.prev_intra8x8_pred_mode_flag[i] = true;
pred.rem_intra8x8_pred_mode[i] = 0;
}
pred.intra_chroma_pred_mode = 0;
let mb = Macroblock {
mb_type: MbType::INxN,
mb_type_raw: 0,
mb_pred: Some(pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0, transform_size_8x8_flag: true,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: vec![0i32; 4],
residual_chroma_dc_cr: vec![0i32; 4],
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
128,
"intra_8x8 DC with no neighbours and zero residual \
should produce 128 at ({}, {})",
x,
y
);
}
}
}
fn mk_intra4x4_info(mode: u8) -> MbInfo {
MbInfo {
available: true,
is_intra: true,
is_i_pcm: false,
is_intra_nxn: true,
mb_type_raw: 0,
transform_size_8x8_flag: false,
intra_4x4_pred_modes: [mode; 16],
..MbInfo::default()
}
}
fn mk_intra8x8_info(mode: u8) -> MbInfo {
MbInfo {
available: true,
is_intra: true,
is_i_pcm: false,
is_intra_nxn: true,
mb_type_raw: 0,
transform_size_8x8_flag: true,
intra_8x8_pred_modes: [mode; 4],
..MbInfo::default()
}
}
#[test]
fn intra_4x4_derivation_top_left_block0_with_no_neighbours_predicts_dc() {
let grid = MbGrid::new(1, 1);
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = true;
let mode = derive_intra_4x4_pred_mode(&grid, 0, 0, &pred, false, -1);
assert_eq!(mode, 2, "expected DC when both A and B are unavailable");
}
#[test]
fn intra_4x4_derivation_within_mb_uses_a_block_pred_mode() {
let mut grid = MbGrid::new(1, 1);
if let Some(info) = grid.get_mut(0) {
*info = mk_intra4x4_info(0);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[1] = false;
pred.rem_intra4x4_pred_mode[1] = 3;
let mode = derive_intra_4x4_pred_mode(&grid, 0, 1, &pred, false, -1);
assert_eq!(mode, 4);
}
#[test]
fn intra_4x4_derivation_rem_less_than_predicted_yields_rem() {
let mut grid = MbGrid::new(2, 1);
if let Some(info) = grid.get_mut(0) {
*info = mk_intra4x4_info(5);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = false;
pred.rem_intra4x4_pred_mode[0] = 0;
let mode = derive_intra_4x4_pred_mode(&grid, 1, 0, &pred, false, -1);
assert_eq!(mode, 0);
}
#[test]
fn intra_4x4_derivation_both_neighbours_intra_4x4_uses_min_of_modes() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(3) {
*info = mk_intra4x4_info(7);
}
if let Some(info) = grid.get_mut(1) {
*info = mk_intra4x4_info(3);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = false;
pred.rem_intra4x4_pred_mode[0] = 1;
let mode = derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1);
assert_eq!(mode, 1);
}
#[test]
fn intra_4x4_derivation_rem_equal_or_greater_than_predicted_adds_one() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(3) {
*info = mk_intra4x4_info(7);
}
if let Some(info) = grid.get_mut(1) {
*info = mk_intra4x4_info(3);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = false;
pred.rem_intra4x4_pred_mode[0] = 3;
assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1), 4);
pred.rem_intra4x4_pred_mode[0] = 5;
assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1), 6);
}
#[test]
fn intra_4x4_derivation_constrained_intra_pred_treats_inter_as_unavailable() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(3) {
info.available = true;
info.is_intra = false;
}
if let Some(info) = grid.get_mut(1) {
*info = mk_intra4x4_info(6);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = true; assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1), 2);
if let Some(info) = grid.get_mut(3) {
*info = mk_intra4x4_info(0);
}
assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1), 0);
assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, true, -1), 0);
if let Some(info) = grid.get_mut(3) {
info.available = true;
info.is_intra = false;
}
assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, true, -1), 2);
}
#[test]
fn intra_4x4_derivation_intra_16x16_neighbour_contributes_dc() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(3) {
info.available = true;
info.is_intra = true;
info.is_i_pcm = false;
info.is_intra_nxn = false; info.mb_type_raw = 4; info.intra_4x4_pred_modes = [5; 16]; }
if let Some(info) = grid.get_mut(1) {
*info = mk_intra4x4_info(7);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = true;
assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1), 2);
}
#[test]
fn intra_4x4_derivation_i_slice_raw5_is_intra_16x16_not_i_nxn() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(1) {
info.available = true;
info.is_intra = true;
info.is_i_pcm = false;
info.is_intra_nxn = false; info.mb_type_raw = 5; info.intra_4x4_pred_modes = [7; 16]; info.intra_8x8_pred_modes = [7; 4];
}
if let Some(info) = grid.get_mut(3) {
*info = mk_intra4x4_info(3);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = true;
assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1), 2);
}
#[test]
fn intra_4x4_derivation_intra_8x8_neighbour_uses_blk_shr_2() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(3) {
info.available = true;
info.is_intra = true;
info.is_i_pcm = false;
info.mb_type_raw = 0;
info.transform_size_8x8_flag = true;
info.intra_8x8_pred_modes = [1, 2, 3, 4];
}
if let Some(info) = grid.get_mut(1) {
*info = mk_intra4x4_info(6);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = true;
assert_eq!(derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1), 2);
}
#[test]
fn intra_8x8_derivation_top_left_blk_0_with_no_neighbours_predicts_dc() {
let grid = MbGrid::new(1, 1);
let mut pred = MbPred::default();
pred.prev_intra8x8_pred_mode_flag[0] = true;
assert_eq!(derive_intra_8x8_pred_mode(&grid, 0, 0, &pred, false, -1), 2);
}
#[test]
fn intra_8x8_derivation_both_neighbours_intra_8x8_uses_min_of_modes() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(3) {
*info = mk_intra8x8_info(3);
}
if let Some(info) = grid.get_mut(1) {
*info = mk_intra8x8_info(7);
}
let mut pred = MbPred::default();
pred.prev_intra8x8_pred_mode_flag[0] = true;
assert_eq!(derive_intra_8x8_pred_mode(&grid, 4, 0, &pred, false, -1), 3);
pred.prev_intra8x8_pred_mode_flag[0] = false;
pred.rem_intra8x8_pred_mode[0] = 2;
assert_eq!(derive_intra_8x8_pred_mode(&grid, 4, 0, &pred, false, -1), 2);
pred.rem_intra8x8_pred_mode[0] = 4;
assert_eq!(derive_intra_8x8_pred_mode(&grid, 4, 0, &pred, false, -1), 5);
}
#[test]
fn intra_8x8_derivation_intra_4x4_neighbour_uses_eq_8_72_sub_indices() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(3) {
*info = mk_intra4x4_info(0);
info.intra_4x4_pred_modes[5] = 4;
}
if let Some(info) = grid.get_mut(1) {
*info = mk_intra4x4_info(0);
info.intra_4x4_pred_modes[10] = 6;
}
let mut pred = MbPred::default();
pred.prev_intra8x8_pred_mode_flag[0] = true;
assert_eq!(derive_intra_8x8_pred_mode(&grid, 4, 0, &pred, false, -1), 4);
}
#[test]
fn inverse_scan_8x8_zigzag_roundtrip_matches_table_8_14() {
let mut scan = [0i32; 64];
scan[0] = 11;
scan[1] = 12;
scan[2] = 13;
scan[7] = 17;
scan[63] = 77;
let m = inverse_scan_8x8_zigzag(&scan);
assert_eq!(m[0], 11, "idx 0 → c00");
assert_eq!(m[1], 12, "idx 1 → c01");
assert_eq!(m[8], 13, "idx 2 → c10");
assert_eq!(m[8 + 2], 17, "idx 7 → c12");
assert_eq!(m[7 * 8 + 7], 77, "idx 63 → c77");
}
#[test]
fn deblocking_alters_intra_edge() {
let mut sps = make_sps(2, 1);
let _ = &mut sps;
let pps = make_pps();
let mut sh = make_slice_header();
sh.disable_deblocking_filter_idc = 0;
sh.slice_qp_delta = 20;
let left_luma = vec![100u32; 256];
let right_luma = vec![115u32; 256];
let cb = vec![128u32; 64];
let cr = vec![128u32; 64];
let mk = |luma: Vec<u32>| Macroblock {
mb_type: MbType::IPcm,
mb_type_raw: 25,
mb_pred: None,
sub_mb_pred: None,
pcm_samples: Some(PcmSamples {
luma,
chroma_cb: cb.clone(),
chroma_cr: cr.clone(),
}),
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: Vec::new(),
residual_chroma_dc_cr: Vec::new(),
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let mb0 = mk(left_luma);
let mb1 = mk(right_luma);
let slice_data = SliceData {
macroblocks: vec![mb0, mb1],
mb_field_decoding_flags: vec![false, false],
last_mb_addr: 1,
};
let mut pic_off = Picture::new(32, 16, 1, 8, 8);
let mut grid_off = MbGrid::new(2, 1);
let mut sh_off = sh.clone();
sh_off.disable_deblocking_filter_idc = 1; reconstruct_slice(
&slice_data,
&sh_off,
&sps,
&pps,
&NoRefs,
&mut pic_off,
&mut grid_off,
)
.unwrap();
let mut pic_on = Picture::new(32, 16, 1, 8, 8);
let mut grid_on = MbGrid::new(2, 1);
reconstruct_slice(
&slice_data,
&sh,
&sps,
&pps,
&NoRefs,
&mut pic_on,
&mut grid_on,
)
.unwrap();
let changed = pic_on.luma != pic_off.luma;
assert!(
changed,
"deblocking filter did not modify any samples, \
expected at least the edge column to change"
);
}
#[test]
fn deblock_444_chroma_uses_luma_style_filter_at_mb_edge() {
let pps = make_pps();
let mut pic = Picture::new(32, 16, 3, 8, 8);
assert_eq!(pic.chroma_width(), 32, "4:4:4 chroma is full-res");
assert_eq!(pic.chroma_height(), 16);
for y in 0..16 {
for x in 0..32 {
pic.set_luma(x, y, 110);
let v = if x < 16 { 100 } else { 116 };
pic.set_cb(x, y, v);
pic.set_cr(x, y, v);
}
}
let mut grid = MbGrid::new(2, 1);
for addr in 0..2u32 {
if let Some(info) = grid.get_mut(addr) {
*info = mk_intra4x4_info(2);
info.qp_y = 46;
}
}
let before_cb: Vec<i32> = pic.cb.clone();
let before_cr: Vec<i32> = pic.cr.clone();
deblock_plane_chroma_444(
&mut pic,
&grid,
0,
0,
8,
&pps,
false,
false,
&[false, false],
);
let cw = pic.chroma_width() as usize;
let edge_changed_cb = (0..16).any(|y| {
let row = y * cw;
pic.cb[row + 15] != before_cb[row + 15] || pic.cb[row + 16] != before_cb[row + 16]
});
let edge_changed_cr = (0..16).any(|y| {
let row = y * cw;
pic.cr[row + 15] != before_cr[row + 15] || pic.cr[row + 16] != before_cr[row + 16]
});
assert!(edge_changed_cb, "Cb chroma edge not filtered at 4:4:4");
assert!(edge_changed_cr, "Cr chroma edge not filtered at 4:4:4");
let interior_unchanged = (0..16).all(|y| {
let row = y * cw;
pic.cb[row + 1] == before_cb[row + 1]
});
assert!(
interior_unchanged,
"deblock altered a non-edge interior chroma column"
);
}
#[test]
fn deblock_444_chroma_skips_zero_bs_edge() {
let pps = make_pps();
let mut pic = Picture::new(32, 16, 3, 8, 8);
for y in 0..16 {
for x in 0..32 {
pic.set_luma(x, y, 110);
let v = if x < 16 { 100 } else { 116 };
pic.set_cb(x, y, v);
pic.set_cr(x, y, v);
}
}
let mut grid = MbGrid::new(2, 1);
for addr in 0..2u32 {
if let Some(info) = grid.get_mut(addr) {
info.available = true;
info.is_intra = false;
info.qp_y = 46;
info.luma_nonzero_4x4 = 0;
info.ref_idx_l0 = [0; 4];
info.ref_idx_l1 = [-1; 4];
info.ref_poc_l0 = [0; 4];
}
}
let before_cb = pic.cb.clone();
deblock_plane_chroma_444(
&mut pic,
&grid,
0,
0,
8,
&pps,
false,
false,
&[false, false],
);
assert_eq!(
pic.cb, before_cb,
"bS=0 chroma edge must not be filtered at 4:4:4"
);
}
fn make_p_slice_header() -> SliceHeader {
let mut sh = make_slice_header();
sh.slice_type_raw = 0;
sh.slice_type = SliceType::P;
sh
}
fn make_b_slice_header() -> SliceHeader {
let mut sh = make_slice_header();
sh.slice_type_raw = 1;
sh.slice_type = SliceType::B;
sh
}
fn make_ref_pic<F: FnMut(i32, i32) -> i32>(w: u32, h: u32, mut f: F) -> Picture {
let mut p = Picture::new(w, h, 1, 8, 8);
for y in 0..h as i32 {
for x in 0..w as i32 {
p.set_luma(x, y, f(x, y));
}
}
for y in 0..(h / 2) as i32 {
for x in 0..(w / 2) as i32 {
p.set_cb(x, y, 128);
p.set_cr(x, y, 128);
}
}
p
}
fn make_p_l0_16x16(ref_idx: u32, mvd: [i32; 2]) -> Macroblock {
let pred = MbPred {
ref_idx_l0: vec![ref_idx],
mvd_l0: vec![mvd],
..MbPred::default()
};
Macroblock {
mb_type: MbType::PL016x16,
mb_type_raw: 0,
mb_pred: Some(pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: vec![0i32; 4],
residual_chroma_dc_cr: vec![0i32; 4],
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
}
}
#[test]
fn p_skip_with_empty_refs_returns_missing_ref_pic() {
let sps = make_sps(1, 1);
let pps = make_pps();
let sh = make_p_slice_header();
let mb = Macroblock::new_skip(SliceType::P);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
let err = reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid)
.unwrap_err();
match err {
ReconstructError::MissingRefPic { list, idx } => {
assert_eq!(list, 0);
assert_eq!(idx, 0);
}
other => panic!("unexpected error: {:?}", other),
}
}
#[test]
fn p_l0_16x16_zero_mv_copies_reference_plane() {
let sps = make_sps(1, 1);
let pps = make_pps();
let sh = make_p_slice_header();
let ref_pic = make_ref_pic(16, 16, |x, y| (y * 16 + x) & 0xFF);
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
let expected = (y * 16 + x) & 0xFF;
assert_eq!(pic.luma_at(x, y), expected, "mismatch at ({}, {})", x, y);
}
}
}
#[test]
fn p_l0_16x16_half_pel_horizontal_mv_matches_6tap() {
let sps = make_sps(2, 2);
let pps = make_pps();
let sh = make_p_slice_header();
let mut ref_pic = Picture::new(32, 32, 1, 8, 8);
for (i, v) in [10, 20, 30, 40, 50, 60].iter().enumerate() {
ref_pic.set_luma(8 + i as i32, 8, *v);
}
for y in 0..16 {
for x in 0..16 {
ref_pic.set_cb(x, y, 128);
ref_pic.set_cr(x, y, 128);
}
}
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb = make_p_l0_16x16(0, [2, 0]);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
assert_eq!(pic.luma_at(10, 8), 35);
}
#[test]
fn p_8x8_with_b_direct_sub_partitions_runs_without_error() {
let sps = make_sps(1, 1);
let pps = make_pps();
let sh = make_p_slice_header();
let ref_pic = make_ref_pic(16, 16, |_, _| 50);
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mut smp = SubMbPred::default();
for i in 0..4 {
smp.sub_mb_type[i] = SubMbType::BDirect8x8;
smp.ref_idx_l0[i] = 0;
smp.ref_idx_l1[i] = 0;
smp.mvd_l0[i] = vec![[0, 0]];
smp.mvd_l1[i] = vec![[0, 0]];
}
let mb = Macroblock {
mb_type: MbType::P8x8,
mb_type_raw: 3,
mb_pred: None,
sub_mb_pred: Some(smp),
pcm_samples: None,
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: vec![0i32; 4],
residual_chroma_dc_cr: vec![0i32; 4],
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(pic.luma_at(x, y), 50);
}
}
}
#[test]
fn b_skip_with_two_refs_runs_direct_mode() {
let sps = make_sps(1, 1);
let pps = make_pps();
let sh = make_b_slice_header();
let l0 = make_ref_pic(16, 16, |_, _| 80);
let l1 = make_ref_pic(16, 16, |_, _| 120);
let mut store = RefPicStore::new();
store.insert(0, l0);
store.insert(1, l1);
store.set_list_0(vec![0]);
store.set_list_1(vec![1]);
let mb = Macroblock::new_skip(SliceType::B);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
let v = pic.luma_at(x, y);
assert!(
(95..=105).contains(&v),
"expected ~100 at ({}, {}), got {}",
x,
y,
v
);
}
}
}
#[test]
fn p_l0_l0_16x8_bottom_partition_reads_top_partition_mv_from_same_mb() {
let sps = make_sps(1, 2);
let pps = make_pps();
let sh = make_p_slice_header();
let ref_pic = make_ref_pic(32, 32, |x, _| (x * 2) & 0xFF);
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let pred = crate::macroblock_layer::MbPred {
ref_idx_l0: vec![0, 0],
mvd_l0: vec![[16, 0], [0, 0]],
..crate::macroblock_layer::MbPred::default()
};
let mb = Macroblock {
mb_type: MbType::PL0L016x8,
mb_type_raw: 1,
mb_pred: Some(pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: vec![0i32; 4],
residual_chroma_dc_cr: vec![0i32; 4],
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let mb1 = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb, mb1],
mb_field_decoding_flags: vec![false, false],
last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
assert_eq!(
pic.luma_at(0, 8),
8,
"bottom partition did not inherit top partition MV \
(same-MB neighbour read regression)"
);
assert_eq!(pic.luma_at(0, 0), 8);
}
#[test]
fn p_l0_16x16_with_zero_mvd_and_ref0_does_not_apply_p_skip_zero_substitution() {
let sps = make_sps(2, 1);
let pps = make_pps();
let sh = make_p_slice_header();
let ref_pic = make_ref_pic(64, 16, |x, _| (x * 2) & 0xff);
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb0 = make_p_l0_16x16(0, [3, 4]);
let mb1 = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb0, mb1],
mb_field_decoding_flags: vec![false, false],
last_mb_addr: 1,
};
let mut pic = Picture::new(32, 16, 1, 8, 8);
let mut grid = MbGrid::new(2, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
let info1 = grid.get(1).unwrap();
for blk4 in 0..16 {
assert_eq!(
info1.mv_l0[blk4],
(3i16, 4i16),
"MB 1's blk{blk4} MV should inherit MB 0's MV via median, \
NOT be forced to zero by the P_Skip zero-MV substitution",
);
}
}
#[test]
fn inter_mb_records_mv_and_ref_idx_in_grid() {
let sps = make_sps(2, 1);
let pps = make_pps();
let sh = make_p_slice_header();
let ref_pic = make_ref_pic(32, 16, |_, _| 50);
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb0 = make_p_l0_16x16(0, [4, 8]);
let mb1 = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb0, mb1],
mb_field_decoding_flags: vec![false, false],
last_mb_addr: 1,
};
let mut pic = Picture::new(32, 16, 1, 8, 8);
let mut grid = MbGrid::new(2, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
let info0 = grid.get(0).unwrap();
assert!(info0.available);
assert!(!info0.is_intra);
assert_eq!(info0.ref_idx_l0, [0, 0, 0, 0]);
for blk4 in 0..16 {
assert_eq!(info0.mv_l0[blk4], (4i16, 8i16));
}
}
fn make_pwt_single_entry(
log2wd_y: u32,
log2wd_c: u32,
l0: Option<(i32, i32)>,
l1: Option<(i32, i32)>,
chroma_l0: Option<(i32, i32, i32, i32)>,
chroma_l1: Option<(i32, i32, i32, i32)>,
) -> PredWeightTable {
PredWeightTable {
luma_log2_weight_denom: log2wd_y,
chroma_log2_weight_denom: log2wd_c,
luma_weights_l0: vec![l0],
chroma_weights_l0: vec![chroma_l0.map(|(a, b, c, d)| [(a, b), (c, d)])],
luma_weights_l1: vec![l1],
chroma_weights_l1: vec![chroma_l1.map(|(a, b, c, d)| [(a, b), (c, d)])],
}
}
#[test]
fn p_l0_16x16_explicit_weighted_applies_formula() {
let sps = make_sps(1, 1);
let mut pps = make_pps();
pps.weighted_pred_flag = true;
let mut sh = make_p_slice_header();
sh.pred_weight_table = Some(make_pwt_single_entry(
4,
0,
Some((16, 4)),
None,
Some((1 << 0, 0, 1 << 0, 0)),
None,
));
let ref_pic = make_ref_pic(16, 16, |_, _| 10);
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
14,
"expected 14 at ({}, {}), got {}",
x,
y,
pic.luma_at(x, y)
);
}
}
}
#[test]
fn p_l0_16x16_weighted_pred_flag_off_matches_default() {
let sps = make_sps(1, 1);
let mut pps = make_pps();
pps.weighted_pred_flag = false;
let mut sh = make_p_slice_header();
sh.pred_weight_table = Some(make_pwt_single_entry(
4,
0,
Some((32, 10)), None,
Some((1 << 0, 0, 1 << 0, 0)),
None,
));
let ref_pic = make_ref_pic(16, 16, |x, y| (y * 16 + x) & 0xFF);
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
let expected = (y * 16 + x) & 0xFF;
assert_eq!(
pic.luma_at(x, y),
expected,
"weighted_pred_flag=off must copy ref; mismatch at ({}, {})",
x,
y
);
}
}
}
fn make_b_bi_16x16(ref_idx_l0: u32, ref_idx_l1: u32) -> Macroblock {
let pred = MbPred {
ref_idx_l0: vec![ref_idx_l0],
ref_idx_l1: vec![ref_idx_l1],
mvd_l0: vec![[0, 0]],
mvd_l1: vec![[0, 0]],
..MbPred::default()
};
Macroblock {
mb_type: MbType::BBi16x16,
mb_type_raw: 3,
mb_pred: Some(pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: vec![0i32; 4],
residual_chroma_dc_cr: vec![0i32; 4],
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
}
}
#[test]
fn b_bi_16x16_explicit_weighted_applies_bipred_formula() {
let sps = make_sps(1, 1);
let mut pps = make_pps();
pps.weighted_bipred_idc = 1;
let mut sh = make_b_slice_header();
sh.num_ref_idx_l0_active_minus1 = 0;
sh.num_ref_idx_l1_active_minus1 = 0;
sh.pred_weight_table = Some(make_pwt_single_entry(
1,
0,
Some((1, 0)),
Some((1, 0)),
Some((1 << 0, 0, 1 << 0, 0)),
Some((1 << 0, 0, 1 << 0, 0)),
));
let l0 = make_ref_pic(16, 16, |_, _| 40);
let l1 = make_ref_pic(16, 16, |_, _| 80);
let mut store = RefPicStore::new();
store.insert(0, l0);
store.insert(1, l1);
store.set_list_0(vec![0]);
store.set_list_1(vec![1]);
let mb = make_b_bi_16x16(0, 0);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
30,
"expected 30 at ({}, {}), got {}",
x,
y,
pic.luma_at(x, y)
);
}
}
}
#[test]
fn b_bi_16x16_implicit_bipred_idc_td_zero_falls_back_to_equal_weights() {
let sps = make_sps(1, 1);
let mut pps = make_pps();
pps.weighted_bipred_idc = 2; let sh = make_b_slice_header();
let l0 = make_ref_pic(16, 16, |_, _| 80);
let l1 = make_ref_pic(16, 16, |_, _| 120);
let mut store = RefPicStore::new();
store.insert(0, l0);
store.insert(1, l1);
store.set_list_0(vec![0]);
store.set_list_1(vec![1]);
let mb = make_b_bi_16x16(0, 0);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
100,
"expected 100 (implicit w0=w1=32 via td=0 fallback) at \
({}, {}), got {}",
x,
y,
pic.luma_at(x, y)
);
}
}
}
#[test]
fn b_bi_16x16_implicit_bipred_idc_poc_midpoint_applies_equal_weights() {
let sps = make_sps(1, 1);
let mut pps = make_pps();
pps.weighted_bipred_idc = 2;
let sh = make_b_slice_header();
let mut l0 = make_ref_pic(16, 16, |_, _| 40);
l0.pic_order_cnt = 0;
let mut l1 = make_ref_pic(16, 16, |_, _| 120);
l1.pic_order_cnt = 8;
let mut store = RefPicStore::new();
store.insert(0, l0);
store.insert(1, l1);
store.set_list_0(vec![0]);
store.set_list_1(vec![1]);
let mb = make_b_bi_16x16(0, 0);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
pic.pic_order_cnt = 4;
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
80,
"expected 80 (implicit midpoint w0=w1=32) at ({}, {}), got {}",
x,
y,
pic.luma_at(x, y)
);
}
}
}
#[test]
fn b_bi_16x16_implicit_bipred_idc_asymmetric_poc_weights_lean_on_closer_ref() {
let sps = make_sps(1, 1);
let mut pps = make_pps();
pps.weighted_bipred_idc = 2;
let sh = make_b_slice_header();
let mut l0 = make_ref_pic(16, 16, |_, _| 100);
l0.pic_order_cnt = 0;
let mut l1 = make_ref_pic(16, 16, |_, _| 20);
l1.pic_order_cnt = 8;
let mut store = RefPicStore::new();
store.insert(0, l0);
store.insert(1, l1);
store.set_list_0(vec![0]);
store.set_list_1(vec![1]);
let mb = make_b_bi_16x16(0, 0);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
pic.pic_order_cnt = 2;
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
80,
"expected 80 (implicit asymmetric w0=48, w1=16) at \
({}, {}), got {}",
x,
y,
pic.luma_at(x, y)
);
}
}
}
#[test]
fn implicit_bipred_weights_td_zero_returns_equal_weights() {
assert_eq!(
implicit_bipred_weights(0, 0, 0, false),
(32, 32, 5),
"td == 0 should yield w0 = w1 = 32"
);
}
#[test]
fn implicit_bipred_weights_long_term_returns_equal_weights() {
assert_eq!(
implicit_bipred_weights(4, 0, 8, true),
(32, 32, 5),
"long-term ref should yield w0 = w1 = 32"
);
}
#[test]
fn implicit_bipred_weights_midpoint_yields_equal_weights() {
assert_eq!(
implicit_bipred_weights(4, 0, 8, false),
(32, 32, 5),
"midpoint → equal weights"
);
}
#[test]
fn implicit_bipred_weights_hand_computed_asymmetric() {
assert_eq!(
implicit_bipred_weights(2, 0, 8, false),
(48, 16, 5),
"curr closer to L0 (tb=2, td=8)"
);
assert_eq!(
implicit_bipred_weights(6, 0, 8, false),
(16, 48, 5),
"curr closer to L1 (tb=6, td=8)"
);
}
#[test]
fn implicit_bipred_weights_extrapolation_out_of_band_falls_back() {
assert_eq!(
implicit_bipred_weights(-100, 0, 8, false),
(32, 32, 5),
"out-of-band DistScaleFactor should fall back to equal weights"
);
}
fn make_mbaff_sps(w_mbs: u32, h_map_units: u32) -> Sps {
let mut sps = make_sps(w_mbs, h_map_units);
sps.mb_adaptive_frame_field_flag = true;
sps.frame_mbs_only_flag = false;
sps
}
#[test]
fn reconstruct_slice_accepts_mbaff_frames() {
let sps = make_mbaff_sps(1, 1); let pps = make_pps();
let sh = make_slice_header();
let mb0 = make_empty_ipcm_mb();
let mb1 = make_empty_ipcm_mb();
let slice_data = SliceData {
macroblocks: vec![mb0, mb1],
mb_field_decoding_flags: vec![false, false], last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid)
.expect("MBAFF frame reconstruction must no longer error");
}
#[test]
fn reconstruct_mbaff_frame_mb_pair_places_samples_in_stacked_layout() {
let sps = make_mbaff_sps(1, 1);
let pps = make_pps();
let sh = make_slice_header();
let mut mb_top = make_empty_ipcm_mb();
let mut mb_bot = make_empty_ipcm_mb();
for v in mb_top.pcm_samples.as_mut().unwrap().luma.iter_mut() {
*v = 10;
}
for v in mb_bot.pcm_samples.as_mut().unwrap().luma.iter_mut() {
*v = 20;
}
let slice_data = SliceData {
macroblocks: vec![mb_top, mb_bot],
mb_field_decoding_flags: vec![false, false],
last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid).unwrap();
for y in 0..16 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
10,
"frame-MB pair top: expected 10 at ({}, {})",
x,
y
);
}
}
for y in 16..32 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
20,
"frame-MB pair bottom: expected 20 at ({}, {})",
x,
y
);
}
}
}
#[test]
fn reconstruct_mbaff_field_mb_pair_does_not_error() {
let sps = make_mbaff_sps(1, 1);
let pps = make_pps();
let sh = make_slice_header();
let mb_top = make_empty_ipcm_mb();
let mb_bot = make_empty_ipcm_mb();
let slice_data = SliceData {
macroblocks: vec![mb_top, mb_bot],
mb_field_decoding_flags: vec![true, true], last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid)
.expect("MBAFF field pair reconstruction must no longer error");
}
#[test]
fn reconstruct_mbaff_intra16x16_pair_dc_fills_both_mbs() {
let sps = make_mbaff_sps(1, 1);
let pps = make_pps();
let sh = make_slice_header();
let mb_top = make_intra16x16_dc_mb(2);
let mb_bot = make_intra16x16_dc_mb(2);
let slice_data = SliceData {
macroblocks: vec![mb_top, mb_bot],
mb_field_decoding_flags: vec![false, false],
last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid).unwrap();
for y in 0..32 {
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
128,
"intra-16x16 DC fallback in MBAFF: expected 128 at ({}, {})",
x,
y
);
}
}
}
#[test]
fn mbaff_field_mb_y_stride_interleaves_top_and_bottom() {
let sps = make_mbaff_sps(1, 1);
let pps = make_pps();
let sh = make_slice_header();
let mut mb_top = make_empty_ipcm_mb();
let mut mb_bot = make_empty_ipcm_mb();
for v in mb_top.pcm_samples.as_mut().unwrap().luma.iter_mut() {
*v = 10;
}
for v in mb_bot.pcm_samples.as_mut().unwrap().luma.iter_mut() {
*v = 20;
}
let slice_data = SliceData {
macroblocks: vec![mb_top, mb_bot],
mb_field_decoding_flags: vec![true, true], last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid).unwrap();
for k in 0..16 {
let y = k * 2;
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
10,
"field-MB top: sample (x={}, y={}) (row k={}) expected 10",
x,
y,
k
);
}
}
for k in 0..16 {
let y = k * 2 + 1;
for x in 0..16 {
assert_eq!(
pic.luma_at(x, y),
20,
"field-MB bottom: sample (x={}, y={}) (row k={}) expected 20",
x,
y,
k
);
}
}
}
#[test]
fn mbaff_field_mb_writer_places_samples_per_eq_6_10() {
let grid = MbGrid::new(1, 2);
let mut pic = Picture::new(16, 32, 1, 8, 8);
let (mb_px_top, mb_py_top) = mb_sample_origin(&grid, 0, true, true);
assert_eq!((mb_px_top, mb_py_top), (0, 0), "top-of-pair field origin");
let w_top = MbWriter::new(&grid, 0, mb_px_top, mb_py_top, true, true, 1);
w_top.set_luma(&mut pic, 0, 0, 111);
w_top.set_luma(&mut pic, 0, 1, 222);
let (mb_px_bot, mb_py_bot) = mb_sample_origin(&grid, 1, true, true);
assert_eq!((mb_px_bot, mb_py_bot), (0, 1), "bot-of-pair field origin");
let w_bot = MbWriter::new(&grid, 1, mb_px_bot, mb_py_bot, true, true, 1);
w_bot.set_luma(&mut pic, 0, 0, 77);
assert_eq!(pic.luma_at(0, 0), 111, "top MB (0, 0) → picture (0, 0)");
assert_eq!(pic.luma_at(0, 2), 222, "top MB (0, 1) → picture (0, 2)");
assert_eq!(pic.luma_at(0, 1), 77, "bot MB (0, 0) → picture (0, 1)");
}
#[test]
fn mbaff_deblocking_completes_without_panic() {
let sps = make_mbaff_sps(1, 1);
let pps = make_pps();
let mut sh = make_slice_header();
sh.disable_deblocking_filter_idc = 0; sh.slice_qp_delta = 20; let mb_top = make_intra16x16_dc_mb(2);
let mb_bot = make_intra16x16_dc_mb(2);
let slice_data = SliceData {
macroblocks: vec![mb_top, mb_bot],
mb_field_decoding_flags: vec![false, false],
last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid)
.expect("MBAFF + deblocking must complete without error");
}
#[test]
fn mbaff_field_pair_deblocking_completes_without_panic() {
let sps = make_mbaff_sps(1, 1);
let pps = make_pps();
let mut sh = make_slice_header();
sh.disable_deblocking_filter_idc = 0;
sh.slice_qp_delta = 20;
let mb_top = make_intra16x16_dc_mb(2);
let mb_bot = make_intra16x16_dc_mb(2);
let slice_data = SliceData {
macroblocks: vec![mb_top, mb_bot],
mb_field_decoding_flags: vec![true, true], last_mb_addr: 1,
};
let mut pic = Picture::new(16, 32, 1, 8, 8);
let mut grid = MbGrid::new(1, 2);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid)
.expect("MBAFF field pair + deblocking must complete without error");
}
#[test]
fn constrained_intra_pred_flag_forces_inter_neighbour_to_dc() {
let mut grid = MbGrid::new(3, 3);
if let Some(info) = grid.get_mut(3) {
info.available = true;
info.is_intra = false;
}
if let Some(info) = grid.get_mut(1) {
*info = mk_intra4x4_info(0);
}
let mut pred = MbPred::default();
pred.prev_intra4x4_pred_mode_flag[0] = true;
let mode_unconstrained = derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, false, -1);
assert_eq!(
mode_unconstrained, 0,
"flag=0: inter-left → mode 2; top=0 → min = 0"
);
let mode_constrained = derive_intra_4x4_pred_mode(&grid, 4, 0, &pred, true, -1);
assert_eq!(
mode_constrained, 2,
"flag=1: inter-left unavailable → DC fallback (mode 2)"
);
assert_ne!(
mode_constrained, mode_unconstrained,
"constrained_intra_pred_flag must change the outcome here"
);
}
#[test]
fn constrained_intra_pred_flag_plumbed_through_pps() {
let sps = make_sps(1, 1);
let mut pps_on = make_pps();
pps_on.constrained_intra_pred_flag = true;
let pps_off = make_pps();
let sh = make_slice_header();
let build_nxn_mb = || {
let mut pred = MbPred::default();
for i in 0..16 {
pred.prev_intra4x4_pred_mode_flag[i] = true;
pred.rem_intra4x4_pred_mode[i] = 0;
}
pred.intra_chroma_pred_mode = 0;
Macroblock {
mb_type: MbType::INxN,
mb_type_raw: 0,
mb_pred: Some(pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: vec![0i32; 4],
residual_chroma_dc_cr: vec![0i32; 4],
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
}
};
for pps in [&pps_off, &pps_on] {
let slice_data = SliceData {
macroblocks: vec![build_nxn_mb()],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, pps, &NoRefs, &mut pic, &mut grid).unwrap();
}
}
#[test]
fn gather_samples_4x4_top_right_unavailable_for_scan_boundary_blocks() {
let pic = Picture::new(64, 32, 1, 8, 8);
let xy = super::LUMA_4X4_XY;
let grid = crate::mb_grid::MbGrid::new(4, 2);
for (idx, (bx, by)) in xy.iter().copied().enumerate() {
let s = super::gather_samples_4x4(&pic, &grid, bx, by, idx, -1, false, 0, bx, by);
let expected = match idx {
3 | 7 | 11 | 13 | 15 => false,
_ if by == 0 => false,
_ => true,
};
assert_eq!(
s.availability.top_right, expected,
"blk {} (bx={}, by={}) expected tr={} got {}",
idx, bx, by, expected, s.availability.top_right
);
}
}
#[test]
fn gather_samples_8x8_top_right_unavailable_for_blk3() {
let pic = Picture::new(64, 32, 1, 8, 8);
let grid = crate::mb_grid::MbGrid::new(4, 2);
let s = super::gather_samples_8x8(&pic, &grid, 8, 8, 3, -1, false, 0, 8, 8);
assert!(
!s.availability.top_right,
"blk8 3: tr should be false (right-neighbour MB not decoded)"
);
let s2 = super::gather_samples_8x8(&pic, &grid, 0, 8, 2, -1, false, 0, 0, 8);
assert!(s2.availability.top_right);
}
#[test]
fn intra4x4_residual_indexing_respects_cbp_luma_gaps() {
use crate::macroblock_layer::{Macroblock, MbPred, MbType};
use crate::mb_grid::MbGrid;
use crate::slice_data::SliceData;
let sps = make_sps(1, 1);
let pps = make_pps();
let sh = make_slice_header();
for (cbp, residual_first_at_block) in [(0x7u8, 0usize), (0xEu8, 4usize)] {
let mut first_entry = [0i32; 16];
first_entry[0] = 4; let n = (cbp & 0x0F).count_ones() as usize * 4;
let mut residual_luma = Vec::with_capacity(n);
for i in 0..n {
if i == 0 {
residual_luma.push(first_entry);
} else {
residual_luma.push([0i32; 16]);
}
}
let mb_pred = MbPred {
prev_intra4x4_pred_mode_flag: [true; 16],
rem_intra4x4_pred_mode: [0; 16],
intra_chroma_pred_mode: 0,
..MbPred::default()
};
let mb = Macroblock {
mb_type: MbType::INxN,
mb_type_raw: 0,
mb_pred: Some(mb_pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: cbp as u32,
transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma,
residual_luma_dc: None,
residual_chroma_dc_cb: Vec::new(),
residual_chroma_dc_cr: Vec::new(),
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 1, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid).unwrap();
let (bx, by) = super::LUMA_4X4_XY[residual_first_at_block];
let mut sum = 0i32;
for y in 0..4 {
for x in 0..4 {
sum += pic.luma_at(bx + x, by + y);
}
}
let flat_dc_sum = 128 * 16;
assert_ne!(
sum, flat_dc_sum,
"cbp=0x{:x} block {} should have non-flat output \
(residual_first_at_block stored in residual_luma[0] must route there)",
cbp, residual_first_at_block,
);
if residual_first_at_block != 0 {
let (ox, oy) = super::LUMA_4X4_XY[0];
let mut other_sum = 0i32;
for y in 0..4 {
for x in 0..4 {
other_sum += pic.luma_at(ox + x, oy + y);
}
}
assert_eq!(
other_sum, flat_dc_sum,
"cbp=0x{:x} block 0 should be flat DC=128 (cbp bit 0 clear)",
cbp,
);
}
}
}
#[test]
fn intra_4x4_zero_residual_dc_reconstructs_444_chroma() {
let mut sps = make_sps(1, 1);
sps.chroma_format_idc = 3; assert_eq!(sps.chroma_array_type(), 3);
let pps = make_pps();
let sh = make_slice_header();
let mut pred = MbPred::default();
for i in 0..16 {
pred.prev_intra4x4_pred_mode_flag[i] = true;
pred.rem_intra4x4_pred_mode[i] = 0;
}
pred.intra_chroma_pred_mode = 0;
let mb = Macroblock {
mb_type: MbType::INxN,
mb_type_raw: 0,
mb_pred: Some(pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0, transform_size_8x8_flag: false,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: Vec::new(),
residual_chroma_dc_cr: Vec::new(),
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 3, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid)
.expect("4:4:4 I_NxN must reconstruct (no longer rejected)");
for y in 0..16 {
for x in 0..16 {
assert_eq!(pic.luma_at(x, y), 128, "luma DC at ({x},{y})");
assert_eq!(pic.cb_at(x, y), 128, "Cb DC at ({x},{y})");
assert_eq!(pic.cr_at(x, y), 128, "Cr DC at ({x},{y})");
}
}
}
#[test]
fn intra_8x8_zero_residual_dc_reconstructs_444_chroma() {
let mut sps = make_sps(1, 1);
sps.chroma_format_idc = 3;
let pps = make_pps();
let sh = make_slice_header();
let mut pred = MbPred::default();
for i in 0..4 {
pred.prev_intra8x8_pred_mode_flag[i] = true;
pred.rem_intra8x8_pred_mode[i] = 0;
}
pred.intra_chroma_pred_mode = 0;
let mb = Macroblock {
mb_type: MbType::INxN,
mb_type_raw: 0,
mb_pred: Some(pred),
sub_mb_pred: None,
pcm_samples: None,
coded_block_pattern: 0,
transform_size_8x8_flag: true,
mb_qp_delta: 0,
residual_luma: Vec::new(),
residual_luma_dc: None,
residual_chroma_dc_cb: Vec::new(),
residual_chroma_dc_cr: Vec::new(),
residual_chroma_ac_cb: Vec::new(),
residual_chroma_ac_cr: Vec::new(),
residual_cb_luma_like: Vec::new(),
residual_cr_luma_like: Vec::new(),
residual_cb_16x16_dc: None,
residual_cr_16x16_dc: None,
is_skip: false,
};
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 3, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &NoRefs, &mut pic, &mut grid)
.expect("4:4:4 Intra_8x8 must reconstruct");
for y in 0..16 {
for x in 0..16 {
assert_eq!(pic.cb_at(x, y), 128, "Cb DC at ({x},{y})");
assert_eq!(pic.cr_at(x, y), 128, "Cr DC at ({x},{y})");
}
}
}
#[test]
fn mc_chroma_444_is_bit_identical_to_luma_mc() {
let mut ref_pic = Picture::new(32, 32, 3, 8, 8);
for y in 0..32i32 {
for x in 0..32i32 {
let v = (y * 13 + x * 7) & 0xFF;
ref_pic.set_luma(x, y, v);
ref_pic.set_cb(x, y, v);
ref_pic.set_cr(x, y, v);
}
}
let mv = Mv { x: 6, y: 10 };
let (w, h) = (16u32, 16u32);
let (px, py) = (0i32, 0i32);
let mut luma = vec![0i32; (w * h) as usize];
mc_luma_partition(&ref_pic, px, py, mv, w, h, 8, &mut luma, None).unwrap();
let mut cb = vec![0i32; (w * h) as usize];
let mut cr = vec![0i32; (w * h) as usize];
mc_chroma_partition_444(&ref_pic, px, py, mv, w, h, 8, &mut cb, &mut cr, None).unwrap();
assert_eq!(cb, luma, "4:4:4 Cb MC must match luma MC bit-for-bit");
assert_eq!(cr, luma, "4:4:4 Cr MC must match luma MC bit-for-bit");
}
#[test]
fn p_l0_16x16_444_zero_mv_copies_chroma_planes() {
let mut sps = make_sps(1, 1);
sps.profile_idc = 244;
sps.chroma_format_idc = 3;
let pps = make_pps();
let sh = make_p_slice_header();
let mut ref_pic = Picture::new(16, 16, 3, 8, 8);
for y in 0..16i32 {
for x in 0..16i32 {
ref_pic.set_luma(x, y, (y * 16 + x) & 0xFF);
ref_pic.set_cb(x, y, (y * 3 + x * 5 + 17) & 0xFF);
ref_pic.set_cr(x, y, (y * 7 + x * 2 + 40) & 0xFF);
}
}
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 3, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid)
.expect("4:4:4 P_L0_16x16 must reconstruct");
for y in 0..16i32 {
for x in 0..16i32 {
assert_eq!(pic.luma_at(x, y), (y * 16 + x) & 0xFF, "Y at ({x},{y})");
assert_eq!(
pic.cb_at(x, y),
(y * 3 + x * 5 + 17) & 0xFF,
"Cb at ({x},{y})"
);
assert_eq!(
pic.cr_at(x, y),
(y * 7 + x * 2 + 40) & 0xFF,
"Cr at ({x},{y})"
);
}
}
}
#[test]
fn p_l0_16x16_444_half_pel_chroma_uses_luma_6tap() {
let mut sps = make_sps(2, 2);
sps.profile_idc = 244;
sps.chroma_format_idc = 3;
let pps = make_pps();
let sh = make_p_slice_header();
let mut ref_pic = Picture::new(32, 32, 3, 8, 8);
for (i, v) in [10, 20, 30, 40, 50, 60].iter().enumerate() {
ref_pic.set_luma(8 + i as i32, 8, *v);
ref_pic.set_cb(8 + i as i32, 8, *v);
ref_pic.set_cr(8 + i as i32, 8, *v);
}
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb = make_p_l0_16x16(0, [2, 0]); let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 3, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid)
.expect("4:4:4 half-pel P_L0_16x16 must reconstruct");
assert_eq!(pic.luma_at(10, 8), 35, "Y half-pel");
assert_eq!(pic.cb_at(10, 8), 35, "Cb half-pel (luma 6-tap)");
assert_eq!(pic.cr_at(10, 8), 35, "Cr half-pel (luma 6-tap)");
}
#[test]
fn mc_chroma_422_vertical_quarter_pel_uses_subheightc_1() {
let w = 32u32;
let h = 32u32;
let mut ref_pic = Picture::new(w, h, 2, 8, 8);
assert_eq!(ref_pic.chroma_width(), 16, "4:2:2 chroma is W/2");
assert_eq!(ref_pic.chroma_height(), 32, "4:2:2 chroma is full H");
for y in 0..ref_pic.chroma_height() as i32 {
for x in 0..ref_pic.chroma_width() as i32 {
ref_pic.set_cb(x, y, 16 + 8 * y);
ref_pic.set_cr(x, y, 16 + 8 * y);
}
}
let mv = Mv { x: 0, y: 3 };
let (cw, ch) = (4u32, 4u32);
let (c_part_x, c_part_y) = (2i32, 2i32);
let mut cb = vec![0i32; (cw * ch) as usize];
let mut cr = vec![0i32; (cw * ch) as usize];
mc_chroma_partition(
&ref_pic, c_part_x, c_part_y, mv, cw, ch, 2, 8, &mut cb, &mut cr, None,
)
.unwrap();
for yy in 0..ch as i32 {
for xx in 0..cw as i32 {
let base = 16 + 8 * (c_part_y + yy);
let exp = base + 6; assert_eq!(
cb[(yy * cw as i32 + xx) as usize],
exp,
"Cb 4:2:2 vertical 1/4-pel at ({xx},{yy})"
);
assert_eq!(cr[(yy * cw as i32 + xx) as usize], exp, "Cr 4:2:2");
}
}
}
#[test]
fn mc_chroma_422_horizontal_quarter_pel_matches_420_x_rule() {
let w = 32u32;
let h = 32u32;
let mut ref_pic = Picture::new(w, h, 2, 8, 8);
for y in 0..ref_pic.chroma_height() as i32 {
for x in 0..ref_pic.chroma_width() as i32 {
ref_pic.set_cb(x, y, 16 + 8 * x);
ref_pic.set_cr(x, y, 16 + 8 * x);
}
}
let mv = Mv { x: 3, y: 0 };
let (cw, ch) = (4u32, 4u32);
let (c_part_x, c_part_y) = (2i32, 2i32);
let mut cb = vec![0i32; (cw * ch) as usize];
let mut cr = vec![0i32; (cw * ch) as usize];
mc_chroma_partition(
&ref_pic, c_part_x, c_part_y, mv, cw, ch, 2, 8, &mut cb, &mut cr, None,
)
.unwrap();
for yy in 0..ch as i32 {
for xx in 0..cw as i32 {
let base = 16 + 8 * (c_part_x + xx);
let exp = base + 3; assert_eq!(
cb[(yy * cw as i32 + xx) as usize],
exp,
"Cb 4:2:2 horizontal 1/4-pel at ({xx},{yy})"
);
}
}
}
#[test]
fn p_l0_16x16_422_zero_mv_copies_full_height_chroma() {
let mut sps = make_sps(1, 1);
sps.profile_idc = 122;
sps.chroma_format_idc = 2;
let pps = make_pps();
let sh = make_p_slice_header();
let mut ref_pic = Picture::new(16, 16, 2, 8, 8);
assert_eq!(ref_pic.chroma_width(), 8);
assert_eq!(ref_pic.chroma_height(), 16);
for y in 0..16i32 {
for x in 0..16i32 {
ref_pic.set_luma(x, y, (y * 16 + x) & 0xFF);
}
}
for y in 0..16i32 {
for x in 0..8i32 {
ref_pic.set_cb(x, y, (y * 5 + x * 3 + 17) & 0xFF);
ref_pic.set_cr(x, y, (y * 2 + x * 7 + 40) & 0xFF);
}
}
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 2, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid)
.expect("4:2:2 P_L0_16x16 must reconstruct");
for y in 0..16i32 {
for x in 0..8i32 {
assert_eq!(
pic.cb_at(x, y),
(y * 5 + x * 3 + 17) & 0xFF,
"Cb 4:2:2 at ({x},{y})"
);
assert_eq!(
pic.cr_at(x, y),
(y * 2 + x * 7 + 40) & 0xFF,
"Cr 4:2:2 at ({x},{y})"
);
}
}
}
#[test]
fn p_l0_16x16_422_explicit_weighted_per_plane_chroma() {
let mut sps = make_sps(1, 1);
sps.profile_idc = 122;
sps.chroma_format_idc = 2;
let mut pps = make_pps();
pps.weighted_pred_flag = true;
let mut sh = make_p_slice_header();
sh.pred_weight_table = Some(make_pwt_single_entry(
0,
2,
Some((1 << 0, 0)),
None,
Some((5, 1, 3, 7)),
None,
));
let mut ref_pic = Picture::new(16, 16, 2, 8, 8);
for y in 0..16i32 {
for x in 0..16i32 {
ref_pic.set_luma(x, y, 30);
}
}
for y in 0..16i32 {
for x in 0..8i32 {
ref_pic.set_cb(x, y, 80);
ref_pic.set_cr(x, y, 80);
}
}
let mut store = RefPicStore::new();
store.insert(0, ref_pic);
store.set_list_0(vec![0]);
let mb = make_p_l0_16x16(0, [0, 0]);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 2, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid)
.expect("4:2:2 explicit-weighted P_L0_16x16 must reconstruct");
for y in 0..16i32 {
assert_eq!(pic.luma_at(0, y), 30, "Y identity-weight at row {y}");
for x in 0..8i32 {
assert_eq!(pic.cb_at(x, y), 101, "Cb 4:2:2 explicit at ({x},{y})");
assert_eq!(pic.cr_at(x, y), 67, "Cr 4:2:2 explicit at ({x},{y})");
}
}
}
#[test]
fn b_bi_16x16_422_default_averages_full_height_chroma() {
let mut sps = make_sps(1, 1);
sps.profile_idc = 122;
sps.chroma_format_idc = 2;
let pps = make_pps(); let sh = make_b_slice_header();
let mut l0 = Picture::new(16, 16, 2, 8, 8);
let mut l1 = Picture::new(16, 16, 2, 8, 8);
for y in 0..16i32 {
for x in 0..16i32 {
l0.set_luma(x, y, 40);
l1.set_luma(x, y, 80);
}
}
for y in 0..16i32 {
for x in 0..8i32 {
l0.set_cb(x, y, 40);
l0.set_cr(x, y, 40);
l1.set_cb(x, y, 80);
l1.set_cr(x, y, 80);
}
}
let mut store = RefPicStore::new();
store.insert(0, l0);
store.insert(1, l1);
store.set_list_0(vec![0]);
store.set_list_1(vec![1]);
let mb = make_b_bi_16x16(0, 0);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 2, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid)
.expect("4:2:2 B_Bi_16x16 must reconstruct");
for y in 0..16i32 {
assert_eq!(pic.luma_at(0, y), 60, "Y bipred avg at row {y}");
for x in 0..8i32 {
assert_eq!(pic.cb_at(x, y), 60, "Cb 4:2:2 bipred avg at ({x},{y})");
assert_eq!(pic.cr_at(x, y), 60, "Cr 4:2:2 bipred avg at ({x},{y})");
}
}
}
#[test]
fn b_bi_16x16_444_default_averages_full_resolution_chroma() {
let mut sps = make_sps(1, 1);
sps.profile_idc = 244;
sps.chroma_format_idc = 3;
let pps = make_pps(); let sh = make_b_slice_header();
let mut l0 = Picture::new(16, 16, 3, 8, 8);
let mut l1 = Picture::new(16, 16, 3, 8, 8);
for y in 0..16i32 {
for x in 0..16i32 {
l0.set_luma(x, y, 50);
l0.set_cb(x, y, 50);
l0.set_cr(x, y, 50);
l1.set_luma(x, y, 90);
l1.set_cb(x, y, 90);
l1.set_cr(x, y, 90);
}
}
let mut store = RefPicStore::new();
store.insert(0, l0);
store.insert(1, l1);
store.set_list_0(vec![0]);
store.set_list_1(vec![1]);
let mb = make_b_bi_16x16(0, 0);
let slice_data = SliceData {
macroblocks: vec![mb],
mb_field_decoding_flags: vec![false],
last_mb_addr: 0,
};
let mut pic = Picture::new(16, 16, 3, 8, 8);
let mut grid = MbGrid::new(1, 1);
reconstruct_slice(&slice_data, &sh, &sps, &pps, &store, &mut pic, &mut grid)
.expect("4:4:4 B_Bi_16x16 must reconstruct");
for y in 0..16i32 {
for x in 0..16i32 {
assert_eq!(pic.luma_at(x, y), 70, "Y 4:4:4 bipred avg at ({x},{y})");
assert_eq!(pic.cb_at(x, y), 70, "Cb 4:4:4 bipred avg at ({x},{y})");
assert_eq!(pic.cr_at(x, y), 70, "Cr 4:4:4 bipred avg at ({x},{y})");
}
}
}
}