#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
use crate::bitstream::{BitError, BitReader};
use crate::cabac::{CabacDecoder, CabacError};
use crate::cabac_ctx::{
decode_coded_block_flag, decode_coded_block_pattern, decode_coeff_abs_level_minus1,
decode_coeff_sign_flag, decode_intra_chroma_pred_mode, decode_last_significant_coeff_flag,
decode_mb_qp_delta, decode_mb_type_b, decode_mb_type_i, decode_mb_type_p, decode_mvd_lx,
decode_prev_intra_pred_mode_flag, decode_ref_idx_lx, decode_rem_intra_pred_mode,
decode_significant_coeff_flag, decode_transform_size_8x8_flag, BlockType, CabacContexts,
MvdComponent, NeighbourCtx, SliceKind,
};
use crate::cavlc::{parse_residual_block_cavlc, CavlcError, CoeffTokenContext};
use crate::mv_deriv::{neighbour_4x4_map, NeighbourSource};
use crate::pps::Pps;
use crate::slice_header::{SliceHeader, SliceType};
use crate::sps::Sps;
#[inline]
fn dbg_general_enabled() -> bool {
use std::sync::OnceLock;
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| std::env::var("OXIDEAV_H264_DEBUG").is_ok())
}
#[inline]
fn dbg_mbtype_trace_enabled() -> bool {
use std::sync::OnceLock;
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| std::env::var_os("OXIDEAV_H264_MBTYPE_TRACE").is_some())
}
#[inline]
fn dbg_mb_trace_target() -> Option<u32> {
use std::sync::OnceLock;
static CACHED: OnceLock<Option<u32>> = OnceLock::new();
*CACHED.get_or_init(|| {
std::env::var("OXIDEAV_H264_MB_TRACE")
.ok()
.and_then(|v| v.parse::<u32>().ok())
})
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum MacroblockLayerError {
#[error("bitstream read failed: {0}")]
Bitstream(#[from] BitError),
#[error("CABAC decoding failed: {0}")]
Cabac(#[from] CabacError),
#[error("CAVLC residual block decode failed: {0}")]
Cavlc(#[from] CavlcError),
#[error("mb_type raw value {0} out of range for slice type")]
MbTypeOutOfRange(u32),
#[error("coded_block_pattern value {0} out of range")]
CbpOutOfRange(u32),
#[error("mb_qp_delta value {0} out of range")]
MbQpDeltaOutOfRange(i32),
#[error("sub_mb_type raw value {0} out of range")]
SubMbTypeOutOfRange(u32),
#[error("ChromaArrayType {0} not supported in residual walker")]
UnsupportedChromaArrayType(u32),
#[error("CABAC entropy coding in an SI slice is not supported")]
CabacSiSliceUnsupported,
#[error("slice data partitioning with CABAC entropy coding is not supported")]
DataPartitioningCabacUnsupported,
#[error("slice data partition {0} required by the slice data is missing")]
MissingDataPartition(char),
#[error("I_PCM macroblock in CABAC mode requires slice-level reinit at byte {cabac_byte_pos} bit {cabac_bit_pos}")]
IPcmNeedsCabacReinit {
cabac_byte_pos: usize,
cabac_bit_pos: u8,
},
}
pub type McblResult<T> = Result<T, MacroblockLayerError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Intra16x16 {
pub pred_mode: u8,
pub cbp_luma: u8,
pub cbp_chroma: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MbPartPredMode {
PredL0,
PredL1,
BiPred,
Direct,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MbType {
INxN,
Intra16x16(Intra16x16),
IPcm,
SI,
PL016x16,
PL0L016x8,
PL0L08x16,
P8x8,
P8x8Ref0,
PSkip,
BDirect16x16,
BL016x16,
BL116x16,
BBi16x16,
BL0L016x8,
BL0L08x16,
BL1L116x8,
BL1L18x16,
BL0L116x8,
BL0L18x16,
BL1L016x8,
BL1L08x16,
BL0Bi16x8,
BL0Bi8x16,
BL1Bi16x8,
BL1Bi8x16,
BBiL016x8,
BBiL08x16,
BBiL116x8,
BBiL18x16,
BBiBi16x8,
BBiBi8x16,
B8x8,
BSkip,
Reserved(u32),
}
impl MbType {
pub fn is_i_pcm(&self) -> bool {
matches!(self, MbType::IPcm)
}
pub fn is_i_nxn(&self) -> bool {
matches!(self, MbType::INxN | MbType::SI)
}
pub fn is_si(&self) -> bool {
matches!(self, MbType::SI)
}
pub fn is_intra_16x16(&self) -> bool {
matches!(self, MbType::Intra16x16(_))
}
pub fn is_intra(&self) -> bool {
matches!(
self,
MbType::INxN | MbType::Intra16x16(_) | MbType::IPcm | MbType::SI
)
}
pub fn is_skip(&self) -> bool {
matches!(self, MbType::PSkip | MbType::BSkip)
}
pub fn is_b_skip_or_direct(&self) -> bool {
matches!(self, MbType::BSkip | MbType::BDirect16x16)
}
pub fn num_mb_part(&self) -> u8 {
use MbType::*;
match self {
INxN | Intra16x16(_) | IPcm | SI => 1,
PL016x16 | PSkip => 1,
PL0L016x8 | PL0L08x16 => 2,
P8x8 | P8x8Ref0 => 4,
BDirect16x16 | BSkip | BL016x16 | BL116x16 | BBi16x16 => 1,
BL0L016x8 | BL0L08x16 | BL1L116x8 | BL1L18x16 | BL0L116x8 | BL0L18x16 | BL1L016x8
| BL1L08x16 | BL0Bi16x8 | BL0Bi8x16 | BL1Bi16x8 | BL1Bi8x16 | BBiL016x8 | BBiL08x16
| BBiL116x8 | BBiL18x16 | BBiBi16x8 | BBiBi8x16 => 2,
B8x8 => 4,
Reserved(_) => 1,
}
}
pub fn is_sub_mb_path(&self) -> bool {
matches!(self, MbType::P8x8 | MbType::P8x8Ref0 | MbType::B8x8)
}
pub fn is_partitioned_16x8(&self) -> bool {
use MbType::*;
matches!(
self,
PL0L016x8
| BL0L016x8
| BL1L116x8
| BL0L116x8
| BL1L016x8
| BL0Bi16x8
| BL1Bi16x8
| BBiL016x8
| BBiL116x8
| BBiBi16x8
)
}
pub fn mb_part_pred_mode(&self, mb_part_idx: usize) -> Option<MbPartPredMode> {
use MbPartPredMode::*;
use MbType::*;
if mb_part_idx >= self.num_mb_part() as usize {
return None;
}
match self {
PL016x16 | PSkip => Some(PredL0),
PL0L016x8 | PL0L08x16 => Some(PredL0),
P8x8 | P8x8Ref0 => None,
BDirect16x16 | BSkip => Some(Direct),
BL016x16 => Some(PredL0),
BL116x16 => Some(PredL1),
BBi16x16 => Some(BiPred),
BL0L016x8 | BL0L08x16 => Some(PredL0),
BL1L116x8 | BL1L18x16 => Some(PredL1),
BL0L116x8 | BL0L18x16 => {
if mb_part_idx == 0 {
Some(PredL0)
} else {
Some(PredL1)
}
}
BL1L016x8 | BL1L08x16 => {
if mb_part_idx == 0 {
Some(PredL1)
} else {
Some(PredL0)
}
}
BL0Bi16x8 | BL0Bi8x16 => {
if mb_part_idx == 0 {
Some(PredL0)
} else {
Some(BiPred)
}
}
BL1Bi16x8 | BL1Bi8x16 => {
if mb_part_idx == 0 {
Some(PredL1)
} else {
Some(BiPred)
}
}
BBiL016x8 | BBiL08x16 => {
if mb_part_idx == 0 {
Some(BiPred)
} else {
Some(PredL0)
}
}
BBiL116x8 | BBiL18x16 => {
if mb_part_idx == 0 {
Some(BiPred)
} else {
Some(PredL1)
}
}
BBiBi16x8 | BBiBi8x16 => Some(BiPred),
B8x8 => None,
INxN | Intra16x16(_) | IPcm | SI | Reserved(_) => None,
}
}
pub fn intra_16x16_cbp_luma(&self) -> Option<u8> {
if let MbType::Intra16x16(i) = self {
Some(i.cbp_luma)
} else {
None
}
}
pub fn intra_16x16_cbp_chroma(&self) -> Option<u8> {
if let MbType::Intra16x16(i) = self {
Some(i.cbp_chroma)
} else {
None
}
}
pub fn from_si_slice(raw: u32) -> McblResult<Self> {
if raw == 0 {
Ok(MbType::SI)
} else {
Self::from_i_slice(raw - 1)
}
}
pub fn from_i_slice(raw: u32) -> McblResult<Self> {
match raw {
0 => Ok(MbType::INxN),
1..=24 => {
let r = raw - 1;
let pred_mode = (r % 4) as u8;
let group = r / 4;
let (cbp_luma, cbp_chroma) = match group {
0 => (0, 0),
1 => (0, 1),
2 => (0, 2),
3 => (15, 0),
4 => (15, 1),
5 => (15, 2),
_ => return Err(MacroblockLayerError::MbTypeOutOfRange(raw)),
};
Ok(MbType::Intra16x16(Intra16x16 {
pred_mode,
cbp_luma,
cbp_chroma,
}))
}
25 => Ok(MbType::IPcm),
_ => Err(MacroblockLayerError::MbTypeOutOfRange(raw)),
}
}
pub fn from_p_slice(raw: u32) -> McblResult<Self> {
Ok(match raw {
0 => MbType::PL016x16,
1 => MbType::PL0L016x8,
2 => MbType::PL0L08x16,
3 => MbType::P8x8,
4 => MbType::P8x8Ref0,
5..=30 => Self::from_i_slice(raw - 5)?,
_ => return Err(MacroblockLayerError::MbTypeOutOfRange(raw)),
})
}
pub fn from_b_slice(raw: u32) -> McblResult<Self> {
Ok(match raw {
0 => MbType::BDirect16x16,
1 => MbType::BL016x16,
2 => MbType::BL116x16,
3 => MbType::BBi16x16,
4 => MbType::BL0L016x8,
5 => MbType::BL0L08x16,
6 => MbType::BL1L116x8,
7 => MbType::BL1L18x16,
8 => MbType::BL0L116x8,
9 => MbType::BL0L18x16,
10 => MbType::BL1L016x8,
11 => MbType::BL1L08x16,
12 => MbType::BL0Bi16x8,
13 => MbType::BL0Bi8x16,
14 => MbType::BL1Bi16x8,
15 => MbType::BL1Bi8x16,
16 => MbType::BBiL016x8,
17 => MbType::BBiL08x16,
18 => MbType::BBiL116x8,
19 => MbType::BBiL18x16,
20 => MbType::BBiBi16x8,
21 => MbType::BBiBi8x16,
22 => MbType::B8x8,
23..=48 => Self::from_i_slice(raw - 23)?,
_ => return Err(MacroblockLayerError::MbTypeOutOfRange(raw)),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubMbType {
PL08x8,
PL08x4,
PL04x8,
PL04x4,
BDirect8x8,
BL08x8,
BL18x8,
BBi8x8,
BL08x4,
BL04x8,
BL18x4,
BL14x8,
BBi8x4,
BBi4x8,
BL04x4,
BL14x4,
BBi4x4,
Reserved(u32),
}
impl SubMbType {
pub fn num_sub_mb_part(&self) -> u8 {
use SubMbType::*;
match self {
PL08x8 | BDirect8x8 | BL08x8 | BL18x8 | BBi8x8 => 1,
PL08x4 | PL04x8 | BL08x4 | BL04x8 | BL18x4 | BL14x8 | BBi8x4 | BBi4x8 => 2,
PL04x4 | BL04x4 | BL14x4 | BBi4x4 => 4,
Reserved(_) => 1,
}
}
pub fn is_8x4(&self) -> bool {
use SubMbType::*;
matches!(self, PL08x4 | BL08x4 | BL18x4 | BBi8x4)
}
pub fn from_p(raw: u32) -> McblResult<Self> {
Ok(match raw {
0 => SubMbType::PL08x8,
1 => SubMbType::PL08x4,
2 => SubMbType::PL04x8,
3 => SubMbType::PL04x4,
_ => SubMbType::Reserved(raw),
})
}
pub fn from_b(raw: u32) -> McblResult<Self> {
Ok(match raw {
0 => SubMbType::BDirect8x8,
1 => SubMbType::BL08x8,
2 => SubMbType::BL18x8,
3 => SubMbType::BBi8x8,
4 => SubMbType::BL08x4,
5 => SubMbType::BL04x8,
6 => SubMbType::BL18x4,
7 => SubMbType::BL14x8,
8 => SubMbType::BBi8x4,
9 => SubMbType::BBi4x8,
10 => SubMbType::BL04x4,
11 => SubMbType::BL14x4,
12 => SubMbType::BBi4x4,
_ => SubMbType::Reserved(raw),
})
}
pub fn sub_mb_pred_mode(&self) -> Option<MbPartPredMode> {
use MbPartPredMode::*;
use SubMbType::*;
match self {
PL08x8 | PL08x4 | PL04x8 | PL04x4 => Some(PredL0),
BDirect8x8 => Some(Direct),
BL08x8 | BL08x4 | BL04x8 | BL04x4 => Some(PredL0),
BL18x8 | BL18x4 | BL14x8 | BL14x4 => Some(PredL1),
BBi8x8 | BBi8x4 | BBi4x8 | BBi4x4 => Some(BiPred),
Reserved(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PcmSamples {
pub luma: Vec<u32>,
pub chroma_cb: Vec<u32>,
pub chroma_cr: Vec<u32>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MbPred {
pub prev_intra4x4_pred_mode_flag: [bool; 16],
pub rem_intra4x4_pred_mode: [u8; 16],
pub prev_intra8x8_pred_mode_flag: [bool; 4],
pub rem_intra8x8_pred_mode: [u8; 4],
pub intra_chroma_pred_mode: u8,
pub ref_idx_l0: Vec<u32>,
pub ref_idx_l1: Vec<u32>,
pub mvd_l0: Vec<[i32; 2]>,
pub mvd_l1: Vec<[i32; 2]>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SubMbPred {
pub sub_mb_type: [SubMbType; 4],
pub ref_idx_l0: [u32; 4],
pub ref_idx_l1: [u32; 4],
pub mvd_l0: [Vec<[i32; 2]>; 4],
pub mvd_l1: [Vec<[i32; 2]>; 4],
}
impl Default for SubMbType {
fn default() -> Self {
SubMbType::Reserved(0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Macroblock {
pub mb_type: MbType,
pub mb_type_raw: u32,
pub mb_pred: Option<MbPred>,
pub sub_mb_pred: Option<SubMbPred>,
pub pcm_samples: Option<PcmSamples>,
pub coded_block_pattern: u32,
pub transform_size_8x8_flag: bool,
pub mb_qp_delta: i32,
pub residual_luma: Vec<[i32; 16]>,
pub residual_luma_dc: Option<[i32; 16]>,
pub residual_chroma_dc_cb: Vec<i32>,
pub residual_chroma_dc_cr: Vec<i32>,
pub residual_chroma_ac_cb: Vec<[i32; 16]>,
pub residual_chroma_ac_cr: Vec<[i32; 16]>,
pub residual_cb_luma_like: Vec<[i32; 16]>,
pub residual_cr_luma_like: Vec<[i32; 16]>,
pub residual_cb_16x16_dc: Option<[i32; 16]>,
pub residual_cr_16x16_dc: Option<[i32; 16]>,
pub is_skip: bool,
}
impl Macroblock {
pub fn new_skip(slice_type: SliceType) -> Self {
let mb_type = match slice_type {
SliceType::B => MbType::BSkip,
SliceType::P | SliceType::SP => MbType::PSkip,
_ => MbType::PSkip,
};
Self {
mb_type,
mb_type_raw: 0,
mb_pred: None,
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: true,
}
}
}
#[rustfmt::skip]
static ME_INTRA_420_422: [u8; 48] = [
47, 31, 15, 0, 23, 27, 29, 30, 7, 11, 13, 14, 39, 43, 45, 46,
16, 3, 5, 10, 12, 19, 21, 26, 28, 35, 37, 42, 44, 1, 2, 4,
8, 17, 18, 20, 24, 6, 9, 22, 25, 32, 33, 34, 36, 40, 38, 41,
];
#[rustfmt::skip]
static ME_INTER_420_422: [u8; 48] = [
0, 16, 1, 2, 4, 8, 32, 3, 5, 10, 12, 15, 47, 7, 11, 13,
14, 6, 9, 31, 35, 37, 42, 44, 33, 34, 36, 40, 39, 43, 45, 46,
17, 18, 20, 24, 19, 21, 26, 28, 23, 27, 29, 30, 22, 25, 38, 41,
];
#[rustfmt::skip]
static ME_INTRA_0_3: [u8; 16] = [
15, 0, 7, 11, 13, 14, 3, 5, 10, 12, 1, 2, 4, 8, 6, 9,
];
#[rustfmt::skip]
static ME_INTER_0_3: [u8; 16] = [
0, 1, 2, 4, 8, 3, 5, 10, 12, 15, 7, 11, 13, 14, 6, 9,
];
fn parse_cbp_me(r: &mut BitReader<'_>, chroma_array_type: u32, is_intra: bool) -> McblResult<u32> {
let k = r.ue()?;
let val = if chroma_array_type == 1 || chroma_array_type == 2 {
let idx = k as usize;
if idx >= 48 {
return Err(MacroblockLayerError::CbpOutOfRange(k));
}
if is_intra {
ME_INTRA_420_422[idx]
} else {
ME_INTER_420_422[idx]
}
} else {
let idx = k as usize;
if idx >= 16 {
return Err(MacroblockLayerError::CbpOutOfRange(k));
}
if is_intra {
ME_INTRA_0_3[idx]
} else {
ME_INTER_0_3[idx]
}
};
Ok(val as u32)
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CavlcMbNc {
pub is_available: bool,
pub is_skip: bool,
pub is_i_pcm: bool,
pub is_intra: bool,
pub luma_total_coeff: [u8; 16],
pub cb_total_coeff: [u8; 8],
pub cr_total_coeff: [u8; 8],
pub cb_luma_total_coeff: [u8; 16],
pub cr_luma_total_coeff: [u8; 16],
pub mb_field_decoding_flag: bool,
}
#[derive(Debug, Clone)]
pub struct CavlcNcGrid {
pub pic_width_in_mbs: u32,
pub pic_height_in_mbs: u32,
pub mbs: Vec<CavlcMbNc>,
pub mbaff_frame_flag: bool,
}
impl CavlcNcGrid {
pub fn new(pic_width_in_mbs: u32, pic_height_in_mbs: u32) -> Self {
let len = (pic_width_in_mbs as usize) * (pic_height_in_mbs as usize);
Self {
pic_width_in_mbs,
pic_height_in_mbs,
mbs: vec![CavlcMbNc::default(); len],
mbaff_frame_flag: false,
}
}
pub fn new_mbaff(pic_width_in_mbs: u32, pic_height_in_mbs: u32, mbaff: bool) -> Self {
let mut g = Self::new(pic_width_in_mbs, pic_height_in_mbs);
g.mbaff_frame_flag = mbaff;
g
}
pub fn neighbour_block_loc(
&self,
mb_addr: u32,
xn: i32,
yn: i32,
max_w: i32,
max_h: i32,
) -> Option<(u32, i32, i32)> {
let w = self.pic_width_in_mbs;
if w == 0 {
return None;
}
if self.mbaff_frame_flag {
let curr_field = self
.mbs
.get(mb_addr as usize)
.map(|m| m.mb_field_decoding_flag)
.unwrap_or(false);
let pair = crate::mb_address::mbaff_pair_neighbour_addrs(mb_addr, w);
let frame_flag_of = |addr: u32| -> bool {
self.mbs
.get(addr as usize)
.map(|m| !m.mb_field_decoding_flag)
.unwrap_or(true)
};
return crate::mb_address::mbaff_neigh_location(
xn,
yn,
max_w,
max_h,
!curr_field,
mb_addr % 2 == 0,
mb_addr,
pair,
frame_flag_of,
);
}
let x = mb_addr % w;
let y = mb_addr / w;
if xn < 0 && (0..max_h).contains(&yn) {
if x == 0 {
return None;
}
Some((mb_addr - 1, xn + max_w, yn))
} else if yn < 0 && (0..max_w).contains(&xn) {
if y == 0 {
return None;
}
Some((mb_addr - w, xn, yn + max_h))
} else if (0..max_w).contains(&xn) && (0..max_h).contains(&yn) {
Some((mb_addr, xn, yn))
} else {
None
}
}
#[inline]
pub fn neighbour_mb_addrs(&self, mb_addr: u32) -> (Option<u32>, Option<u32>) {
let w = self.pic_width_in_mbs;
if w == 0 {
return (None, None);
}
let x = mb_addr % w;
let y = mb_addr / w;
let a = if x > 0 { Some(mb_addr - 1) } else { None };
let b = if y > 0 { Some(mb_addr - w) } else { None };
(a, b)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LumaNcKind {
Ac,
Intra16x16Dc,
}
pub(crate) fn derive_nc_luma(
grid: &CavlcNcGrid,
current_mb_addr: u32,
blk_idx: u8,
kind: LumaNcKind,
current_is_intra: bool,
constrained_intra_pred_flag: bool,
) -> i32 {
let blk = match kind {
LumaNcKind::Intra16x16Dc => 0u8,
LumaNcKind::Ac => blk_idx,
};
let ((a_mb_addr, a_blk), (b_mb_addr, b_blk)) = if grid.mbaff_frame_flag {
let (bx, by) = blk4x4_xy(blk);
let probe = |xn: i32, yn: i32| -> (Option<u32>, u8) {
match grid.neighbour_block_loc(current_mb_addr, xn, yn, 16, 16) {
Some((addr, wx, wy)) => (Some(addr), blk4x4_idx(wx, wy)),
None => (None, 0),
}
};
(probe(bx - 1, by), probe(bx, by - 1))
} else {
let [a_src, b_src, _c, _d] = neighbour_4x4_map(blk);
(
resolve_luma_neighbour(grid, current_mb_addr, a_src),
resolve_luma_neighbour(grid, current_mb_addr, b_src),
)
};
let (avail_a, n_a) = nc_nn_luma(
grid,
a_mb_addr,
a_blk,
current_is_intra,
constrained_intra_pred_flag,
);
let (avail_b, n_b) = nc_nn_luma(
grid,
b_mb_addr,
b_blk,
current_is_intra,
constrained_intra_pred_flag,
);
combine_nc(avail_a, n_a, avail_b, n_b)
}
pub(crate) fn derive_nc_plane_luma_like(
grid: &CavlcNcGrid,
current_mb_addr: u32,
blk_idx: u8,
is_cr: bool,
current_is_intra: bool,
constrained_intra_pred_flag: bool,
) -> i32 {
let ((a_mb_addr, a_blk), (b_mb_addr, b_blk)) = if grid.mbaff_frame_flag {
let (bx, by) = blk4x4_xy(blk_idx);
let probe = |xn: i32, yn: i32| -> (Option<u32>, u8) {
match grid.neighbour_block_loc(current_mb_addr, xn, yn, 16, 16) {
Some((addr, wx, wy)) => (Some(addr), blk4x4_idx(wx, wy)),
None => (None, 0),
}
};
(probe(bx - 1, by), probe(bx, by - 1))
} else {
let [a_src, b_src, _c, _d] = neighbour_4x4_map(blk_idx);
(
resolve_luma_neighbour(grid, current_mb_addr, a_src),
resolve_luma_neighbour(grid, current_mb_addr, b_src),
)
};
let (avail_a, n_a) = nc_nn_plane_luma_like(
grid,
a_mb_addr,
a_blk,
is_cr,
current_is_intra,
constrained_intra_pred_flag,
);
let (avail_b, n_b) = nc_nn_plane_luma_like(
grid,
b_mb_addr,
b_blk,
is_cr,
current_is_intra,
constrained_intra_pred_flag,
);
combine_nc(avail_a, n_a, avail_b, n_b)
}
fn resolve_luma_neighbour(
grid: &CavlcNcGrid,
current_mb_addr: u32,
src: NeighbourSource,
) -> (Option<u32>, u8) {
let w = grid.pic_width_in_mbs;
let x = if w == 0 { 0 } else { current_mb_addr % w };
let y = current_mb_addr.checked_div(w).unwrap_or(0);
match src {
NeighbourSource::InternalBlock(i) => (Some(current_mb_addr), i),
NeighbourSource::ExternalLeft(i) => {
if x > 0 {
(Some(current_mb_addr - 1), i)
} else {
(None, i)
}
}
NeighbourSource::ExternalAbove(i) => {
if y > 0 {
(Some(current_mb_addr - w), i)
} else {
(None, i)
}
}
NeighbourSource::ExternalAboveRight(i) => {
if y > 0 && x + 1 < w {
(Some(current_mb_addr - w + 1), i)
} else {
(None, i)
}
}
NeighbourSource::ExternalAboveLeft(i) => {
if y > 0 && x > 0 {
(Some(current_mb_addr - w - 1), i)
} else {
(None, i)
}
}
}
}
fn nc_nn_luma(
grid: &CavlcNcGrid,
mb_addr: Option<u32>,
blk: u8,
current_is_intra: bool,
constrained_intra_pred_flag: bool,
) -> (bool, u8) {
let Some(addr) = mb_addr else {
return (false, 0);
};
let Some(info) = grid.mbs.get(addr as usize) else {
return (false, 0);
};
if !info.is_available {
return (false, 0);
}
let _ = (current_is_intra, constrained_intra_pred_flag);
if info.is_skip {
return (true, 0);
}
if info.is_i_pcm {
return (true, 16);
}
let idx = blk as usize;
let n = info.luma_total_coeff.get(idx).copied().unwrap_or(0);
(true, n)
}
fn nc_nn_plane_luma_like(
grid: &CavlcNcGrid,
mb_addr: Option<u32>,
blk: u8,
is_cr: bool,
current_is_intra: bool,
constrained_intra_pred_flag: bool,
) -> (bool, u8) {
let Some(addr) = mb_addr else {
return (false, 0);
};
let Some(info) = grid.mbs.get(addr as usize) else {
return (false, 0);
};
if !info.is_available {
return (false, 0);
}
let _ = (current_is_intra, constrained_intra_pred_flag);
if info.is_skip {
return (true, 0);
}
if info.is_i_pcm {
return (true, 16);
}
let arr = if is_cr {
&info.cr_luma_total_coeff
} else {
&info.cb_luma_total_coeff
};
let n = arr.get(blk as usize).copied().unwrap_or(0);
(true, n)
}
pub(crate) fn derive_nc_chroma_ac(
grid: &CavlcNcGrid,
current_mb_addr: u32,
blk_idx: u8,
is_cr: bool,
chroma_array_type: u32,
current_is_intra: bool,
constrained_intra_pred_flag: bool,
) -> i32 {
let max_w: i32 = 8; let max_h: i32 = if chroma_array_type == 2 { 16 } else { 8 };
let x = ((blk_idx as i32) % 2) * 4;
let y = ((blk_idx as i32) / 2) * 4;
let (xa, ya) = (x - 1, y);
let (xb, yb) = (x, y - 1);
let (a_mb_addr, a_blk) = resolve_chroma_neighbour(grid, current_mb_addr, xa, ya, max_w, max_h);
let (b_mb_addr, b_blk) = resolve_chroma_neighbour(grid, current_mb_addr, xb, yb, max_w, max_h);
let (avail_a, n_a) = nc_nn_chroma(
grid,
a_mb_addr,
a_blk,
is_cr,
current_is_intra,
constrained_intra_pred_flag,
);
let (avail_b, n_b) = nc_nn_chroma(
grid,
b_mb_addr,
b_blk,
is_cr,
current_is_intra,
constrained_intra_pred_flag,
);
combine_nc(avail_a, n_a, avail_b, n_b)
}
fn resolve_chroma_neighbour(
grid: &CavlcNcGrid,
current_mb_addr: u32,
xn: i32,
yn: i32,
max_w: i32,
max_h: i32,
) -> (Option<u32>, u8) {
if grid.mbaff_frame_flag {
return match grid.neighbour_block_loc(current_mb_addr, xn, yn, max_w, max_h) {
Some((addr, xw, yw)) => (Some(addr), (2 * (yw / 4) + (xw / 4)) as u8),
None => (None, 0),
};
}
let w = grid.pic_width_in_mbs;
let x = if w == 0 { 0 } else { current_mb_addr % w };
let y = current_mb_addr.checked_div(w).unwrap_or(0);
let (mb_opt, xw, yw) = if xn < 0 && yn < 0 {
let addr = if y > 0 && x > 0 {
Some(current_mb_addr - w - 1)
} else {
None
};
(addr, xn + max_w, yn + max_h)
} else if xn < 0 && (0..max_h).contains(&yn) {
let addr = if x > 0 {
Some(current_mb_addr - 1)
} else {
None
};
(addr, xn + max_w, yn)
} else if (0..max_w).contains(&xn) && yn < 0 {
let addr = if y > 0 {
Some(current_mb_addr - w)
} else {
None
};
(addr, xn, yn + max_h)
} else if (0..max_w).contains(&xn) && (0..max_h).contains(&yn) {
(Some(current_mb_addr), xn, yn)
} else if xn > max_w - 1 && yn < 0 {
let addr = if y > 0 && x + 1 < w {
Some(current_mb_addr - w + 1)
} else {
None
};
(addr, xn - max_w, yn + max_h)
} else {
(None, 0, 0)
};
let blk = if mb_opt.is_some() {
(2 * (yw / 4) + (xw / 4)) as u8
} else {
0
};
(mb_opt, blk)
}
fn nc_nn_chroma(
grid: &CavlcNcGrid,
mb_addr: Option<u32>,
blk: u8,
is_cr: bool,
current_is_intra: bool,
constrained_intra_pred_flag: bool,
) -> (bool, u8) {
let Some(addr) = mb_addr else {
return (false, 0);
};
let Some(info) = grid.mbs.get(addr as usize) else {
return (false, 0);
};
if !info.is_available {
return (false, 0);
}
let _ = (current_is_intra, constrained_intra_pred_flag);
if info.is_skip {
return (true, 0);
}
if info.is_i_pcm {
return (true, 16);
}
let arr = if is_cr {
&info.cr_total_coeff
} else {
&info.cb_total_coeff
};
let n = arr.get(blk as usize).copied().unwrap_or(0);
(true, n)
}
#[inline]
fn combine_nc(avail_a: bool, n_a: u8, avail_b: bool, n_b: u8) -> i32 {
match (avail_a, avail_b) {
(true, true) => (n_a as i32 + n_b as i32 + 1) >> 1,
(true, false) => n_a as i32,
(false, true) => n_b as i32,
(false, false) => 0,
}
}
#[derive(Debug, Clone, Copy)]
pub struct CabacMbNeighbourInfo {
pub available: bool,
pub is_skip: bool,
pub is_intra: bool,
pub is_i_pcm: bool,
pub ref_idx_l0: [i8; 4],
pub ref_idx_l1: [i8; 4],
pub mvd_l0_x: [i16; 16],
pub mvd_l0_y: [i16; 16],
pub mvd_l1_x: [i16; 16],
pub mvd_l1_y: [i16; 16],
pub cbf_luma_4x4: [bool; 16],
pub cbf_cb_dc: bool,
pub cbf_cr_dc: bool,
pub cbf_cb_ac: [bool; 8],
pub cbf_cr_ac: [bool; 8],
pub cbf_luma_16x16_dc: bool,
pub cbf_luma_16x16_ac: [bool; 16],
pub cbf_cb_16x16_dc: bool,
pub cbf_cb_16x16_ac: [bool; 16],
pub cbf_cb_luma_4x4: [bool; 16],
pub cbf_cr_16x16_dc: bool,
pub cbf_cr_16x16_ac: [bool; 16],
pub cbf_cr_luma_4x4: [bool; 16],
pub coded_block_pattern_luma: u8,
pub coded_block_pattern_chroma: u8,
pub is_i_nxn: bool,
pub is_b_skip_or_direct: bool,
pub intra_chroma_pred_mode: u8,
pub transform_size_8x8_flag: bool,
pub mb_field_decoding_flag: bool,
}
impl Default for CabacMbNeighbourInfo {
fn default() -> Self {
Self {
available: false,
is_skip: false,
is_intra: false,
is_i_pcm: false,
ref_idx_l0: [-1; 4],
ref_idx_l1: [-1; 4],
mvd_l0_x: [0; 16],
mvd_l0_y: [0; 16],
mvd_l1_x: [0; 16],
mvd_l1_y: [0; 16],
cbf_luma_4x4: [false; 16],
cbf_cb_dc: false,
cbf_cr_dc: false,
cbf_cb_ac: [false; 8],
cbf_cr_ac: [false; 8],
cbf_luma_16x16_dc: false,
cbf_luma_16x16_ac: [false; 16],
cbf_cb_16x16_dc: false,
cbf_cb_16x16_ac: [false; 16],
cbf_cb_luma_4x4: [false; 16],
cbf_cr_16x16_dc: false,
cbf_cr_16x16_ac: [false; 16],
cbf_cr_luma_4x4: [false; 16],
coded_block_pattern_luma: 0,
coded_block_pattern_chroma: 0,
is_i_nxn: false,
is_b_skip_or_direct: false,
intra_chroma_pred_mode: 0,
transform_size_8x8_flag: false,
mb_field_decoding_flag: false,
}
}
}
#[derive(Debug, Clone)]
pub struct CabacNeighbourGrid {
pub width_in_mbs: u32,
pub height_in_mbs: u32,
pub mbs: Vec<CabacMbNeighbourInfo>,
pub mbaff_frame_flag: bool,
}
impl CabacNeighbourGrid {
pub fn new(width_in_mbs: u32, height_in_mbs: u32) -> Self {
let len = (width_in_mbs as usize) * (height_in_mbs as usize);
Self {
width_in_mbs,
height_in_mbs,
mbs: vec![CabacMbNeighbourInfo::default(); len],
mbaff_frame_flag: false,
}
}
pub fn new_mbaff(width_in_mbs: u32, height_in_mbs: u32, mbaff_frame_flag: bool) -> Self {
let mut g = Self::new(width_in_mbs, height_in_mbs);
g.mbaff_frame_flag = mbaff_frame_flag;
g
}
#[inline]
pub fn neighbour_mb_addrs(&self, mb_addr: u32) -> (Option<u32>, Option<u32>) {
let w = self.width_in_mbs;
if w == 0 {
return (None, None);
}
let x = mb_addr % w;
let y = mb_addr / w;
let a = if x > 0 { Some(mb_addr - 1) } else { None };
let b = if y > 0 { Some(mb_addr - w) } else { None };
(a, b)
}
#[inline]
pub fn neighbour_mb_addrs_mbaff(
&self,
mb_addr: u32,
mbaff_frame_flag: bool,
) -> (Option<u32>, Option<u32>) {
if !mbaff_frame_flag {
return self.neighbour_mb_addrs(mb_addr);
}
let curr_field = self
.mbs
.get(mb_addr as usize)
.map(|m| m.mb_field_decoding_flag)
.unwrap_or(false);
let curr_frame = !curr_field;
let mb_is_top = mb_addr % 2 == 0;
let pair = crate::mb_address::mbaff_pair_neighbour_addrs(mb_addr, self.width_in_mbs);
let frame_flag_of = |addr: u32| -> bool {
self.mbs
.get(addr as usize)
.map(|m| !m.mb_field_decoding_flag)
.unwrap_or(true)
};
let a = crate::mb_address::mbaff_neigh_location(
-1,
0,
16,
16,
curr_frame,
mb_is_top,
mb_addr,
pair,
frame_flag_of,
)
.map(|(addr, _, _)| addr);
let b = crate::mb_address::mbaff_neigh_location(
0,
-1,
16,
16,
curr_frame,
mb_is_top,
mb_addr,
pair,
frame_flag_of,
)
.map(|(addr, _, _)| addr);
(a, b)
}
pub fn neighbour_block_loc(
&self,
mb_addr: u32,
xn: i32,
yn: i32,
max_w: i32,
max_h: i32,
) -> Option<(u32, i32, i32)> {
let w = self.width_in_mbs;
if w == 0 {
return None;
}
if self.mbaff_frame_flag {
let curr_field = self
.mbs
.get(mb_addr as usize)
.map(|m| m.mb_field_decoding_flag)
.unwrap_or(false);
let pair = crate::mb_address::mbaff_pair_neighbour_addrs(mb_addr, w);
let frame_flag_of = |addr: u32| -> bool {
self.mbs
.get(addr as usize)
.map(|m| !m.mb_field_decoding_flag)
.unwrap_or(true)
};
return crate::mb_address::mbaff_neigh_location(
xn,
yn,
max_w,
max_h,
!curr_field,
mb_addr % 2 == 0,
mb_addr,
pair,
frame_flag_of,
);
}
let x = mb_addr % w;
let y = mb_addr / w;
if xn < 0 && (0..max_h).contains(&yn) {
if x == 0 {
return None;
}
Some((mb_addr - 1, xn + max_w, yn))
} else if yn < 0 && (0..max_w).contains(&xn) {
if y == 0 {
return None;
}
Some((mb_addr - w, xn, yn + max_h))
} else if (0..max_w).contains(&xn) && (0..max_h).contains(&yn) {
Some((mb_addr, xn, yn))
} else {
None
}
}
}
pub(crate) fn cabac_ref_idx_cond_terms(
grid: &CabacNeighbourGrid,
current_mb_addr: u32,
current_mb: &CabacMbNeighbourInfo,
list: u8,
part8x8: u8,
) -> (bool, bool) {
let px = ((part8x8 as i32) % 2) * 8;
let py = ((part8x8 as i32) / 2) * 8;
let curr_field = grid.mbaff_frame_flag
&& grid
.mbs
.get(current_mb_addr as usize)
.map(|m| m.mb_field_decoding_flag)
.unwrap_or(false);
let cond = |xn: i32, yn: i32| -> bool {
let (info, wxn, wyn, ext): (&CabacMbNeighbourInfo, i32, i32, bool) = if xn >= 0 && yn >= 0 {
(current_mb, xn, yn, false)
} else {
let Some((addr, wx, wy)) = grid.neighbour_block_loc(current_mb_addr, xn, yn, 16, 16)
else {
return false;
};
let Some(info) = grid.mbs.get(addr as usize) else {
return false;
};
if !info.available {
return false;
}
(info, wx, wy, true)
};
if info.is_skip || info.is_intra {
return false;
}
let part = ((wyn / 8) * 2 + (wxn / 8)) as usize;
let arr = if list == 0 {
info.ref_idx_l0
} else {
info.ref_idx_l1
};
let threshold: i8 =
if ext && grid.mbaff_frame_flag && !curr_field && info.mb_field_decoding_flag {
1
} else {
0
};
arr[part] > threshold
};
(cond(px - 1, py), cond(px, py - 1))
}
#[inline]
pub(crate) fn blk4x4_idx(x: i32, y: i32) -> u8 {
(8 * (y / 8) + 4 * (x / 8) + 2 * ((y % 8) / 4) + ((x % 8) / 4)) as u8
}
#[inline]
fn blk4x4_xy(idx: u8) -> (i32, i32) {
let hi = (idx / 4) as i32; let lo = (idx % 4) as i32; let x_hi = (hi % 2) * 8;
let y_hi = (hi / 2) * 8;
let x_lo = (lo % 2) * 4;
let y_lo = (lo / 2) * 4;
(x_hi + x_lo, y_hi + y_lo)
}
pub(crate) fn cabac_mvd_abs_sum(
grid: &CabacNeighbourGrid,
current_mb_addr: u32,
current_mb: &CabacMbNeighbourInfo,
list: u8,
comp: MvdComponent,
blk4x4: u8,
) -> u32 {
let (bx, by) = blk4x4_xy(blk4x4);
let curr_field = grid.mbaff_frame_flag
&& grid
.mbs
.get(current_mb_addr as usize)
.map(|m| m.mb_field_decoding_flag)
.unwrap_or(false);
let pick = |xn: i32, yn: i32| -> u32 {
if xn >= 0 && yn >= 0 {
let idx = blk4x4_idx(xn, yn) as usize;
let v = match (list, comp) {
(0, MvdComponent::X) => current_mb.mvd_l0_x[idx],
(0, MvdComponent::Y) => current_mb.mvd_l0_y[idx],
(1, MvdComponent::X) => current_mb.mvd_l1_x[idx],
_ => current_mb.mvd_l1_y[idx],
};
return v.unsigned_abs() as u32;
}
let Some((nbr_mb_addr, wxn, wyn)) =
grid.neighbour_block_loc(current_mb_addr, xn, yn, 16, 16)
else {
return 0;
};
let Some(info) = grid.mbs.get(nbr_mb_addr as usize) else {
return 0;
};
if !info.available {
return 0;
}
if info.is_skip || info.is_intra {
return 0;
}
let idx = blk4x4_idx(wxn, wyn) as usize;
let v = match (list, comp) {
(0, MvdComponent::X) => info.mvd_l0_x[idx],
(0, MvdComponent::Y) => info.mvd_l0_y[idx],
(1, MvdComponent::X) => info.mvd_l1_x[idx],
_ => info.mvd_l1_y[idx],
};
let abs = v.unsigned_abs() as u32;
if grid.mbaff_frame_flag && matches!(comp, MvdComponent::Y) {
let nbr_field = info.mb_field_decoding_flag;
if !curr_field && nbr_field {
return abs * 2;
}
if curr_field && !nbr_field {
return abs / 2;
}
}
abs
};
pick(bx - 1, by) + pick(bx, by - 1)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn cabac_cbf_cond_terms(
grid: &CabacNeighbourGrid,
current_mb_addr: u32,
current_mb: &CabacMbNeighbourInfo,
current_is_intra: bool,
block_type: BlockType,
blk_idx: u8,
is_cr: bool,
chroma_array_type: u32,
) -> (bool, bool) {
let (a_loc, b_loc) = match block_type {
BlockType::Luma4x4
| BlockType::Luma16x16Ac
| BlockType::CbLuma4x4
| BlockType::CbIntra16x16Ac
| BlockType::CrLuma4x4
| BlockType::CrIntra16x16Ac => {
let (bx, by) = blk4x4_xy(blk_idx);
(
CbfLoc::Internal4x4(bx - 1, by),
CbfLoc::Internal4x4(bx, by - 1),
)
}
BlockType::ChromaAc => {
let cx = ((blk_idx as i32) % 2) * 4;
let cy = ((blk_idx as i32) / 2) * 4;
(
CbfLoc::InternalChromaAc(cx - 1, cy, blk_idx),
CbfLoc::InternalChromaAc(cx, cy - 1, blk_idx),
)
}
BlockType::Luma8x8 | BlockType::Cb8x8 | BlockType::Cr8x8 => {
let px = ((blk_idx as i32) % 2) * 8;
let py = ((blk_idx as i32) / 2) * 8;
(
CbfLoc::Internal8x8(px - 1, py),
CbfLoc::Internal8x8(px, py - 1),
)
}
BlockType::ChromaDc
| BlockType::Luma16x16Dc
| BlockType::CbIntra16x16Dc
| BlockType::CrIntra16x16Dc => (CbfLoc::MbLevel(-1, 0), CbfLoc::MbLevel(0, -1)),
};
let cond_a = cbf_cond_for(
grid,
current_mb_addr,
current_mb,
current_is_intra,
block_type,
a_loc,
is_cr,
chroma_array_type,
);
let cond_b = cbf_cond_for(
grid,
current_mb_addr,
current_mb,
current_is_intra,
block_type,
b_loc,
is_cr,
chroma_array_type,
);
(cond_a, cond_b)
}
#[derive(Debug, Clone, Copy)]
enum CbfLoc {
Internal4x4(i32, i32),
Internal8x8(i32, i32),
InternalChromaAc(i32, i32, u8),
MbLevel(i32, i32),
}
#[allow(clippy::too_many_arguments)]
fn cbf_cond_for(
grid: &CabacNeighbourGrid,
current_mb_addr: u32,
current_mb: &CabacMbNeighbourInfo,
current_is_intra: bool,
block_type: BlockType,
loc: CbfLoc,
is_cr: bool,
chroma_array_type: u32,
) -> bool {
match loc {
CbfLoc::Internal8x8(px, py) => {
if px >= 0 && py >= 0 {
let blk8 = ((py / 8) * 2 + (px / 8)) as usize;
cbf_8x8_for(current_mb, block_type, blk8)
} else {
let Some((addr, wx, wy)) =
grid.neighbour_block_loc(current_mb_addr, px, py, 16, 16)
else {
return unavail_cbf(current_is_intra, block_type);
};
let Some(info) = grid.mbs.get(addr as usize) else {
return unavail_cbf(current_is_intra, block_type);
};
if !info.available {
return unavail_cbf(current_is_intra, block_type);
}
if info.is_i_pcm {
return true;
}
if info.is_skip {
return false;
}
let blk8n = ((wy / 8) * 2 + (wx / 8)) as usize;
if ((info.coded_block_pattern_luma >> blk8n) & 1) == 0
|| !info.transform_size_8x8_flag
{
return trans_block_unavail_cbf();
}
cbf_8x8_for(info, block_type, blk8n)
}
}
CbfLoc::Internal4x4(xn, yn) => {
if xn >= 0 && yn >= 0 {
let bi = blk4x4_idx(xn, yn) as usize;
cbf_luma_for(current_mb, block_type, bi)
} else {
let Some((addr, wx, wy)) =
grid.neighbour_block_loc(current_mb_addr, xn, yn, 16, 16)
else {
return unavail_cbf(current_is_intra, block_type);
};
let Some(info) = grid.mbs.get(addr as usize) else {
return unavail_cbf(current_is_intra, block_type);
};
if !info.available {
return unavail_cbf(current_is_intra, block_type);
}
if info.is_i_pcm {
return true;
} if info.is_skip {
return false;
}
let bi = blk4x4_idx(wx, wy) as usize;
match block_type {
BlockType::Luma4x4
| BlockType::Luma16x16Ac
| BlockType::CbLuma4x4
| BlockType::CrLuma4x4
| BlockType::CbIntra16x16Ac
| BlockType::CrIntra16x16Ac => {
let blk8 = (bi >> 2) as u8;
let cbp = match block_type {
BlockType::CbLuma4x4 | BlockType::CbIntra16x16Ac => {
info.coded_block_pattern_luma }
BlockType::CrLuma4x4 | BlockType::CrIntra16x16Ac => {
info.coded_block_pattern_luma
}
_ => info.coded_block_pattern_luma,
};
if ((cbp >> blk8) & 1) == 0 {
return trans_block_unavail_cbf();
}
}
_ => {}
}
cbf_luma_for(info, block_type, bi)
}
}
CbfLoc::InternalChromaAc(xn, yn, _blk_idx) => {
if xn >= 0 && yn >= 0 {
let bi = (2 * (yn / 4) + (xn / 4)) as usize;
cbf_chroma_ac_for(current_mb, block_type, bi.min(7), is_cr)
} else {
let mbh_c = if chroma_array_type == 2 { 16 } else { 8 };
let Some((addr, wx, wy)) =
grid.neighbour_block_loc(current_mb_addr, xn, yn, 8, mbh_c)
else {
return unavail_cbf(current_is_intra, block_type);
};
let Some(info) = grid.mbs.get(addr as usize) else {
return unavail_cbf(current_is_intra, block_type);
};
if !info.available {
return unavail_cbf(current_is_intra, block_type);
}
if info.is_i_pcm {
return true;
}
if info.is_skip {
return false;
}
if matches!(block_type, BlockType::ChromaAc) && info.coded_block_pattern_chroma != 2
{
return trans_block_unavail_cbf();
}
let bi = (2 * (wy / 4) + (wx / 4)) as usize;
cbf_chroma_ac_for(info, block_type, bi.min(7), is_cr)
}
}
CbfLoc::MbLevel(xn, yn) => {
let Some((addr, _wx, _wy)) = grid.neighbour_block_loc(current_mb_addr, xn, yn, 16, 16)
else {
return unavail_cbf(current_is_intra, block_type);
};
let Some(info) = grid.mbs.get(addr as usize) else {
return unavail_cbf(current_is_intra, block_type);
};
if !info.available {
return unavail_cbf(current_is_intra, block_type);
}
if info.is_i_pcm {
return true;
}
if info.is_skip {
return false;
}
let neighbour_has_transblock = match block_type {
BlockType::Luma16x16Dc | BlockType::CbIntra16x16Dc | BlockType::CrIntra16x16Dc => {
info.is_intra && !info.is_i_nxn
}
BlockType::ChromaDc => info.coded_block_pattern_chroma != 0,
_ => true,
};
if !neighbour_has_transblock {
return trans_block_unavail_cbf();
}
cbf_mb_level_for(info, block_type, is_cr)
}
}
}
#[inline]
fn unavail_cbf(current_is_intra: bool, _block_type: BlockType) -> bool {
current_is_intra
}
#[inline]
fn trans_block_unavail_cbf() -> bool {
false
}
#[inline]
fn cbf_luma_for(info: &CabacMbNeighbourInfo, block_type: BlockType, bi: usize) -> bool {
match block_type {
BlockType::Luma4x4 | BlockType::Luma16x16Ac => {
info.cbf_luma_4x4.get(bi).copied().unwrap_or(false)
|| info.cbf_luma_16x16_ac.get(bi).copied().unwrap_or(false)
}
BlockType::CbLuma4x4 | BlockType::CbIntra16x16Ac => {
info.cbf_cb_luma_4x4.get(bi).copied().unwrap_or(false)
|| info.cbf_cb_16x16_ac.get(bi).copied().unwrap_or(false)
}
BlockType::CrLuma4x4 | BlockType::CrIntra16x16Ac => {
info.cbf_cr_luma_4x4.get(bi).copied().unwrap_or(false)
|| info.cbf_cr_16x16_ac.get(bi).copied().unwrap_or(false)
}
_ => false,
}
}
#[inline]
fn cbf_8x8_for(info: &CabacMbNeighbourInfo, block_type: BlockType, blk8: usize) -> bool {
let bi = blk8 * 4;
match block_type {
BlockType::Luma8x8 => info.cbf_luma_4x4.get(bi).copied().unwrap_or(false),
BlockType::Cb8x8 => info.cbf_cb_luma_4x4.get(bi).copied().unwrap_or(false),
BlockType::Cr8x8 => info.cbf_cr_luma_4x4.get(bi).copied().unwrap_or(false),
_ => false,
}
}
#[inline]
fn cbf_chroma_ac_for(
info: &CabacMbNeighbourInfo,
block_type: BlockType,
bi: usize,
is_cr: bool,
) -> bool {
match block_type {
BlockType::ChromaAc => {
if is_cr {
info.cbf_cr_ac.get(bi).copied().unwrap_or(false)
} else {
info.cbf_cb_ac.get(bi).copied().unwrap_or(false)
}
}
_ => false,
}
}
#[inline]
fn cbf_mb_level_for(info: &CabacMbNeighbourInfo, block_type: BlockType, is_cr: bool) -> bool {
match block_type {
BlockType::ChromaDc => {
if is_cr {
info.cbf_cr_dc
} else {
info.cbf_cb_dc
}
}
BlockType::Luma16x16Dc => info.cbf_luma_16x16_dc,
BlockType::CbIntra16x16Dc => info.cbf_cb_16x16_dc,
BlockType::CrIntra16x16Dc => info.cbf_cr_16x16_dc,
_ => false,
}
}
pub struct EntropyState<'ctx, 'data> {
pub cabac: Option<(&'ctx mut CabacDecoder<'data>, &'ctx mut CabacContexts)>,
pub slice_kind: SliceKind,
pub neighbours: NeighbourCtx,
pub prev_mb_qp_delta_nonzero: bool,
pub chroma_array_type: u32,
pub transform_8x8_mode_flag: bool,
pub cavlc_nc: Option<&'ctx mut CavlcNcGrid>,
pub current_mb_addr: u32,
pub constrained_intra_pred_flag: bool,
pub num_ref_idx_l0_active_minus1: u32,
pub num_ref_idx_l1_active_minus1: u32,
pub mbaff_frame_flag: bool,
pub mb_field_decoding_flag: bool,
pub cabac_nb: Option<&'ctx mut CabacNeighbourGrid>,
pub pic_width_in_mbs: u32,
pub bit_depth_luma_minus8: u32,
pub bit_depth_chroma_minus8: u32,
}
#[allow(clippy::type_complexity)]
fn read_pcm_payload(
pr: &mut BitReader<'_>,
bit_depth_y: u32,
bit_depth_c: u32,
num_cb: usize,
num_cr: usize,
) -> McblResult<(Vec<u32>, Vec<u32>, Vec<u32>)> {
while !pr.byte_aligned() {
let _ = pr.u(1)?;
}
let mut luma = Vec::with_capacity(256);
for _ in 0..256 {
luma.push(pr.u(bit_depth_y)?);
}
let mut cb = Vec::with_capacity(num_cb);
for _ in 0..num_cb {
cb.push(pr.u(bit_depth_c)?);
}
let mut cr = Vec::with_capacity(num_cr);
for _ in 0..num_cr {
cr.push(pr.u(bit_depth_c)?);
}
Ok((luma, cb, cr))
}
pub struct DpResidualReaders<'a> {
pub b: Option<BitReader<'a>>,
pub c: Option<BitReader<'a>>,
}
impl<'a> DpResidualReaders<'a> {
fn select(&mut self, intra: bool) -> McblResult<&mut BitReader<'a>> {
if intra {
self.b
.as_mut()
.ok_or(MacroblockLayerError::MissingDataPartition('B'))
} else {
self.c
.as_mut()
.ok_or(MacroblockLayerError::MissingDataPartition('C'))
}
}
}
pub fn parse_macroblock(
r: &mut BitReader<'_>,
entropy: &mut EntropyState<'_, '_>,
slice_header: &SliceHeader,
sps: &Sps,
pps: &Pps,
current_mb_addr: u32,
) -> McblResult<Macroblock> {
parse_macroblock_dp(r, None, entropy, slice_header, sps, pps, current_mb_addr)
}
#[allow(clippy::too_many_arguments)]
pub fn parse_macroblock_dp(
r: &mut BitReader<'_>,
mut dp: Option<&mut DpResidualReaders<'_>>,
entropy: &mut EntropyState<'_, '_>,
slice_header: &SliceHeader,
sps: &Sps,
_pps: &Pps,
_current_mb_addr: u32,
) -> McblResult<Macroblock> {
if dp.is_some() && entropy.cabac.is_some() {
return Err(MacroblockLayerError::DataPartitioningCabacUnsupported);
}
let slice_type = slice_header.slice_type;
let dbg = dbg_general_enabled();
let mb_addr_dbg = entropy.current_mb_addr;
if dbg {
let (b, bi) = r.position();
let (cb, cbi, bins) = if let Some((dec, _)) = entropy.cabac.as_ref() {
let (pb, pbi) = dec.position();
(pb as i64, pbi as i64, dec.bin_count() as i64)
} else {
(-1, -1, -1)
};
eprintln!(
"[MB {:>4}] enter cursor=({},{}) cabac=({},{}) bins={} slice_type={:?}",
mb_addr_dbg, b, bi, cb, cbi, bins, slice_type
);
}
let dbg_mb_enter = dbg_mb_trace_target();
let dbg_mb_enter_on = dbg_mb_enter == Some(entropy.current_mb_addr);
let dbg_mb_type_all = dbg_mbtype_trace_enabled();
let mb_type_raw = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let bins_before = dec.bin_count();
let v = match slice_type {
SliceType::I => decode_mb_type_i(dec, ctxs, &entropy.neighbours)?,
SliceType::SI => {
return Err(MacroblockLayerError::CabacSiSliceUnsupported);
}
SliceType::P | SliceType::SP => decode_mb_type_p(dec, ctxs, &entropy.neighbours)?,
SliceType::B => decode_mb_type_b(dec, ctxs, &entropy.neighbours)?,
};
if dbg_mb_enter_on || dbg_mb_type_all {
eprintln!(
"[MBTYPE {}] raw={} bins_consumed={} slice={:?}",
entropy.current_mb_addr,
v,
dec.bin_count() - bins_before,
slice_type
);
}
v
} else {
r.ue()?
};
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] after mb_type raw={} cursor=({},{})",
mb_addr_dbg, mb_type_raw, b, bi
);
}
let mb_type = match slice_type {
SliceType::I => MbType::from_i_slice(mb_type_raw)?,
SliceType::SI => MbType::from_si_slice(mb_type_raw)?,
SliceType::P | SliceType::SP => MbType::from_p_slice(mb_type_raw)?,
SliceType::B => MbType::from_b_slice(mb_type_raw)?,
};
if mb_type.is_i_pcm() {
if let Some((dec, _)) = entropy.cabac.as_ref() {
let (byte, bit) = dec.position();
return Err(MacroblockLayerError::IPcmNeedsCabacReinit {
cabac_byte_pos: byte,
cabac_bit_pos: bit,
});
}
let bit_depth_y: u32 = 8 + entropy.bit_depth_luma_minus8;
let bit_depth_c: u32 = 8 + entropy.bit_depth_chroma_minus8;
let (num_cb, num_cr): (usize, usize) = match entropy.chroma_array_type {
0 => (0, 0),
1 => (64, 64),
2 => (128, 128),
3 => (256, 256),
_ => {
return Err(MacroblockLayerError::UnsupportedChromaArrayType(
entropy.chroma_array_type,
))
}
};
let (luma, cb, cr) = match dp.as_deref_mut() {
Some(d) => read_pcm_payload(d.select(true)?, bit_depth_y, bit_depth_c, num_cb, num_cr)?,
None => read_pcm_payload(r, bit_depth_y, bit_depth_c, num_cb, num_cr)?,
};
entropy.prev_mb_qp_delta_nonzero = false;
if entropy.cabac.is_none() {
if let Some(grid) = entropy.cavlc_nc.as_deref_mut() {
if let Some(slot) = grid.mbs.get_mut(entropy.current_mb_addr as usize) {
slot.is_available = true;
slot.is_skip = false;
slot.is_intra = true;
slot.is_i_pcm = true;
slot.luma_total_coeff = [0; 16];
slot.cb_total_coeff = [0; 8];
slot.cr_total_coeff = [0; 8];
slot.cb_luma_total_coeff = [0; 16];
slot.cr_luma_total_coeff = [0; 16];
}
}
}
return Ok(Macroblock {
mb_type,
mb_type_raw,
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 mb_pred: Option<MbPred> = None;
let mut sub_mb_pred: Option<SubMbPred> = None;
let mut transform_size_8x8_flag = false;
if mb_type.is_sub_mb_path() {
sub_mb_pred = Some(parse_sub_mb_pred(r, entropy, &mb_type)?);
} else {
if matches!(mb_type, MbType::INxN) && entropy.transform_8x8_mode_flag {
transform_size_8x8_flag = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
decode_transform_size_8x8_flag(dec, ctxs, &entropy.neighbours)?
} else {
r.u(1)? == 1
};
}
mb_pred = Some(parse_mb_pred(
r,
entropy,
&mb_type,
transform_size_8x8_flag,
)?);
}
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] after mb_pred/sub_mb_pred mb_type={:?} cursor=({},{})",
mb_addr_dbg, mb_type, b, bi
);
}
let cbp_luma: u32;
let cbp_chroma: u32;
let cbp_total: u32;
if let Some(i16) = mb_type.intra_16x16_cbp_luma() {
cbp_luma = i16 as u32;
cbp_chroma = mb_type.intra_16x16_cbp_chroma().unwrap_or(0) as u32;
cbp_total = cbp_luma | (cbp_chroma << 4);
} else {
cbp_total = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let bb = dec.bin_count();
let v = decode_coded_block_pattern(
dec,
ctxs,
&entropy.neighbours,
entropy.chroma_array_type,
)?;
if dbg {
eprintln!(
"[MB {:>4}] CBP bins_consumed={} value={}",
mb_addr_dbg,
dec.bin_count() - bb,
v
);
}
v
} else {
parse_cbp_me(r, entropy.chroma_array_type, mb_type.is_intra())?
};
cbp_luma = cbp_total & 0x0F;
cbp_chroma = (cbp_total >> 4) & 0x03;
}
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] after CBP total={} luma={} chroma={} cursor=({},{})",
mb_addr_dbg, cbp_total, cbp_luma, cbp_chroma, b, bi
);
}
let direct_8x8_inference_flag = sps.direct_8x8_inference_flag;
let no_sub_lt_8x8 = if let Some(sub) = sub_mb_pred.as_ref() {
let mut flag = true;
for i in 0..4 {
let sub_t = sub.sub_mb_type[i];
if matches!(sub_t, SubMbType::BDirect8x8) {
if !direct_8x8_inference_flag {
flag = false;
}
} else if sub_t.num_sub_mb_part() > 1 {
flag = false;
}
}
flag
} else {
true
};
let t8x8_gate = cbp_luma > 0
&& entropy.transform_8x8_mode_flag
&& !mb_type.is_i_nxn()
&& !mb_type.is_intra_16x16()
&& no_sub_lt_8x8
&& (!matches!(mb_type, MbType::BDirect16x16) || direct_8x8_inference_flag);
if t8x8_gate {
transform_size_8x8_flag = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
decode_transform_size_8x8_flag(dec, ctxs, &entropy.neighbours)?
} else {
r.u(1)? == 1
};
}
let needs_qp_delta = cbp_luma > 0 || cbp_chroma > 0 || mb_type.is_intra_16x16();
let mb_qp_delta = if needs_qp_delta {
if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let bins_before = dec.bin_count();
let v = decode_mb_qp_delta(dec, ctxs, entropy.prev_mb_qp_delta_nonzero)?;
let bins_after = dec.bin_count();
if dbg {
eprintln!(
"[MB {:>4}] mb_qp_delta decode: prev_nz={} bins_consumed={} value={}",
mb_addr_dbg,
entropy.prev_mb_qp_delta_nonzero,
bins_after - bins_before,
v
);
}
v
} else {
r.se()?
}
} else {
0
};
entropy.prev_mb_qp_delta_nonzero = mb_qp_delta != 0;
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] after mb_qp_delta={} t8x8_flag={} cursor=({},{})",
mb_addr_dbg, mb_qp_delta, transform_size_8x8_flag, b, bi
);
}
let mut out = Macroblock {
mb_type: mb_type.clone(),
mb_type_raw,
mb_pred,
sub_mb_pred,
pcm_samples: None,
coded_block_pattern: cbp_total,
transform_size_8x8_flag,
mb_qp_delta,
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,
};
if let Some((cabac, ctxs)) = entropy.cabac.as_mut() {
let current_mb_addr = entropy.current_mb_addr;
let chroma_at = entropy.chroma_array_type;
let current_is_intra = mb_type.is_intra();
let nb_grid = entropy.cabac_nb.as_deref_mut();
let field_mb = entropy.mb_field_decoding_flag || slice_header.field_pic_flag;
parse_residual_cabac_only(
cabac,
ctxs,
chroma_at,
&mb_type,
cbp_luma,
cbp_chroma,
transform_size_8x8_flag,
&mut out,
nb_grid,
current_mb_addr,
current_is_intra,
field_mb,
)?;
if let Some(grid) = entropy.cabac_nb.as_deref_mut() {
if let Some(slot) = grid.mbs.get_mut(entropy.current_mb_addr as usize) {
slot.coded_block_pattern_luma = (cbp_total & 0x0F) as u8;
slot.coded_block_pattern_chroma = ((cbp_total >> 4) & 0x03) as u8;
slot.is_i_nxn = mb_type.is_i_nxn();
slot.is_b_skip_or_direct = mb_type.is_b_skip_or_direct();
slot.transform_size_8x8_flag = transform_size_8x8_flag;
if let Some(pred) = out.mb_pred.as_ref() {
slot.intra_chroma_pred_mode = pred.intra_chroma_pred_mode;
}
}
}
} else {
match dp {
Some(d) => parse_residual_cavlc_only(
d.select(mb_type.is_intra())?,
entropy,
&mb_type,
cbp_luma,
cbp_chroma,
transform_size_8x8_flag,
&mut out,
)?,
None => parse_residual_cavlc_only(
r,
entropy,
&mb_type,
cbp_luma,
cbp_chroma,
transform_size_8x8_flag,
&mut out,
)?,
}
}
if entropy.cabac.is_none() {
if let Some(grid) = entropy.cavlc_nc.as_deref_mut() {
let addr = entropy.current_mb_addr as usize;
if let Some(slot) = grid.mbs.get_mut(addr) {
slot.is_available = true;
slot.is_skip = false;
slot.is_intra = mb_type.is_intra();
slot.is_i_pcm = mb_type.is_i_pcm();
if mb_type.is_i_pcm() {
slot.luma_total_coeff = [0; 16];
slot.cb_total_coeff = [0; 8];
slot.cr_total_coeff = [0; 8];
}
}
}
}
Ok(out)
}
fn parse_mb_pred(
r: &mut BitReader<'_>,
entropy: &mut EntropyState<'_, '_>,
mb_type: &MbType,
transform_size_8x8_flag: bool,
) -> McblResult<MbPred> {
let mut pred = MbPred::default();
let dbg_mb = dbg_mb_trace_target();
let dbg_on = dbg_mb == Some(entropy.current_mb_addr);
if mb_type.is_i_nxn() {
if transform_size_8x8_flag {
for i in 0..4 {
let flag = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
decode_prev_intra_pred_mode_flag(dec, ctxs)?
} else {
r.u(1)? == 1
};
pred.prev_intra8x8_pred_mode_flag[i] = flag;
if !flag {
let rem = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
decode_rem_intra_pred_mode(dec, ctxs)?
} else {
r.u(3)?
};
pred.rem_intra8x8_pred_mode[i] = rem as u8;
}
}
} else {
for i in 0..16 {
let flag = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
decode_prev_intra_pred_mode_flag(dec, ctxs)?
} else {
r.u(1)? == 1
};
pred.prev_intra4x4_pred_mode_flag[i] = flag;
if !flag {
let rem = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
decode_rem_intra_pred_mode(dec, ctxs)?
} else {
r.u(3)?
};
pred.rem_intra4x4_pred_mode[i] = rem as u8;
}
}
}
if matches!(entropy.chroma_array_type, 1 | 2) {
let mode = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
decode_intra_chroma_pred_mode(dec, ctxs, &entropy.neighbours)?
} else {
r.ue()?
};
pred.intra_chroma_pred_mode = mode as u8;
}
} else if mb_type.is_intra_16x16() {
if matches!(entropy.chroma_array_type, 1 | 2) {
let mode = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
decode_intra_chroma_pred_mode(dec, ctxs, &entropy.neighbours)?
} else {
r.ue()?
};
pred.intra_chroma_pred_mode = mode as u8;
}
} else {
let num_parts = mb_type.num_mb_part() as usize;
let mbaff_override = entropy.mbaff_frame_flag && entropy.mb_field_decoding_flag;
let ref_l0_present = entropy.num_ref_idx_l0_active_minus1 > 0 || mbaff_override;
let ref_l1_present = entropy.num_ref_idx_l1_active_minus1 > 0 || mbaff_override;
let x_l0 = if mbaff_override {
2 * entropy.num_ref_idx_l0_active_minus1 + 1
} else {
entropy.num_ref_idx_l0_active_minus1
};
let x_l1 = if mbaff_override {
2 * entropy.num_ref_idx_l1_active_minus1 + 1
} else {
entropy.num_ref_idx_l1_active_minus1
};
let part_to_8x8 = |part: usize| -> u8 {
match (num_parts, part) {
(1, _) => 0,
(2, 0) => 0,
(2, 1) => {
if mb_type.is_partitioned_16x8() {
2
} else {
1
}
}
(4, p) => p as u8,
_ => 0,
}
};
let mut curr_nb = CabacMbNeighbourInfo {
available: false, ..CabacMbNeighbourInfo::default()
};
for part in 0..num_parts {
let mode = mb_type.mb_part_pred_mode(part);
if ref_l0_present
&& mode != Some(MbPartPredMode::PredL1)
&& mode != Some(MbPartPredMode::Direct)
{
let v = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let (ca, cb) = neighbour_ref_idx_gt_0(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
0,
part_to_8x8(part),
);
decode_ref_idx_lx(dec, ctxs, ca, cb)?
} else {
r.te(x_l0)?
};
if dbg_on {
eprintln!(
"[MBP {}] ref_idx_l0 part={} gated=true val={}",
entropy.current_mb_addr, part, v
);
}
for p8 in partition_8x8_range(num_parts, part, mb_type.is_partitioned_16x8()) {
curr_nb.ref_idx_l0[p8 as usize] = v.min(127) as i8;
}
pred.ref_idx_l0.push(v);
} else {
pred.ref_idx_l0.push(0);
}
}
for part in 0..num_parts {
let mode = mb_type.mb_part_pred_mode(part);
if ref_l1_present
&& mode != Some(MbPartPredMode::PredL0)
&& mode != Some(MbPartPredMode::Direct)
&& entropy.slice_kind == SliceKind::B
{
let v = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let (ca, cb) = neighbour_ref_idx_gt_0(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
1,
part_to_8x8(part),
);
decode_ref_idx_lx(dec, ctxs, ca, cb)?
} else {
r.te(x_l1)?
};
for p8 in partition_8x8_range(num_parts, part, mb_type.is_partitioned_16x8()) {
curr_nb.ref_idx_l1[p8 as usize] = v.min(127) as i8;
}
pred.ref_idx_l1.push(v);
} else {
pred.ref_idx_l1.push(0);
}
}
for part in 0..num_parts {
let mode = mb_type.mb_part_pred_mode(part);
if mode != Some(MbPartPredMode::PredL1) && mode != Some(MbPartPredMode::Direct) {
let (mx, my) = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let blk4 = mb_part_top_left_4x4(num_parts, part, mb_type.is_partitioned_16x8());
let sum_x = neighbour_mvd_abs_sum(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
0,
MvdComponent::X,
blk4,
);
let sum_y = neighbour_mvd_abs_sum(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
0,
MvdComponent::Y,
blk4,
);
let bb_mvd = dec.bin_count();
let mx = decode_mvd_lx(dec, ctxs, MvdComponent::X, sum_x)?;
let mb_x = dec.bin_count();
let my = decode_mvd_lx(dec, ctxs, MvdComponent::Y, sum_y)?;
let mb_y = dec.bin_count();
if dbg_on {
eprintln!("[MBP {}] mvd_l0 part={} blk4={} sum_x={} sum_y={} mx={} (bins={}) my={} (bins={})",
entropy.current_mb_addr, part, blk4, sum_x, sum_y,
mx, mb_x - bb_mvd, my, mb_y - mb_x);
}
for b4 in mb_part_4x4_range(num_parts, part, mb_type.is_partitioned_16x8()) {
curr_nb.mvd_l0_x[b4 as usize] =
mx.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
curr_nb.mvd_l0_y[b4 as usize] =
my.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
}
(mx, my)
} else {
(r.se()?, r.se()?)
};
pred.mvd_l0.push([mx, my]);
} else {
pred.mvd_l0.push([0, 0]);
}
}
for part in 0..num_parts {
let mode = mb_type.mb_part_pred_mode(part);
if mode != Some(MbPartPredMode::PredL0)
&& mode != Some(MbPartPredMode::Direct)
&& entropy.slice_kind == SliceKind::B
{
let (mx, my) = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let blk4 = mb_part_top_left_4x4(num_parts, part, mb_type.is_partitioned_16x8());
let sum_x = neighbour_mvd_abs_sum(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
1,
MvdComponent::X,
blk4,
);
let sum_y = neighbour_mvd_abs_sum(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
1,
MvdComponent::Y,
blk4,
);
let mx = decode_mvd_lx(dec, ctxs, MvdComponent::X, sum_x)?;
let my = decode_mvd_lx(dec, ctxs, MvdComponent::Y, sum_y)?;
for b4 in mb_part_4x4_range(num_parts, part, mb_type.is_partitioned_16x8()) {
curr_nb.mvd_l1_x[b4 as usize] =
mx.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
curr_nb.mvd_l1_y[b4 as usize] =
my.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
}
(mx, my)
} else {
(r.se()?, r.se()?)
};
pred.mvd_l1.push([mx, my]);
} else {
pred.mvd_l1.push([0, 0]);
}
}
if let Some(grid) = entropy.cabac_nb.as_deref_mut() {
if let Some(slot) = grid.mbs.get_mut(entropy.current_mb_addr as usize) {
slot.ref_idx_l0 = curr_nb.ref_idx_l0;
slot.ref_idx_l1 = curr_nb.ref_idx_l1;
slot.mvd_l0_x = curr_nb.mvd_l0_x;
slot.mvd_l0_y = curr_nb.mvd_l0_y;
slot.mvd_l1_x = curr_nb.mvd_l1_x;
slot.mvd_l1_y = curr_nb.mvd_l1_y;
}
}
}
Ok(pred)
}
fn partition_8x8_range(num_parts: usize, part: usize, is_16x8: bool) -> Vec<u8> {
match (num_parts, part, is_16x8) {
(1, _, _) => vec![0, 1, 2, 3],
(2, 0, true) => vec![0, 1], (2, 1, true) => vec![2, 3], (2, 0, false) => vec![0, 2], (2, 1, false) => vec![1, 3], (4, p, _) => vec![p as u8],
_ => vec![0],
}
}
fn mb_part_4x4_range(num_parts: usize, part: usize, is_16x8: bool) -> Vec<u8> {
match (num_parts, part, is_16x8) {
(1, _, _) => (0..16u8).collect(),
(2, 0, true) => vec![0, 1, 2, 3, 4, 5, 6, 7], (2, 1, true) => vec![8, 9, 10, 11, 12, 13, 14, 15], (2, 0, false) => vec![0, 1, 2, 3, 8, 9, 10, 11], (2, 1, false) => vec![4, 5, 6, 7, 12, 13, 14, 15], (4, 0, _) => vec![0, 1, 2, 3],
(4, 1, _) => vec![4, 5, 6, 7],
(4, 2, _) => vec![8, 9, 10, 11],
(4, 3, _) => vec![12, 13, 14, 15],
_ => vec![0],
}
}
fn mb_part_top_left_4x4(num_parts: usize, part: usize, is_16x8: bool) -> u8 {
match (num_parts, part, is_16x8) {
(1, _, _) => 0,
(2, 0, true) => 0,
(2, 1, true) => 8,
(2, 0, false) => 0,
(2, 1, false) => 4,
(4, 0, _) => 0,
(4, 1, _) => 4,
(4, 2, _) => 8,
(4, 3, _) => 12,
_ => 0,
}
}
fn neighbour_ref_idx_gt_0(
grid: Option<&CabacNeighbourGrid>,
current_mb_addr: u32,
current_mb: &CabacMbNeighbourInfo,
list: u8,
part8x8: u8,
) -> (bool, bool) {
let Some(g) = grid else { return (false, false) };
if g.width_in_mbs == 0 {
return (false, false);
}
cabac_ref_idx_cond_terms(g, current_mb_addr, current_mb, list, part8x8)
}
fn neighbour_mvd_abs_sum(
grid: Option<&CabacNeighbourGrid>,
current_mb_addr: u32,
current_mb: &CabacMbNeighbourInfo,
list: u8,
comp: MvdComponent,
blk4x4: u8,
) -> u32 {
let Some(g) = grid else { return 0 };
if g.width_in_mbs == 0 {
return 0;
}
cabac_mvd_abs_sum(g, current_mb_addr, current_mb, list, comp, blk4x4)
}
fn parse_sub_mb_pred(
r: &mut BitReader<'_>,
entropy: &mut EntropyState<'_, '_>,
mb_type: &MbType,
) -> McblResult<SubMbPred> {
let mut out = SubMbPred::default();
let is_b = matches!(mb_type, MbType::B8x8);
let is_p8x8_ref0 = matches!(mb_type, MbType::P8x8Ref0);
let dbg_mb = dbg_mb_trace_target();
let dbg_on = dbg_mb == Some(entropy.current_mb_addr);
for i in 0..4 {
let raw = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
match entropy.slice_kind {
crate::cabac_ctx::SliceKind::B => {
crate::cabac_ctx::decode_sub_mb_type_b(dec, ctxs)?
}
_ => crate::cabac_ctx::decode_sub_mb_type_p(dec, ctxs)?,
}
} else {
r.ue()?
};
out.sub_mb_type[i] = if is_b {
SubMbType::from_b(raw)?
} else {
SubMbType::from_p(raw)?
};
if dbg_on {
eprintln!(
"[SUB {}] sub_mb_type[{}] raw={} -> {:?}",
entropy.current_mb_addr, i, raw, out.sub_mb_type[i]
);
}
}
let mbaff_override = entropy.mbaff_frame_flag && entropy.mb_field_decoding_flag;
let ref_l0_present = entropy.num_ref_idx_l0_active_minus1 > 0 || mbaff_override;
let ref_l1_present = entropy.num_ref_idx_l1_active_minus1 > 0 || mbaff_override;
let x_l0 = if mbaff_override {
2 * entropy.num_ref_idx_l0_active_minus1 + 1
} else {
entropy.num_ref_idx_l0_active_minus1
};
let x_l1 = if mbaff_override {
2 * entropy.num_ref_idx_l1_active_minus1 + 1
} else {
entropy.num_ref_idx_l1_active_minus1
};
let mut curr_nb = CabacMbNeighbourInfo::default();
for i in 0..4 {
let sub = out.sub_mb_type[i];
let mode = sub.sub_mb_pred_mode();
let is_direct_sub = matches!(sub, SubMbType::BDirect8x8);
let gated = ref_l0_present
&& !is_p8x8_ref0
&& !is_direct_sub
&& mode != Some(MbPartPredMode::PredL1);
let v = if gated {
if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let (ca, cb) = neighbour_ref_idx_gt_0(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
0,
i as u8,
);
decode_ref_idx_lx(dec, ctxs, ca, cb)?
} else {
r.te(x_l0)?
}
} else {
0
};
out.ref_idx_l0[i] = v;
curr_nb.ref_idx_l0[i] = v.min(127) as i8;
}
if is_b {
for i in 0..4 {
let sub = out.sub_mb_type[i];
let mode = sub.sub_mb_pred_mode();
let is_direct_sub = matches!(sub, SubMbType::BDirect8x8);
let gated = ref_l1_present && !is_direct_sub && mode != Some(MbPartPredMode::PredL0);
let v = if gated {
if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let (ca, cb) = neighbour_ref_idx_gt_0(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
1,
i as u8,
);
decode_ref_idx_lx(dec, ctxs, ca, cb)?
} else {
r.te(x_l1)?
}
} else {
0
};
out.ref_idx_l1[i] = v;
curr_nb.ref_idx_l1[i] = v.min(127) as i8;
}
}
for i in 0..4 {
let sub = out.sub_mb_type[i];
let mode = sub.sub_mb_pred_mode();
let is_direct_sub = matches!(sub, SubMbType::BDirect8x8);
if !is_direct_sub && mode != Some(MbPartPredMode::PredL1) {
let n = sub.num_sub_mb_part() as usize;
for sp in 0..n {
let (mx, my) = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let blk4 = sub_mb_sub_part_4x4(i, sp, sub);
let sum_x = neighbour_mvd_abs_sum(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
0,
MvdComponent::X,
blk4,
);
let sum_y = neighbour_mvd_abs_sum(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
0,
MvdComponent::Y,
blk4,
);
let mx = decode_mvd_lx(dec, ctxs, MvdComponent::X, sum_x)?;
let my = decode_mvd_lx(dec, ctxs, MvdComponent::Y, sum_y)?;
if dbg_on {
eprintln!(
"[SUB {}] mvd_l0 blk8={} sp={} blk4={} sum_x={} sum_y={} mx={} my={}",
entropy.current_mb_addr, i, sp, blk4, sum_x, sum_y, mx, my
);
}
for b4 in sub_mb_sub_part_4x4_range(i, sp, sub) {
curr_nb.mvd_l0_x[b4 as usize] =
mx.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
curr_nb.mvd_l0_y[b4 as usize] =
my.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
}
(mx, my)
} else {
(r.se()?, r.se()?)
};
out.mvd_l0[i].push([mx, my]);
}
}
}
if is_b {
for i in 0..4 {
let sub = out.sub_mb_type[i];
let mode = sub.sub_mb_pred_mode();
let is_direct_sub = matches!(sub, SubMbType::BDirect8x8);
if !is_direct_sub && mode != Some(MbPartPredMode::PredL0) {
let n = sub.num_sub_mb_part() as usize;
for sp in 0..n {
let (mx, my) = if let Some((dec, ctxs)) = entropy.cabac.as_mut() {
let blk4 = sub_mb_sub_part_4x4(i, sp, sub);
let sum_x = neighbour_mvd_abs_sum(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
1,
MvdComponent::X,
blk4,
);
let sum_y = neighbour_mvd_abs_sum(
entropy.cabac_nb.as_deref(),
entropy.current_mb_addr,
&curr_nb,
1,
MvdComponent::Y,
blk4,
);
let mx = decode_mvd_lx(dec, ctxs, MvdComponent::X, sum_x)?;
let my = decode_mvd_lx(dec, ctxs, MvdComponent::Y, sum_y)?;
for b4 in sub_mb_sub_part_4x4_range(i, sp, sub) {
curr_nb.mvd_l1_x[b4 as usize] =
mx.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
curr_nb.mvd_l1_y[b4 as usize] =
my.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
}
(mx, my)
} else {
(r.se()?, r.se()?)
};
out.mvd_l1[i].push([mx, my]);
}
}
}
}
if let Some(grid) = entropy.cabac_nb.as_deref_mut() {
if let Some(slot) = grid.mbs.get_mut(entropy.current_mb_addr as usize) {
slot.ref_idx_l0 = curr_nb.ref_idx_l0;
slot.ref_idx_l1 = curr_nb.ref_idx_l1;
slot.mvd_l0_x = curr_nb.mvd_l0_x;
slot.mvd_l0_y = curr_nb.mvd_l0_y;
slot.mvd_l1_x = curr_nb.mvd_l1_x;
slot.mvd_l1_y = curr_nb.mvd_l1_y;
}
}
Ok(out)
}
fn sub_mb_sub_part_4x4(i: usize, sp: usize, sub: SubMbType) -> u8 {
let base = (i as u8) * 4;
let offset = match sub.num_sub_mb_part() {
1 => 0u8,
2 => {
if sub.is_8x4() {
if sp == 0 {
0
} else {
2
}
} else if sp == 0 {
0
} else {
1
}
}
4 => sp as u8,
_ => 0,
};
base + offset
}
fn sub_mb_sub_part_4x4_range(i: usize, sp: usize, sub: SubMbType) -> Vec<u8> {
let base = (i as u8) * 4;
match sub.num_sub_mb_part() {
1 => vec![base, base + 1, base + 2, base + 3],
2 => {
if sub.is_8x4() {
if sp == 0 {
vec![base, base + 1]
} else {
vec![base + 2, base + 3]
}
} else if sp == 0 {
vec![base, base + 2]
} else {
vec![base + 1, base + 3]
}
}
4 => vec![base + sp as u8],
_ => vec![base],
}
}
fn pad_to_16(v: Vec<i32>) -> [i32; 16] {
let mut out = [0i32; 16];
for (i, x) in v.into_iter().enumerate() {
if i >= 16 {
break;
}
out[i] = x;
}
out
}
#[inline]
fn count_nonzero(buf: &[i32]) -> u8 {
buf.iter().filter(|&&v| v != 0).count().min(255) as u8
}
#[inline]
fn num_chroma_4x4_blocks(chroma_array_type: u32) -> usize {
match chroma_array_type {
1 => 4,
2 => 8,
_ => 0,
}
}
fn parse_residual_cavlc_only(
r: &mut BitReader<'_>,
entropy: &mut EntropyState<'_, '_>,
mb_type: &MbType,
cbp_luma: u32,
cbp_chroma: u32,
transform_size_8x8_flag: bool,
out: &mut Macroblock,
) -> McblResult<()> {
let chroma_array_type = entropy.chroma_array_type;
let current_mb_addr = entropy.current_mb_addr;
let current_is_intra = mb_type.is_intra();
let cip = entropy.constrained_intra_pred_flag;
let mut own_luma_totals: [u8; 16] = [0; 16];
let mut own_cb_totals: [u8; 8] = [0; 8];
let mut own_cr_totals: [u8; 8] = [0; 8];
let mut own_cb_luma_totals: [u8; 16] = [0; 16];
let mut own_cr_luma_totals: [u8; 16] = [0; 16];
let derive_luma =
|blk_idx: u8, kind: LumaNcKind, own: &[u8; 16], grid: Option<&CavlcNcGrid>| -> i32 {
let blk = match kind {
LumaNcKind::Intra16x16Dc => 0u8,
LumaNcKind::Ac => blk_idx,
};
if let Some(g) = grid.filter(|g| g.mbaff_frame_flag) {
let (bx, by) = blk4x4_xy(blk);
let probe = |xn: i32, yn: i32| -> (bool, u8) {
match g.neighbour_block_loc(current_mb_addr, xn, yn, 16, 16) {
Some((addr, wx, wy)) => {
let bi = blk4x4_idx(wx, wy);
if addr == current_mb_addr {
(true, own[bi as usize])
} else {
nc_nn_luma(g, Some(addr), bi, current_is_intra, cip)
}
}
None => (false, 0),
}
};
let (avail_a, n_a) = probe(bx - 1, by);
let (avail_b, n_b) = probe(bx, by - 1);
return combine_nc(avail_a, n_a, avail_b, n_b);
}
let [a_src, b_src, _c, _d] = neighbour_4x4_map(blk);
let (avail_a, n_a) = match a_src {
NeighbourSource::InternalBlock(i) => (true, own[i as usize]),
_ => {
let Some(g) = grid else { return 0 };
let (addr, bi) = resolve_luma_neighbour(g, current_mb_addr, a_src);
nc_nn_luma(g, addr, bi, current_is_intra, cip)
}
};
let (avail_b, n_b) = match b_src {
NeighbourSource::InternalBlock(i) => (true, own[i as usize]),
_ => {
let Some(g) = grid else { return 0 };
let (addr, bi) = resolve_luma_neighbour(g, current_mb_addr, b_src);
nc_nn_luma(g, addr, bi, current_is_intra, cip)
}
};
combine_nc(avail_a, n_a, avail_b, n_b)
};
let derive_chroma = |blk_idx: u8,
is_cr: bool,
own_cb: &[u8; 8],
own_cr: &[u8; 8],
grid: Option<&CavlcNcGrid>|
-> i32 {
let x = ((blk_idx as i32) % 2) * 4;
let y = ((blk_idx as i32) / 2) * 4;
let max_w: i32 = 8;
let max_h: i32 = if chroma_array_type == 2 { 16 } else { 8 };
let (xa, ya) = (x - 1, y);
let (xb, yb) = (x, y - 1);
let own_arr = if is_cr { own_cr } else { own_cb };
let (avail_a, n_a) = if xa >= 0 && xa < max_w && ya >= 0 && ya < max_h {
let bi = (2 * (ya / 4) + (xa / 4)) as usize;
(true, own_arr[bi])
} else {
let Some(g) = grid else { return 0 };
let (addr, bi) = resolve_chroma_neighbour(g, current_mb_addr, xa, ya, max_w, max_h);
nc_nn_chroma(g, addr, bi, is_cr, current_is_intra, cip)
};
let (avail_b, n_b) = if xb >= 0 && xb < max_w && yb >= 0 && yb < max_h {
let bi = (2 * (yb / 4) + (xb / 4)) as usize;
(true, own_arr[bi])
} else {
let Some(g) = grid else { return 0 };
let (addr, bi) = resolve_chroma_neighbour(g, current_mb_addr, xb, yb, max_w, max_h);
nc_nn_chroma(g, addr, bi, is_cr, current_is_intra, cip)
};
combine_nc(avail_a, n_a, avail_b, n_b)
};
let derive_plane_luma_like = |blk_idx: u8,
is_cr: bool,
own_cb_lt: &[u8; 16],
own_cr_lt: &[u8; 16],
grid: Option<&CavlcNcGrid>|
-> i32 {
let own = if is_cr { own_cr_lt } else { own_cb_lt };
if let Some(g) = grid.filter(|g| g.mbaff_frame_flag) {
let (bx, by) = blk4x4_xy(blk_idx);
let probe = |xn: i32, yn: i32| -> (bool, u8) {
match g.neighbour_block_loc(current_mb_addr, xn, yn, 16, 16) {
Some((addr, wx, wy)) => {
let bi = blk4x4_idx(wx, wy);
if addr == current_mb_addr {
(true, own[bi as usize])
} else {
nc_nn_plane_luma_like(g, Some(addr), bi, is_cr, current_is_intra, cip)
}
}
None => (false, 0),
}
};
let (avail_a, n_a) = probe(bx - 1, by);
let (avail_b, n_b) = probe(bx, by - 1);
return combine_nc(avail_a, n_a, avail_b, n_b);
}
let [a_src, b_src, _c, _d] = neighbour_4x4_map(blk_idx);
let (avail_a, n_a) = match a_src {
NeighbourSource::InternalBlock(i) => (true, own[i as usize]),
_ => {
let Some(g) = grid else { return 0 };
let (addr, bi) = resolve_luma_neighbour(g, current_mb_addr, a_src);
nc_nn_plane_luma_like(g, addr, bi, is_cr, current_is_intra, cip)
}
};
let (avail_b, n_b) = match b_src {
NeighbourSource::InternalBlock(i) => (true, own[i as usize]),
_ => {
let Some(g) = grid else { return 0 };
let (addr, bi) = resolve_luma_neighbour(g, current_mb_addr, b_src);
nc_nn_plane_luma_like(g, addr, bi, is_cr, current_is_intra, cip)
}
};
combine_nc(avail_a, n_a, avail_b, n_b)
};
let grid_ref: Option<&CavlcNcGrid> = entropy.cavlc_nc.as_deref();
let dbg = dbg_general_enabled();
if mb_type.is_intra_16x16() {
let nc = derive_luma(0, LumaNcKind::Intra16x16Dc, &own_luma_totals, grid_ref);
let blk = parse_residual_block_cavlc(r, CoeffTokenContext::Numeric(nc), 0, 15, 16)?;
out.residual_luma_dc = Some(pad_to_16(blk));
}
if mb_type.is_intra_16x16() {
if cbp_luma == 15 {
for blk_idx in 0..16u8 {
let nc = derive_luma(blk_idx, LumaNcKind::Ac, &own_luma_totals, grid_ref);
let blk = parse_residual_block_cavlc(r, CoeffTokenContext::Numeric(nc), 0, 14, 15)?;
let tc = count_nonzero(&blk);
own_luma_totals[blk_idx as usize] = tc;
out.residual_luma.push(pad_to_16(blk));
}
}
} else if transform_size_8x8_flag {
for blk8 in 0..4u8 {
if (cbp_luma >> blk8) & 1 == 1 {
let mut sub_scan = [[0i32; 16]; 4];
for (sub, dst) in sub_scan.iter_mut().enumerate() {
let blk_idx = blk8 * 4 + sub as u8;
let nc = derive_luma(blk_idx, LumaNcKind::Ac, &own_luma_totals, grid_ref);
let blk =
parse_residual_block_cavlc(r, CoeffTokenContext::Numeric(nc), 0, 15, 16)?;
let tc = count_nonzero(&blk);
own_luma_totals[blk_idx as usize] = tc;
*dst = pad_to_16(blk);
}
let mut scan8 = [0i32; 64];
for (i4x4, sub) in sub_scan.iter().enumerate() {
for (i, &c) in sub.iter().enumerate() {
scan8[4 * i + i4x4] = c;
}
}
for chunk in scan8.chunks_exact(16) {
let mut store = [0i32; 16];
store.copy_from_slice(chunk);
out.residual_luma.push(store);
}
}
}
} else {
for blk8 in 0..4u8 {
if (cbp_luma >> blk8) & 1 == 1 {
for sub in 0..4u8 {
let blk_idx = blk8 * 4 + sub;
let nc = derive_luma(blk_idx, LumaNcKind::Ac, &own_luma_totals, grid_ref);
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] luma4x4 blk={} nc={} cursor=({},{})",
current_mb_addr, blk_idx, nc, b, bi
);
}
let blk =
parse_residual_block_cavlc(r, CoeffTokenContext::Numeric(nc), 0, 15, 16)?;
let tc = count_nonzero(&blk);
own_luma_totals[blk_idx as usize] = tc;
out.residual_luma.push(pad_to_16(blk));
}
}
}
}
if chroma_array_type == 1 || chroma_array_type == 2 {
let num_c_blk = num_chroma_4x4_blocks(chroma_array_type);
if cbp_chroma > 0 {
let dc_ctx = if chroma_array_type == 1 {
CoeffTokenContext::ChromaDc420
} else {
CoeffTokenContext::ChromaDc422
};
let max_dc = if chroma_array_type == 1 { 4 } else { 8 };
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] chromaDc cb cursor=({},{})",
current_mb_addr, b, bi
);
}
let cb_dc = parse_residual_block_cavlc(r, dc_ctx, 0, max_dc - 1, max_dc)?;
out.residual_chroma_dc_cb = cb_dc;
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] chromaDc cr cursor=({},{})",
current_mb_addr, b, bi
);
}
let cr_dc = parse_residual_block_cavlc(r, dc_ctx, 0, max_dc - 1, max_dc)?;
out.residual_chroma_dc_cr = cr_dc;
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] chromaDc done cursor=({},{})",
current_mb_addr, b, bi
);
}
}
if cbp_chroma == 2 {
if dbg {
let (b, bi) = r.position();
eprintln!(
"[MB {:>4}] chromaAc start cursor=({},{})",
current_mb_addr, b, bi
);
}
for blk_idx in 0..num_c_blk as u8 {
let nc = derive_chroma(
blk_idx,
false,
&own_cb_totals,
&own_cr_totals,
grid_ref,
);
let blk = parse_residual_block_cavlc(r, CoeffTokenContext::Numeric(nc), 0, 14, 15)?;
let tc = count_nonzero(&blk);
own_cb_totals[blk_idx as usize] = tc;
out.residual_chroma_ac_cb.push(pad_to_16(blk));
}
for blk_idx in 0..num_c_blk as u8 {
let nc = derive_chroma(
blk_idx,
true,
&own_cb_totals,
&own_cr_totals,
grid_ref,
);
let blk = parse_residual_block_cavlc(r, CoeffTokenContext::Numeric(nc), 0, 14, 15)?;
let tc = count_nonzero(&blk);
own_cr_totals[blk_idx as usize] = tc;
out.residual_chroma_ac_cr.push(pad_to_16(blk));
}
}
} else if chroma_array_type == 0 {
} else if chroma_array_type == 3 {
for plane_is_cr in [false, true] {
if mb_type.is_intra_16x16() {
let nc = derive_plane_luma_like(
0,
plane_is_cr,
&own_cb_luma_totals,
&own_cr_luma_totals,
grid_ref,
);
let blk = parse_residual_block_cavlc(r, CoeffTokenContext::Numeric(nc), 0, 15, 16)?;
if plane_is_cr {
out.residual_cr_16x16_dc = Some(pad_to_16(blk));
} else {
out.residual_cb_16x16_dc = Some(pad_to_16(blk));
}
}
if mb_type.is_intra_16x16() {
if cbp_luma == 15 {
for blk_idx in 0..16u8 {
let nc = derive_plane_luma_like(
blk_idx,
plane_is_cr,
&own_cb_luma_totals,
&own_cr_luma_totals,
grid_ref,
);
let blk = parse_residual_block_cavlc(
r,
CoeffTokenContext::Numeric(nc),
0,
14,
15,
)?;
let tc = count_nonzero(&blk);
if plane_is_cr {
own_cr_luma_totals[blk_idx as usize] = tc;
out.residual_cr_luma_like.push(pad_to_16(blk));
} else {
own_cb_luma_totals[blk_idx as usize] = tc;
out.residual_cb_luma_like.push(pad_to_16(blk));
}
}
}
} else if transform_size_8x8_flag {
for blk8 in 0..4u8 {
if (cbp_luma >> blk8) & 1 == 1 {
let mut sub_scan = [[0i32; 16]; 4];
for (sub, dst) in sub_scan.iter_mut().enumerate() {
let blk_idx = blk8 * 4 + sub as u8;
let nc = derive_plane_luma_like(
blk_idx,
plane_is_cr,
&own_cb_luma_totals,
&own_cr_luma_totals,
grid_ref,
);
let blk = parse_residual_block_cavlc(
r,
CoeffTokenContext::Numeric(nc),
0,
15,
16,
)?;
let tc = count_nonzero(&blk);
if plane_is_cr {
own_cr_luma_totals[blk_idx as usize] = tc;
} else {
own_cb_luma_totals[blk_idx as usize] = tc;
}
*dst = pad_to_16(blk);
}
let mut scan8 = [0i32; 64];
for (i4x4, sub) in sub_scan.iter().enumerate() {
for (i, &c) in sub.iter().enumerate() {
scan8[4 * i + i4x4] = c;
}
}
for chunk in scan8.chunks_exact(16) {
let mut store = [0i32; 16];
store.copy_from_slice(chunk);
if plane_is_cr {
out.residual_cr_luma_like.push(store);
} else {
out.residual_cb_luma_like.push(store);
}
}
}
}
} else {
for blk8 in 0..4u8 {
if (cbp_luma >> blk8) & 1 == 1 {
for sub in 0..4u8 {
let blk_idx = blk8 * 4 + sub;
let nc = derive_plane_luma_like(
blk_idx,
plane_is_cr,
&own_cb_luma_totals,
&own_cr_luma_totals,
grid_ref,
);
let blk = parse_residual_block_cavlc(
r,
CoeffTokenContext::Numeric(nc),
0,
15,
16,
)?;
let tc = count_nonzero(&blk);
if plane_is_cr {
own_cr_luma_totals[blk_idx as usize] = tc;
out.residual_cr_luma_like.push(pad_to_16(blk));
} else {
own_cb_luma_totals[blk_idx as usize] = tc;
out.residual_cb_luma_like.push(pad_to_16(blk));
}
}
}
}
}
}
} else {
return Err(MacroblockLayerError::UnsupportedChromaArrayType(
chroma_array_type,
));
}
if let Some(grid) = entropy.cavlc_nc.as_deref_mut() {
if let Some(slot) = grid.mbs.get_mut(current_mb_addr as usize) {
slot.luma_total_coeff = own_luma_totals;
slot.cb_total_coeff = own_cb_totals;
slot.cr_total_coeff = own_cr_totals;
slot.cb_luma_total_coeff = own_cb_luma_totals;
slot.cr_luma_total_coeff = own_cr_luma_totals;
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn parse_residual_block_cabac(
cabac: &mut CabacDecoder<'_>,
ctxs: &mut CabacContexts,
block_type: BlockType,
start_idx: u32,
end_idx: u32,
max_num_coeff: u32,
chroma_array_type: u32,
neighbour_cbf_left: Option<bool>,
neighbour_cbf_above: Option<bool>,
field: bool,
) -> McblResult<(Vec<i32>, bool)> {
let len = (end_idx - start_idx + 1) as usize;
let mut out = vec![0i32; len];
let coded = if max_num_coeff != 64 || chroma_array_type == 3 {
decode_coded_block_flag(
cabac,
ctxs,
block_type,
neighbour_cbf_left,
neighbour_cbf_above,
)?
} else {
true
};
if !coded {
return Ok((out, false));
}
let mut significant = vec![false; len];
let span = end_idx - start_idx + 1;
let mut num_coeff_in_scan = span; let num_c8x8 = if chroma_array_type == 2 { 2 } else { 1 };
let mut i: u32 = 0;
while i + 1 < num_coeff_in_scan {
let sig = decode_significant_coeff_flag(cabac, ctxs, block_type, i, field, num_c8x8)?;
significant[i as usize] = sig;
if sig {
let last =
decode_last_significant_coeff_flag(cabac, ctxs, block_type, i, field, num_c8x8)?;
if last {
num_coeff_in_scan = i + 1;
break;
}
}
i += 1;
}
significant[(num_coeff_in_scan - 1) as usize] = true;
let mut num_eq1: u32 = 0;
let mut num_gt1: u32 = 0;
for pos in (0..num_coeff_in_scan).rev() {
if !significant[pos as usize] {
continue;
}
let abs_m1 = decode_coeff_abs_level_minus1(cabac, ctxs, block_type, num_eq1, num_gt1)?;
let sign = decode_coeff_sign_flag(cabac)?;
let val = ((abs_m1 + 1) as i32) * if sign { -1 } else { 1 };
out[pos as usize] = val;
if abs_m1 == 0 {
num_eq1 += 1;
} else {
num_gt1 += 1;
}
}
Ok((out, true))
}
#[allow(clippy::too_many_arguments)]
fn parse_residual_cabac_only(
cabac: &mut CabacDecoder<'_>,
ctxs: &mut CabacContexts,
chroma_array_type: u32,
mb_type: &MbType,
cbp_luma: u32,
cbp_chroma: u32,
transform_size_8x8_flag: bool,
out: &mut Macroblock,
nb_grid: Option<&mut CabacNeighbourGrid>,
current_mb_addr: u32,
current_is_intra: bool,
field: bool,
) -> McblResult<()> {
let mut curr_cbf = CabacMbNeighbourInfo {
is_intra: current_is_intra,
is_i_pcm: mb_type.is_i_pcm(),
is_skip: mb_type.is_skip(),
..CabacMbNeighbourInfo::default()
};
let grid_ref: Option<&CabacNeighbourGrid> = nb_grid.as_deref();
if mb_type.is_intra_16x16() {
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
BlockType::Luma16x16Dc,
0,
false,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
BlockType::Luma16x16Dc,
0,
15,
16,
chroma_array_type,
ca,
cb,
field,
)?;
curr_cbf.cbf_luma_16x16_dc = coded;
out.residual_luma_dc = Some(pad_to_16(blk));
}
if mb_type.is_intra_16x16() {
if cbp_luma == 15 {
for blk_idx in 0..16u8 {
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
BlockType::Luma16x16Ac,
blk_idx,
false,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
BlockType::Luma16x16Ac,
0,
14,
15,
chroma_array_type,
ca,
cb,
field,
)?;
curr_cbf.cbf_luma_16x16_ac[blk_idx as usize] = coded;
out.residual_luma.push(pad_to_16(blk));
}
}
} else if transform_size_8x8_flag {
for blk8 in 0..4u8 {
if (cbp_luma >> blk8) & 1 == 1 {
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
BlockType::Luma8x8,
blk8,
false,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
BlockType::Luma8x8,
0,
63,
64,
chroma_array_type,
ca,
cb,
field,
)?;
for sub in 0..4u8 {
let idx = (blk8 * 4 + sub) as usize;
curr_cbf.cbf_luma_4x4[idx] = coded;
}
for sub in 0..4 {
let mut chunk = [0i32; 16];
for k in 0..16 {
chunk[k] = blk[sub * 16 + k];
}
out.residual_luma.push(chunk);
}
}
}
} else {
for blk8 in 0..4u8 {
if (cbp_luma >> blk8) & 1 == 1 {
for sub in 0..4u8 {
let blk_idx = blk8 * 4 + sub;
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
BlockType::Luma4x4,
blk_idx,
false,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
BlockType::Luma4x4,
0,
15,
16,
chroma_array_type,
ca,
cb,
field,
)?;
curr_cbf.cbf_luma_4x4[blk_idx as usize] = coded;
out.residual_luma.push(pad_to_16(blk));
}
}
}
}
if chroma_array_type == 1 || chroma_array_type == 2 {
let num_c8x8 = if chroma_array_type == 1 { 1 } else { 2 };
if cbp_chroma > 0 {
let max_dc = if chroma_array_type == 1 { 4 } else { 8 };
let (ca_cb, cb_cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
BlockType::ChromaDc,
0,
false,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (cb_dc, cb_coded) = parse_residual_block_cabac(
cabac,
ctxs,
BlockType::ChromaDc,
0,
max_dc - 1,
max_dc,
chroma_array_type,
ca_cb,
cb_cb,
field,
)?;
curr_cbf.cbf_cb_dc = cb_coded;
out.residual_chroma_dc_cb = cb_dc;
let (ca_cr, cb_cr) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
BlockType::ChromaDc,
0,
true,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (cr_dc, cr_coded) = parse_residual_block_cabac(
cabac,
ctxs,
BlockType::ChromaDc,
0,
max_dc - 1,
max_dc,
chroma_array_type,
ca_cr,
cb_cr,
field,
)?;
curr_cbf.cbf_cr_dc = cr_coded;
out.residual_chroma_dc_cr = cr_dc;
}
if cbp_chroma == 2 {
let num_ac = 4 * num_c8x8 as usize;
for blk_idx in 0..num_ac as u8 {
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
BlockType::ChromaAc,
blk_idx,
false,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
BlockType::ChromaAc,
0,
14,
15,
chroma_array_type,
ca,
cb,
field,
)?;
curr_cbf.cbf_cb_ac[blk_idx as usize] = coded;
out.residual_chroma_ac_cb.push(pad_to_16(blk));
}
for blk_idx in 0..num_ac as u8 {
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
BlockType::ChromaAc,
blk_idx,
true,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
BlockType::ChromaAc,
0,
14,
15,
chroma_array_type,
ca,
cb,
field,
)?;
curr_cbf.cbf_cr_ac[blk_idx as usize] = coded;
out.residual_chroma_ac_cr.push(pad_to_16(blk));
}
}
} else if chroma_array_type == 0 {
} else if chroma_array_type == 3 {
for plane_is_cr in [false, true] {
let (bt_dc, bt_ac, bt_4x4, bt_8x8) = if plane_is_cr {
(
BlockType::CrIntra16x16Dc,
BlockType::CrIntra16x16Ac,
BlockType::CrLuma4x4,
BlockType::Cr8x8,
)
} else {
(
BlockType::CbIntra16x16Dc,
BlockType::CbIntra16x16Ac,
BlockType::CbLuma4x4,
BlockType::Cb8x8,
)
};
if mb_type.is_intra_16x16() {
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
bt_dc,
0,
plane_is_cr,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
bt_dc,
0,
15,
16,
chroma_array_type,
ca,
cb,
field,
)?;
if plane_is_cr {
curr_cbf.cbf_cr_16x16_dc = coded;
out.residual_cr_16x16_dc = Some(pad_to_16(blk));
} else {
curr_cbf.cbf_cb_16x16_dc = coded;
out.residual_cb_16x16_dc = Some(pad_to_16(blk));
}
}
if mb_type.is_intra_16x16() {
if cbp_luma == 15 {
for blk_idx in 0..16u8 {
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
bt_ac,
blk_idx,
plane_is_cr,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
bt_ac,
0,
14,
15,
chroma_array_type,
ca,
cb,
field,
)?;
if plane_is_cr {
curr_cbf.cbf_cr_16x16_ac[blk_idx as usize] = coded;
out.residual_cr_luma_like.push(pad_to_16(blk));
} else {
curr_cbf.cbf_cb_16x16_ac[blk_idx as usize] = coded;
out.residual_cb_luma_like.push(pad_to_16(blk));
}
}
}
} else if transform_size_8x8_flag {
for blk8 in 0..4u8 {
if (cbp_luma >> blk8) & 1 == 1 {
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
bt_8x8,
blk8,
plane_is_cr,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
bt_8x8,
0,
63,
64,
chroma_array_type,
ca,
cb,
field,
)?;
for sub in 0..4u8 {
let idx = (blk8 * 4 + sub) as usize;
if plane_is_cr {
curr_cbf.cbf_cr_luma_4x4[idx] = coded;
} else {
curr_cbf.cbf_cb_luma_4x4[idx] = coded;
}
}
for sub in 0..4 {
let mut chunk = [0i32; 16];
for k in 0..16 {
chunk[k] = blk[sub * 16 + k];
}
if plane_is_cr {
out.residual_cr_luma_like.push(chunk);
} else {
out.residual_cb_luma_like.push(chunk);
}
}
}
}
} else {
for blk8 in 0..4u8 {
if (cbp_luma >> blk8) & 1 == 1 {
for sub in 0..4u8 {
let blk_idx = blk8 * 4 + sub;
let (ca, cb) = if let Some(g) = grid_ref {
let (a, b) = cabac_cbf_cond_terms(
g,
current_mb_addr,
&curr_cbf,
current_is_intra,
bt_4x4,
blk_idx,
plane_is_cr,
chroma_array_type,
);
(Some(a), Some(b))
} else {
(None, None)
};
let (blk, coded) = parse_residual_block_cabac(
cabac,
ctxs,
bt_4x4,
0,
15,
16,
chroma_array_type,
ca,
cb,
field,
)?;
if plane_is_cr {
curr_cbf.cbf_cr_luma_4x4[blk_idx as usize] = coded;
out.residual_cr_luma_like.push(pad_to_16(blk));
} else {
curr_cbf.cbf_cb_luma_4x4[blk_idx as usize] = coded;
out.residual_cb_luma_like.push(pad_to_16(blk));
}
}
}
}
}
}
} else {
return Err(MacroblockLayerError::UnsupportedChromaArrayType(
chroma_array_type,
));
}
if let Some(grid) = nb_grid {
if let Some(slot) = grid.mbs.get_mut(current_mb_addr as usize) {
slot.available = true;
slot.is_intra = current_is_intra;
slot.is_i_pcm = mb_type.is_i_pcm();
slot.is_skip = mb_type.is_skip();
slot.is_b_skip_or_direct = mb_type.is_b_skip_or_direct();
slot.cbf_luma_4x4 = curr_cbf.cbf_luma_4x4;
slot.cbf_cb_dc = curr_cbf.cbf_cb_dc;
slot.cbf_cr_dc = curr_cbf.cbf_cr_dc;
slot.cbf_cb_ac = curr_cbf.cbf_cb_ac;
slot.cbf_cr_ac = curr_cbf.cbf_cr_ac;
slot.cbf_luma_16x16_dc = curr_cbf.cbf_luma_16x16_dc;
slot.cbf_luma_16x16_ac = curr_cbf.cbf_luma_16x16_ac;
slot.cbf_cb_16x16_dc = curr_cbf.cbf_cb_16x16_dc;
slot.cbf_cb_16x16_ac = curr_cbf.cbf_cb_16x16_ac;
slot.cbf_cb_luma_4x4 = curr_cbf.cbf_cb_luma_4x4;
slot.cbf_cr_16x16_dc = curr_cbf.cbf_cr_16x16_dc;
slot.cbf_cr_16x16_ac = curr_cbf.cbf_cr_16x16_ac;
slot.cbf_cr_luma_4x4 = curr_cbf.cbf_cr_luma_4x4;
if mb_type.is_i_pcm() {
slot.cbf_luma_4x4 = [true; 16];
slot.cbf_cb_dc = true;
slot.cbf_cr_dc = true;
slot.cbf_cb_ac = [true; 8];
slot.cbf_cr_ac = [true; 8];
slot.cbf_luma_16x16_dc = true;
slot.cbf_luma_16x16_ac = [true; 16];
slot.cbf_cb_16x16_dc = true;
slot.cbf_cb_16x16_ac = [true; 16];
slot.cbf_cb_luma_4x4 = [true; 16];
slot.cbf_cr_16x16_dc = true;
slot.cbf_cr_16x16_ac = [true; 16];
slot.cbf_cr_luma_4x4 = [true; 16];
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nal::{NalHeader, NalUnitType};
struct BitWriter {
bytes: Vec<u8>,
bit_pos: u8,
}
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
bit_pos: 0,
}
}
fn u(&mut self, bits: u32, value: u32) {
for i in (0..bits).rev() {
let bit = ((value >> i) & 1) as u8;
if self.bit_pos == 0 {
self.bytes.push(0);
}
let idx = self.bytes.len() - 1;
self.bytes[idx] |= bit << (7 - self.bit_pos);
self.bit_pos = (self.bit_pos + 1) % 8;
}
}
fn ue(&mut self, value: u32) {
let v = value + 1;
let leading = 31 - v.leading_zeros();
for _ in 0..leading {
self.u(1, 0);
}
self.u(leading + 1, v);
}
fn se(&mut self, value: i32) {
let k = if value <= 0 {
(-2 * value) as u32
} else {
(2 * value - 1) as u32
};
self.ue(k);
}
fn trailing(&mut self) {
self.u(1, 1);
while self.bit_pos != 0 {
self.u(1, 0);
}
}
fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}
pub(super) fn dummy_sps() -> 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: 19,
pic_height_in_map_units_minus1: 14,
frame_mbs_only_flag: true,
mb_adaptive_frame_field_flag: false,
direct_8x8_inference_flag: true,
frame_cropping: None,
vui_parameters_present_flag: false,
vui: None,
}
}
pub(super) fn dummy_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,
}
}
pub(super) fn dummy_slice_header(slice_type: SliceType) -> SliceHeader {
use crate::slice_header::{DecRefPicMarking, RefPicListModification};
SliceHeader {
first_mb_in_slice: 0,
slice_type_raw: match slice_type {
SliceType::P => 0,
SliceType::B => 1,
SliceType::I => 2,
SliceType::SP => 3,
SliceType::SI => 4,
},
slice_type,
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,
dec_ref_pic_marking: Some(DecRefPicMarking {
no_output_of_prior_pics_flag: false,
long_term_reference_flag: false,
adaptive_marking: None,
}),
cabac_init_idc: 0,
slice_qp_delta: 0,
sp_for_switch_flag: false,
slice_qs_delta: 0,
disable_deblocking_filter_idc: 0,
slice_alpha_c0_offset_div2: 0,
slice_beta_offset_div2: 0,
slice_group_change_cycle: 0,
}
}
pub(super) fn _nal(t: NalUnitType, ref_idc: u8) -> NalHeader {
NalHeader {
forbidden_zero_bit: 0,
nal_ref_idc: ref_idc,
nal_unit_type: t,
}
}
#[test]
fn si_slice_mb_type_mapping_table_7_12() {
assert_eq!(MbType::from_si_slice(0).unwrap(), MbType::SI);
assert!(MbType::from_si_slice(0).unwrap().is_si());
assert!(MbType::from_si_slice(0).unwrap().is_i_nxn());
assert!(MbType::from_si_slice(0).unwrap().is_intra());
assert_eq!(MbType::from_si_slice(1).unwrap(), MbType::INxN);
assert_eq!(
MbType::from_si_slice(2).unwrap(),
MbType::from_i_slice(1).unwrap()
);
assert_eq!(MbType::from_si_slice(26).unwrap(), MbType::IPcm);
assert!(MbType::from_si_slice(27).is_err());
}
#[test]
fn i_slice_mb_type_0_is_i_nxn() {
assert_eq!(MbType::from_i_slice(0).unwrap(), MbType::INxN);
}
#[test]
fn i_slice_mb_type_25_is_i_pcm() {
assert_eq!(MbType::from_i_slice(25).unwrap(), MbType::IPcm);
}
#[test]
fn i_slice_mb_type_intra_16x16_rows_map_derived_fields() {
assert_eq!(
MbType::from_i_slice(1).unwrap(),
MbType::Intra16x16(Intra16x16 {
pred_mode: 0,
cbp_luma: 0,
cbp_chroma: 0,
})
);
assert_eq!(
MbType::from_i_slice(13).unwrap(),
MbType::Intra16x16(Intra16x16 {
pred_mode: 0,
cbp_luma: 15,
cbp_chroma: 0,
})
);
assert_eq!(
MbType::from_i_slice(24).unwrap(),
MbType::Intra16x16(Intra16x16 {
pred_mode: 3,
cbp_luma: 15,
cbp_chroma: 2,
})
);
}
#[test]
fn p_slice_mb_type_maps_to_inter_or_intra() {
assert_eq!(MbType::from_p_slice(0).unwrap(), MbType::PL016x16);
assert_eq!(MbType::from_p_slice(3).unwrap(), MbType::P8x8);
assert_eq!(MbType::from_p_slice(5).unwrap(), MbType::INxN); }
#[test]
fn b_slice_mb_type_maps_to_inter_or_intra() {
assert_eq!(MbType::from_b_slice(0).unwrap(), MbType::BDirect16x16);
assert_eq!(MbType::from_b_slice(1).unwrap(), MbType::BL016x16);
assert_eq!(MbType::from_b_slice(22).unwrap(), MbType::B8x8);
assert_eq!(MbType::from_b_slice(23).unwrap(), MbType::INxN);
}
#[test]
fn mb_type_out_of_range_rejected() {
assert_eq!(
MbType::from_i_slice(26).unwrap_err(),
MacroblockLayerError::MbTypeOutOfRange(26)
);
}
#[test]
fn num_mb_part_matches_spec() {
assert_eq!(MbType::PL016x16.num_mb_part(), 1);
assert_eq!(MbType::PL0L016x8.num_mb_part(), 2);
assert_eq!(MbType::P8x8.num_mb_part(), 4);
assert_eq!(MbType::BBiBi16x8.num_mb_part(), 2);
assert_eq!(MbType::B8x8.num_mb_part(), 4);
assert!(MbType::P8x8.is_sub_mb_path());
assert!(!MbType::PL016x16.is_sub_mb_path());
}
#[test]
fn me_cbp_420_intra_codeword_0_maps_to_47() {
assert_eq!(ME_INTRA_420_422[0], 47);
}
#[test]
fn me_cbp_420_inter_codeword_0_maps_to_0() {
assert_eq!(ME_INTER_420_422[0], 0);
}
fn cavlc_entropy() -> EntropyState<'static, 'static> {
EntropyState {
cabac: None,
slice_kind: SliceKind::I,
neighbours: NeighbourCtx::default(),
prev_mb_qp_delta_nonzero: false,
chroma_array_type: 1,
transform_8x8_mode_flag: false,
cavlc_nc: None,
current_mb_addr: 0,
constrained_intra_pred_flag: false,
num_ref_idx_l0_active_minus1: 0,
num_ref_idx_l1_active_minus1: 0,
mbaff_frame_flag: false,
mb_field_decoding_flag: false,
cabac_nb: None,
pic_width_in_mbs: 0,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
}
}
#[test]
fn cavlc_i_nxn_reads_transform_size_8x8_flag_before_mb_pred() {
let mut w = BitWriter::new();
w.ue(0); w.u(1, 1); for _ in 0..4 {
w.u(1, 1); }
w.ue(0); w.ue(3); w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let mut pps = dummy_pps();
pps.extension = Some(crate::pps::PpsExtension {
transform_8x8_mode_flag: true,
pic_scaling_matrix_present_flag: false,
pic_scaling_lists: None,
second_chroma_qp_index_offset: 0,
});
let hdr = dummy_slice_header(SliceType::I);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy();
entropy.transform_8x8_mode_flag = true;
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert_eq!(mb.mb_type, MbType::INxN);
assert!(mb.transform_size_8x8_flag);
assert_eq!(mb.coded_block_pattern, 0);
let pred = mb.mb_pred.expect("mb_pred present");
for i in 0..4 {
assert!(
pred.prev_intra8x8_pred_mode_flag[i],
"prev_intra8x8_pred_mode_flag[{i}] should have been parsed as 1"
);
}
}
#[test]
fn cavlc_444_i8x8_plane_residual_is_interleaved() {
use crate::encoder::cavlc::encode_residual_block_cavlc;
assert_eq!(ME_INTRA_0_3[10], 1);
let mut w = crate::encoder::bitstream::BitWriter::new();
w.ue(0); w.u(1, 1); for _ in 0..4 {
w.u(1, 1); }
w.ue(10); w.se(0);
let ctx = crate::cavlc::CoeffTokenContext::Numeric(0);
let mut y_sub0 = [0i32; 16];
y_sub0[0] = 1;
encode_residual_block_cavlc(&mut w, ctx, 16, &y_sub0).unwrap();
for _ in 0..3 {
encode_residual_block_cavlc(&mut w, ctx, 16, &[0i32; 16]).unwrap();
}
let mut cb_sub0 = [0i32; 16];
cb_sub0[0] = 1;
let mut cb_sub1 = [0i32; 16];
cb_sub1[0] = -1;
let mut cb_sub2 = [0i32; 16];
cb_sub2[1] = 1;
encode_residual_block_cavlc(&mut w, ctx, 16, &cb_sub0).unwrap();
encode_residual_block_cavlc(&mut w, ctx, 16, &cb_sub1).unwrap();
encode_residual_block_cavlc(&mut w, ctx, 16, &cb_sub2).unwrap();
encode_residual_block_cavlc(&mut w, ctx, 16, &[0i32; 16]).unwrap();
for _ in 0..4 {
encode_residual_block_cavlc(&mut w, ctx, 16, &[0i32; 16]).unwrap();
}
w.rbsp_trailing_bits();
let bytes = w.into_bytes();
let sps = dummy_sps();
let mut pps = dummy_pps();
pps.extension = Some(crate::pps::PpsExtension {
transform_8x8_mode_flag: true,
pic_scaling_matrix_present_flag: false,
pic_scaling_lists: None,
second_chroma_qp_index_offset: 0,
});
let hdr = dummy_slice_header(SliceType::I);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy_444();
entropy.transform_8x8_mode_flag = true;
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert_eq!(mb.mb_type, MbType::INxN);
assert!(mb.transform_size_8x8_flag);
assert_eq!(mb.coded_block_pattern & 0xF, 1);
assert_eq!(mb.residual_luma.len(), 4);
assert_eq!(mb.residual_luma[0][0], 1);
assert!(mb.residual_luma[0][1..].iter().all(|&c| c == 0));
assert!(mb.residual_luma[1..].iter().flatten().all(|&c| c == 0));
assert_eq!(mb.residual_cb_luma_like.len(), 4);
let cb0 = &mb.residual_cb_luma_like[0];
assert_eq!(cb0[0], 1, "scan8[0]");
assert_eq!(cb0[1], -1, "scan8[1]");
assert_eq!(cb0[6], 1, "scan8[6]");
for (i, &c) in cb0.iter().enumerate() {
if !matches!(i, 0 | 1 | 6) {
assert_eq!(c, 0, "scan8[{i}] must be 0");
}
}
assert!(mb.residual_cb_luma_like[1..]
.iter()
.flatten()
.all(|&c| c == 0));
assert_eq!(mb.residual_cr_luma_like.len(), 4);
assert!(mb.residual_cr_luma_like.iter().flatten().all(|&c| c == 0));
}
#[test]
fn cavlc_i_nxn_with_all_zero_residual() {
assert_eq!(ME_INTRA_420_422[3], 0);
let mut w = BitWriter::new();
w.ue(0); for _ in 0..16 {
w.u(1, 1); }
w.ue(0); w.ue(3); w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::I);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy();
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert_eq!(mb.mb_type, MbType::INxN);
assert_eq!(mb.coded_block_pattern, 0);
assert!(mb.pcm_samples.is_none());
assert!(mb.mb_pred.is_some());
assert!(mb.residual_luma.is_empty());
assert_eq!(mb.mb_qp_delta, 0);
assert!(mb.residual_luma_dc.is_none());
}
#[test]
fn cavlc_intra_16x16_dc_cbp_zero() {
let mut w = BitWriter::new();
w.ue(1); w.ue(0); w.se(0); w.u(1, 1); w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::I);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy();
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert!(matches!(
mb.mb_type,
MbType::Intra16x16(Intra16x16 {
pred_mode: 0,
cbp_luma: 0,
cbp_chroma: 0
})
));
assert_eq!(mb.coded_block_pattern, 0);
assert!(mb.residual_luma_dc.is_some());
assert!(mb.residual_luma.is_empty());
assert_eq!(mb.mb_qp_delta, 0);
}
#[test]
fn cavlc_i_pcm_path_reads_256_plus_chroma_samples() {
let mut w = BitWriter::new();
w.ue(25); while w.bit_pos != 0 {
w.u(1, 0);
}
for i in 0..256 {
w.u(8, (i & 0xFF) as u32);
}
for i in 0..64 {
w.u(8, (i & 0xFF) as u32);
}
for i in 0..64 {
w.u(8, ((i * 2) & 0xFF) as u32);
}
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::I);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy();
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert!(mb.mb_type.is_i_pcm());
let pcm = mb.pcm_samples.as_ref().unwrap();
assert_eq!(pcm.luma.len(), 256);
assert_eq!(pcm.chroma_cb.len(), 64);
assert_eq!(pcm.chroma_cr.len(), 64);
assert_eq!(pcm.luma[1], 1);
assert_eq!(pcm.chroma_cb[10], 10);
assert_eq!(pcm.chroma_cr[3], 6);
}
fn fill_mb(grid: &mut CavlcNcGrid, addr: u32, luma_totals: [u8; 16]) {
let slot = &mut grid.mbs[addr as usize];
slot.is_available = true;
slot.is_skip = false;
slot.is_i_pcm = false;
slot.is_intra = true;
slot.luma_total_coeff = luma_totals;
}
#[test]
fn nc_top_left_mb_both_neighbours_unavailable() {
let grid = CavlcNcGrid::new(4, 4);
let nc = derive_nc_luma(&grid, 0, 0, LumaNcKind::Ac, true, false);
assert_eq!(nc, 0);
}
#[test]
fn nc_mb0_block5_uses_in_mb_neighbours_once_filled() {
let grid = CavlcNcGrid::new(4, 4);
let nc = derive_nc_luma(&grid, 0, 5, LumaNcKind::Ac, true, false);
assert_eq!(nc, 0);
}
#[test]
fn nc_mb1_block0_uses_left_mb_block_per_6_4_11_4() {
let mut grid = CavlcNcGrid::new(4, 4);
let mut totals = [0u8; 16];
totals[5] = 5; fill_mb(&mut grid, 0, totals);
let nc = derive_nc_luma(
&grid,
1,
0,
LumaNcKind::Ac,
true,
false,
);
assert_eq!(nc, 5);
}
#[test]
fn nc_mb4_block0_uses_above_mb_block10() {
let mut grid = CavlcNcGrid::new(4, 4);
let mut totals = [0u8; 16];
totals[10] = 7;
fill_mb(&mut grid, 0, totals);
let nc = derive_nc_luma(
&grid,
4,
0,
LumaNcKind::Ac,
true,
false,
);
assert_eq!(nc, 7);
}
#[test]
fn nc_mb5_block0_averages_left_and_above() {
let mut grid = CavlcNcGrid::new(4, 4);
let mut t4 = [0u8; 16];
t4[5] = 4;
fill_mb(&mut grid, 4, t4);
let mut t1 = [0u8; 16];
t1[10] = 6;
fill_mb(&mut grid, 1, t1);
let nc = derive_nc_luma(
&grid,
5,
0,
LumaNcKind::Ac,
true,
false,
);
assert_eq!(nc, 5);
}
#[test]
fn nc_skip_neighbour_contributes_zero() {
let mut grid = CavlcNcGrid::new(4, 4);
let mut totals = [0u8; 16];
totals[5] = 9;
fill_mb(&mut grid, 0, totals);
grid.mbs[0].is_skip = true;
let nc = derive_nc_luma(&grid, 1, 0, LumaNcKind::Ac, true, false);
assert_eq!(nc, 0);
}
#[test]
fn nc_i_pcm_neighbour_contributes_sixteen() {
let mut grid = CavlcNcGrid::new(4, 4);
fill_mb(&mut grid, 0, [0; 16]); grid.mbs[0].is_i_pcm = true;
let nc = derive_nc_luma(&grid, 1, 0, LumaNcKind::Ac, true, false);
assert_eq!(nc, 16);
}
#[test]
fn nc_intra16x16_dc_ignores_block_idx() {
let mut grid = CavlcNcGrid::new(4, 4);
let mut totals = [0u8; 16];
totals[5] = 8;
fill_mb(&mut grid, 0, totals);
let nc = derive_nc_luma(&grid, 1, 15, LumaNcKind::Intra16x16Dc, true, false);
assert_eq!(nc, 8);
}
#[test]
fn nc_chroma_ac_420_top_left_block_uses_left_mb() {
let mut grid = CavlcNcGrid::new(4, 4);
grid.mbs[0].is_available = true;
grid.mbs[0].cb_total_coeff[2] = 4; let nc = derive_nc_chroma_ac(
&grid, 4, 0, false, 1, true, false,
);
assert_eq!(nc, 4);
}
#[test]
fn nc_grid_neighbour_mb_addrs_corners() {
let grid = CavlcNcGrid::new(4, 4);
assert_eq!(grid.neighbour_mb_addrs(0), (None, None));
assert_eq!(grid.neighbour_mb_addrs(3), (Some(2), None));
assert_eq!(grid.neighbour_mb_addrs(4), (None, Some(0)));
assert_eq!(grid.neighbour_mb_addrs(5), (Some(4), Some(1)));
assert_eq!(grid.neighbour_mb_addrs(15), (Some(14), Some(11)));
}
#[test]
fn combine_nc_formula_matches_spec() {
assert_eq!(combine_nc(false, 0, false, 0), 0);
assert_eq!(combine_nc(true, 5, false, 0), 5);
assert_eq!(combine_nc(false, 0, true, 7), 7);
assert_eq!(combine_nc(true, 3, true, 4), (3 + 4 + 1) >> 1); assert_eq!(combine_nc(true, 0, true, 0), 0);
assert_eq!(combine_nc(true, 16, true, 16), (16 + 16 + 1) >> 1); }
fn mk_cabac_grid() -> CabacNeighbourGrid {
CabacNeighbourGrid::new(4, 4)
}
fn fill_inter_mb(grid: &mut CabacNeighbourGrid, addr: u32, ref_idx: i8) {
let slot = &mut grid.mbs[addr as usize];
slot.available = true;
slot.is_intra = false;
slot.is_skip = false;
slot.is_i_pcm = false;
slot.ref_idx_l0 = [ref_idx; 4];
slot.ref_idx_l1 = [-1; 4];
}
#[test]
fn ref_idx_ctx_inc_both_neighbours_zero() {
let mut grid = mk_cabac_grid();
fill_inter_mb(&mut grid, 4, 0); fill_inter_mb(&mut grid, 1, 0); let curr = CabacMbNeighbourInfo::default();
let (ca, cb) = cabac_ref_idx_cond_terms(&grid, 5, &curr, 0, 0);
assert!(!ca);
assert!(!cb);
let inc = u32::from(ca) + 2 * u32::from(cb);
assert_eq!(inc, 0);
}
#[test]
fn ref_idx_ctx_inc_a_nonzero() {
let mut grid = mk_cabac_grid();
fill_inter_mb(&mut grid, 4, 2); fill_inter_mb(&mut grid, 1, 0); let curr = CabacMbNeighbourInfo::default();
let (ca, cb) = cabac_ref_idx_cond_terms(&grid, 5, &curr, 0, 0);
assert!(ca);
assert!(!cb);
let inc = u32::from(ca) + 2 * u32::from(cb);
assert_eq!(inc, 1);
}
#[test]
fn ref_idx_ctx_inc_b_nonzero() {
let mut grid = mk_cabac_grid();
fill_inter_mb(&mut grid, 4, 0); fill_inter_mb(&mut grid, 1, 1); let curr = CabacMbNeighbourInfo::default();
let (ca, cb) = cabac_ref_idx_cond_terms(&grid, 5, &curr, 0, 0);
assert!(!ca);
assert!(cb);
let inc = u32::from(ca) + 2 * u32::from(cb);
assert_eq!(inc, 2);
}
#[test]
fn ref_idx_ctx_inc_both_nonzero() {
let mut grid = mk_cabac_grid();
fill_inter_mb(&mut grid, 4, 2); fill_inter_mb(&mut grid, 1, 3); let curr = CabacMbNeighbourInfo::default();
let (ca, cb) = cabac_ref_idx_cond_terms(&grid, 5, &curr, 0, 0);
assert!(ca);
assert!(cb);
let inc = u32::from(ca) + 2 * u32::from(cb);
assert_eq!(inc, 3);
}
#[test]
fn ref_idx_intra_neighbour_contributes_zero() {
let mut grid = mk_cabac_grid();
fill_inter_mb(&mut grid, 4, 2);
grid.mbs[4].is_intra = true; fill_inter_mb(&mut grid, 1, 3);
grid.mbs[1].is_skip = true; let curr = CabacMbNeighbourInfo::default();
let (ca, cb) = cabac_ref_idx_cond_terms(&grid, 5, &curr, 0, 0);
assert!(!ca);
assert!(!cb);
}
#[test]
fn ref_idx_unavailable_neighbours_contribute_zero() {
let grid = mk_cabac_grid();
let curr = CabacMbNeighbourInfo::default();
let (ca, cb) = cabac_ref_idx_cond_terms(&grid, 0, &curr, 0, 0);
assert!(!ca);
assert!(!cb);
}
fn fill_inter_mvd(grid: &mut CabacNeighbourGrid, addr: u32, mvd_x: i16, mvd_y: i16) {
let slot = &mut grid.mbs[addr as usize];
slot.available = true;
slot.is_intra = false;
slot.is_skip = false;
slot.is_i_pcm = false;
slot.mvd_l0_x = [mvd_x; 16];
slot.mvd_l0_y = [mvd_y; 16];
}
#[test]
fn mvd_ctx_inc_small_sum() {
let mut grid = mk_cabac_grid();
fill_inter_mvd(&mut grid, 4, 1, 0);
fill_inter_mvd(&mut grid, 1, 1, 0);
let curr = CabacMbNeighbourInfo::default();
let sum = cabac_mvd_abs_sum(&grid, 5, &curr, 0, MvdComponent::X, 0);
assert_eq!(sum, 2);
assert!(sum < 3);
}
#[test]
fn mvd_ctx_inc_medium_sum() {
let mut grid = mk_cabac_grid();
fill_inter_mvd(&mut grid, 4, 2, 0);
fill_inter_mvd(&mut grid, 1, 2, 0);
let curr = CabacMbNeighbourInfo::default();
let sum = cabac_mvd_abs_sum(&grid, 5, &curr, 0, MvdComponent::X, 0);
assert_eq!(sum, 4);
assert!(sum > 2 && sum <= 32);
}
#[test]
fn mvd_ctx_inc_large_sum() {
let mut grid = mk_cabac_grid();
fill_inter_mvd(&mut grid, 4, 20, 0);
fill_inter_mvd(&mut grid, 1, 20, 0);
let curr = CabacMbNeighbourInfo::default();
let sum = cabac_mvd_abs_sum(&grid, 5, &curr, 0, MvdComponent::X, 0);
assert_eq!(sum, 40);
assert!(sum > 32);
}
#[test]
fn mvd_skip_or_intra_neighbour_contributes_zero() {
let mut grid = mk_cabac_grid();
fill_inter_mvd(&mut grid, 4, 10, 0);
grid.mbs[4].is_skip = true; fill_inter_mvd(&mut grid, 1, 10, 0);
grid.mbs[1].is_intra = true; let curr = CabacMbNeighbourInfo::default();
let sum = cabac_mvd_abs_sum(&grid, 5, &curr, 0, MvdComponent::X, 0);
assert_eq!(sum, 0);
}
#[test]
fn mvd_sign_contributes_via_abs() {
let mut grid = mk_cabac_grid();
fill_inter_mvd(&mut grid, 4, -5, 0); fill_inter_mvd(&mut grid, 1, -10, 0); let curr = CabacMbNeighbourInfo::default();
let sum = cabac_mvd_abs_sum(&grid, 5, &curr, 0, MvdComponent::X, 0);
assert_eq!(sum, 15);
}
#[test]
fn cbf_ctx_inc_both_unavailable_inter_current() {
let grid = mk_cabac_grid();
let curr = CabacMbNeighbourInfo::default();
let (ca, cb) = cabac_cbf_cond_terms(
&grid,
0,
&curr,
false,
BlockType::Luma4x4,
0,
false,
1,
);
assert!(!ca);
assert!(!cb);
}
#[test]
fn cbf_ctx_inc_both_unavailable_intra_current() {
let grid = mk_cabac_grid();
let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (ca, cb) = cabac_cbf_cond_terms(
&grid,
0,
&curr,
true,
BlockType::Luma4x4,
0,
false,
1,
);
assert!(ca);
assert!(cb);
}
#[test]
fn cbf_ctx_inc_both_coded() {
let mut grid = mk_cabac_grid();
for addr in [4, 1] {
let slot = &mut grid.mbs[addr as usize];
slot.available = true;
slot.is_intra = true;
slot.cbf_luma_4x4 = [true; 16];
slot.coded_block_pattern_luma = 15;
}
let curr = CabacMbNeighbourInfo::default();
let (ca, cb) = cabac_cbf_cond_terms(&grid, 5, &curr, true, BlockType::Luma4x4, 0, false, 1);
assert!(ca);
assert!(cb);
let inc = u32::from(ca) + 2 * u32::from(cb);
assert_eq!(inc, 3);
}
#[test]
fn cbf_ctx_inc_chroma_dc_neighbour_cbp_chroma_zero_yields_zero() {
let mut grid = mk_cabac_grid();
let slot = &mut grid.mbs[4];
slot.available = true;
slot.is_intra = true;
slot.coded_block_pattern_chroma = 0;
slot.cbf_cb_dc = true; let curr = CabacMbNeighbourInfo::default();
let (ca, _cb) = cabac_cbf_cond_terms(
&grid,
5,
&curr,
true,
BlockType::ChromaDc,
0,
false,
1,
);
assert!(
!ca,
"chroma-CBP=0 neighbour must yield ChromaDC condTermA = 0"
);
}
#[test]
fn cbf_i_pcm_neighbour_all_ones() {
let mut grid = mk_cabac_grid();
grid.mbs[4].available = true;
grid.mbs[4].is_i_pcm = true;
grid.mbs[4].is_intra = true; let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (ca, _cb) =
cabac_cbf_cond_terms(&grid, 1, &curr, true, BlockType::Luma4x4, 0, false, 1);
assert!(ca);
}
#[test]
fn cbf_luma4x4_neighbour_cbp_bit_zero_should_yield_zero() {
let mut grid = mk_cabac_grid();
let slot = &mut grid.mbs[4];
slot.available = true;
slot.is_intra = true;
slot.coded_block_pattern_luma = 0x3;
slot.cbf_luma_4x4[13] = true; let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (ca, _cb) = cabac_cbf_cond_terms(
&grid,
5,
&curr,
true,
BlockType::Luma4x4,
8,
false,
1,
);
assert!(
!ca,
"spec §9.3.3.1.1.9 says condTermA = 0 when neighbour cbp bit is 0"
);
}
#[test]
fn cbf_chroma_ac_neighbour_cbp_chroma_neq_2_should_yield_zero() {
let mut grid = mk_cabac_grid();
let slot = &mut grid.mbs[4];
slot.available = true;
slot.is_intra = true;
slot.coded_block_pattern_chroma = 1; slot.cbf_cb_ac[0] = true; let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (ca, _cb) = cabac_cbf_cond_terms(
&grid,
5,
&curr,
true,
BlockType::ChromaAc,
0,
false,
1,
);
assert!(
!ca,
"spec §9.3.3.1.1.9 cat=4 says condTermA = 0 when neighbour cbp_chroma != 2"
);
}
#[test]
fn cbf_chroma_ac_422_above_wraps_to_bottom_row() {
let mut grid = mk_cabac_grid();
let slot = &mut grid.mbs[1];
slot.available = true;
slot.is_intra = true;
slot.coded_block_pattern_chroma = 2;
slot.cbf_cb_ac = [false, false, false, false, false, false, true, true];
let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (_ca, cb) = cabac_cbf_cond_terms(
&grid,
5,
&curr,
true,
BlockType::ChromaAc,
0,
false,
2,
);
assert!(cb, "4:2:2 above-neighbour must wrap to the bottom 4x4 row");
let (_ca, cb_420) = cabac_cbf_cond_terms(
&grid,
5,
&curr,
true,
BlockType::ChromaAc,
0,
false,
1,
);
assert!(!cb_420, "4:2:0 wrap reads block 2 which is not coded");
}
#[test]
fn cbf_luma8x8_first_intra_mb_unavailable_neighbours_yield_ones() {
let grid = mk_cabac_grid();
let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (ca, cb) = cabac_cbf_cond_terms(
&grid,
0,
&curr,
true,
BlockType::Luma8x8,
0,
false,
3,
);
assert!(ca && cb, "unavailable neighbours + intra MB → condTerm 1");
}
#[test]
fn cbf_luma8x8_left_neighbour_transform_gate() {
let mut grid = mk_cabac_grid();
let slot = &mut grid.mbs[4];
slot.available = true;
slot.is_intra = true;
slot.coded_block_pattern_luma = 0xf;
slot.transform_size_8x8_flag = true;
slot.cbf_luma_4x4[4] = true;
slot.cbf_luma_4x4[5] = true;
slot.cbf_luma_4x4[6] = true;
slot.cbf_luma_4x4[7] = true;
let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (ca, _cb) =
cabac_cbf_cond_terms(&grid, 5, &curr, true, BlockType::Luma8x8, 0, false, 3);
assert!(ca, "left neighbour 8x8 #1 CBF must be read (folded slot 4)");
grid.mbs[4].transform_size_8x8_flag = false;
let (ca2, _cb) =
cabac_cbf_cond_terms(&grid, 5, &curr, true, BlockType::Luma8x8, 0, false, 3);
assert!(
!ca2,
"4x4-transform neighbour → cat-5 transBlockN unavailable"
);
grid.mbs[4].transform_size_8x8_flag = true;
grid.mbs[4].coded_block_pattern_luma = 0xd; let (ca3, _cb) =
cabac_cbf_cond_terms(&grid, 5, &curr, true, BlockType::Luma8x8, 0, false, 3);
assert!(!ca3, "CBP bit 0 for the wrapped 8x8 → condTerm 0");
}
#[test]
fn cbf_cb8x8_internal_neighbour_reads_folded_plane_slots() {
let grid = mk_cabac_grid();
let mut curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
curr.cbf_cb_luma_4x4[0] = true;
let (ca, cb) = cabac_cbf_cond_terms(&grid, 0, &curr, true, BlockType::Cb8x8, 1, false, 3);
assert!(ca, "in-MB Cb 8x8 #0 folded CBF must be read");
assert!(cb, "unavailable above + intra → 1");
let (ca_cr, _cb) =
cabac_cbf_cond_terms(&grid, 0, &curr, true, BlockType::Cr8x8, 1, true, 3);
assert!(!ca_cr, "Cr plane folded slots are independent of Cb");
}
#[test]
fn cbf_skip_neighbour_all_zero() {
let mut grid = mk_cabac_grid();
grid.mbs[4].available = true;
grid.mbs[4].is_skip = true;
grid.mbs[4].cbf_luma_4x4 = [true; 16]; let curr = CabacMbNeighbourInfo::default();
let (ca, _cb) =
cabac_cbf_cond_terms(&grid, 1, &curr, false, BlockType::Luma4x4, 0, false, 1);
assert!(!ca);
}
#[test]
fn cbf_internal_block_sees_current_mb_progressive_state() {
let grid = mk_cabac_grid();
let mut curr = CabacMbNeighbourInfo::default();
curr.cbf_luma_4x4[4] = true;
curr.cbf_luma_4x4[1] = false;
let (ca, cb) = cabac_cbf_cond_terms(
&grid,
0,
&curr,
false,
BlockType::Luma4x4,
5,
false,
1,
);
assert!(ca, "A (internal block 4) has cbf=true");
assert!(!cb, "B (internal block 1) has cbf=false");
}
#[test]
fn cabac_neighbour_grid_default_all_unavailable() {
let grid = mk_cabac_grid();
assert_eq!(grid.width_in_mbs, 4);
assert_eq!(grid.height_in_mbs, 4);
assert_eq!(grid.mbs.len(), 16);
for info in &grid.mbs {
assert!(!info.available);
assert_eq!(info.ref_idx_l0, [-1; 4]);
}
}
#[test]
fn cabac_neighbour_grid_addr_to_xy() {
let grid = mk_cabac_grid();
assert_eq!(grid.neighbour_mb_addrs(0), (None, None));
assert_eq!(grid.neighbour_mb_addrs(3), (Some(2), None));
assert_eq!(grid.neighbour_mb_addrs(4), (None, Some(0)));
assert_eq!(grid.neighbour_mb_addrs(5), (Some(4), Some(1)));
assert_eq!(grid.neighbour_mb_addrs(15), (Some(14), Some(11)));
}
#[test]
fn cbf_chroma_dc_separates_cb_and_cr() {
let mut grid = mk_cabac_grid();
grid.mbs[0].available = true;
grid.mbs[0].is_intra = true;
grid.mbs[0].coded_block_pattern_chroma = 1;
grid.mbs[0].cbf_cb_dc = true;
grid.mbs[0].cbf_cr_dc = false;
let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (ca_cb, _cb_cb) = cabac_cbf_cond_terms(
&grid,
1,
&curr,
true,
BlockType::ChromaDc,
0,
false,
1,
);
let (ca_cr, _cb_cr) = cabac_cbf_cond_terms(
&grid,
1,
&curr,
true,
BlockType::ChromaDc,
0,
true,
1,
);
assert!(ca_cb, "Cb DC should see neighbour cbf_cb_dc=true");
assert!(!ca_cr, "Cr DC should see neighbour cbf_cr_dc=false");
}
#[test]
fn cbf_chroma_ac_separates_cb_and_cr() {
let mut grid = mk_cabac_grid();
grid.mbs[0].available = true;
grid.mbs[0].is_intra = true;
grid.mbs[0].coded_block_pattern_chroma = 2;
grid.mbs[0].cbf_cb_ac[0] = true;
grid.mbs[0].cbf_cr_ac[0] = false;
grid.mbs[0].cbf_cb_ac[1] = true;
grid.mbs[0].cbf_cr_ac[1] = false;
let curr = CabacMbNeighbourInfo {
is_intra: true,
..Default::default()
};
let (ca_cb, _cb_cb) =
cabac_cbf_cond_terms(&grid, 1, &curr, true, BlockType::ChromaAc, 0, false, 1);
let (ca_cr, _cb_cr) =
cabac_cbf_cond_terms(&grid, 1, &curr, true, BlockType::ChromaAc, 0, true, 1);
assert!(ca_cb, "Cb AC should see neighbour cbf_cb_ac=true");
assert!(!ca_cr, "Cr AC should see neighbour cbf_cr_ac=false");
}
#[test]
fn cbf_chroma_ac_internal_separates_cb_and_cr() {
let grid = mk_cabac_grid();
let mut curr = CabacMbNeighbourInfo {
is_intra: true,
..CabacMbNeighbourInfo::default()
};
curr.cbf_cb_ac[0] = true;
curr.cbf_cr_ac[0] = false;
let (ca_cb, _cb_cb) =
cabac_cbf_cond_terms(&grid, 0, &curr, true, BlockType::ChromaAc, 1, false, 1);
let (ca_cr, _cb_cr) =
cabac_cbf_cond_terms(&grid, 0, &curr, true, BlockType::ChromaAc, 1, true, 1);
assert!(ca_cb, "Cb AC internal should see cbf_cb_ac[0]=true");
assert!(!ca_cr, "Cr AC internal should see cbf_cr_ac[0]=false");
}
#[test]
fn cavlc_p_l0_16x16_with_single_ref_reads_no_ref_idx() {
let mut w = BitWriter::new();
w.ue(0); w.ue(0); w.ue(0); w.ue(0); w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::P);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy();
entropy.slice_kind = SliceKind::P;
entropy.num_ref_idx_l0_active_minus1 = 0;
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert_eq!(mb.mb_type, MbType::PL016x16);
assert_eq!(mb.coded_block_pattern, 0);
let pred = mb.mb_pred.expect("mb_pred");
assert_eq!(pred.ref_idx_l0.len(), 1);
assert_eq!(pred.ref_idx_l0[0], 0);
assert_eq!(pred.mvd_l0.len(), 1);
assert_eq!(pred.mvd_l0[0], [0, 0]);
}
#[test]
fn cavlc_p_l0_16x16_with_two_refs_reads_ref_idx_te() {
let mut w = BitWriter::new();
w.ue(0); w.u(1, 0); w.ue(0); w.ue(0); w.ue(0); w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::P);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy();
entropy.slice_kind = SliceKind::P;
entropy.num_ref_idx_l0_active_minus1 = 1;
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert_eq!(mb.mb_type, MbType::PL016x16);
let pred = mb.mb_pred.expect("mb_pred");
assert_eq!(pred.ref_idx_l0[0], 1);
}
#[test]
fn mb_part_pred_mode_p_slice_cases() {
assert_eq!(
MbType::PL016x16.mb_part_pred_mode(0),
Some(MbPartPredMode::PredL0)
);
assert_eq!(MbType::PL016x16.mb_part_pred_mode(1), None);
assert_eq!(
MbType::PL0L016x8.mb_part_pred_mode(0),
Some(MbPartPredMode::PredL0)
);
assert_eq!(
MbType::PL0L016x8.mb_part_pred_mode(1),
Some(MbPartPredMode::PredL0)
);
assert_eq!(MbType::P8x8.mb_part_pred_mode(0), None);
assert_eq!(MbType::P8x8Ref0.mb_part_pred_mode(0), None);
}
#[test]
fn mb_part_pred_mode_b_slice_cases() {
assert_eq!(
MbType::BL016x16.mb_part_pred_mode(0),
Some(MbPartPredMode::PredL0)
);
assert_eq!(
MbType::BL116x16.mb_part_pred_mode(0),
Some(MbPartPredMode::PredL1)
);
assert_eq!(
MbType::BBi16x16.mb_part_pred_mode(0),
Some(MbPartPredMode::BiPred)
);
assert_eq!(
MbType::BDirect16x16.mb_part_pred_mode(0),
Some(MbPartPredMode::Direct)
);
assert_eq!(
MbType::BL0L116x8.mb_part_pred_mode(0),
Some(MbPartPredMode::PredL0)
);
assert_eq!(
MbType::BL0L116x8.mb_part_pred_mode(1),
Some(MbPartPredMode::PredL1)
);
assert_eq!(
MbType::BBiL08x16.mb_part_pred_mode(0),
Some(MbPartPredMode::BiPred)
);
assert_eq!(
MbType::BBiL08x16.mb_part_pred_mode(1),
Some(MbPartPredMode::PredL0)
);
}
#[test]
fn sub_mb_pred_mode_p_and_b_tables() {
assert_eq!(
SubMbType::PL08x8.sub_mb_pred_mode(),
Some(MbPartPredMode::PredL0)
);
assert_eq!(
SubMbType::PL04x4.sub_mb_pred_mode(),
Some(MbPartPredMode::PredL0)
);
assert_eq!(
SubMbType::BDirect8x8.sub_mb_pred_mode(),
Some(MbPartPredMode::Direct)
);
assert_eq!(
SubMbType::BL08x8.sub_mb_pred_mode(),
Some(MbPartPredMode::PredL0)
);
assert_eq!(
SubMbType::BL18x4.sub_mb_pred_mode(),
Some(MbPartPredMode::PredL1)
);
assert_eq!(
SubMbType::BBi4x4.sub_mb_pred_mode(),
Some(MbPartPredMode::BiPred)
);
}
#[test]
fn te_x_max_zero_consumes_no_bits() {
let bytes = [0xFFu8];
let mut r = BitReader::new(&bytes);
assert_eq!(r.te(0).unwrap(), 0);
let (byte, bit) = r.position();
assert_eq!((byte, bit), (0, 0));
}
#[test]
fn table_9_4a_intra_420_422_full_row_audit() {
let expected: [u8; 48] = [
47, 31, 15, 0, 23, 27, 29, 30, 7, 11, 13, 14, 39, 43, 45, 46, 16, 3, 5, 10, 12, 19, 21,
26, 28, 35, 37, 42, 44, 1, 2, 4, 8, 17, 18, 20, 24, 6, 9, 22, 25, 32, 33, 34, 36, 40,
38, 41,
];
for i in 0..48 {
assert_eq!(ME_INTRA_420_422[i], expected[i], "row {i}");
}
}
#[test]
fn table_9_4a_inter_420_422_full_row_audit() {
let expected: [u8; 48] = [
0, 16, 1, 2, 4, 8, 32, 3, 5, 10, 12, 15, 47, 7, 11, 13, 14, 6, 9, 31, 35, 37, 42, 44,
33, 34, 36, 40, 39, 43, 45, 46, 17, 18, 20, 24, 19, 21, 26, 28, 23, 27, 29, 30, 22, 25,
38, 41,
];
for i in 0..48 {
assert_eq!(ME_INTER_420_422[i], expected[i], "row {i}");
}
}
#[test]
fn table_9_4b_intra_0_3_full_row_audit() {
let expected: [u8; 16] = [15, 0, 7, 11, 13, 14, 3, 5, 10, 12, 1, 2, 4, 8, 6, 9];
for i in 0..16 {
assert_eq!(ME_INTRA_0_3[i], expected[i], "row {i}");
}
}
#[test]
fn table_9_4b_inter_0_3_full_row_audit() {
let expected: [u8; 16] = [0, 1, 2, 4, 8, 3, 5, 10, 12, 15, 7, 11, 13, 14, 6, 9];
for i in 0..16 {
assert_eq!(ME_INTER_0_3[i], expected[i], "row {i}");
}
}
#[test]
fn p_slice_mb_type_boundary_cases() {
assert_eq!(MbType::from_p_slice(0).unwrap(), MbType::PL016x16);
assert_eq!(MbType::from_p_slice(4).unwrap(), MbType::P8x8Ref0);
assert_eq!(MbType::from_p_slice(5).unwrap(), MbType::INxN);
assert_eq!(MbType::from_p_slice(30).unwrap(), MbType::IPcm);
assert!(matches!(
MbType::from_p_slice(31).unwrap_err(),
MacroblockLayerError::MbTypeOutOfRange(31)
));
}
#[test]
fn b_slice_mb_type_boundary_cases() {
assert_eq!(MbType::from_b_slice(0).unwrap(), MbType::BDirect16x16);
assert_eq!(MbType::from_b_slice(22).unwrap(), MbType::B8x8);
assert_eq!(MbType::from_b_slice(23).unwrap(), MbType::INxN);
assert_eq!(MbType::from_b_slice(48).unwrap(), MbType::IPcm);
assert!(matches!(
MbType::from_b_slice(49).unwrap_err(),
MacroblockLayerError::MbTypeOutOfRange(49)
));
}
fn run_cabac_mb_pred(
data: &[u8],
slice_kind: SliceKind,
mb_type: &MbType,
num_ref_idx_l0_active_minus1: u32,
num_ref_idx_l1_active_minus1: u32,
) -> (MbPred, u64) {
let mut dec = CabacDecoder::new(BitReader::new(data)).unwrap();
let cabac_init_idc = match slice_kind {
SliceKind::I | SliceKind::SI => None,
_ => Some(0u32),
};
let mut ctxs = CabacContexts::init(slice_kind, cabac_init_idc, 26).unwrap();
let bins_before = dec.bin_count();
let dummy = [0u8; 1];
let mut r = BitReader::new(&dummy);
let mut entropy = EntropyState {
cabac: Some((&mut dec, &mut ctxs)),
slice_kind,
neighbours: NeighbourCtx::default(),
prev_mb_qp_delta_nonzero: false,
chroma_array_type: 1,
transform_8x8_mode_flag: false,
cavlc_nc: None,
current_mb_addr: 0,
constrained_intra_pred_flag: false,
num_ref_idx_l0_active_minus1,
num_ref_idx_l1_active_minus1,
mbaff_frame_flag: false,
mb_field_decoding_flag: false,
cabac_nb: None,
pic_width_in_mbs: 0,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
};
let pred = parse_mb_pred(&mut r, &mut entropy, mb_type, false).unwrap();
let bins_used = dec.bin_count() - bins_before;
(pred, bins_used)
}
fn oracle_mb_pred_calls(
data: &[u8],
slice_kind: SliceKind,
mb_type: &MbType,
num_ref_idx_l0_active_minus1: u32,
num_ref_idx_l1_active_minus1: u32,
) -> (MbPred, u64) {
let mut dec = CabacDecoder::new(BitReader::new(data)).unwrap();
let cabac_init_idc = match slice_kind {
SliceKind::I | SliceKind::SI => None,
_ => Some(0u32),
};
let mut ctxs = CabacContexts::init(slice_kind, cabac_init_idc, 26).unwrap();
let bins_before = dec.bin_count();
let num_parts = mb_type.num_mb_part() as usize;
let ref_l0_present = num_ref_idx_l0_active_minus1 > 0;
let ref_l1_present = num_ref_idx_l1_active_minus1 > 0;
let mut pred = MbPred::default();
for part in 0..num_parts {
let mode = mb_type.mb_part_pred_mode(part);
if ref_l0_present
&& mode != Some(MbPartPredMode::PredL1)
&& mode != Some(MbPartPredMode::Direct)
{
let v = decode_ref_idx_lx(&mut dec, &mut ctxs, false, false).unwrap();
pred.ref_idx_l0.push(v);
} else {
pred.ref_idx_l0.push(0);
}
}
for part in 0..num_parts {
let mode = mb_type.mb_part_pred_mode(part);
if ref_l1_present
&& mode != Some(MbPartPredMode::PredL0)
&& mode != Some(MbPartPredMode::Direct)
&& slice_kind == SliceKind::B
{
let v = decode_ref_idx_lx(&mut dec, &mut ctxs, false, false).unwrap();
pred.ref_idx_l1.push(v);
} else {
pred.ref_idx_l1.push(0);
}
}
for part in 0..num_parts {
let mode = mb_type.mb_part_pred_mode(part);
if mode != Some(MbPartPredMode::PredL1) && mode != Some(MbPartPredMode::Direct) {
let mx = decode_mvd_lx(&mut dec, &mut ctxs, MvdComponent::X, 0).unwrap();
let my = decode_mvd_lx(&mut dec, &mut ctxs, MvdComponent::Y, 0).unwrap();
pred.mvd_l0.push([mx, my]);
} else {
pred.mvd_l0.push([0, 0]);
}
}
for part in 0..num_parts {
let mode = mb_type.mb_part_pred_mode(part);
if mode != Some(MbPartPredMode::PredL0)
&& mode != Some(MbPartPredMode::Direct)
&& slice_kind == SliceKind::B
{
let mx = decode_mvd_lx(&mut dec, &mut ctxs, MvdComponent::X, 0).unwrap();
let my = decode_mvd_lx(&mut dec, &mut ctxs, MvdComponent::Y, 0).unwrap();
pred.mvd_l1.push([mx, my]);
} else {
pred.mvd_l1.push([0, 0]);
}
}
let bins_used = dec.bin_count() - bins_before;
(pred, bins_used)
}
fn oracle_single_mvd(data: &[u8], slice_kind: SliceKind, comp: MvdComponent) -> (i32, u64) {
let mut dec = CabacDecoder::new(BitReader::new(data)).unwrap();
let cabac_init_idc = match slice_kind {
SliceKind::I | SliceKind::SI => None,
_ => Some(0u32),
};
let mut ctxs = CabacContexts::init(slice_kind, cabac_init_idc, 26).unwrap();
let before = dec.bin_count();
let v = decode_mvd_lx(&mut dec, &mut ctxs, comp, 0).unwrap();
(v, dec.bin_count() - before)
}
#[test]
fn cabac_p_l0_16x16_single_ref_skips_ref_idx() {
let data: [u8; 16] = [
0xA5, 0x5A, 0x3C, 0xC3, 0x81, 0x7E, 0x12, 0xED, 0x00, 0xFF, 0x80, 0x01, 0x22, 0x44,
0x88, 0x77,
];
let mb_type = MbType::PL016x16;
let (pred, bins) = run_cabac_mb_pred(&data, SliceKind::P, &mb_type, 0, 0);
let (expected, oracle_bins) = oracle_mb_pred_calls(&data, SliceKind::P, &mb_type, 0, 0);
assert_eq!(pred.ref_idx_l0, expected.ref_idx_l0);
assert_eq!(pred.mvd_l0, expected.mvd_l0);
assert_eq!(pred.mvd_l1, expected.mvd_l1);
assert_eq!(bins, oracle_bins, "bin consumption must match oracle");
assert!(bins > 0, "CABAC mvd_lx must consume bins");
assert_eq!(pred.ref_idx_l0.len(), 1);
assert_eq!(pred.ref_idx_l0[0], 0);
}
#[test]
fn cabac_p_l0_16x16_multi_ref_reads_ref_idx() {
let data: [u8; 16] = [
0xA5, 0x5A, 0x3C, 0xC3, 0x81, 0x7E, 0x12, 0xED, 0x00, 0xFF, 0x80, 0x01, 0x22, 0x44,
0x88, 0x77,
];
let mb_type = MbType::PL016x16;
let (pred, bins) = run_cabac_mb_pred(&data, SliceKind::P, &mb_type, 2, 0);
let (expected, oracle_bins) = oracle_mb_pred_calls(&data, SliceKind::P, &mb_type, 2, 0);
assert_eq!(pred.ref_idx_l0, expected.ref_idx_l0);
assert_eq!(pred.mvd_l0, expected.mvd_l0);
assert_eq!(bins, oracle_bins);
assert!(bins > 0, "CABAC ref_idx + mvd must consume bins");
let (_, bins_single_ref) = run_cabac_mb_pred(&data, SliceKind::P, &mb_type, 0, 0);
assert!(
bins > bins_single_ref,
"multi-ref path must read ref_idx bins (got {bins} vs single-ref {bins_single_ref})",
);
}
#[test]
fn cabac_b_l0_l1_16x8_per_list_gating() {
let data: [u8; 16] = [
0xA5, 0x5A, 0x3C, 0xC3, 0x81, 0x7E, 0x12, 0xED, 0x00, 0xFF, 0x80, 0x01, 0x22, 0x44,
0x88, 0x77,
];
let mb_type = MbType::BL0L116x8;
let (pred, bins) = run_cabac_mb_pred(&data, SliceKind::B, &mb_type, 2, 2);
let (expected, oracle_bins) = oracle_mb_pred_calls(&data, SliceKind::B, &mb_type, 2, 2);
assert_eq!(pred.ref_idx_l0, expected.ref_idx_l0);
assert_eq!(pred.ref_idx_l1, expected.ref_idx_l1);
assert_eq!(pred.mvd_l0, expected.mvd_l0);
assert_eq!(pred.mvd_l1, expected.mvd_l1);
assert_eq!(bins, oracle_bins);
assert!(
bins >= 4,
"B_L0_L1_16x8 CABAC must consume ≥ 4 bins across partitions (got {bins})",
);
}
#[test]
fn cabac_mvd_matches_standalone_oracle() {
let streams: &[[u8; 16]] = &[
[0x00; 16],
[0xFF; 16],
[
0xA5, 0x5A, 0x3C, 0xC3, 0x81, 0x7E, 0x12, 0xED, 0x00, 0xFF, 0x80, 0x01, 0x22, 0x44,
0x88, 0x77,
],
[
0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB,
0xCD, 0xEF,
],
];
let mb_type = MbType::PL016x16;
for (i, data) in streams.iter().enumerate() {
let (pred, _) = run_cabac_mb_pred(data, SliceKind::P, &mb_type, 0, 0);
let mut dec = CabacDecoder::new(BitReader::new(data)).unwrap();
let mut ctxs = CabacContexts::init(SliceKind::P, Some(0), 26).unwrap();
let mx = decode_mvd_lx(&mut dec, &mut ctxs, MvdComponent::X, 0).unwrap();
let my = decode_mvd_lx(&mut dec, &mut ctxs, MvdComponent::Y, 0).unwrap();
assert_eq!(
pred.mvd_l0[0],
[mx, my],
"stream #{i}: parse_mb_pred mvd_l0[0] must match standalone decode_mvd_lx pair",
);
let (single_mx, bins_mx) = oracle_single_mvd(data, SliceKind::P, MvdComponent::X);
assert!(
bins_mx > 0,
"stream #{i}: decode_mvd_lx must consume ≥1 bin",
);
assert_eq!(
pred.mvd_l0[0][0], single_mx,
"stream #{i}: first mvd X must match",
);
}
let mut any_nonzero = false;
for data in streams {
let (pred, _) = run_cabac_mb_pred(data, SliceKind::P, &mb_type, 0, 0);
if pred.mvd_l0[0][0] != 0 || pred.mvd_l0[0][1] != 0 {
any_nonzero = true;
break;
}
}
assert!(
any_nonzero,
"no test stream produced a non-zero mvd — tighten inputs",
);
}
fn run_cabac_sub_mb_pred(
data: &[u8],
slice_kind: SliceKind,
mb_type: &MbType,
num_ref_idx_l0_active_minus1: u32,
num_ref_idx_l1_active_minus1: u32,
) -> (SubMbPred, u64) {
let mut dec = CabacDecoder::new(BitReader::new(data)).unwrap();
let cabac_init_idc = match slice_kind {
SliceKind::I | SliceKind::SI => None,
_ => Some(0u32),
};
let mut ctxs = CabacContexts::init(slice_kind, cabac_init_idc, 26).unwrap();
let bins_before = dec.bin_count();
let dummy = [0u8; 1];
let mut r = BitReader::new(&dummy);
let mut entropy = EntropyState {
cabac: Some((&mut dec, &mut ctxs)),
slice_kind,
neighbours: NeighbourCtx::default(),
prev_mb_qp_delta_nonzero: false,
chroma_array_type: 1,
transform_8x8_mode_flag: false,
cavlc_nc: None,
current_mb_addr: 0,
constrained_intra_pred_flag: false,
num_ref_idx_l0_active_minus1,
num_ref_idx_l1_active_minus1,
mbaff_frame_flag: false,
mb_field_decoding_flag: false,
cabac_nb: None,
pic_width_in_mbs: 0,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
};
let pred = parse_sub_mb_pred(&mut r, &mut entropy, mb_type).unwrap();
let bins_used = dec.bin_count() - bins_before;
(pred, bins_used)
}
#[test]
fn cabac_sub_mb_type_p_consumes_bins() {
let data: [u8; 16] = [0xFF; 16];
let mb_type = MbType::P8x8;
let (_pred, bins) = run_cabac_sub_mb_pred(&data, SliceKind::P, &mb_type, 0, 0);
assert!(
bins >= 4,
"P-slice sub_mb_type must consume ≥ 4 bins (one per part) — got {bins}",
);
}
#[test]
fn cabac_sub_mb_type_p_matches_standalone_p_decoder() {
let data: [u8; 16] = [
0xA5, 0x5A, 0x3C, 0xC3, 0x81, 0x7E, 0x12, 0xED, 0x00, 0xFF, 0x80, 0x01, 0x22, 0x44,
0x88, 0x77,
];
let mb_type = MbType::P8x8;
let (pred, _) = run_cabac_sub_mb_pred(&data, SliceKind::P, &mb_type, 0, 0);
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
let mut ctxs = CabacContexts::init(SliceKind::P, Some(0), 26).unwrap();
let mut expected_raws = [0u32; 4];
for r in expected_raws.iter_mut() {
*r = crate::cabac_ctx::decode_sub_mb_type_p(&mut dec, &mut ctxs).unwrap();
}
for (i, &raw) in expected_raws.iter().enumerate() {
let expected = SubMbType::from_p(raw).unwrap();
assert_eq!(
pred.sub_mb_type[i], expected,
"sub_mb_type[{i}] mismatch: expected {expected:?} got {:?}",
pred.sub_mb_type[i],
);
}
}
#[test]
fn cabac_sub_mb_type_b_matches_standalone_b_decoder() {
let data: [u8; 16] = [
0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB,
0xCD, 0xEF,
];
let mb_type = MbType::B8x8;
let (pred, bins) = run_cabac_sub_mb_pred(&data, SliceKind::B, &mb_type, 0, 0);
assert!(
bins >= 4,
"B-slice sub_mb_type must consume ≥ 4 bins (one per part) — got {bins}",
);
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
let mut ctxs = CabacContexts::init(SliceKind::B, Some(0), 26).unwrap();
let mut expected_raws = [0u32; 4];
for r in expected_raws.iter_mut() {
*r = crate::cabac_ctx::decode_sub_mb_type_b(&mut dec, &mut ctxs).unwrap();
}
for (i, &raw) in expected_raws.iter().enumerate() {
let expected = SubMbType::from_b(raw).unwrap();
assert_eq!(
pred.sub_mb_type[i], expected,
"sub_mb_type[{i}] mismatch: expected {expected:?} got {:?}",
pred.sub_mb_type[i],
);
}
}
#[test]
fn cabac_sub_mb_type_not_stubbed_zero_bins() {
let data: [u8; 16] = [
0x80, 0x00, 0x01, 0xFE, 0x7F, 0x55, 0xAA, 0x33, 0xCC, 0x11, 0xEE, 0x00, 0xFF, 0x00,
0xFF, 0x00,
];
let (_, p_bins) = run_cabac_sub_mb_pred(&data, SliceKind::P, &MbType::P8x8, 0, 0);
assert!(p_bins > 0, "P sub_mb_type must consume bins (got 0)");
let (_, b_bins) = run_cabac_sub_mb_pred(&data, SliceKind::B, &MbType::B8x8, 0, 0);
assert!(b_bins > 0, "B sub_mb_type must consume bins (got 0)");
}
fn cavlc_entropy_444() -> EntropyState<'static, 'static> {
EntropyState {
cabac: None,
slice_kind: SliceKind::I,
neighbours: NeighbourCtx::default(),
prev_mb_qp_delta_nonzero: false,
chroma_array_type: 3,
transform_8x8_mode_flag: false,
cavlc_nc: None,
current_mb_addr: 0,
constrained_intra_pred_flag: false,
num_ref_idx_l0_active_minus1: 0,
num_ref_idx_l1_active_minus1: 0,
mbaff_frame_flag: false,
mb_field_decoding_flag: false,
cabac_nb: None,
pic_width_in_mbs: 0,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
}
}
#[test]
fn cavlc_intra_16x16_444_all_zero_dc() {
let mut w = BitWriter::new();
w.ue(1); w.se(0); w.u(1, 1); w.u(1, 1); w.u(1, 1); w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::I);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy_444();
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert!(matches!(
mb.mb_type,
MbType::Intra16x16(Intra16x16 {
pred_mode: 0,
cbp_luma: 0,
cbp_chroma: 0
})
));
assert_eq!(mb.coded_block_pattern, 0);
assert!(mb.residual_luma_dc.is_some());
assert!(mb.residual_cb_16x16_dc.is_some());
assert!(mb.residual_cr_16x16_dc.is_some());
assert_eq!(mb.residual_luma_dc.unwrap(), [0; 16]);
assert_eq!(mb.residual_cb_16x16_dc.unwrap(), [0; 16]);
assert_eq!(mb.residual_cr_16x16_dc.unwrap(), [0; 16]);
assert!(mb.residual_luma.is_empty());
assert!(mb.residual_cb_luma_like.is_empty());
assert!(mb.residual_cr_luma_like.is_empty());
assert!(mb.residual_chroma_dc_cb.is_empty());
assert!(mb.residual_chroma_ac_cb.is_empty());
}
#[test]
fn cavlc_intra_16x16_444_all_zero_with_ac() {
let mut w = BitWriter::new();
w.ue(13); w.se(0); for _ in 0..(3 * 17) {
w.u(1, 1);
}
w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::I);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy_444();
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert!(matches!(
mb.mb_type,
MbType::Intra16x16(Intra16x16 {
pred_mode: 0,
cbp_luma: 15,
cbp_chroma: 0
})
));
assert!(mb.residual_luma_dc.is_some());
assert_eq!(mb.residual_luma.len(), 16);
assert!(mb.residual_cb_16x16_dc.is_some());
assert_eq!(mb.residual_cb_luma_like.len(), 16);
assert!(mb.residual_cr_16x16_dc.is_some());
assert_eq!(mb.residual_cr_luma_like.len(), 16);
for blk in &mb.residual_luma {
assert_eq!(*blk, [0; 16]);
}
for blk in &mb.residual_cb_luma_like {
assert_eq!(*blk, [0; 16]);
}
for blk in &mb.residual_cr_luma_like {
assert_eq!(*blk, [0; 16]);
}
}
#[test]
fn cavlc_i_nxn_444_all_zero_residual() {
assert_eq!(ME_INTRA_0_3[1], 0);
let mut w = BitWriter::new();
w.ue(0); for _ in 0..16 {
w.u(1, 1); }
w.ue(1); w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::I);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy_444();
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert_eq!(mb.mb_type, MbType::INxN);
assert_eq!(mb.coded_block_pattern, 0);
assert!(mb.residual_luma.is_empty());
assert!(mb.residual_cb_luma_like.is_empty());
assert!(mb.residual_cr_luma_like.is_empty());
assert!(mb.residual_cb_16x16_dc.is_none());
assert!(mb.residual_cr_16x16_dc.is_none());
}
#[test]
fn cavlc_inter_444_all_zero_residual() {
assert_eq!(ME_INTER_420_422[0], 0);
let mut w = BitWriter::new();
w.ue(0); w.se(0); w.se(0); w.ue(0); w.trailing();
let bytes = w.into_bytes();
let sps = dummy_sps();
let pps = dummy_pps();
let hdr = dummy_slice_header(SliceType::P);
let mut r = BitReader::new(&bytes);
let mut entropy = cavlc_entropy_444();
entropy.slice_kind = SliceKind::P;
let mb = parse_macroblock(&mut r, &mut entropy, &hdr, &sps, &pps, 0).unwrap();
assert_eq!(mb.mb_type, MbType::PL016x16);
assert_eq!(mb.coded_block_pattern, 0);
assert!(mb.residual_luma.is_empty());
assert!(mb.residual_cb_luma_like.is_empty());
assert!(mb.residual_cr_luma_like.is_empty());
assert!(mb.residual_luma_dc.is_none());
assert!(mb.residual_cb_16x16_dc.is_none());
assert!(mb.residual_cr_16x16_dc.is_none());
}
#[test]
fn cavlc_444_nc_plane_reads_per_plane_totals() {
let mut grid = CavlcNcGrid::new(2, 2);
let slot = &mut grid.mbs[0];
slot.is_available = true;
slot.is_intra = true;
slot.cb_luma_total_coeff[5] = 7;
slot.cr_luma_total_coeff[5] = 11;
let (avail, n) = nc_nn_plane_luma_like(&grid, Some(0), 5, false, true, false);
assert!(avail);
assert_eq!(n, 7);
let (avail, n) = nc_nn_plane_luma_like(&grid, Some(0), 5, true, true, false);
assert!(avail);
assert_eq!(n, 11);
let (avail, _) = nc_nn_plane_luma_like(&grid, Some(99), 5, false, true, false);
assert!(!avail);
grid.mbs[1].is_available = true;
grid.mbs[1].is_skip = true;
grid.mbs[1].cb_luma_total_coeff[5] = 9;
let (avail, n) = nc_nn_plane_luma_like(&grid, Some(1), 5, false, true, false);
assert!(avail);
assert_eq!(n, 0);
grid.mbs[1].is_skip = false;
grid.mbs[1].is_i_pcm = true;
let (avail, n) = nc_nn_plane_luma_like(&grid, Some(1), 5, false, true, false);
assert!(avail);
assert_eq!(n, 16);
}
#[test]
fn cabac_444_intra_16x16_residual_path_reachable() {
let data: [u8; 32] = [
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let mut dec = CabacDecoder::new(BitReader::new(&data)).unwrap();
let mut ctxs = CabacContexts::init(SliceKind::I, None, 26).unwrap();
let mut out = Macroblock::new_skip(SliceType::I);
out.is_skip = false;
let mb_type = MbType::Intra16x16(Intra16x16 {
pred_mode: 0,
cbp_luma: 0,
cbp_chroma: 0,
});
let res = parse_residual_cabac_only(
&mut dec, &mut ctxs, 3, &mb_type, 0,
0, false, &mut out,
None, 0, true,
false,
);
assert!(
!matches!(
res,
Err(MacroblockLayerError::UnsupportedChromaArrayType(3))
),
"CABAC 4:4:4 must not return UnsupportedChromaArrayType(3)"
);
if res.is_ok() {
assert!(out.residual_luma_dc.is_some());
assert!(out.residual_cb_16x16_dc.is_some());
assert!(out.residual_cr_16x16_dc.is_some());
}
}
#[test]
fn cabac_444_cbf_helpers_route_per_plane() {
let mut info = CabacMbNeighbourInfo {
available: true,
..CabacMbNeighbourInfo::default()
};
info.cbf_luma_4x4[3] = true;
info.cbf_cb_luma_4x4[3] = false;
info.cbf_cr_luma_4x4[3] = true;
info.cbf_luma_16x16_dc = true;
info.cbf_cb_16x16_dc = false;
info.cbf_cr_16x16_dc = true;
info.cbf_luma_16x16_ac[5] = true;
info.cbf_cb_16x16_ac[5] = false;
info.cbf_cr_16x16_ac[5] = true;
assert!(cbf_luma_for(&info, BlockType::Luma4x4, 3));
assert!(!cbf_luma_for(&info, BlockType::CbLuma4x4, 3));
assert!(cbf_luma_for(&info, BlockType::CrLuma4x4, 3));
assert!(cbf_luma_for(&info, BlockType::Luma16x16Ac, 5));
assert!(!cbf_luma_for(&info, BlockType::CbIntra16x16Ac, 5));
assert!(cbf_luma_for(&info, BlockType::CrIntra16x16Ac, 5));
assert!(cbf_mb_level_for(&info, BlockType::Luma16x16Dc, false));
assert!(!cbf_mb_level_for(&info, BlockType::CbIntra16x16Dc, false));
assert!(cbf_mb_level_for(&info, BlockType::CrIntra16x16Dc, true));
}
}