use crate::{DecodedFrame, NalUnit, NalUnitType, VideoCodec, VideoDecoder, VideoError};
use super::h264_bitstream::BitstreamReader;
use super::h264_cabac::CabacDecoder;
use super::h264_params::{
Pps, SliceHeader, Sps, parse_pps, parse_slice_header, parse_sps, remove_emulation_prevention,
};
use super::h264_transform::{
MAX_COEFF, MAX_LEVEL, clamp_coeffs, dequant_4x4, dequant_8x8, inverse_dct_4x4, inverse_dct_8x8,
unscan_4x4, unscan_8x8,
};
use super::h264_yuv::{
chroma_dimensions, deinterlace_fields, generate_slice_group_map, yuv_to_rgb8_by_format,
};
#[inline]
fn bitstream_err() -> VideoError {
VideoError::Codec("bitstream exhausted".into())
}
#[inline]
fn read_mvd(reader: &mut super::cavlc::BitReader<'_>) -> Result<(i16, i16), VideoError> {
let x = reader.read_se().ok_or_else(bitstream_err)?;
let y = reader.read_se().ok_or_else(bitstream_err)?;
Ok((x as i16, y as i16))
}
pub(crate) struct MbCtx {
pub nnz_luma: Vec<u8>,
pub nnz_cb: Vec<u8>,
pub nnz_cr: Vec<u8>,
pub modes4x4: Vec<u8>,
pub nnz_decoded: Vec<bool>,
pub grid_w4: usize,
pub grid_w2: usize,
pub qp_prev: i32,
pub chroma_qp_index_offset: i32,
pub slice_first_mb: usize,
pub mb_w: usize,
pub mb_cbp: Vec<u32>,
pub mb_chroma_pred: Vec<u8>,
pub mb_i16_pcm: Vec<bool>,
pub mb_intra: Vec<bool>,
pub mb_tr8x8: Vec<bool>,
pub direct4: Vec<bool>,
}
const NOT_I4X4: u8 = 0xFF;
impl MbCtx {
pub(crate) fn new(
mb_w: usize,
mb_h: usize,
slice_qp: i32,
chroma_qp_index_offset: i32,
) -> Self {
Self {
nnz_luma: vec![0; mb_w * 4 * mb_h * 4],
nnz_cb: vec![0; mb_w * 2 * mb_h * 2],
nnz_cr: vec![0; mb_w * 2 * mb_h * 2],
modes4x4: vec![NOT_I4X4; mb_w * 4 * mb_h * 4],
nnz_decoded: vec![false; mb_w * 4 * mb_h * 4],
grid_w4: mb_w * 4,
grid_w2: mb_w * 2,
qp_prev: slice_qp,
chroma_qp_index_offset,
slice_first_mb: 0,
mb_w,
mb_cbp: vec![0; mb_w * mb_h],
mb_chroma_pred: vec![0; mb_w * mb_h],
mb_i16_pcm: vec![false; mb_w * mb_h],
mb_intra: vec![false; mb_w * mb_h],
mb_tr8x8: vec![false; mb_w * mb_h],
direct4: vec![false; mb_w * 4 * mb_h * 4],
}
}
#[inline]
const fn sample_mb_avail(&self, x: i32, y: i32, mb_shift: u32) -> bool {
if x < 0 || y < 0 {
return false;
}
let mb = (y >> mb_shift) as usize * self.mb_w + (x >> mb_shift) as usize;
mb >= self.slice_first_mb
}
#[inline]
const fn block_avail(&self, bx: i32, by: i32, sub_shift: u32) -> bool {
if bx < 0 || by < 0 {
return false;
}
let mb = (by >> sub_shift) as usize * self.mb_w + (bx >> sub_shift) as usize;
mb >= self.slice_first_mb
}
}
const fn nc_pred(nnz: &[u8], grid_w: usize, bx: usize, by: usize, left: bool, top: bool) -> i32 {
let a = if left {
nnz[by * grid_w + bx - 1] as i32
} else {
0
};
let b = if top {
nnz[(by - 1) * grid_w + bx] as i32
} else {
0
};
match (left, top) {
(true, true) => (a + b + 1) >> 1,
(true, false) => a,
(false, true) => b,
(false, false) => 0,
}
}
#[inline]
fn clip_u8(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
fn read_residual(
reader: &mut super::cavlc::BitReader<'_>,
nc: i32,
ac: bool,
max_coeff: usize,
) -> Option<([i32; 16], usize)> {
let result = super::cavlc::decode_cavlc_block_max(reader, nc, max_coeff)?;
let mut tmp = [0i32; 16];
super::cavlc::expand_cavlc_to_coefficients_into(&result, &mut tmp[..max_coeff.max(1)]);
let mut scan = [0i32; 16];
let off = usize::from(ac);
scan[off..off + max_coeff].copy_from_slice(&tmp[..max_coeff]);
let mut out = [0i32; 16];
unscan_4x4(&scan, &mut out);
Some((out, result.total_coeffs))
}
fn intra4x4_pred(
mode: u8,
t: &[i32; 8],
l: &[i32; 4],
tl: i32,
top: bool,
left: bool,
) -> [i32; 16] {
let mut p = [0i32; 16];
let tx = |x: i32| -> i32 { if x < 0 { tl } else { t[x as usize] } };
let ly = |y: i32| -> i32 { if y < 0 { tl } else { l[y.min(3) as usize] } };
for y in 0..4i32 {
for x in 0..4i32 {
let v = match mode {
0 => t[x as usize], 1 => l[y as usize], 2 => {
if top && left {
(t[0] + t[1] + t[2] + t[3] + l[0] + l[1] + l[2] + l[3] + 4) >> 3
} else if top {
(t[0] + t[1] + t[2] + t[3] + 2) >> 2
} else if left {
(l[0] + l[1] + l[2] + l[3] + 2) >> 2
} else {
128
}
}
3 => {
let i = (x + y) as usize;
if x == 3 && y == 3 {
(t[6] + 3 * t[7] + 2) >> 2
} else {
(t[i] + 2 * t[i + 1] + t[i + 2] + 2) >> 2
}
}
4 => {
if x > y {
(tx(x - y - 2) + 2 * tx(x - y - 1) + tx(x - y) + 2) >> 2
} else if x < y {
(ly(y - x - 2) + 2 * ly(y - x - 1) + ly(y - x) + 2) >> 2
} else {
(tx(0) + 2 * tl + ly(0) + 2) >> 2
}
}
5 => {
let z = 2 * x - y;
if z >= 0 && z % 2 == 0 {
(tx(x - (y >> 1) - 1) + tx(x - (y >> 1)) + 1) >> 1
} else if z >= 0 {
(tx(x - (y >> 1) - 2) + 2 * tx(x - (y >> 1) - 1) + tx(x - (y >> 1)) + 2)
>> 2
} else if z == -1 {
(ly(0) + 2 * tl + tx(0) + 2) >> 2
} else {
(ly(y - 1) + 2 * ly(y - 2) + ly(y - 3) + 2) >> 2
}
}
6 => {
let z = 2 * y - x;
if z >= 0 && z % 2 == 0 {
(ly(y - (x >> 1) - 1) + ly(y - (x >> 1)) + 1) >> 1
} else if z >= 0 {
(ly(y - (x >> 1) - 2) + 2 * ly(y - (x >> 1) - 1) + ly(y - (x >> 1)) + 2)
>> 2
} else if z == -1 {
(ly(0) + 2 * tl + tx(0) + 2) >> 2
} else {
(tx(x - 1) + 2 * tx(x - 2) + tx(x - 3) + 2) >> 2
}
}
7 => {
let i = (x + (y >> 1)) as usize;
if y % 2 == 0 {
(t[i] + t[i + 1] + 1) >> 1
} else {
(t[i] + 2 * t[i + 1] + t[i + 2] + 2) >> 2
}
}
_ => {
let z = x + 2 * y;
if z == 5 {
(l[2] + 3 * l[3] + 2) >> 2
} else if z > 5 {
l[3]
} else if z % 2 == 0 {
let i = (y + (x >> 1)) as usize;
(l[i] + l[i + 1] + 1) >> 1
} else {
let i = (y + (x >> 1)) as usize;
(l[i] + 2 * l[i + 1] + l[i + 2] + 2) >> 2
}
}
};
p[(y * 4 + x) as usize] = v;
}
}
p
}
#[allow(clippy::too_many_arguments)]
fn intra8x8_pred(
mode: u8,
t: &[i32; 16],
l: &[i32; 8],
tl: i32,
top: bool,
left: bool,
tl_avail: bool,
) -> [i32; 64] {
let mut ft = [0i32; 16];
let mut fl = [0i32; 8];
if top {
ft[0] = if tl_avail {
(tl + 2 * t[0] + t[1] + 2) >> 2
} else {
(3 * t[0] + t[1] + 2) >> 2
};
for x in 1..15 {
ft[x] = (t[x - 1] + 2 * t[x] + t[x + 1] + 2) >> 2;
}
ft[15] = (t[14] + 3 * t[15] + 2) >> 2;
}
if left {
fl[0] = if tl_avail {
(tl + 2 * l[0] + l[1] + 2) >> 2
} else {
(3 * l[0] + l[1] + 2) >> 2
};
for y in 1..7 {
fl[y] = (l[y - 1] + 2 * l[y] + l[y + 1] + 2) >> 2;
}
fl[7] = (l[6] + 3 * l[7] + 2) >> 2;
}
let ftl = if !tl_avail {
tl
} else if top && left {
(t[0] + 2 * tl + l[0] + 2) >> 2
} else if top {
(3 * tl + t[0] + 2) >> 2
} else {
(3 * tl + l[0] + 2) >> 2
};
let tx = |x: i32| -> i32 { if x < 0 { ftl } else { ft[x as usize] } };
let ly = |y: i32| -> i32 { if y < 0 { ftl } else { fl[y.min(7) as usize] } };
let mut p = [0i32; 64];
for y in 0..8i32 {
for x in 0..8i32 {
let v = match mode {
0 => ft[x as usize], 1 => fl[y as usize], 2 => {
if top && left {
(ft[..8].iter().sum::<i32>() + fl.iter().sum::<i32>() + 8) >> 4
} else if top {
(ft[..8].iter().sum::<i32>() + 4) >> 3
} else if left {
(fl.iter().sum::<i32>() + 4) >> 3
} else {
128
}
}
3 => {
let i = (x + y) as usize;
if x == 7 && y == 7 {
(ft[14] + 3 * ft[15] + 2) >> 2
} else {
(ft[i] + 2 * ft[i + 1] + ft[i + 2] + 2) >> 2
}
}
4 => {
if x > y {
(tx(x - y - 2) + 2 * tx(x - y - 1) + tx(x - y) + 2) >> 2
} else if x < y {
(ly(y - x - 2) + 2 * ly(y - x - 1) + ly(y - x) + 2) >> 2
} else {
(tx(0) + 2 * ftl + ly(0) + 2) >> 2
}
}
5 => {
let z = 2 * x - y;
if z >= 0 && z % 2 == 0 {
(tx(x - (y >> 1) - 1) + tx(x - (y >> 1)) + 1) >> 1
} else if z >= 0 {
(tx(x - (y >> 1) - 2) + 2 * tx(x - (y >> 1) - 1) + tx(x - (y >> 1)) + 2)
>> 2
} else if z == -1 {
(ly(0) + 2 * ftl + tx(0) + 2) >> 2
} else {
(ly(y - 2 * x - 1) + 2 * ly(y - 2 * x - 2) + ly(y - 2 * x - 3) + 2) >> 2
}
}
6 => {
let z = 2 * y - x;
if z >= 0 && z % 2 == 0 {
(ly(y - (x >> 1) - 1) + ly(y - (x >> 1)) + 1) >> 1
} else if z >= 0 {
(ly(y - (x >> 1) - 2) + 2 * ly(y - (x >> 1) - 1) + ly(y - (x >> 1)) + 2)
>> 2
} else if z == -1 {
(ly(0) + 2 * ftl + tx(0) + 2) >> 2
} else {
(tx(x - 2 * y - 1) + 2 * tx(x - 2 * y - 2) + tx(x - 2 * y - 3) + 2) >> 2
}
}
7 => {
let i = (x + (y >> 1)) as usize;
if y % 2 == 0 {
(ft[i] + ft[i + 1] + 1) >> 1
} else {
(ft[i] + 2 * ft[i + 1] + ft[i + 2] + 2) >> 2
}
}
_ => {
let z = x + 2 * y;
if z == 13 {
(fl[6] + 3 * fl[7] + 2) >> 2
} else if z > 13 {
fl[7]
} else if z % 2 == 0 {
let i = (y + (x >> 1)) as usize;
(fl[i] + fl[i + 1] + 1) >> 1
} else {
let i = (y + (x >> 1)) as usize;
(fl[i] + 2 * fl[i + 1] + fl[i + 2] + 2) >> 2
}
}
};
p[(y * 8 + x) as usize] = v;
}
}
p
}
fn intra_plane_pred(
mode: u8,
size: usize,
top: Option<&[i32]>,
left: Option<&[i32]>,
tl: i32,
out: &mut [i32],
) {
let dc_value = || -> i32 {
match (top, left) {
(Some(t), Some(l)) => {
(t[..size].iter().sum::<i32>() + l[..size].iter().sum::<i32>() + size as i32)
>> (size.trailing_zeros() + 1)
}
(Some(t), None) => {
(t[..size].iter().sum::<i32>() + (size as i32 >> 1)) >> size.trailing_zeros()
}
(None, Some(l)) => {
(l[..size].iter().sum::<i32>() + (size as i32 >> 1)) >> size.trailing_zeros()
}
(None, None) => 128,
}
};
match mode {
0 if top.is_some() => {
let t = top.unwrap();
for y in 0..size {
for x in 0..size {
out[y * size + x] = t[x];
}
}
}
1 if left.is_some() => {
let l = left.unwrap();
for y in 0..size {
for x in 0..size {
out[y * size + x] = l[y];
}
}
}
3 if top.is_some() && left.is_some() => {
let t = top.unwrap();
let l = left.unwrap();
let m = (size / 2 - 1) as i32; let mut h = 0i32;
let mut v = 0i32;
for xp in 0..=m {
let a = t[(size / 2) + xp as usize];
let b = if (size / 2) as i32 - 2 - xp >= 0 {
t[((size / 2) as i32 - 2 - xp) as usize]
} else {
tl
};
h += (xp + 1) * (a - b);
}
for yp in 0..=m {
let a = l[(size / 2) + yp as usize];
let b = if (size / 2) as i32 - 2 - yp >= 0 {
l[((size / 2) as i32 - 2 - yp) as usize]
} else {
tl
};
v += (yp + 1) * (a - b);
}
let (bb, cc, aa);
if size == 16 {
bb = (5 * h + 32) >> 6;
cc = (5 * v + 32) >> 6;
aa = 16 * (l[15] + t[15]);
} else {
bb = (17 * h + 16) >> 5;
cc = (17 * v + 16) >> 5;
aa = 16 * (l[size - 1] + t[size - 1]);
}
for y in 0..size as i32 {
for x in 0..size as i32 {
let val = (aa + bb * (x - m) + cc * (y - m) + 16) >> 5;
out[(y * size as i32 + x) as usize] = val.clamp(0, 255);
}
}
}
_ => {
out[..size * size].fill(dc_value());
}
}
}
#[allow(clippy::too_many_arguments)]
fn decode_macroblock(
reader: &mut super::cavlc::BitReader<'_>,
ctx: &mut MbCtx,
mb_type: u32,
mb_x: usize,
mb_y: usize,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
stride_y: usize,
stride_uv: usize,
) -> Result<(), VideoError> {
let px = mb_x * 16;
let py = mb_y * 16;
let bx0 = mb_x * 4; let by0 = mb_y * 4;
let cbx0 = mb_x * 2; let cby0 = mb_y * 2;
if mb_type == 25 {
reader.align_byte();
for row in 0..16 {
for col in 0..16 {
let val = reader.read_bits(8).ok_or_else(bitstream_err)? as u8;
y_plane[(py + row) * stride_y + px + col] = val;
}
}
for plane in [&mut *u_plane, &mut *v_plane] {
for row in 0..8 {
for col in 0..8 {
let val = reader.read_bits(8).ok_or_else(bitstream_err)? as u8;
plane[(mb_y * 8 + row) * stride_uv + mb_x * 8 + col] = val;
}
}
}
for r in 0..4 {
for c in 0..4 {
ctx.nnz_luma[(by0 + r) * ctx.grid_w4 + bx0 + c] = 16;
}
}
for r in 0..2 {
for c in 0..2 {
ctx.nnz_cb[(cby0 + r) * ctx.grid_w2 + cbx0 + c] = 16;
ctx.nnz_cr[(cby0 + r) * ctx.grid_w2 + cbx0 + c] = 16;
}
}
for r in 0..4 {
for c in 0..4 {
ctx.modes4x4[(by0 + r) * ctx.grid_w4 + bx0 + c] = NOT_I4X4;
}
}
return Ok(());
}
let is_i16x16 = (1..=24).contains(&mb_type);
const BLK: [(usize, usize); 16] = LUMA_BLK_SCAN;
let mut modes = [2u8; 16]; let (i16_mode, chroma_mode);
if is_i16x16 {
i16_mode = ((mb_type - 1) % 4) as u8;
chroma_mode = reader.read_ue().ok_or_else(bitstream_err)? as u8;
} else {
for (blk_idx, &(br, bc)) in BLK.iter().enumerate() {
let gx = bx0 + bc / 4;
let gy = by0 + br / 4;
let a_avail = ctx.block_avail(gx as i32 - 1, gy as i32, 2);
let b_avail = ctx.block_avail(gx as i32, gy as i32 - 1, 2);
let pred_mode = if !a_avail || !b_avail {
2
} else {
let raw_a = ctx.modes4x4[gy * ctx.grid_w4 + gx - 1];
let raw_b = ctx.modes4x4[(gy - 1) * ctx.grid_w4 + gx];
let mode_a = if raw_a == NOT_I4X4 { 2 } else { raw_a };
let mode_b = if raw_b == NOT_I4X4 { 2 } else { raw_b };
mode_a.min(mode_b)
};
let prev_flag = reader.read_bits(1).ok_or_else(bitstream_err)?;
let mode = if prev_flag == 1 {
pred_mode
} else {
let rem = reader.read_bits(3).ok_or_else(bitstream_err)? as u8;
if rem < pred_mode { rem } else { rem + 1 }
};
modes[blk_idx] = mode;
ctx.modes4x4[gy * ctx.grid_w4 + gx] = mode;
}
i16_mode = 0;
chroma_mode = reader.read_ue().ok_or_else(bitstream_err)? as u8;
}
let cbp = if is_i16x16 {
let cbp_luma = if (mb_type - 1) / 12 >= 1 { 15 } else { 0 };
let cbp_chroma = ((mb_type - 1) / 4) % 3;
cbp_luma | (cbp_chroma << 4)
} else {
let cbp_code = reader.read_ue().ok_or_else(bitstream_err)?;
const CBP_INTRA: [u32; 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,
];
*CBP_INTRA.get(cbp_code as usize).unwrap_or(&0)
};
if is_i16x16 {
for r in 0..4 {
for c in 0..4 {
ctx.modes4x4[(by0 + r) * ctx.grid_w4 + bx0 + c] = NOT_I4X4;
}
}
}
let cbp_luma = cbp & 0xF;
let chroma_cbp = (cbp >> 4) & 3;
let qp = if cbp > 0 || is_i16x16 {
let qp_delta = reader.read_se().ok_or_else(bitstream_err)?;
(ctx.qp_prev + qp_delta).rem_euclid(52)
} else {
ctx.qp_prev
};
ctx.qp_prev = qp;
let mut luma_dc = [0i32; 16];
if is_i16x16 {
let (bl, bt) = (
ctx.block_avail(bx0 as i32 - 1, by0 as i32, 2),
ctx.block_avail(bx0 as i32, by0 as i32 - 1, 2),
);
let nc = nc_pred(&ctx.nnz_luma, ctx.grid_w4, bx0, by0, bl, bt);
if let Some((coeffs, _tc)) = read_residual(reader, nc, false, 16) {
hadamard4x4_dc(&coeffs, &mut luma_dc, qp);
}
}
let mut pred16 = [0i32; 256];
if is_i16x16 {
let top = ctx.sample_mb_avail(px as i32, py as i32 - 1, 4).then(|| {
let mut a = [0i32; 16];
for (x, v) in a.iter_mut().enumerate() {
*v = y_plane[(py - 1) * stride_y + px + x] as i32;
}
a
});
let left = ctx.sample_mb_avail(px as i32 - 1, py as i32, 4).then(|| {
let mut a = [0i32; 16];
for (y, v) in a.iter_mut().enumerate() {
*v = y_plane[(py + y) * stride_y + px - 1] as i32;
}
a
});
let tl = if ctx.sample_mb_avail(px as i32 - 1, py as i32 - 1, 4) {
y_plane[(py - 1) * stride_y + px - 1] as i32
} else {
128
};
intra_plane_pred(
i16_mode,
16,
top.as_ref().map(|a| a.as_slice()),
left.as_ref().map(|a| a.as_slice()),
tl,
&mut pred16,
);
}
for (blk_idx, &(br, bc)) in BLK.iter().enumerate() {
let block_x = px + bc;
let block_y = py + br;
let gx = bx0 + bc / 4;
let gy = by0 + br / 4;
let group = blk_idx / 4;
let mut pred = [0i32; 16];
if is_i16x16 {
for r in 0..4 {
for c in 0..4 {
pred[r * 4 + c] = pred16[(br + r) * 16 + bc + c];
}
}
} else {
let top = ctx.sample_mb_avail(block_x as i32, block_y as i32 - 1, 4);
let left = ctx.sample_mb_avail(block_x as i32 - 1, block_y as i32, 4);
let mut t = [128i32; 8];
let mut l = [128i32; 4];
let mut tl = 128i32;
if top {
for (x, tv) in t.iter_mut().take(4).enumerate() {
*tv = y_plane[(block_y - 1) * stride_y + block_x + x] as i32;
}
let tr_ok = block_x + 4 < ctx.mb_w * 16
&& top_right_available(bx0, by0, bc, br, ctx.grid_w4, &ctx.nnz_decoded);
for x in 4..8 {
t[x] = if tr_ok {
y_plane[(block_y - 1) * stride_y + block_x + x] as i32
} else {
t[3]
};
}
}
if left {
for (y, lv) in l.iter_mut().enumerate() {
*lv = y_plane[(block_y + y) * stride_y + block_x - 1] as i32;
}
}
if top && left && ctx.sample_mb_avail(block_x as i32 - 1, block_y as i32 - 1, 4) {
tl = y_plane[(block_y - 1) * stride_y + block_x - 1] as i32;
}
pred = intra4x4_pred(modes[blk_idx], &t, &l, tl, top, left);
}
let mut nnz = 0usize;
let coded = (cbp_luma & (1 << group)) != 0;
if coded {
let (bl, bt) = (
ctx.block_avail(gx as i32 - 1, gy as i32, 2),
ctx.block_avail(gx as i32, gy as i32 - 1, 2),
);
let nc = nc_pred(&ctx.nnz_luma, ctx.grid_w4, gx, gy, bl, bt);
let max_coeff = if is_i16x16 { 15 } else { 16 };
if let Some((mut coeffs, tc)) = read_residual(reader, nc, is_i16x16, max_coeff) {
nnz = tc;
if is_i16x16 {
coeffs[0] = luma_dc[(br / 4) * 4 + bc / 4];
dequant_4x4_ac(&mut coeffs, qp);
} else {
dequant_4x4(&mut coeffs, qp);
}
inverse_dct_4x4(&mut coeffs);
for r in 0..4 {
let row = (block_y + r) * stride_y + block_x;
for c in 0..4 {
y_plane[row + c] = clip_u8(pred[r * 4 + c] + coeffs[r * 4 + c]);
}
}
} else {
write_pred_luma(y_plane, stride_y, block_x, block_y, &pred);
}
} else if is_i16x16 {
let mut coeffs = [0i32; 16];
coeffs[0] = luma_dc[(br / 4) * 4 + bc / 4];
inverse_dct_4x4(&mut coeffs);
for r in 0..4 {
let row = (block_y + r) * stride_y + block_x;
for c in 0..4 {
y_plane[row + c] = clip_u8(pred[r * 4 + c] + coeffs[r * 4 + c]);
}
}
} else {
write_pred_luma(y_plane, stride_y, block_x, block_y, &pred);
}
ctx.nnz_luma[gy * ctx.grid_w4 + gx] = nnz as u8;
ctx.nnz_decoded[gy * ctx.grid_w4 + gx] = true;
}
decode_chroma(
reader,
ctx,
mb_x,
mb_y,
chroma_mode,
chroma_cbp,
qp,
u_plane,
v_plane,
stride_uv,
)?;
Ok(())
}
fn write_pred_luma(y: &mut [u8], stride: usize, bx: usize, by: usize, pred: &[i32; 16]) {
for r in 0..4 {
let row = (by + r) * stride + bx;
for c in 0..4 {
y[row + c] = clip_u8(pred[r * 4 + c]);
}
}
}
fn hadamard4x4_dc(scan: &[i32; 16], out: &mut [i32; 16], qp: i32) {
let mut f = *scan;
clamp_coeffs(&mut f, MAX_LEVEL);
for i in 0..4 {
let (a, b, c, d) = (f[i], f[4 + i], f[8 + i], f[12 + i]);
let (s0, s1, s2, s3) = (a + c, a - c, b - d, b + d);
f[i] = s0 + s3;
f[4 + i] = s1 + s2;
f[8 + i] = s1 - s2;
f[12 + i] = s0 - s3;
}
for i in 0..4 {
let r = i * 4;
let (a, b, c, d) = (f[r], f[r + 1], f[r + 2], f[r + 3]);
let (s0, s1, s2, s3) = (a + c, a - c, b - d, b + d);
f[r] = s0 + s3;
f[r + 1] = s1 + s2;
f[r + 2] = s1 - s2;
f[r + 3] = s0 - s3;
}
let qp6 = qp / 6;
let qpm = (qp % 6) as usize;
let v = DEQUANT_DC[qpm];
for i in 0..16 {
let scaled = i64::from(f[i]) * i64::from(v);
let dc = if qp >= 36 {
scaled << (qp6 - 6)
} else {
(scaled + (1 << (5 - qp6))) >> (6 - qp6)
};
out[i] = dc.clamp(-i64::from(MAX_COEFF), i64::from(MAX_COEFF)) as i32;
}
}
const DEQUANT_DC: [i32; 6] = [160, 176, 208, 224, 256, 288];
fn dequant_4x4_ac(coeffs: &mut [i32; 16], qp: i32) {
let dc = coeffs[0];
dequant_4x4(coeffs, qp);
coeffs[0] = dc;
}
const fn top_right_available(
bx0: usize,
by0: usize,
bc: usize,
br: usize,
grid_w: usize,
decoded: &[bool],
) -> bool {
if br == 0 {
by0 > 0 && decoded[(by0 - 1) * grid_w + bx0 + bc / 4 + 1]
} else {
let gx = bx0 + bc / 4 + 1;
let gy = by0 + br / 4 - 1;
gx < bx0 + 4 && decoded[gy * grid_w + gx]
}
}
fn read_chroma_dc(reader: &mut super::cavlc::BitReader<'_>) -> Option<[i32; 4]> {
let result = super::cavlc::decode_cavlc_block_max(reader, -1, 4)?;
let mut scan = [0i32; 16];
super::cavlc::expand_cavlc_to_coefficients_into(&result, &mut scan[..4]);
Some([scan[0], scan[1], scan[2], scan[3]])
}
fn chroma_dc_transform(c: &[i32; 4], qpc: i32) -> [i32; 4] {
let mut c = *c;
clamp_coeffs(&mut c, MAX_LEVEL);
let f = [
c[0] + c[1] + c[2] + c[3],
c[0] - c[1] + c[2] - c[3],
c[0] + c[1] - c[2] - c[3],
c[0] - c[1] - c[2] + c[3],
];
let scale = DEQUANT_DC[(qpc % 6) as usize];
let shift = qpc / 6;
let mut out = [0i32; 4];
for i in 0..4 {
let dc = ((i64::from(f[i]) * i64::from(scale)) << shift) >> 5;
out[i] = dc.clamp(-i64::from(MAX_COEFF), i64::from(MAX_COEFF)) as i32;
}
out
}
#[allow(clippy::too_many_arguments)]
fn decode_chroma(
reader: &mut super::cavlc::BitReader<'_>,
ctx: &mut MbCtx,
mb_x: usize,
mb_y: usize,
chroma_mode: u8,
chroma_cbp: u32,
qp_y: i32,
u_plane: &mut [u8],
v_plane: &mut [u8],
stride_uv: usize,
) -> Result<(), VideoError> {
let cpx = mb_x * 8;
let cpy = mb_y * 8;
let cbx0 = mb_x * 2;
let cby0 = mb_y * 2;
let qpc = chroma_qp_from_luma_qp(qp_y, ctx.chroma_qp_index_offset);
let plane_mode = [2u8, 1, 0, 3][(chroma_mode as usize).min(3)];
const CBLK: [(usize, usize); 4] = [(0, 0), (0, 4), (4, 0), (4, 4)];
let mut dc = [[0i32; 4]; 2];
if chroma_cbp >= 1 {
for d in dc.iter_mut() {
if let Some(c) = read_chroma_dc(reader) {
*d = chroma_dc_transform(&c, qpc);
}
}
}
let mut ac = [[[0i32; 16]; 4]; 2];
if chroma_cbp >= 2 {
for pi in 0..2 {
for (bi, &(br, bc)) in CBLK.iter().enumerate() {
let gx = cbx0 + bc / 4;
let gy = cby0 + br / 4;
let (bl, bt) = (
ctx.block_avail(gx as i32 - 1, gy as i32, 1),
ctx.block_avail(gx as i32, gy as i32 - 1, 1),
);
let nc = {
let grid = if pi == 0 { &ctx.nnz_cb } else { &ctx.nnz_cr };
nc_pred(grid, ctx.grid_w2, gx, gy, bl, bt)
};
let tc = if let Some(result) = super::cavlc::decode_cavlc_block_max(reader, nc, 15)
{
let mut tmp = [0i32; 16];
super::cavlc::expand_cavlc_to_coefficients_into(&result, &mut tmp[..15]);
let mut scan = [0i32; 16];
scan[1..16].copy_from_slice(&tmp[..15]);
unscan_4x4(&scan, &mut ac[pi][bi]);
result.total_coeffs
} else {
0
};
let grid = if pi == 0 {
&mut ctx.nnz_cb
} else {
&mut ctx.nnz_cr
};
grid[gy * ctx.grid_w2 + gx] = tc as u8;
}
}
} else {
for pi in 0..2 {
for &(br, bc) in CBLK.iter() {
let idx = (cby0 + br / 4) * ctx.grid_w2 + cbx0 + bc / 4;
if pi == 0 {
ctx.nnz_cb[idx] = 0;
} else {
ctx.nnz_cr[idx] = 0;
}
}
}
}
let top_ok = ctx.sample_mb_avail(cpx as i32, cpy as i32 - 1, 3);
let left_ok = ctx.sample_mb_avail(cpx as i32 - 1, cpy as i32, 3);
let tl_ok = ctx.sample_mb_avail(cpx as i32 - 1, cpy as i32 - 1, 3);
for pi in 0..2 {
let plane = if pi == 0 {
&mut *u_plane
} else {
&mut *v_plane
};
let top = top_ok.then(|| {
let mut a = [0i32; 8];
for (x, s) in a.iter_mut().enumerate() {
*s = plane[(cpy - 1) * stride_uv + cpx + x] as i32;
}
a
});
let left = left_ok.then(|| {
let mut a = [0i32; 8];
for (y, s) in a.iter_mut().enumerate() {
*s = plane[(cpy + y) * stride_uv + cpx - 1] as i32;
}
a
});
let tl = if tl_ok {
plane[(cpy - 1) * stride_uv + cpx - 1] as i32
} else {
128
};
let mut pred = [0i32; 64];
if plane_mode == 2 {
let s4 = |a: &[i32], off: usize| a[off] + a[off + 1] + a[off + 2] + a[off + 3];
let quad_dc = |qx: usize, qy: usize| -> i32 {
let prefer_top = qx == 1 && qy == 0;
let prefer_left = qx == 0 && qy == 1;
match (&top, &left) {
(Some(t), Some(l)) => {
if prefer_top {
(s4(t, 4) + 2) >> 2
} else if prefer_left {
(s4(l, 4) + 2) >> 2
} else {
let to = qx * 4;
let lo = qy * 4;
(s4(t, to) + s4(l, lo) + 4) >> 3
}
}
(Some(t), None) => (s4(t, qx * 4) + 2) >> 2,
(None, Some(l)) => (s4(l, qy * 4) + 2) >> 2,
(None, None) => 128,
}
};
for qy in 0..2 {
for qx in 0..2 {
let d = quad_dc(qx, qy);
for r in 0..4 {
for c in 0..4 {
pred[(qy * 4 + r) * 8 + qx * 4 + c] = d;
}
}
}
}
} else {
intra_plane_pred(
plane_mode,
8,
top.as_ref().map(|a| a.as_slice()),
left.as_ref().map(|a| a.as_slice()),
tl,
&mut pred,
);
}
for (bi, &(br, bc)) in CBLK.iter().enumerate() {
let mut coeffs = ac[pi][bi];
coeffs[0] = dc[pi][bi];
if chroma_cbp >= 2 {
dequant_4x4_ac(&mut coeffs, qpc);
}
inverse_dct_4x4(&mut coeffs);
for r in 0..4 {
let row = (cpy + br + r) * stride_uv + cpx + bc;
for c in 0..4 {
plane[row + c] = clip_u8(pred[(br + r) * 8 + bc + c] + coeffs[r * 4 + c]);
}
}
}
}
Ok(())
}
const LUMA_BLK_SCAN: [(usize, usize); 16] = [
(0, 0),
(0, 4),
(4, 0),
(4, 4),
(0, 8),
(0, 12),
(4, 8),
(4, 12),
(8, 0),
(8, 4),
(12, 0),
(12, 4),
(8, 8),
(8, 12),
(12, 8),
(12, 12),
];
const CBP_INTER: [u32; 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,
];
struct InterMv {
cells: Vec<u64>,
amvd_x: Vec<u8>,
amvd_y: Vec<u8>,
gw4: usize,
gh4: usize,
}
const MV_AVAIL: u64 = 1 << 40;
#[inline]
const fn mv_pack(dx: i16, dy: i16, refi: i8, pic_id: i8, avail: bool) -> u64 {
(dx as u16 as u64)
| ((dy as u16 as u64) << 16)
| ((refi as u8 as u64) << 32)
| ((avail as u64) << 40)
| ((pic_id as u8 as u64) << 41)
}
#[inline]
const fn mv_refi(cell: u64) -> i8 {
(cell >> 32) as u8 as i8
}
#[inline]
const fn mv_pic_id(cell: u64) -> i8 {
(cell >> 41) as u8 as i8
}
impl InterMv {
fn new(mb_w: usize, mb_h: usize) -> Self {
let (gw4, gh4) = (mb_w * 4, mb_h * 4);
Self {
cells: vec![mv_pack(0, 0, -1, -1, false); gw4 * gh4],
amvd_x: vec![0; gw4 * gh4],
amvd_y: vec![0; gw4 * gh4],
gw4,
gh4,
}
}
fn amvd_sum(&self, bx: i32, by: i32, comp: usize) -> u32 {
let g = if comp == 0 {
&self.amvd_x
} else {
&self.amvd_y
};
let at = |x: i32, y: i32| -> u32 {
if x < 0 || y < 0 || x >= self.gw4 as i32 || y >= self.gh4 as i32 {
0
} else {
g[y as usize * self.gw4 + x as usize] as u32
}
};
at(bx - 1, by) + at(bx, by - 1)
}
fn set_amvd(&mut self, bx4: usize, by4: usize, w4: usize, h4: usize, ax: u8, ay: u8) {
for r in 0..h4 {
for c in 0..w4 {
let i = (by4 + r) * self.gw4 + bx4 + c;
self.amvd_x[i] = ax;
self.amvd_y[i] = ay;
}
}
}
fn neighbor(&self, bx: i32, by: i32) -> (bool, i16, i16, i8) {
if bx < 0 || by < 0 || bx >= self.gw4 as i32 || by >= self.gh4 as i32 {
return (false, 0, 0, -1);
}
let cell = self.cells[by as usize * self.gw4 + bx as usize];
if cell & MV_AVAIL == 0 {
return (false, 0, 0, -1);
}
(
true,
cell as u16 as i16,
(cell >> 16) as u16 as i16,
mv_refi(cell),
)
}
#[allow(clippy::too_many_arguments)]
fn set(
&mut self,
bx4: usize,
by4: usize,
w4: usize,
h4: usize,
dx: i16,
dy: i16,
refi: i8,
pic_id: i8,
) {
let cell = mv_pack(dx, dy, refi, pic_id, true);
for r in 0..h4 {
let i = (by4 + r) * self.gw4 + bx4;
for c in &mut self.cells[i..i + w4] {
*c = cell;
}
}
}
}
#[derive(Clone, Copy, PartialEq)]
enum PartShape {
Normal,
Top16x8,
Bottom16x8,
Left8x16,
Right8x16,
}
#[inline]
fn median3(a: i16, b: i16, c: i16) -> i16 {
a.max(b).min(a.min(b).max(c))
}
fn mvp_predict(
field: &InterMv,
px4: i32,
py4: i32,
pw4: i32,
curr_ref: i8,
shape: PartShape,
) -> (i16, i16) {
let a = field.neighbor(px4 - 1, py4); let mut b = field.neighbor(px4, py4 - 1); let mut c = field.neighbor(px4 + pw4, py4 - 1); if !c.0 {
c = field.neighbor(px4 - 1, py4 - 1); }
if !b.0 && !c.0 && a.0 {
b = a;
c = a;
}
match shape {
PartShape::Top16x8 if b.3 == curr_ref => return (b.1, b.2),
PartShape::Bottom16x8 if a.3 == curr_ref => return (a.1, a.2),
PartShape::Left8x16 if a.3 == curr_ref => return (a.1, a.2),
PartShape::Right8x16 if c.3 == curr_ref => return (c.1, c.2),
_ => {}
}
let (ma, mb, mc) = (a.3 == curr_ref, b.3 == curr_ref, c.3 == curr_ref);
if ma && !mb && !mc {
(a.1, a.2)
} else if mb && !ma && !mc {
(b.1, b.2)
} else if mc && !ma && !mb {
(c.1, c.2)
} else {
(median3(a.1, b.1, c.1), median3(a.2, b.2, c.2))
}
}
fn add_residual_8x8(plane: &mut [u8], stride: usize, bx: usize, by: usize, res: &[i32; 64]) {
for qy in 0..2 {
for qx in 0..2 {
let mut q = [0i32; 16];
for r in 0..4 {
for c in 0..4 {
q[r * 4 + c] = res[(qy * 4 + r) * 8 + qx * 4 + c];
}
}
add_residual_4x4(plane, stride, bx + qx * 4, by + qy * 4, &q);
}
}
}
#[allow(unsafe_code)]
fn add_residual_4x4(plane: &mut [u8], stride: usize, bx: usize, by: usize, res: &[i32; 16]) {
let base = by * stride + bx;
if base + 3 * stride + 4 <= plane.len() {
#[cfg(target_arch = "aarch64")]
{
unsafe {
add_residual_4x4_neon(plane, base, stride, res);
}
return;
}
#[cfg(target_arch = "x86_64")]
{
if yscv_cpu::host_cpu().features.avx2 {
unsafe {
add_residual_4x4_avx2(plane, base, stride, res);
}
return;
}
if yscv_cpu::host_cpu().features.sse2 {
unsafe {
add_residual_4x4_sse2(plane, base, stride, res);
}
return;
}
}
}
for r in 0..4 {
let row = (by + r) * stride + bx;
for c in 0..4 {
plane[row + c] = clip_u8(plane[row + c] as i32 + res[r * 4 + c]);
}
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
unsafe fn add_residual_4x4_neon(plane: &mut [u8], base: usize, stride: usize, res: &[i32; 16]) {
use std::arch::aarch64::*;
let p = plane.as_mut_ptr().add(base);
let rows = [
(p as *const u32).read_unaligned(),
(p.add(stride) as *const u32).read_unaligned(),
(p.add(2 * stride) as *const u32).read_unaligned(),
(p.add(3 * stride) as *const u32).read_unaligned(),
];
let pix = vreinterpretq_u8_u32(vld1q_u32(rows.as_ptr()));
let lo = vmovl_u8(vget_low_u8(pix));
let hi = vmovl_u8(vget_high_u8(pix));
let s0 = vaddq_s32(
vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(lo))),
vld1q_s32(res.as_ptr()),
);
let s1 = vaddq_s32(
vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(lo))),
vld1q_s32(res.as_ptr().add(4)),
);
let s2 = vaddq_s32(
vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(hi))),
vld1q_s32(res.as_ptr().add(8)),
);
let s3 = vaddq_s32(
vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(hi))),
vld1q_s32(res.as_ptr().add(12)),
);
let n01 = vcombine_u16(vqmovun_s32(s0), vqmovun_s32(s1));
let n23 = vcombine_u16(vqmovun_s32(s2), vqmovun_s32(s3));
let out = vreinterpretq_u32_u8(vcombine_u8(vqmovn_u16(n01), vqmovn_u16(n23)));
let mut back = [0u32; 4];
vst1q_u32(back.as_mut_ptr(), out);
(p as *mut u32).write_unaligned(back[0]);
(p.add(stride) as *mut u32).write_unaligned(back[1]);
(p.add(2 * stride) as *mut u32).write_unaligned(back[2]);
(p.add(3 * stride) as *mut u32).write_unaligned(back[3]);
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse2")]
#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
unsafe fn add_residual_4x4_sse2(plane: &mut [u8], base: usize, stride: usize, res: &[i32; 16]) {
use std::arch::x86_64::*;
let p = plane.as_mut_ptr().add(base);
let pix = _mm_set_epi32(
(p.add(3 * stride) as *const i32).read_unaligned(),
(p.add(2 * stride) as *const i32).read_unaligned(),
(p.add(stride) as *const i32).read_unaligned(),
(p as *const i32).read_unaligned(),
);
let zero = _mm_setzero_si128();
let lo = _mm_unpacklo_epi8(pix, zero);
let hi = _mm_unpackhi_epi8(pix, zero);
let r = |i: usize| unsafe { _mm_loadu_si128(res.as_ptr().add(i) as *const __m128i) };
let s0 = _mm_add_epi32(_mm_unpacklo_epi16(lo, zero), r(0));
let s1 = _mm_add_epi32(_mm_unpackhi_epi16(lo, zero), r(4));
let s2 = _mm_add_epi32(_mm_unpacklo_epi16(hi, zero), r(8));
let s3 = _mm_add_epi32(_mm_unpackhi_epi16(hi, zero), r(12));
let out = _mm_packus_epi16(_mm_packs_epi32(s0, s1), _mm_packs_epi32(s2, s3));
(p as *mut i32).write_unaligned(_mm_cvtsi128_si32(out));
(p.add(stride) as *mut i32).write_unaligned(_mm_cvtsi128_si32(_mm_srli_si128(out, 4)));
(p.add(2 * stride) as *mut i32).write_unaligned(_mm_cvtsi128_si32(_mm_srli_si128(out, 8)));
(p.add(3 * stride) as *mut i32).write_unaligned(_mm_cvtsi128_si32(_mm_srli_si128(out, 12)));
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
unsafe fn add_residual_4x4_avx2(plane: &mut [u8], base: usize, stride: usize, res: &[i32; 16]) {
use std::arch::x86_64::*;
let p = plane.as_mut_ptr().add(base);
let pix = _mm_set_epi32(
(p.add(3 * stride) as *const i32).read_unaligned(),
(p.add(2 * stride) as *const i32).read_unaligned(),
(p.add(stride) as *const i32).read_unaligned(),
(p as *const i32).read_unaligned(),
);
let lo = _mm256_cvtepu8_epi32(pix);
let hi = _mm256_cvtepu8_epi32(_mm_srli_si128(pix, 8));
let s01 = _mm256_add_epi32(lo, _mm256_loadu_si256(res.as_ptr() as *const __m256i));
let s23 = _mm256_add_epi32(
hi,
_mm256_loadu_si256(res.as_ptr().add(8) as *const __m256i),
);
let n = _mm256_permute4x64_epi64(_mm256_packs_epi32(s01, s23), 0b11_01_10_00);
let out = _mm256_castsi256_si128(_mm256_permute4x64_epi64(
_mm256_packus_epi16(n, n),
0b00_00_10_00,
));
(p as *mut i32).write_unaligned(_mm_cvtsi128_si32(out));
(p.add(stride) as *mut i32).write_unaligned(_mm_cvtsi128_si32(_mm_srli_si128(out, 4)));
(p.add(2 * stride) as *mut i32).write_unaligned(_mm_cvtsi128_si32(_mm_srli_si128(out, 8)));
(p.add(3 * stride) as *mut i32).write_unaligned(_mm_cvtsi128_si32(_mm_srli_si128(out, 12)));
}
fn read_ref_idx(reader: &mut super::cavlc::BitReader<'_>, num_ref: usize) -> i8 {
if num_ref <= 1 {
0
} else if num_ref == 2 {
(1 - reader.read_bits(1).unwrap_or(0)) as i8
} else {
reader.read_ue().unwrap_or(0) as i8
}
}
#[derive(Clone, Copy)]
struct PlaneGeo {
w: usize,
h: usize,
stride_y: usize,
origin_y: usize,
cw: usize,
ch: usize,
stride_c: usize,
origin_c: usize,
}
#[allow(clippy::too_many_arguments)]
fn mc_partition(
rp: &RefPic,
geo: &PlaneGeo,
mvx: i32,
mvy: i32,
dst_x: usize,
dst_y: usize,
pw: usize,
ph: usize,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
) {
super::h264_motion::mc_luma(
&rp.y,
geo.origin_y,
geo.stride_y,
geo.w,
geo.h,
mvx,
mvy,
dst_x,
dst_y,
pw,
ph,
y_plane,
geo.stride_y,
);
let (cdx, cdy, cpw, cph) = (dst_x / 2, dst_y / 2, pw / 2, ph / 2);
for (ref_c, out_c) in [(&rp.u, &mut *u_plane), (&rp.v, &mut *v_plane)] {
super::h264_motion::mc_chroma(
ref_c,
geo.origin_c,
geo.stride_c,
geo.cw,
geo.ch,
mvx,
mvy,
cdx,
cdy,
cpw,
cph,
out_c,
geo.stride_c,
);
}
}
#[allow(clippy::too_many_arguments)]
fn apply_partition_weight(
wt: &super::h264_params::WeightTable,
refi: i8,
geo: &PlaneGeo,
dst_x: usize,
dst_y: usize,
pw: usize,
ph: usize,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
) {
apply_partition_weight_list(
wt, 0, refi, geo, dst_x, dst_y, pw, ph, y_plane, u_plane, v_plane,
);
}
#[allow(clippy::too_many_arguments)]
fn apply_partition_weight_list(
wt: &super::h264_params::WeightTable,
list: usize,
refi: i8,
geo: &PlaneGeo,
dst_x: usize,
dst_y: usize,
pw: usize,
ph: usize,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
) {
let ri = refi.max(0) as usize;
let (luma, chroma) = if list == 0 {
(&wt.luma_l0, &wt.chroma_l0)
} else {
(&wt.luma_l1, &wt.chroma_l1)
};
let luma_default = 1i32 << wt.luma_log2_denom;
let chroma_default = 1i32 << wt.chroma_log2_denom;
if let Some(lw) = luma.get(ri)
&& (lw.weight != luma_default || lw.offset != 0)
{
super::h264_motion::apply_weighted_pred(
y_plane,
geo.stride_y,
dst_x,
dst_y,
pw,
ph,
lw.weight,
lw.offset,
wt.luma_log2_denom,
);
}
if let Some(cw) = chroma.get(ri) {
for (plane, entry) in [(&mut *u_plane, &cw[0]), (&mut *v_plane, &cw[1])] {
if entry.weight != chroma_default || entry.offset != 0 {
super::h264_motion::apply_weighted_pred(
plane,
geo.stride_c,
dst_x / 2,
dst_y / 2,
pw / 2,
ph / 2,
entry.weight,
entry.offset,
wt.chroma_log2_denom,
);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn decode_inter_mb_cavlc(
reader: &mut super::cavlc::BitReader<'_>,
ctx: &mut MbCtx,
field: &mut InterMv,
mb_x: usize,
mb_y: usize,
p_mb_type: u32,
num_ref: usize,
refs: &[&RefPic],
ref_pic_id: &[i8],
geo: &PlaneGeo,
wt: Option<&super::h264_params::WeightTable>,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
) -> Result<(), VideoError> {
let px = mb_x * 16;
let py = mb_y * 16;
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let do_part = |field: &mut InterMv,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
ox: usize,
oy: usize,
pw: usize,
ph: usize,
refi: i8,
mvd: (i16, i16),
shape: PartShape| {
let px4 = (bx0 + ox / 4) as i32;
let py4 = (by0 + oy / 4) as i32;
let (pdx, pdy) = mvp_predict(field, px4, py4, (pw / 4) as i32, refi, shape);
let mvx = pdx + mvd.0;
let mvy = pdy + mvd.1;
let rp = refs[(refi.max(0) as usize).min(refs.len() - 1)];
mc_partition(
rp,
geo,
mvx as i32,
mvy as i32,
px + ox,
py + oy,
pw,
ph,
y,
u,
v,
);
if let Some(wt) = wt {
apply_partition_weight(wt, refi, geo, px + ox, py + oy, pw, ph, y, u, v);
}
let pic_id = ref_pic_id.get(refi.max(0) as usize).copied().unwrap_or(-1);
field.set(
px4 as usize,
py4 as usize,
pw / 4,
ph / 4,
mvx,
mvy,
refi,
pic_id,
);
};
match p_mb_type {
0 => {
let refi = read_ref_idx(reader, num_ref);
let mvd = read_mvd(reader)?;
do_part(
field,
y_plane,
u_plane,
v_plane,
0,
0,
16,
16,
refi,
mvd,
PartShape::Normal,
);
}
1 | 2 => {
let parts: [(usize, usize, usize, usize, PartShape); 2] = if p_mb_type == 1 {
[
(0, 0, 16, 8, PartShape::Top16x8),
(0, 8, 16, 8, PartShape::Bottom16x8),
]
} else {
[
(0, 0, 8, 16, PartShape::Left8x16),
(8, 0, 8, 16, PartShape::Right8x16),
]
};
let refs = [read_ref_idx(reader, num_ref), read_ref_idx(reader, num_ref)];
for (i, &(ox, oy, pw, ph, shape)) in parts.iter().enumerate() {
let mvd = read_mvd(reader)?;
do_part(
field, y_plane, u_plane, v_plane, ox, oy, pw, ph, refs[i], mvd, shape,
);
}
}
_ => {
let mut sub_types = [0u32; 4];
for st in sub_types.iter_mut() {
*st = reader.read_ue().ok_or_else(bitstream_err)?;
}
let refs = if p_mb_type == 4 {
[0i8; 4]
} else {
let mut r = [0i8; 4];
for ri in r.iter_mut() {
*ri = read_ref_idx(reader, num_ref);
}
r
};
for (sub, &st) in sub_types.iter().enumerate() {
let (sox, soy) = ((sub % 2) * 8, (sub / 2) * 8);
let subparts: &[(usize, usize, usize, usize)] = match st {
1 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
2 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
3 => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)],
_ => &[(0, 0, 8, 8)],
};
for &(ppx, ppy, pw, ph) in subparts {
let mvd = read_mvd(reader)?;
do_part(
field,
y_plane,
u_plane,
v_plane,
sox + ppx,
soy + ppy,
pw,
ph,
refs[sub],
mvd,
PartShape::Normal,
);
}
}
}
}
finish_inter_mb_cavlc(reader, ctx, mb_x, mb_y, geo, y_plane, u_plane, v_plane)
}
#[allow(clippy::too_many_arguments)]
fn finish_inter_mb_cavlc(
reader: &mut super::cavlc::BitReader<'_>,
ctx: &mut MbCtx,
mb_x: usize,
mb_y: usize,
geo: &PlaneGeo,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
) -> Result<(), VideoError> {
let px = mb_x * 16;
let py = mb_y * 16;
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
for r in 0..4 {
for c in 0..4 {
ctx.modes4x4[(by0 + r) * ctx.grid_w4 + bx0 + c] = NOT_I4X4;
}
}
let cbp_code = reader.read_ue().ok_or_else(bitstream_err)?;
let cbp = *CBP_INTER.get(cbp_code as usize).unwrap_or(&0);
let cbp_luma = cbp & 0xF;
let cbp_chroma = (cbp >> 4) & 3;
let qp = if cbp > 0 {
let qp_delta = reader.read_se().ok_or_else(bitstream_err)?;
(ctx.qp_prev + qp_delta).rem_euclid(52)
} else {
ctx.qp_prev
};
ctx.qp_prev = qp;
for (blk_idx, &(br, bc)) in LUMA_BLK_SCAN.iter().enumerate() {
let gx = bx0 + bc / 4;
let gy = by0 + br / 4;
let group = blk_idx / 4;
let mut nnz = 0usize;
if (cbp_luma & (1 << group)) != 0 {
let (bl, bt) = (
ctx.block_avail(gx as i32 - 1, gy as i32, 2),
ctx.block_avail(gx as i32, gy as i32 - 1, 2),
);
let nc = nc_pred(&ctx.nnz_luma, ctx.grid_w4, gx, gy, bl, bt);
if let Some((mut coeffs, tc)) = read_residual(reader, nc, false, 16) {
nnz = tc;
dequant_4x4(&mut coeffs, qp);
inverse_dct_4x4(&mut coeffs);
add_residual_4x4(y_plane, geo.stride_y, px + bc, py + br, &coeffs);
}
}
ctx.nnz_luma[gy * ctx.grid_w4 + gx] = nnz as u8;
ctx.nnz_decoded[gy * ctx.grid_w4 + gx] = true;
}
add_inter_chroma_residual(
reader,
ctx,
mb_x,
mb_y,
cbp_chroma,
qp,
u_plane,
v_plane,
geo.stride_c,
);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn add_inter_chroma_residual(
reader: &mut super::cavlc::BitReader<'_>,
ctx: &mut MbCtx,
mb_x: usize,
mb_y: usize,
cbp_chroma: u32,
qp_y: i32,
u_plane: &mut [u8],
v_plane: &mut [u8],
stride_uv: usize,
) {
let cpx = mb_x * 8;
let cpy = mb_y * 8;
let cbx0 = mb_x * 2;
let cby0 = mb_y * 2;
let qpc = chroma_qp_from_luma_qp(qp_y, ctx.chroma_qp_index_offset);
const CBLK: [(usize, usize); 4] = [(0, 0), (0, 4), (4, 0), (4, 4)];
let mut dc = [[0i32; 4]; 2];
if cbp_chroma >= 1 {
for d in dc.iter_mut() {
if let Some(c) = read_chroma_dc(reader) {
*d = chroma_dc_transform(&c, qpc);
}
}
}
let mut ac = [[[0i32; 16]; 4]; 2];
if cbp_chroma >= 2 {
for pi in 0..2 {
for (bi, &(br, bc)) in CBLK.iter().enumerate() {
let gx = cbx0 + bc / 4;
let gy = cby0 + br / 4;
let (bl, bt) = (
ctx.block_avail(gx as i32 - 1, gy as i32, 1),
ctx.block_avail(gx as i32, gy as i32 - 1, 1),
);
let nc = {
let grid = if pi == 0 { &ctx.nnz_cb } else { &ctx.nnz_cr };
nc_pred(grid, ctx.grid_w2, gx, gy, bl, bt)
};
let tc = if let Some(result) = super::cavlc::decode_cavlc_block_max(reader, nc, 15)
{
let mut tmp = [0i32; 16];
super::cavlc::expand_cavlc_to_coefficients_into(&result, &mut tmp[..15]);
let mut scan = [0i32; 16];
scan[1..16].copy_from_slice(&tmp[..15]);
unscan_4x4(&scan, &mut ac[pi][bi]);
result.total_coeffs
} else {
0
};
let grid = if pi == 0 {
&mut ctx.nnz_cb
} else {
&mut ctx.nnz_cr
};
grid[gy * ctx.grid_w2 + gx] = tc as u8;
}
}
} else {
for pi in 0..2 {
for &(br, bc) in CBLK.iter() {
let idx = (cby0 + br / 4) * ctx.grid_w2 + cbx0 + bc / 4;
if pi == 0 {
ctx.nnz_cb[idx] = 0;
} else {
ctx.nnz_cr[idx] = 0;
}
}
}
}
if cbp_chroma == 0 {
return;
}
for (pi, plane) in [&mut *u_plane, &mut *v_plane].into_iter().enumerate() {
for (bi, &(br, bc)) in CBLK.iter().enumerate() {
let mut coeffs = ac[pi][bi];
coeffs[0] = dc[pi][bi];
if cbp_chroma >= 2 {
dequant_4x4_ac(&mut coeffs, qpc);
}
inverse_dct_4x4(&mut coeffs);
add_residual_4x4(plane, stride_uv, cpx + bc, cpy + br, &coeffs);
}
}
}
const CBP_LUMA_DC: u32 = 0x100;
fn cabac_neighbor_cbp(ctx: &MbCtx, mb_x: i32, mb_y: i32, cur_intra: bool) -> u32 {
if mb_x < 0 || mb_y < 0 {
return if cur_intra { 0x7CF } else { 0x00F };
}
let mb = mb_y as usize * ctx.mb_w + mb_x as usize;
if mb < ctx.slice_first_mb {
return if cur_intra { 0x7CF } else { 0x00F };
}
ctx.mb_cbp[mb]
}
const fn cbf_ac_inc(
nnz: &[u8],
grid_w: usize,
gx: i32,
gy: i32,
left_avail: bool,
top_avail: bool,
cur_intra: bool,
) -> usize {
let dflt: u8 = if cur_intra { 1 } else { 0 };
let nza = if left_avail {
nnz[gy as usize * grid_w + (gx - 1) as usize]
} else {
dflt
};
let nzb = if top_avail {
nnz[(gy - 1) as usize * grid_w + gx as usize]
} else {
dflt
};
(nza > 0) as usize + 2 * (nzb > 0) as usize
}
#[allow(clippy::too_many_arguments)]
fn decode_macroblock_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
mb_x: usize,
mb_y: usize,
p_slice: bool,
is_b: bool,
transform_8x8_mode: bool,
qp_delta_nonzero: &mut bool,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
stride_y: usize,
stride_uv: usize,
) -> Result<(), VideoError> {
let px = mb_x * 16;
let py = mb_y * 16;
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let cbx0 = mb_x * 2;
let cby0 = mb_y * 2;
let mb_idx = mb_y * ctx.mb_w + mb_x;
let left_mb = ctx.sample_mb_avail(px as i32 - 1, py as i32, 4);
let top_mb = ctx.sample_mb_avail(px as i32, py as i32 - 1, 4);
let mb_type = if p_slice {
super::h264_cabac::decode_intra_mb_type(cabac, st, if is_b { 32 } else { 17 }, false, 0)
} else {
let inc = (left_mb && ctx.mb_i16_pcm[mb_idx - 1]) as usize
+ (top_mb && ctx.mb_i16_pcm[mb_idx - ctx.mb_w]) as usize;
super::h264_cabac::decode_intra_mb_type(cabac, st, 3, true, inc)
};
ctx.mb_intra[mb_idx] = true;
if mb_type == 25 {
cabac.align_to_byte();
for row in 0..16 {
for col in 0..16 {
y_plane[(py + row) * stride_y + px + col] = cabac.read_pcm_byte();
}
}
for plane in [&mut *u_plane, &mut *v_plane] {
for row in 0..8 {
for col in 0..8 {
plane[(mb_y * 8 + row) * stride_uv + mb_x * 8 + col] = cabac.read_pcm_byte();
}
}
}
cabac.reinit_after_pcm();
for r in 0..4 {
for c in 0..4 {
ctx.nnz_luma[(by0 + r) * ctx.grid_w4 + bx0 + c] = 16;
ctx.nnz_decoded[(by0 + r) * ctx.grid_w4 + bx0 + c] = true;
ctx.modes4x4[(by0 + r) * ctx.grid_w4 + bx0 + c] = NOT_I4X4;
}
}
for r in 0..2 {
for c in 0..2 {
ctx.nnz_cb[(cby0 + r) * ctx.grid_w2 + cbx0 + c] = 16;
ctx.nnz_cr[(cby0 + r) * ctx.grid_w2 + cbx0 + c] = 16;
}
}
ctx.mb_cbp[mb_idx] = 0x1EF | (0x3 << 6);
ctx.mb_chroma_pred[mb_idx] = 0;
ctx.mb_i16_pcm[mb_idx] = true;
*qp_delta_nonzero = false;
return Ok(());
}
let is_i16x16 = (1..=24).contains(&mb_type);
ctx.mb_i16_pcm[mb_idx] = is_i16x16;
const BLK: [(usize, usize); 16] = LUMA_BLK_SCAN;
let gw4 = ctx.grid_w4;
let mut modes = [2u8; 16];
let mut modes8 = [2u8; 4];
let i16_mode;
let tr8x8 = if !is_i16x16 && transform_8x8_mode {
let inc = (left_mb && ctx.mb_tr8x8[mb_idx - 1]) as usize
+ (top_mb && ctx.mb_tr8x8[mb_idx - ctx.mb_w]) as usize;
super::h264_cabac::decode_transform_size_8x8_flag(cabac, st, inc)
} else {
false
};
ctx.mb_tr8x8[mb_idx] = tr8x8;
if is_i16x16 {
i16_mode = ((mb_type - 1) % 4) as u8;
for r in 0..4 {
for c in 0..4 {
ctx.modes4x4[(by0 + r) * gw4 + bx0 + c] = NOT_I4X4;
}
}
} else if tr8x8 {
i16_mode = 0;
for blk8 in 0..4 {
let gx = bx0 + (blk8 % 2) * 2;
let gy = by0 + (blk8 / 2) * 2;
let a_avail = ctx.block_avail(gx as i32 - 1, gy as i32, 2);
let b_avail = ctx.block_avail(gx as i32, gy as i32 - 1, 2);
let pred_mode = if !a_avail || !b_avail {
2
} else {
let raw_a = ctx.modes4x4[gy * gw4 + gx - 1];
let raw_b = ctx.modes4x4[(gy - 1) * gw4 + gx];
let mode_a = if raw_a == NOT_I4X4 { 2 } else { raw_a };
let mode_b = if raw_b == NOT_I4X4 { 2 } else { raw_b };
mode_a.min(mode_b)
};
let mode = super::h264_cabac::decode_intra4x4_pred_mode(cabac, st, pred_mode);
modes8[blk8] = mode;
for r in 0..2 {
for c in 0..2 {
ctx.modes4x4[(gy + r) * gw4 + gx + c] = mode;
}
}
}
} else {
i16_mode = 0;
for (blk_idx, &(br, bc)) in BLK.iter().enumerate() {
let gx = bx0 + bc / 4;
let gy = by0 + br / 4;
let a_avail = ctx.block_avail(gx as i32 - 1, gy as i32, 2);
let b_avail = ctx.block_avail(gx as i32, gy as i32 - 1, 2);
let pred_mode = if !a_avail || !b_avail {
2
} else {
let raw_a = ctx.modes4x4[gy * gw4 + gx - 1];
let raw_b = ctx.modes4x4[(gy - 1) * gw4 + gx];
let mode_a = if raw_a == NOT_I4X4 { 2 } else { raw_a };
let mode_b = if raw_b == NOT_I4X4 { 2 } else { raw_b };
mode_a.min(mode_b)
};
let mode = super::h264_cabac::decode_intra4x4_pred_mode(cabac, st, pred_mode);
modes[blk_idx] = mode;
ctx.modes4x4[gy * gw4 + gx] = mode;
}
}
let chroma_inc = (left_mb && ctx.mb_chroma_pred[mb_idx - 1] != 0) as usize
+ (top_mb && ctx.mb_chroma_pred[mb_idx - ctx.mb_w] != 0) as usize;
let chroma_mode = super::h264_cabac::decode_chroma_pred_mode(cabac, st, chroma_inc);
ctx.mb_chroma_pred[mb_idx] = chroma_mode;
let (cbp_luma, chroma_cbp) = if is_i16x16 {
let cbp_luma = if (mb_type - 1) / 12 >= 1 { 15u32 } else { 0 };
let cbp_chroma = ((mb_type - 1) / 4) % 3;
(cbp_luma, cbp_chroma)
} else {
let cbp_a = cabac_neighbor_cbp(ctx, mb_x as i32 - 1, mb_y as i32, true);
let cbp_b = cabac_neighbor_cbp(ctx, mb_x as i32, mb_y as i32 - 1, true);
let cbp_luma = super::h264_cabac::decode_cbp_luma(cabac, st, cbp_a, cbp_b);
let cbp_chroma =
super::h264_cabac::decode_cbp_chroma(cabac, st, (cbp_a >> 4) & 3, (cbp_b >> 4) & 3);
(cbp_luma, cbp_chroma)
};
let cbp = cbp_luma | (chroma_cbp << 4);
let qp = if cbp > 0 || is_i16x16 {
let d = super::h264_cabac::decode_mb_qp_delta(cabac, st, *qp_delta_nonzero);
*qp_delta_nonzero = d != 0;
(ctx.qp_prev + d).rem_euclid(52)
} else {
*qp_delta_nonzero = false;
ctx.qp_prev
};
ctx.qp_prev = qp;
let mut cbp_word = cbp_luma | (chroma_cbp << 4);
let mut luma_dc = [0i32; 16];
if is_i16x16 {
let left_val = cabac_neighbor_cbp(ctx, mb_x as i32 - 1, mb_y as i32, true);
let top_val = cabac_neighbor_cbp(ctx, mb_x as i32, mb_y as i32 - 1, true);
let inc =
((left_val & CBP_LUMA_DC) != 0) as usize + 2 * ((top_val & CBP_LUMA_DC) != 0) as usize;
if super::h264_cabac::decode_cbf(cabac, st, 0, inc) {
let mut scan = [0i32; 16];
super::h264_cabac::decode_residual_levels(cabac, st, 0, 16, &mut scan);
let mut raster = [0i32; 16];
unscan_4x4(&scan, &mut raster);
hadamard4x4_dc(&raster, &mut luma_dc, qp);
cbp_word |= CBP_LUMA_DC;
}
}
let mut pred16 = [0i32; 256];
if is_i16x16 {
let top = ctx.sample_mb_avail(px as i32, py as i32 - 1, 4).then(|| {
let mut a = [0i32; 16];
for (x, v) in a.iter_mut().enumerate() {
*v = y_plane[(py - 1) * stride_y + px + x] as i32;
}
a
});
let left = ctx.sample_mb_avail(px as i32 - 1, py as i32, 4).then(|| {
let mut a = [0i32; 16];
for (y, v) in a.iter_mut().enumerate() {
*v = y_plane[(py + y) * stride_y + px - 1] as i32;
}
a
});
let tl = if ctx.sample_mb_avail(px as i32 - 1, py as i32 - 1, 4) {
y_plane[(py - 1) * stride_y + px - 1] as i32
} else {
128
};
intra_plane_pred(
i16_mode,
16,
top.as_ref().map(|a| a.as_slice()),
left.as_ref().map(|a| a.as_slice()),
tl,
&mut pred16,
);
}
if tr8x8 {
reconstruct_intra8x8_cabac(
cabac, st, ctx, px, py, bx0, by0, &modes8, cbp_luma, qp, y_plane, stride_y,
);
}
for (blk_idx, &(br, bc)) in BLK.iter().enumerate().take(if tr8x8 { 0 } else { 16 }) {
let block_x = px + bc;
let block_y = py + br;
let gx = bx0 + bc / 4;
let gy = by0 + br / 4;
let group = blk_idx / 4;
let mut pred = [0i32; 16];
if is_i16x16 {
for r in 0..4 {
for c in 0..4 {
pred[r * 4 + c] = pred16[(br + r) * 16 + bc + c];
}
}
} else {
let top = ctx.sample_mb_avail(block_x as i32, block_y as i32 - 1, 4);
let left = ctx.sample_mb_avail(block_x as i32 - 1, block_y as i32, 4);
let mut t = [128i32; 8];
let mut l = [128i32; 4];
let mut tl = 128i32;
if top {
for (x, tv) in t.iter_mut().take(4).enumerate() {
*tv = y_plane[(block_y - 1) * stride_y + block_x + x] as i32;
}
let tr_ok = block_x + 4 < ctx.mb_w * 16
&& top_right_available(bx0, by0, bc, br, ctx.grid_w4, &ctx.nnz_decoded);
for x in 4..8 {
t[x] = if tr_ok {
y_plane[(block_y - 1) * stride_y + block_x + x] as i32
} else {
t[3]
};
}
}
if left {
for (y, lv) in l.iter_mut().enumerate() {
*lv = y_plane[(block_y + y) * stride_y + block_x - 1] as i32;
}
}
if top && left && ctx.sample_mb_avail(block_x as i32 - 1, block_y as i32 - 1, 4) {
tl = y_plane[(block_y - 1) * stride_y + block_x - 1] as i32;
}
pred = intra4x4_pred(modes[blk_idx], &t, &l, tl, top, left);
}
let mut nnz = 0usize;
let coded = (cbp_luma & (1 << group)) != 0;
if coded {
let cat = if is_i16x16 { 1 } else { 2 };
let inc = cbf_ac_inc(
&ctx.nnz_luma,
ctx.grid_w4,
gx as i32,
gy as i32,
ctx.block_avail(gx as i32 - 1, gy as i32, 2),
ctx.block_avail(gx as i32, gy as i32 - 1, 2),
true,
);
if super::h264_cabac::decode_cbf(cabac, st, cat, inc) {
let max_coeff = if is_i16x16 { 15 } else { 16 };
let mut tmp = [0i32; 16];
nnz =
super::h264_cabac::decode_residual_levels(cabac, st, cat, max_coeff, &mut tmp);
let mut scan = [0i32; 16];
if is_i16x16 {
scan[1..16].copy_from_slice(&tmp[..15]);
} else {
scan = tmp;
}
let mut raster = [0i32; 16];
unscan_4x4(&scan, &mut raster);
if is_i16x16 {
raster[0] = luma_dc[(br / 4) * 4 + bc / 4];
dequant_4x4_ac(&mut raster, qp);
} else {
dequant_4x4(&mut raster, qp);
}
inverse_dct_4x4(&mut raster);
for r in 0..4 {
let row = (block_y + r) * stride_y + block_x;
for c in 0..4 {
y_plane[row + c] = clip_u8(pred[r * 4 + c] + raster[r * 4 + c]);
}
}
} else if is_i16x16 {
reconstruct_i16_dc(
y_plane,
stride_y,
block_x,
block_y,
&pred,
luma_dc[(br / 4) * 4 + bc / 4],
);
} else {
write_pred_luma(y_plane, stride_y, block_x, block_y, &pred);
}
} else if is_i16x16 {
reconstruct_i16_dc(
y_plane,
stride_y,
block_x,
block_y,
&pred,
luma_dc[(br / 4) * 4 + bc / 4],
);
} else {
write_pred_luma(y_plane, stride_y, block_x, block_y, &pred);
}
ctx.nnz_luma[gy * ctx.grid_w4 + gx] = nnz as u8;
ctx.nnz_decoded[gy * ctx.grid_w4 + gx] = true;
}
decode_chroma_cabac(
cabac,
st,
ctx,
mb_x,
mb_y,
chroma_mode,
chroma_cbp,
qp,
&mut cbp_word,
u_plane,
v_plane,
stride_uv,
);
ctx.mb_cbp[mb_idx] = cbp_word;
Ok(())
}
fn reconstruct_i16_dc(
y: &mut [u8],
stride: usize,
bx: usize,
by: usize,
pred: &[i32; 16],
dc: i32,
) {
let mut coeffs = [0i32; 16];
coeffs[0] = dc;
inverse_dct_4x4(&mut coeffs);
for r in 0..4 {
let row = (by + r) * stride + bx;
for c in 0..4 {
y[row + c] = clip_u8(pred[r * 4 + c] + coeffs[r * 4 + c]);
}
}
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_intra8x8_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
px: usize,
py: usize,
bx0: usize,
by0: usize,
modes8: &[u8; 4],
cbp_luma: u32,
qp: i32,
y_plane: &mut [u8],
stride_y: usize,
) {
let gw4 = ctx.grid_w4;
let frame_w = ctx.mb_w * 16;
for blk8 in 0..4 {
let (sx, sy) = ((blk8 % 2) * 8, (blk8 / 2) * 8);
let block_x = px + sx;
let block_y = py + sy;
let gx = bx0 + (blk8 % 2) * 2;
let gy = by0 + (blk8 / 2) * 2;
let top = ctx.sample_mb_avail(block_x as i32, block_y as i32 - 1, 4);
let left = ctx.sample_mb_avail(block_x as i32 - 1, block_y as i32, 4);
let tl_avail = ctx.sample_mb_avail(block_x as i32 - 1, block_y as i32 - 1, 4);
let mut t = [0i32; 16];
let mut l = [0i32; 8];
let mut tl = 0i32;
if top {
let base = (block_y - 1) * stride_y + block_x;
for (x, tv) in t.iter_mut().take(8).enumerate() {
*tv = y_plane[base + x] as i32;
}
let tr_ok = block_x + 8 < frame_w
&& gy > 0
&& gx + 2 < gw4
&& ctx.nnz_decoded[(gy - 1) * gw4 + gx + 2];
for x in 8..16 {
t[x] = if tr_ok {
y_plane[base + x] as i32
} else {
t[7]
};
}
}
if left {
for (yy, lv) in l.iter_mut().enumerate() {
*lv = y_plane[(block_y + yy) * stride_y + block_x - 1] as i32;
}
}
if tl_avail {
tl = y_plane[(block_y - 1) * stride_y + block_x - 1] as i32;
}
let pred = intra8x8_pred(modes8[blk8], &t, &l, tl, top, left, tl_avail);
let mut nnz8 = 0usize;
if (cbp_luma & (1 << blk8)) != 0 {
let mut scan = [0i32; 64];
nnz8 = super::h264_cabac::decode_residual_8x8(cabac, st, &mut scan);
let mut raster = [0i32; 64];
unscan_8x8(&scan, &mut raster);
dequant_8x8(&mut raster, qp);
inverse_dct_8x8(&mut raster);
for r in 0..8 {
let row = (block_y + r) * stride_y + block_x;
for c in 0..8 {
y_plane[row + c] = clip_u8(pred[r * 8 + c] + raster[r * 8 + c]);
}
}
} else {
for r in 0..8 {
let row = (block_y + r) * stride_y + block_x;
for c in 0..8 {
y_plane[row + c] = clip_u8(pred[r * 8 + c]);
}
}
}
for r in 0..2 {
for c in 0..2 {
ctx.nnz_luma[(gy + r) * gw4 + gx + c] = nnz8 as u8;
ctx.nnz_decoded[(gy + r) * gw4 + gx + c] = true;
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn decode_chroma_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
mb_x: usize,
mb_y: usize,
chroma_mode: u8,
chroma_cbp: u32,
qp_y: i32,
cbp_word: &mut u32,
u_plane: &mut [u8],
v_plane: &mut [u8],
stride_uv: usize,
) {
let cpx = mb_x * 8;
let cpy = mb_y * 8;
let cbx0 = mb_x * 2;
let cby0 = mb_y * 2;
let qpc = chroma_qp_from_luma_qp(qp_y, ctx.chroma_qp_index_offset);
let plane_mode = [2u8, 1, 0, 3][(chroma_mode as usize).min(3)];
const CBLK: [(usize, usize); 4] = [(0, 0), (0, 4), (4, 0), (4, 4)];
let mut dc = [[0i32; 4]; 2];
if chroma_cbp >= 1 {
for (pi, d) in dc.iter_mut().enumerate() {
let left_val = cabac_neighbor_cbp(ctx, mb_x as i32 - 1, mb_y as i32, true);
let top_val = cabac_neighbor_cbp(ctx, mb_x as i32, mb_y as i32 - 1, true);
let inc =
((left_val >> (6 + pi)) & 1) as usize + 2 * ((top_val >> (6 + pi)) & 1) as usize;
if super::h264_cabac::decode_cbf(cabac, st, 3, inc) {
let mut c = [0i32; 16];
super::h264_cabac::decode_residual_levels(cabac, st, 3, 4, &mut c);
*d = chroma_dc_transform(&[c[0], c[1], c[2], c[3]], qpc);
*cbp_word |= 0x40 << pi;
}
}
}
let mut ac = [[[0i32; 16]; 4]; 2];
if chroma_cbp >= 2 {
for pi in 0..2 {
for (bi, &(br, bc)) in CBLK.iter().enumerate() {
let gx = cbx0 + bc / 4;
let gy = cby0 + br / 4;
let nnz_grid = if pi == 0 { &ctx.nnz_cb } else { &ctx.nnz_cr };
let inc = cbf_ac_inc(
nnz_grid,
ctx.grid_w2,
gx as i32,
gy as i32,
ctx.block_avail(gx as i32 - 1, gy as i32, 1),
ctx.block_avail(gx as i32, gy as i32 - 1, 1),
true,
);
let mut tc = 0usize;
if super::h264_cabac::decode_cbf(cabac, st, 4, inc) {
let mut tmp = [0i32; 16];
tc = super::h264_cabac::decode_residual_levels(cabac, st, 4, 15, &mut tmp);
let mut scan = [0i32; 16];
scan[1..16].copy_from_slice(&tmp[..15]);
unscan_4x4(&scan, &mut ac[pi][bi]);
}
let grid = if pi == 0 {
&mut ctx.nnz_cb
} else {
&mut ctx.nnz_cr
};
grid[gy * ctx.grid_w2 + gx] = tc as u8;
}
}
} else {
for pi in 0..2 {
for &(br, bc) in CBLK.iter() {
let idx = (cby0 + br / 4) * ctx.grid_w2 + cbx0 + bc / 4;
if pi == 0 {
ctx.nnz_cb[idx] = 0;
} else {
ctx.nnz_cr[idx] = 0;
}
}
}
}
let top_ok = ctx.sample_mb_avail(cpx as i32, cpy as i32 - 1, 3);
let left_ok = ctx.sample_mb_avail(cpx as i32 - 1, cpy as i32, 3);
let tl_ok = ctx.sample_mb_avail(cpx as i32 - 1, cpy as i32 - 1, 3);
for pi in 0..2 {
let plane = if pi == 0 {
&mut *u_plane
} else {
&mut *v_plane
};
let top = top_ok.then(|| {
let mut a = [0i32; 8];
for (x, s) in a.iter_mut().enumerate() {
*s = plane[(cpy - 1) * stride_uv + cpx + x] as i32;
}
a
});
let left = left_ok.then(|| {
let mut a = [0i32; 8];
for (y, s) in a.iter_mut().enumerate() {
*s = plane[(cpy + y) * stride_uv + cpx - 1] as i32;
}
a
});
let tl = if tl_ok {
plane[(cpy - 1) * stride_uv + cpx - 1] as i32
} else {
128
};
let mut pred = [0i32; 64];
if plane_mode == 2 {
let s4 = |a: &[i32], off: usize| a[off] + a[off + 1] + a[off + 2] + a[off + 3];
let quad_dc = |qx: usize, qy: usize| -> i32 {
let prefer_top = qx == 1 && qy == 0;
let prefer_left = qx == 0 && qy == 1;
match (&top, &left) {
(Some(t), Some(l)) => {
if prefer_top {
(s4(t, 4) + 2) >> 2
} else if prefer_left {
(s4(l, 4) + 2) >> 2
} else {
(s4(t, qx * 4) + s4(l, qy * 4) + 4) >> 3
}
}
(Some(t), None) => (s4(t, qx * 4) + 2) >> 2,
(None, Some(l)) => (s4(l, qy * 4) + 2) >> 2,
(None, None) => 128,
}
};
for qy in 0..2 {
for qx in 0..2 {
let d = quad_dc(qx, qy);
for r in 0..4 {
for c in 0..4 {
pred[(qy * 4 + r) * 8 + qx * 4 + c] = d;
}
}
}
}
} else {
intra_plane_pred(
plane_mode,
8,
top.as_ref().map(|a| a.as_slice()),
left.as_ref().map(|a| a.as_slice()),
tl,
&mut pred,
);
}
for (bi, &(br, bc)) in CBLK.iter().enumerate() {
let mut coeffs = ac[pi][bi];
coeffs[0] = dc[pi][bi];
if chroma_cbp >= 2 {
dequant_4x4_ac(&mut coeffs, qpc);
}
inverse_dct_4x4(&mut coeffs);
for r in 0..4 {
let row = (cpy + br + r) * stride_uv + cpx + bc;
for c in 0..4 {
plane[row + c] = clip_u8(pred[(br + r) * 8 + bc + c] + coeffs[r * 4 + c]);
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn decode_inter_mb_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
field: &mut InterMv,
mb_x: usize,
mb_y: usize,
p_type: u32,
num_ref: usize,
refs: &[&RefPic],
ref_pic_id: &[i8],
geo: &PlaneGeo,
wt: Option<&super::h264_params::WeightTable>,
transform_8x8_mode: bool,
qp_delta_nonzero: &mut bool,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
) -> Result<(), VideoError> {
let px = mb_x * 16;
let py = mb_y * 16;
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let mut no_sub_8x8 = true;
let decode_ref = |cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
field: &InterMv,
px4: i32,
py4: i32|
-> i8 {
if num_ref <= 1 {
return 0;
}
let a = field.neighbor(px4 - 1, py4);
let b = field.neighbor(px4, py4 - 1);
let inc = (a.3 > 0) as usize + 2 * (b.3 > 0) as usize;
super::h264_cabac::decode_ref_idx(cabac, st, inc) as i8
};
#[allow(clippy::too_many_arguments)]
fn do_part(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
field: &mut InterMv,
refs: &[&RefPic],
ref_pic_id: &[i8],
geo: &PlaneGeo,
wt: Option<&super::h264_params::WeightTable>,
bx0: usize,
by0: usize,
px: usize,
py: usize,
ox: usize,
oy: usize,
pw: usize,
ph: usize,
refi: i8,
shape: PartShape,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) {
let px4 = (bx0 + ox / 4) as i32;
let py4 = (by0 + oy / 4) as i32;
let ax = field.amvd_sum(px4, py4, 0);
let ay = field.amvd_sum(px4, py4, 1);
let (mdx, sx) = super::h264_cabac::decode_mvd(cabac, st, 40, ax);
let (mdy, sy) = super::h264_cabac::decode_mvd(cabac, st, 47, ay);
let (pdx, pdy) = mvp_predict(field, px4, py4, (pw / 4) as i32, refi, shape);
let mvx = pdx + mdx as i16;
let mvy = pdy + mdy as i16;
let rp = refs[(refi.max(0) as usize).min(refs.len() - 1)];
mc_partition(
rp,
geo,
mvx as i32,
mvy as i32,
px + ox,
py + oy,
pw,
ph,
y,
u,
v,
);
if let Some(wt) = wt {
apply_partition_weight(wt, refi, geo, px + ox, py + oy, pw, ph, y, u, v);
}
let pic_id = ref_pic_id.get(refi.max(0) as usize).copied().unwrap_or(-1);
field.set(
px4 as usize,
py4 as usize,
pw / 4,
ph / 4,
mvx,
mvy,
refi,
pic_id,
);
field.set_amvd(px4 as usize, py4 as usize, pw / 4, ph / 4, sx, sy);
}
match p_type {
0 => {
let refi = decode_ref(cabac, st, field, bx0 as i32, by0 as i32);
do_part(
cabac,
st,
field,
refs,
ref_pic_id,
geo,
wt,
bx0,
by0,
px,
py,
0,
0,
16,
16,
refi,
PartShape::Normal,
y_plane,
u_plane,
v_plane,
);
}
1 | 2 => {
let parts: [(usize, usize, usize, usize, PartShape); 2] = if p_type == 1 {
[
(0, 0, 16, 8, PartShape::Top16x8),
(0, 8, 16, 8, PartShape::Bottom16x8),
]
} else {
[
(0, 0, 8, 16, PartShape::Left8x16),
(8, 0, 8, 16, PartShape::Right8x16),
]
};
let r0 = decode_ref(
cabac,
st,
field,
(bx0 + parts[0].0 / 4) as i32,
(by0 + parts[0].1 / 4) as i32,
);
let p0id = ref_pic_id.get(r0.max(0) as usize).copied().unwrap_or(-1);
field.set(
bx0 + parts[0].0 / 4,
by0 + parts[0].1 / 4,
parts[0].2 / 4,
parts[0].3 / 4,
0,
0,
r0,
p0id,
);
let r1 = decode_ref(
cabac,
st,
field,
(bx0 + parts[1].0 / 4) as i32,
(by0 + parts[1].1 / 4) as i32,
);
let refs_p = [r0, r1];
for (i, &(ox, oy, pw, ph, shape)) in parts.iter().enumerate() {
do_part(
cabac, st, field, refs, ref_pic_id, geo, wt, bx0, by0, px, py, ox, oy, pw, ph,
refs_p[i], shape, y_plane, u_plane, v_plane,
);
}
}
_ => {
let mut sub_types = [0u32; 4];
for stp in sub_types.iter_mut() {
*stp = super::h264_cabac::decode_sub_mb_type_p(cabac, st);
}
no_sub_8x8 = sub_types.iter().all(|&t| t == 0);
let mut sub_refs = [0i8; 4];
for (sub, sr) in sub_refs.iter_mut().enumerate() {
let (sox, soy) = ((sub % 2) * 8, (sub / 2) * 8);
*sr = decode_ref(
cabac,
st,
field,
(bx0 + sox / 4) as i32,
(by0 + soy / 4) as i32,
);
let srid = ref_pic_id.get((*sr).max(0) as usize).copied().unwrap_or(-1);
field.set(bx0 + sox / 4, by0 + soy / 4, 2, 2, 0, 0, *sr, srid);
}
for (sub, &sub_type) in sub_types.iter().enumerate() {
let (sox, soy) = ((sub % 2) * 8, (sub / 2) * 8);
let subparts: &[(usize, usize, usize, usize)] = match sub_type {
1 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
2 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
3 => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)],
_ => &[(0, 0, 8, 8)],
};
for &(ppx, ppy, pw, ph) in subparts {
do_part(
cabac,
st,
field,
refs,
ref_pic_id,
geo,
wt,
bx0,
by0,
px,
py,
sox + ppx,
soy + ppy,
pw,
ph,
sub_refs[sub],
PartShape::Normal,
y_plane,
u_plane,
v_plane,
);
}
}
}
}
finish_inter_mb_cabac(
cabac,
st,
ctx,
mb_x,
mb_y,
no_sub_8x8,
transform_8x8_mode,
qp_delta_nonzero,
geo,
y_plane,
u_plane,
v_plane,
)
}
#[allow(clippy::too_many_arguments)]
fn finish_inter_mb_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
mb_x: usize,
mb_y: usize,
no_sub_8x8: bool,
transform_8x8_mode: bool,
qp_delta_nonzero: &mut bool,
geo: &PlaneGeo,
y_plane: &mut [u8],
u_plane: &mut [u8],
v_plane: &mut [u8],
) -> Result<(), VideoError> {
let px = mb_x * 16;
let py = mb_y * 16;
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let mb_idx = mb_y * ctx.mb_w + mb_x;
for r in 0..4 {
for c in 0..4 {
ctx.modes4x4[(by0 + r) * ctx.grid_w4 + bx0 + c] = NOT_I4X4;
}
}
ctx.mb_intra[mb_idx] = false;
ctx.mb_i16_pcm[mb_idx] = false;
ctx.mb_chroma_pred[mb_idx] = 0;
let cbp_a = cabac_neighbor_cbp(ctx, mb_x as i32 - 1, mb_y as i32, false);
let cbp_b = cabac_neighbor_cbp(ctx, mb_x as i32, mb_y as i32 - 1, false);
let cbp_luma = super::h264_cabac::decode_cbp_luma(cabac, st, cbp_a, cbp_b);
let cbp_chroma =
super::h264_cabac::decode_cbp_chroma(cabac, st, (cbp_a >> 4) & 3, (cbp_b >> 4) & 3);
let cbp = cbp_luma | (cbp_chroma << 4);
let mut cbp_word = cbp;
let tr8x8 = cbp_luma > 0 && transform_8x8_mode && no_sub_8x8 && {
let left_mb = ctx.sample_mb_avail(px as i32 - 1, py as i32, 4);
let top_mb = ctx.sample_mb_avail(px as i32, py as i32 - 1, 4);
let inc = (left_mb && ctx.mb_tr8x8[mb_idx - 1]) as usize
+ (top_mb && ctx.mb_tr8x8[mb_idx - ctx.mb_w]) as usize;
super::h264_cabac::decode_transform_size_8x8_flag(cabac, st, inc)
};
ctx.mb_tr8x8[mb_idx] = tr8x8;
let qp = if cbp > 0 {
let d = super::h264_cabac::decode_mb_qp_delta(cabac, st, *qp_delta_nonzero);
*qp_delta_nonzero = d != 0;
(ctx.qp_prev + d).rem_euclid(52)
} else {
*qp_delta_nonzero = false;
ctx.qp_prev
};
ctx.qp_prev = qp;
if tr8x8 {
let gw4 = ctx.grid_w4;
for blk8 in 0..4 {
let (sx, sy) = ((blk8 % 2) * 8, (blk8 / 2) * 8);
let (gx, gy) = (bx0 + (blk8 % 2) * 2, by0 + (blk8 / 2) * 2);
let mut nnz8 = 0usize;
if (cbp_luma & (1 << blk8)) != 0 {
let mut scan = [0i32; 64];
nnz8 = super::h264_cabac::decode_residual_8x8(cabac, st, &mut scan);
let mut raster = [0i32; 64];
unscan_8x8(&scan, &mut raster);
dequant_8x8(&mut raster, qp);
inverse_dct_8x8(&mut raster);
add_residual_8x8(y_plane, geo.stride_y, px + sx, py + sy, &raster);
}
for r in 0..2 {
for c in 0..2 {
ctx.nnz_luma[(gy + r) * gw4 + gx + c] = nnz8 as u8;
ctx.nnz_decoded[(gy + r) * gw4 + gx + c] = true;
}
}
}
} else {
for (blk_idx, &(br, bc)) in LUMA_BLK_SCAN.iter().enumerate() {
let gx = bx0 + bc / 4;
let gy = by0 + br / 4;
let group = blk_idx / 4;
let mut nnz = 0usize;
if (cbp_luma & (1 << group)) != 0 {
let inc = cbf_ac_inc(
&ctx.nnz_luma,
ctx.grid_w4,
gx as i32,
gy as i32,
ctx.block_avail(gx as i32 - 1, gy as i32, 2),
ctx.block_avail(gx as i32, gy as i32 - 1, 2),
false,
);
if super::h264_cabac::decode_cbf(cabac, st, 2, inc) {
let mut scan = [0i32; 16];
nnz = super::h264_cabac::decode_residual_levels(cabac, st, 2, 16, &mut scan);
let mut raster = [0i32; 16];
unscan_4x4(&scan, &mut raster);
dequant_4x4(&mut raster, qp);
inverse_dct_4x4(&mut raster);
add_residual_4x4(y_plane, geo.stride_y, px + bc, py + br, &raster);
}
}
ctx.nnz_luma[gy * ctx.grid_w4 + gx] = nnz as u8;
ctx.nnz_decoded[gy * ctx.grid_w4 + gx] = true;
}
}
add_inter_chroma_cabac(
cabac,
st,
ctx,
mb_x,
mb_y,
cbp_chroma,
qp,
&mut cbp_word,
u_plane,
v_plane,
geo.stride_c,
);
ctx.mb_cbp[mb_idx] = cbp_word;
Ok(())
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum BDir {
L0,
L1,
Bi,
}
#[inline]
const fn uses_l0(d: BDir) -> bool {
matches!(d, BDir::L0 | BDir::Bi)
}
#[inline]
const fn uses_l1(d: BDir) -> bool {
matches!(d, BDir::L1 | BDir::Bi)
}
struct BFrame<'a> {
list0: &'a [&'a RefPic],
list1: &'a [&'a RefPic],
ref_pic_id0: &'a [i8],
ref_pic_id1: &'a [i8],
l0_poc: &'a [i32],
l1_poc: &'a [i32],
geo: &'a PlaneGeo,
wt: Option<&'a super::h264_params::WeightTable>,
weighted_bipred_idc: u32,
implicit_default: bool,
curr_poc: i32,
direct_spatial: bool,
direct_8x8_inference: bool,
num_ref0: usize,
num_ref1: usize,
col: Option<&'a ColMotion>,
}
fn b_mb_type_parts(
mb_type: u32,
) -> (
[(usize, usize, usize, usize); 2],
[BDir; 2],
[PartShape; 2],
usize,
) {
let dir = |x: u32| match x {
0 => BDir::L0,
1 => BDir::L1,
_ => BDir::Bi,
};
match mb_type {
1 => (
[(0, 0, 16, 16), (0, 0, 0, 0)],
[BDir::L0; 2],
[PartShape::Normal; 2],
1,
),
2 => (
[(0, 0, 16, 16), (0, 0, 0, 0)],
[BDir::L1; 2],
[PartShape::Normal; 2],
1,
),
3 => (
[(0, 0, 16, 16), (0, 0, 0, 0)],
[BDir::Bi; 2],
[PartShape::Normal; 2],
1,
),
_ => {
let idx = mb_type - 4; let pair = idx / 2;
let is_8x16 = (idx & 1) == 1;
let (d0, d1) = match pair {
0 => (0, 0),
1 => (1, 1),
2 => (0, 1),
3 => (1, 0),
4 => (0, 2),
5 => (1, 2),
6 => (2, 0),
7 => (2, 1),
_ => (2, 2),
};
let (parts, shapes) = if is_8x16 {
(
[(0, 0, 8, 16), (8, 0, 8, 16)],
[PartShape::Left8x16, PartShape::Right8x16],
)
} else {
(
[(0, 0, 16, 8), (0, 8, 16, 8)],
[PartShape::Top16x8, PartShape::Bottom16x8],
)
};
(parts, [dir(d0), dir(d1)], shapes, 2)
}
}
}
#[inline]
fn min_pos(x: i8, y: i8) -> i8 {
if x >= 0 && y >= 0 { x.min(y) } else { x.max(y) }
}
#[inline]
fn clip_i8(v: i32) -> i32 {
v.clamp(-128, 127)
}
fn temporal_scale(cur_poc: i32, poc0: i32, poc1: i32) -> i32 {
let td = clip_i8(poc1 - poc0);
if td == 0 {
return 256;
}
let tb = clip_i8(cur_poc - poc0);
let tx = (16384 + (td.abs() >> 1)) / td;
((tb * tx + 32) >> 6).clamp(-1024, 1023)
}
fn implicit_weight_l0(cur_poc: i32, poc0: i32, poc1: i32) -> i32 {
let td = clip_i8(poc1 - poc0);
if td == 0 {
return 32;
}
let tb = clip_i8(cur_poc - poc0);
let tx = (16384 + (td.abs() >> 1)) / td;
let dsf = (tb * tx + 32) >> 8;
if (-64..=128).contains(&dsf) {
64 - dsf
} else {
32
}
}
#[allow(clippy::too_many_arguments)]
fn b_avg_plane(
plane: &mut [u8],
stride: usize,
x: usize,
y: usize,
w: usize,
h: usize,
pred1: &[u8],
p1_stride: usize,
) {
for r in 0..h {
let base = (y + r) * stride + x;
let prow = r * p1_stride;
for c in 0..w {
let a = plane[base + c] as i32;
let b = pred1[prow + c] as i32;
plane[base + c] = clip_u8((a + b + 1) >> 1);
}
}
}
#[allow(clippy::too_many_arguments)]
fn b_weight_plane(
plane: &mut [u8],
stride: usize,
x: usize,
y: usize,
w: usize,
h: usize,
pred1: &[u8],
p1_stride: usize,
w0: i32,
w1: i32,
logwd: u32,
offset: i32,
) {
let off = ((offset + 1) | 1) << logwd;
let sh = logwd + 1;
for r in 0..h {
let base = (y + r) * stride + x;
let prow = r * p1_stride;
for c in 0..w {
let p0 = plane[base + c] as i32;
let p1 = pred1[prow + c] as i32;
plane[base + c] = clip_u8((p1 * w1 + p0 * w0 + off) >> sh);
}
}
}
#[allow(clippy::too_many_arguments)]
fn bmc_partition(
bf: &BFrame,
dir: BDir,
mv0: (i16, i16),
ref0: i8,
mv1: (i16, i16),
ref1: i8,
px: usize,
py: usize,
pw: usize,
ph: usize,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) {
let geo = bf.geo;
let idx0 = (ref0.max(0) as usize).min(bf.list0.len().saturating_sub(1));
let idx1 = (ref1.max(0) as usize).min(bf.list1.len().saturating_sub(1));
match dir {
BDir::L0 => {
if let Some(&rp) = bf.list0.get(idx0) {
mc_partition(rp, geo, mv0.0 as i32, mv0.1 as i32, px, py, pw, ph, y, u, v);
if bf.weighted_bipred_idc == 1
&& let Some(wt) = bf.wt
{
apply_partition_weight_list(wt, 0, ref0, geo, px, py, pw, ph, y, u, v);
}
}
}
BDir::L1 => {
if let Some(&rp) = bf.list1.get(idx1) {
mc_partition(rp, geo, mv1.0 as i32, mv1.1 as i32, px, py, pw, ph, y, u, v);
if bf.weighted_bipred_idc == 1
&& let Some(wt) = bf.wt
{
apply_partition_weight_list(wt, 1, ref1, geo, px, py, pw, ph, y, u, v);
}
}
}
BDir::Bi => {
let (Some(&rp0), Some(&rp1)) = (bf.list0.get(idx0), bf.list1.get(idx1)) else {
return;
};
mc_partition(
rp0,
geo,
mv0.0 as i32,
mv0.1 as i32,
px,
py,
pw,
ph,
y,
u,
v,
);
let mut ty = [0u8; 256];
let mut tu = [0u8; 64];
let mut tv = [0u8; 64];
super::h264_motion::mc_luma_block(
&rp1.y,
geo.origin_y,
geo.stride_y,
geo.w,
geo.h,
mv1.0 as i32,
mv1.1 as i32,
px,
py,
pw,
ph,
&mut ty,
pw,
);
let (cpw, cph) = (pw / 2, ph / 2);
let (cx, cy) = (px / 2, py / 2);
super::h264_motion::mc_chroma_block(
&rp1.u,
geo.origin_c,
geo.stride_c,
geo.cw,
geo.ch,
mv1.0 as i32,
mv1.1 as i32,
cx,
cy,
cpw,
cph,
&mut tu,
cpw,
);
super::h264_motion::mc_chroma_block(
&rp1.v,
geo.origin_c,
geo.stride_c,
geo.cw,
geo.ch,
mv1.0 as i32,
mv1.1 as i32,
cx,
cy,
cpw,
cph,
&mut tv,
cpw,
);
match bf.weighted_bipred_idc {
1 => {
if let Some(wt) = bf.wt {
let (r0, r1) = (ref0.max(0) as usize, ref1.max(0) as usize);
let ld = wt.luma_log2_denom;
let dflt = 1 << ld;
let (lw0, lo0) = wt
.luma_l0
.get(r0)
.map_or((dflt, 0), |e| (e.weight, e.offset));
let (lw1, lo1) = wt
.luma_l1
.get(r1)
.map_or((dflt, 0), |e| (e.weight, e.offset));
b_weight_plane(
y,
geo.stride_y,
px,
py,
pw,
ph,
&ty,
pw,
lw0,
lw1,
ld,
lo0 + lo1,
);
let cd = wt.chroma_log2_denom;
let cdflt = 1 << cd;
for (ci, (plane, tmp)) in
[(&mut *u, &tu), (&mut *v, &tv)].into_iter().enumerate()
{
let (cw0, co0) = wt
.chroma_l0
.get(r0)
.map_or((cdflt, 0), |e| (e[ci].weight, e[ci].offset));
let (cw1, co1) = wt
.chroma_l1
.get(r1)
.map_or((cdflt, 0), |e| (e[ci].weight, e[ci].offset));
b_weight_plane(
plane,
geo.stride_c,
cx,
cy,
cpw,
cph,
tmp,
cpw,
cw0,
cw1,
cd,
co0 + co1,
);
}
}
}
2 if !bf.implicit_default => {
let p0 = bf.l0_poc.get(r_idx(ref0)).copied().unwrap_or(bf.curr_poc);
let p1 = bf.l1_poc.get(r_idx(ref1)).copied().unwrap_or(bf.curr_poc);
let w0 = implicit_weight_l0(bf.curr_poc, p0, p1);
let w1 = 64 - w0;
b_weight_plane(y, geo.stride_y, px, py, pw, ph, &ty, pw, w0, w1, 5, 0);
b_weight_plane(u, geo.stride_c, cx, cy, cpw, cph, &tu, cpw, w0, w1, 5, 0);
b_weight_plane(v, geo.stride_c, cx, cy, cpw, cph, &tv, cpw, w0, w1, 5, 0);
}
_ => {
b_avg_plane(y, geo.stride_y, px, py, pw, ph, &ty, pw);
b_avg_plane(u, geo.stride_c, cx, cy, cpw, cph, &tu, cpw);
b_avg_plane(v, geo.stride_c, cx, cy, cpw, cph, &tv, cpw);
}
}
}
}
}
#[inline]
fn r_idx(r: i8) -> usize {
r.max(0) as usize
}
#[allow(clippy::too_many_arguments)]
fn record_and_mc(
field: &mut InterMv,
field_l1: &mut InterMv,
ctx: &mut MbCtx,
bf: &BFrame,
mb_x: usize,
mb_y: usize,
subx: usize,
suby: usize,
w: usize,
h: usize,
dir: BDir,
mv0: (i16, i16),
ref0: i8,
mv1: (i16, i16),
ref1: i8,
is_direct: bool,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) {
let bx4 = mb_x * 4 + subx / 4;
let by4 = mb_y * 4 + suby / 4;
let (w4, h4) = (w / 4, h / 4);
let (px, py) = (mb_x * 16 + subx, mb_y * 16 + suby);
if uses_l0(dir) {
let pic = bf.ref_pic_id0.get(r_idx(ref0)).copied().unwrap_or(-1);
field.set(bx4, by4, w4, h4, mv0.0, mv0.1, ref0, pic);
} else {
field.set(bx4, by4, w4, h4, 0, 0, -1, -1);
}
if uses_l1(dir) {
let pic = bf.ref_pic_id1.get(r_idx(ref1)).copied().unwrap_or(-1);
field_l1.set(bx4, by4, w4, h4, mv1.0, mv1.1, ref1, pic);
} else {
field_l1.set(bx4, by4, w4, h4, 0, 0, -1, -1);
}
for r in 0..h4 {
let base = (by4 + r) * ctx.grid_w4 + bx4;
ctx.direct4[base..base + w4].fill(is_direct);
}
bmc_partition(bf, dir, mv0, ref0, mv1, ref1, px, py, w, h, y, u, v);
}
fn spatial_ref_mvp(field: &InterMv, bx0: i32, by0: i32) -> (i8, (i16, i16)) {
let a = field.neighbor(bx0 - 1, by0);
let b = field.neighbor(bx0, by0 - 1);
let mut c = field.neighbor(bx0 + 4, by0 - 1);
if !c.0 {
c = field.neighbor(bx0 - 1, by0 - 1);
}
let refl = min_pos(a.3, min_pos(b.3, c.3));
if refl < 0 {
return (refl, (0, 0));
}
let mc = (a.3 == refl) as i32 + (b.3 == refl) as i32 + (c.3 == refl) as i32;
let mv = if mc > 1 {
(median3(a.1, b.1, c.1), median3(a.2, b.2, c.2))
} else if a.3 == refl {
(a.1, a.2)
} else if b.3 == refl {
(b.1, b.2)
} else {
(c.1, c.2)
};
(refl, mv)
}
#[inline]
fn col_grid_index(bf: &BFrame, bx0: usize, by0: usize, subx: usize, suby: usize) -> usize {
let (rc, rr) = if bf.direct_8x8_inference {
((subx / 8) * 3, (suby / 8) * 3)
} else {
(subx / 4, suby / 4)
};
let gw4 = bf.col.map_or(bx0 + rc + 1, |c| c.gw4);
(by0 + rr) * gw4 + bx0 + rc
}
fn col_zero_flag(bf: &BFrame, bx0: usize, by0: usize, subx: usize, suby: usize) -> bool {
let Some(col) = bf.col else {
return false;
};
let idx = col_grid_index(bf, bx0, by0, subx, suby);
let cl0 = col.l0[idx];
let cr0 = mv_refi(cl0);
let cl1 = col.l1[idx];
let cr1 = mv_refi(cl1);
if cr0 < 0 && cr1 < 0 {
return false; }
if cr0 == 0 {
let (mx, my) = (cl0 as i16, (cl0 >> 16) as i16);
return mx.abs() <= 1 && my.abs() <= 1;
}
if cr0 < 0 && cr1 == 0 {
let (mx, my) = (cl1 as i16, (cl1 >> 16) as i16);
return mx.abs() <= 1 && my.abs() <= 1;
}
false
}
#[allow(clippy::too_many_arguments)]
fn apply_b_direct(
field: &mut InterMv,
field_l1: &mut InterMv,
ctx: &mut MbCtx,
bf: &BFrame,
mb_x: usize,
mb_y: usize,
ox: usize,
oy: usize,
w: usize,
h: usize,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) {
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let step = if bf.direct_8x8_inference { 8 } else { 4 };
if bf.direct_spatial {
let (mut ref0, mut mv0) = spatial_ref_mvp(field, bx0 as i32, by0 as i32);
let (mut ref1, mut mv1) = spatial_ref_mvp(field_l1, bx0 as i32, by0 as i32);
if ref0 < 0 && ref1 < 0 {
ref0 = 0;
ref1 = 0;
mv0 = (0, 0);
mv1 = (0, 0);
}
let pred0 = ref0 >= 0;
let pred1 = ref1 >= 0;
let dir = if pred0 && pred1 {
BDir::Bi
} else if pred0 {
BDir::L0
} else {
BDir::L1
};
let mut suby = oy;
while suby < oy + h {
let mut subx = ox;
while subx < ox + w {
let czf = col_zero_flag(bf, bx0, by0, subx, suby);
let smv0 = if pred0 && ref0 == 0 && czf {
(0, 0)
} else {
mv0
};
let smv1 = if pred1 && ref1 == 0 && czf {
(0, 0)
} else {
mv1
};
record_and_mc(
field, field_l1, ctx, bf, mb_x, mb_y, subx, suby, step, step, dir, smv0, ref0,
smv1, ref1, true, y, u, v,
);
subx += step;
}
suby += step;
}
} else {
let mut suby = oy;
while suby < oy + h {
let mut subx = ox;
while subx < ox + w {
let (dir, mv0, ref0, mv1, ref1) = temporal_sub(bf, bx0, by0, subx, suby);
record_and_mc(
field, field_l1, ctx, bf, mb_x, mb_y, subx, suby, step, step, dir, mv0, ref0,
mv1, ref1, true, y, u, v,
);
subx += step;
}
suby += step;
}
}
}
fn temporal_sub(
bf: &BFrame,
bx0: usize,
by0: usize,
subx: usize,
suby: usize,
) -> (BDir, (i16, i16), i8, (i16, i16), i8) {
let Some(col) = bf.col else {
return (BDir::Bi, (0, 0), 0, (0, 0), 0);
};
let idx = col_grid_index(bf, bx0, by0, subx, suby);
let cl0 = col.l0[idx];
let cr0 = mv_refi(cl0);
let cl1 = col.l1[idx];
let cr1 = mv_refi(cl1);
if cr0 < 0 && cr1 < 0 {
return (BDir::Bi, (0, 0), 0, (0, 0), 0); }
let (mvc, col_poc) = if cr0 >= 0 {
(
(cl0 as i16, (cl0 >> 16) as i16),
col.l0_poc.get(cr0 as usize).copied(),
)
} else {
(
(cl1 as i16, (cl1 >> 16) as i16),
col.l1_poc.get(cr1 as usize).copied(),
)
};
let ref0 = col_poc
.and_then(|p| bf.l0_poc.iter().position(|&q| q == p))
.unwrap_or(0) as i8;
let poc0 = bf.l0_poc.get(ref0 as usize).copied().unwrap_or(bf.curr_poc);
let poc1 = bf.l1_poc.first().copied().unwrap_or(bf.curr_poc);
let scale = temporal_scale(bf.curr_poc, poc0, poc1);
let mv0x = ((scale * mvc.0 as i32 + 128) >> 8) as i16;
let mv0y = ((scale * mvc.1 as i32 + 128) >> 8) as i16;
let mv1x = mv0x - mvc.0;
let mv1y = mv0y - mvc.1;
(BDir::Bi, (mv0x, mv0y), ref0, (mv1x, mv1y), 0)
}
fn decode_ref_b(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
field: &InterMv,
ctx: &MbCtx,
bx4: i32,
by4: i32,
num_ref: usize,
) -> i8 {
if num_ref <= 1 {
return 0;
}
let dir_at = |x: i32, y: i32| -> bool {
x >= 0
&& y >= 0
&& (x as usize) < field.gw4
&& (y as usize) < field.gh4
&& ctx.direct4[y as usize * ctx.grid_w4 + x as usize]
};
let a = field.neighbor(bx4 - 1, by4);
let b = field.neighbor(bx4, by4 - 1);
let inc = ((a.3 > 0) && !dir_at(bx4 - 1, by4)) as usize
+ 2 * ((b.3 > 0) && !dir_at(bx4, by4 - 1)) as usize;
super::h264_cabac::decode_ref_idx(cabac, st, inc) as i8
}
#[allow(clippy::too_many_arguments)]
fn decode_b_parts_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
field: &mut InterMv,
field_l1: &mut InterMv,
bf: &BFrame,
mb_x: usize,
mb_y: usize,
parts: &[(usize, usize, usize, usize)],
dirs: &[BDir],
shapes: &[PartShape],
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) {
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let count = parts.len();
for &(ox, oy, pw, ph) in parts {
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
field.set(bx4, by4, pw / 4, ph / 4, 0, 0, -1, -1);
field_l1.set(bx4, by4, pw / 4, ph / 4, 0, 0, -1, -1);
for r in 0..ph / 4 {
let base = (by4 + r) * ctx.grid_w4 + bx4;
ctx.direct4[base..base + pw / 4].fill(false);
}
}
let mut ref0 = [0i8; 2];
let mut ref1 = [0i8; 2];
for p in 0..count {
if uses_l0(dirs[p]) {
let (ox, oy, pw, ph) = parts[p];
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
ref0[p] = decode_ref_b(cabac, st, field, ctx, bx4 as i32, by4 as i32, bf.num_ref0);
let pic = bf.ref_pic_id0.get(r_idx(ref0[p])).copied().unwrap_or(-1);
field.set(bx4, by4, pw / 4, ph / 4, 0, 0, ref0[p], pic);
}
}
for p in 0..count {
if uses_l1(dirs[p]) {
let (ox, oy, pw, ph) = parts[p];
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
ref1[p] = decode_ref_b(
cabac,
st,
field_l1,
ctx,
bx4 as i32,
by4 as i32,
bf.num_ref1,
);
let pic = bf.ref_pic_id1.get(r_idx(ref1[p])).copied().unwrap_or(-1);
field_l1.set(bx4, by4, pw / 4, ph / 4, 0, 0, ref1[p], pic);
}
}
let mut mv0 = [(0i16, 0i16); 2];
let mut mv1 = [(0i16, 0i16); 2];
for p in 0..count {
if uses_l0(dirs[p]) {
let (ox, oy, pw, ph) = parts[p];
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
let ax = field.amvd_sum(bx4 as i32, by4 as i32, 0);
let ay = field.amvd_sum(bx4 as i32, by4 as i32, 1);
let (mdx, sx) = super::h264_cabac::decode_mvd(cabac, st, 40, ax);
let (mdy, sy) = super::h264_cabac::decode_mvd(cabac, st, 47, ay);
let (pdx, pdy) = mvp_predict(
field,
bx4 as i32,
by4 as i32,
(pw / 4) as i32,
ref0[p],
shapes[p],
);
mv0[p] = (pdx + mdx as i16, pdy + mdy as i16);
let pic = bf.ref_pic_id0.get(r_idx(ref0[p])).copied().unwrap_or(-1);
field.set(bx4, by4, pw / 4, ph / 4, mv0[p].0, mv0[p].1, ref0[p], pic);
field.set_amvd(bx4, by4, pw / 4, ph / 4, sx, sy);
}
}
for p in 0..count {
if uses_l1(dirs[p]) {
let (ox, oy, pw, ph) = parts[p];
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
let ax = field_l1.amvd_sum(bx4 as i32, by4 as i32, 0);
let ay = field_l1.amvd_sum(bx4 as i32, by4 as i32, 1);
let (mdx, sx) = super::h264_cabac::decode_mvd(cabac, st, 40, ax);
let (mdy, sy) = super::h264_cabac::decode_mvd(cabac, st, 47, ay);
let (pdx, pdy) = mvp_predict(
field_l1,
bx4 as i32,
by4 as i32,
(pw / 4) as i32,
ref1[p],
shapes[p],
);
mv1[p] = (pdx + mdx as i16, pdy + mdy as i16);
let pic = bf.ref_pic_id1.get(r_idx(ref1[p])).copied().unwrap_or(-1);
field_l1.set(bx4, by4, pw / 4, ph / 4, mv1[p].0, mv1[p].1, ref1[p], pic);
field_l1.set_amvd(bx4, by4, pw / 4, ph / 4, sx, sy);
}
}
for p in 0..count {
let (ox, oy, pw, ph) = parts[p];
let (px, py) = (mb_x * 16 + ox, mb_y * 16 + oy);
bmc_partition(
bf, dirs[p], mv0[p], ref0[p], mv1[p], ref1[p], px, py, pw, ph, y, u, v,
);
}
}
const fn b_sub_shapes(sub: u32) -> &'static [(usize, usize, usize, usize)] {
match sub {
4 | 6 | 8 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
5 | 7 | 9 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
10..=12 => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)],
_ => &[(0, 0, 8, 8)],
}
}
const fn b_sub_dir(sub: u32) -> BDir {
match sub {
2 | 6 | 7 | 11 => BDir::L1,
3 | 8 | 9 | 12 => BDir::Bi,
_ => BDir::L0,
}
}
#[allow(clippy::too_many_arguments)]
fn decode_b_8x8_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
field: &mut InterMv,
field_l1: &mut InterMv,
bf: &BFrame,
mb_x: usize,
mb_y: usize,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) -> bool {
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let mut sub = [0u32; 4];
for s in sub.iter_mut() {
*s = super::h264_cabac::decode_sub_mb_type_b(cabac, st);
}
for &(sox, soy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
let (bx4, by4) = (bx0 + sox / 4, by0 + soy / 4);
field.set(bx4, by4, 2, 2, 0, 0, -1, -1);
field_l1.set(bx4, by4, 2, 2, 0, 0, -1, -1);
for r in 0..2 {
let base = (by4 + r) * ctx.grid_w4 + bx4;
ctx.direct4[base..base + 2].fill(false);
}
}
for s8 in 0..4 {
if sub[s8] == 0 {
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
apply_b_direct(
field, field_l1, ctx, bf, mb_x, mb_y, sox, soy, 8, 8, y, u, v,
);
}
}
let mut ref0 = [0i8; 4];
let mut ref1 = [0i8; 4];
for s8 in 0..4 {
if sub[s8] != 0 && uses_l0(b_sub_dir(sub[s8])) {
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
let (bx4, by4) = (bx0 + sox / 4, by0 + soy / 4);
ref0[s8] = decode_ref_b(cabac, st, field, ctx, bx4 as i32, by4 as i32, bf.num_ref0);
let pic = bf.ref_pic_id0.get(r_idx(ref0[s8])).copied().unwrap_or(-1);
field.set(bx4, by4, 2, 2, 0, 0, ref0[s8], pic);
}
}
for s8 in 0..4 {
if sub[s8] != 0 && uses_l1(b_sub_dir(sub[s8])) {
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
let (bx4, by4) = (bx0 + sox / 4, by0 + soy / 4);
ref1[s8] = decode_ref_b(
cabac,
st,
field_l1,
ctx,
bx4 as i32,
by4 as i32,
bf.num_ref1,
);
let pic = bf.ref_pic_id1.get(r_idx(ref1[s8])).copied().unwrap_or(-1);
field_l1.set(bx4, by4, 2, 2, 0, 0, ref1[s8], pic);
}
}
let decode_list = |cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
fld: &mut InterMv,
list_is_l1: bool,
refs: &[i8; 4]| {
for s8 in 0..4 {
if sub[s8] == 0 {
continue;
}
let d = b_sub_dir(sub[s8]);
let use_this = if list_is_l1 { uses_l1(d) } else { uses_l0(d) };
if !use_this {
continue;
}
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
let pic_ids = if list_is_l1 {
bf.ref_pic_id1
} else {
bf.ref_pic_id0
};
let pic = pic_ids.get(r_idx(refs[s8])).copied().unwrap_or(-1);
for &(ppx, ppy, pw, ph) in b_sub_shapes(sub[s8]) {
let bx4 = bx0 + (sox + ppx) / 4;
let by4 = by0 + (soy + ppy) / 4;
let ax = fld.amvd_sum(bx4 as i32, by4 as i32, 0);
let ay = fld.amvd_sum(bx4 as i32, by4 as i32, 1);
let (mdx, sx) = super::h264_cabac::decode_mvd(cabac, st, 40, ax);
let (mdy, sy) = super::h264_cabac::decode_mvd(cabac, st, 47, ay);
let (pdx, pdy) = mvp_predict(
fld,
bx4 as i32,
by4 as i32,
(pw / 4) as i32,
refs[s8],
PartShape::Normal,
);
let mvx = pdx + mdx as i16;
let mvy = pdy + mdy as i16;
fld.set(bx4, by4, pw / 4, ph / 4, mvx, mvy, refs[s8], pic);
fld.set_amvd(bx4, by4, pw / 4, ph / 4, sx, sy);
}
}
};
decode_list(cabac, st, field, false, &ref0);
decode_list(cabac, st, field_l1, true, &ref1);
for s8 in 0..4 {
if sub[s8] == 0 {
continue;
}
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
let d = b_sub_dir(sub[s8]);
for &(ppx, ppy, pw, ph) in b_sub_shapes(sub[s8]) {
let bx4 = bx0 + (sox + ppx) / 4;
let by4 = by0 + (soy + ppy) / 4;
let mv0 = (
field.cells[by4 * field.gw4 + bx4] as i16,
(field.cells[by4 * field.gw4 + bx4] >> 16) as i16,
);
let mv1 = (
field_l1.cells[by4 * field_l1.gw4 + bx4] as i16,
(field_l1.cells[by4 * field_l1.gw4 + bx4] >> 16) as i16,
);
let px = mb_x * 16 + sox + ppx;
let py = mb_y * 16 + soy + ppy;
bmc_partition(bf, d, mv0, ref0[s8], mv1, ref1[s8], px, py, pw, ph, y, u, v);
}
}
sub.iter().all(|&t| match t {
0 => bf.direct_8x8_inference,
1..=3 => true,
_ => false,
})
}
#[allow(clippy::too_many_arguments)]
fn decode_b_mb_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
field: &mut InterMv,
field_l1: &mut InterMv,
bf: &BFrame,
mb_x: usize,
mb_y: usize,
mb_type: u32,
transform_8x8_mode: bool,
qp_delta_nonzero: &mut bool,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) -> Result<(), VideoError> {
let mut no_sub_8x8 = true;
match mb_type {
0 => apply_b_direct(field, field_l1, ctx, bf, mb_x, mb_y, 0, 0, 16, 16, y, u, v),
22 => {
no_sub_8x8 =
decode_b_8x8_cabac(cabac, st, ctx, field, field_l1, bf, mb_x, mb_y, y, u, v);
}
_ => {
let (parts, dirs, shapes, count) = b_mb_type_parts(mb_type);
decode_b_parts_cabac(
cabac,
st,
ctx,
field,
field_l1,
bf,
mb_x,
mb_y,
&parts[..count],
&dirs[..count],
&shapes[..count],
y,
u,
v,
);
}
}
finish_inter_mb_cabac(
cabac,
st,
ctx,
mb_x,
mb_y,
no_sub_8x8,
transform_8x8_mode,
qp_delta_nonzero,
bf.geo,
y,
u,
v,
)
}
#[allow(clippy::too_many_arguments)]
fn decode_b_parts_cavlc(
reader: &mut super::cavlc::BitReader<'_>,
ctx: &mut MbCtx,
field: &mut InterMv,
field_l1: &mut InterMv,
bf: &BFrame,
mb_x: usize,
mb_y: usize,
parts: &[(usize, usize, usize, usize)],
dirs: &[BDir],
shapes: &[PartShape],
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) -> Result<(), VideoError> {
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let count = parts.len();
for &(ox, oy, pw, ph) in parts {
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
field.set(bx4, by4, pw / 4, ph / 4, 0, 0, -1, -1);
field_l1.set(bx4, by4, pw / 4, ph / 4, 0, 0, -1, -1);
for r in 0..ph / 4 {
let base = (by4 + r) * ctx.grid_w4 + bx4;
ctx.direct4[base..base + pw / 4].fill(false);
}
}
let mut ref0 = [0i8; 2];
let mut ref1 = [0i8; 2];
for p in 0..count {
if uses_l0(dirs[p]) {
let (ox, oy, pw, ph) = parts[p];
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
ref0[p] = read_ref_idx(reader, bf.num_ref0);
let pic = bf.ref_pic_id0.get(r_idx(ref0[p])).copied().unwrap_or(-1);
field.set(bx4, by4, pw / 4, ph / 4, 0, 0, ref0[p], pic);
}
}
for p in 0..count {
if uses_l1(dirs[p]) {
let (ox, oy, pw, ph) = parts[p];
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
ref1[p] = read_ref_idx(reader, bf.num_ref1);
let pic = bf.ref_pic_id1.get(r_idx(ref1[p])).copied().unwrap_or(-1);
field_l1.set(bx4, by4, pw / 4, ph / 4, 0, 0, ref1[p], pic);
}
}
let mut mv0 = [(0i16, 0i16); 2];
let mut mv1 = [(0i16, 0i16); 2];
for p in 0..count {
if uses_l0(dirs[p]) {
let (ox, oy, pw, ph) = parts[p];
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
let (mdx, mdy) = read_mvd(reader)?;
let (pdx, pdy) = mvp_predict(
field,
bx4 as i32,
by4 as i32,
(pw / 4) as i32,
ref0[p],
shapes[p],
);
mv0[p] = (pdx + mdx, pdy + mdy);
let pic = bf.ref_pic_id0.get(r_idx(ref0[p])).copied().unwrap_or(-1);
field.set(bx4, by4, pw / 4, ph / 4, mv0[p].0, mv0[p].1, ref0[p], pic);
}
}
for p in 0..count {
if uses_l1(dirs[p]) {
let (ox, oy, pw, ph) = parts[p];
let (bx4, by4) = (bx0 + ox / 4, by0 + oy / 4);
let (mdx, mdy) = read_mvd(reader)?;
let (pdx, pdy) = mvp_predict(
field_l1,
bx4 as i32,
by4 as i32,
(pw / 4) as i32,
ref1[p],
shapes[p],
);
mv1[p] = (pdx + mdx, pdy + mdy);
let pic = bf.ref_pic_id1.get(r_idx(ref1[p])).copied().unwrap_or(-1);
field_l1.set(bx4, by4, pw / 4, ph / 4, mv1[p].0, mv1[p].1, ref1[p], pic);
}
}
for p in 0..count {
let (ox, oy, pw, ph) = parts[p];
let (px, py) = (mb_x * 16 + ox, mb_y * 16 + oy);
bmc_partition(
bf, dirs[p], mv0[p], ref0[p], mv1[p], ref1[p], px, py, pw, ph, y, u, v,
);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn decode_b_8x8_cavlc(
reader: &mut super::cavlc::BitReader<'_>,
ctx: &mut MbCtx,
field: &mut InterMv,
field_l1: &mut InterMv,
bf: &BFrame,
mb_x: usize,
mb_y: usize,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) -> Result<(), VideoError> {
let bx0 = mb_x * 4;
let by0 = mb_y * 4;
let mut sub = [0u32; 4];
for s in sub.iter_mut() {
*s = reader.read_ue().ok_or_else(bitstream_err)?;
}
for &(sox, soy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8)] {
let (bx4, by4) = (bx0 + sox / 4, by0 + soy / 4);
field.set(bx4, by4, 2, 2, 0, 0, -1, -1);
field_l1.set(bx4, by4, 2, 2, 0, 0, -1, -1);
for r in 0..2 {
let base = (by4 + r) * ctx.grid_w4 + bx4;
ctx.direct4[base..base + 2].fill(false);
}
}
for s8 in 0..4 {
if sub[s8] == 0 {
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
apply_b_direct(
field, field_l1, ctx, bf, mb_x, mb_y, sox, soy, 8, 8, y, u, v,
);
}
}
let mut ref0 = [0i8; 4];
let mut ref1 = [0i8; 4];
for s8 in 0..4 {
if sub[s8] != 0 && uses_l0(b_sub_dir(sub[s8])) {
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
let (bx4, by4) = (bx0 + sox / 4, by0 + soy / 4);
ref0[s8] = read_ref_idx(reader, bf.num_ref0);
let pic = bf.ref_pic_id0.get(r_idx(ref0[s8])).copied().unwrap_or(-1);
field.set(bx4, by4, 2, 2, 0, 0, ref0[s8], pic);
}
}
for s8 in 0..4 {
if sub[s8] != 0 && uses_l1(b_sub_dir(sub[s8])) {
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
let (bx4, by4) = (bx0 + sox / 4, by0 + soy / 4);
ref1[s8] = read_ref_idx(reader, bf.num_ref1);
let pic = bf.ref_pic_id1.get(r_idx(ref1[s8])).copied().unwrap_or(-1);
field_l1.set(bx4, by4, 2, 2, 0, 0, ref1[s8], pic);
}
}
for (list_is_l1, fld) in [(false, &mut *field), (true, &mut *field_l1)] {
let refs = if list_is_l1 { &ref1 } else { &ref0 };
let pic_ids = if list_is_l1 {
bf.ref_pic_id1
} else {
bf.ref_pic_id0
};
for s8 in 0..4 {
if sub[s8] == 0 {
continue;
}
let d = b_sub_dir(sub[s8]);
let use_this = if list_is_l1 { uses_l1(d) } else { uses_l0(d) };
if !use_this {
continue;
}
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
let pic = pic_ids.get(r_idx(refs[s8])).copied().unwrap_or(-1);
for &(ppx, ppy, pw, ph) in b_sub_shapes(sub[s8]) {
let bx4 = bx0 + (sox + ppx) / 4;
let by4 = by0 + (soy + ppy) / 4;
let (mdx, mdy) = read_mvd(reader)?;
let (pdx, pdy) = mvp_predict(
fld,
bx4 as i32,
by4 as i32,
(pw / 4) as i32,
refs[s8],
PartShape::Normal,
);
fld.set(
bx4,
by4,
pw / 4,
ph / 4,
pdx + mdx,
pdy + mdy,
refs[s8],
pic,
);
}
}
}
for s8 in 0..4 {
if sub[s8] == 0 {
continue;
}
let (sox, soy) = ((s8 % 2) * 8, (s8 / 2) * 8);
let d = b_sub_dir(sub[s8]);
for &(ppx, ppy, pw, ph) in b_sub_shapes(sub[s8]) {
let bx4 = bx0 + (sox + ppx) / 4;
let by4 = by0 + (soy + ppy) / 4;
let c0 = field.cells[by4 * field.gw4 + bx4];
let c1 = field_l1.cells[by4 * field_l1.gw4 + bx4];
let mv0 = (c0 as u16 as i16, (c0 >> 16) as u16 as i16);
let mv1 = (c1 as u16 as i16, (c1 >> 16) as u16 as i16);
let px = mb_x * 16 + sox + ppx;
let py = mb_y * 16 + soy + ppy;
bmc_partition(bf, d, mv0, ref0[s8], mv1, ref1[s8], px, py, pw, ph, y, u, v);
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn decode_b_mb_cavlc(
reader: &mut super::cavlc::BitReader<'_>,
ctx: &mut MbCtx,
field: &mut InterMv,
field_l1: &mut InterMv,
bf: &BFrame,
mb_x: usize,
mb_y: usize,
mb_type: u32,
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
) -> Result<(), VideoError> {
match mb_type {
0 => apply_b_direct(field, field_l1, ctx, bf, mb_x, mb_y, 0, 0, 16, 16, y, u, v),
22 => decode_b_8x8_cavlc(reader, ctx, field, field_l1, bf, mb_x, mb_y, y, u, v)?,
_ => {
let (parts, dirs, shapes, count) = b_mb_type_parts(mb_type);
decode_b_parts_cavlc(
reader,
ctx,
field,
field_l1,
bf,
mb_x,
mb_y,
&parts[..count],
&dirs[..count],
&shapes[..count],
y,
u,
v,
)?;
}
}
finish_inter_mb_cavlc(reader, ctx, mb_x, mb_y, bf.geo, y, u, v)
}
#[allow(clippy::too_many_arguments)]
fn add_inter_chroma_cabac(
cabac: &mut CabacDecoder<'_>,
st: &mut [super::h264_cabac::CabacContext],
ctx: &mut MbCtx,
mb_x: usize,
mb_y: usize,
cbp_chroma: u32,
qp_y: i32,
cbp_word: &mut u32,
u_plane: &mut [u8],
v_plane: &mut [u8],
stride_uv: usize,
) {
let cpx = mb_x * 8;
let cpy = mb_y * 8;
let cbx0 = mb_x * 2;
let cby0 = mb_y * 2;
let qpc = chroma_qp_from_luma_qp(qp_y, ctx.chroma_qp_index_offset);
const CBLK: [(usize, usize); 4] = [(0, 0), (0, 4), (4, 0), (4, 4)];
let mut dc = [[0i32; 4]; 2];
if cbp_chroma >= 1 {
for (pi, d) in dc.iter_mut().enumerate() {
let left_val = cabac_neighbor_cbp(ctx, mb_x as i32 - 1, mb_y as i32, false);
let top_val = cabac_neighbor_cbp(ctx, mb_x as i32, mb_y as i32 - 1, false);
let inc =
((left_val >> (6 + pi)) & 1) as usize + 2 * ((top_val >> (6 + pi)) & 1) as usize;
if super::h264_cabac::decode_cbf(cabac, st, 3, inc) {
let mut c = [0i32; 16];
super::h264_cabac::decode_residual_levels(cabac, st, 3, 4, &mut c);
*d = chroma_dc_transform(&[c[0], c[1], c[2], c[3]], qpc);
*cbp_word |= 0x40 << pi;
}
}
}
let mut ac = [[[0i32; 16]; 4]; 2];
if cbp_chroma >= 2 {
for pi in 0..2 {
for (bi, &(br, bc)) in CBLK.iter().enumerate() {
let gx = cbx0 + bc / 4;
let gy = cby0 + br / 4;
let nnz_grid = if pi == 0 { &ctx.nnz_cb } else { &ctx.nnz_cr };
let inc = cbf_ac_inc(
nnz_grid,
ctx.grid_w2,
gx as i32,
gy as i32,
ctx.block_avail(gx as i32 - 1, gy as i32, 1),
ctx.block_avail(gx as i32, gy as i32 - 1, 1),
false,
);
let mut tc = 0usize;
if super::h264_cabac::decode_cbf(cabac, st, 4, inc) {
let mut tmp = [0i32; 16];
tc = super::h264_cabac::decode_residual_levels(cabac, st, 4, 15, &mut tmp);
let mut scan = [0i32; 16];
scan[1..16].copy_from_slice(&tmp[..15]);
unscan_4x4(&scan, &mut ac[pi][bi]);
}
let grid = if pi == 0 {
&mut ctx.nnz_cb
} else {
&mut ctx.nnz_cr
};
grid[gy * ctx.grid_w2 + gx] = tc as u8;
}
}
} else {
for pi in 0..2 {
for &(br, bc) in CBLK.iter() {
let idx = (cby0 + br / 4) * ctx.grid_w2 + cbx0 + bc / 4;
if pi == 0 {
ctx.nnz_cb[idx] = 0;
} else {
ctx.nnz_cr[idx] = 0;
}
}
}
}
if cbp_chroma == 0 {
return;
}
for (pi, plane) in [&mut *u_plane, &mut *v_plane].into_iter().enumerate() {
for (bi, &(br, bc)) in CBLK.iter().enumerate() {
let mut coeffs = ac[pi][bi];
coeffs[0] = dc[pi][bi];
if cbp_chroma >= 2 {
dequant_4x4_ac(&mut coeffs, qpc);
}
inverse_dct_4x4(&mut coeffs);
add_residual_4x4(plane, stride_uv, cpx + bc, cpy + br, &coeffs);
}
}
}
fn chroma_qp_from_luma_qp(qp_y: i32, chroma_qp_index_offset: i32) -> i32 {
const QPC_TABLE: [i32; 52] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38,
39, 39, 39, 39,
];
let idx = (qp_y + chroma_qp_index_offset).clamp(0, 51) as usize;
QPC_TABLE[idx]
}
pub struct H264Decoder {
sps: Option<Sps>,
pps: Option<Pps>,
_pending_nals: Vec<NalUnit>,
pending_top_field: Option<PendingField>,
dpb: Vec<RefPic>,
frame: Option<FrameCtx>,
ref_width: usize,
ref_height: usize,
plane_pool: Vec<RefPic>,
pub skip_rgb: bool,
pub dump_yuv: bool,
yuv_scratch: [Vec<u8>; 3],
chase: Option<super::h264_chase::ChaseHandle>,
poc_prev_msb: i32,
poc_prev_lsb: i32,
poc_prev_frame_num_offset: i32,
poc_prev_frame_num: u32,
reorder: Vec<(i32, DecodedFrame)>,
emit_queue: std::collections::VecDeque<DecodedFrame>,
}
fn copy_into<T: Copy>(dst: &mut Vec<T>, src: &[T]) {
dst.clear();
dst.extend_from_slice(src);
}
#[allow(clippy::too_many_arguments)]
fn send_chase_row(
chase: &super::h264_chase::ChaseHandle,
r: usize,
y_dec: &[u8],
u_dec: &[u8],
v_dec: &[u8],
geo: &PlaneGeo,
mb_ctx: &MbCtx,
field: &InterMv,
field_l1: Option<&InterMv>,
mb_qp: &[i32],
filter_on: bool,
alpha_c0_offset: i32,
beta_offset: i32,
) {
let mut m = chase.row_buf();
m.mby = r;
m.filter_on = filter_on;
m.alpha_c0_offset = alpha_c0_offset;
m.beta_offset = beta_offset;
copy_into(
&mut m.y,
&y_dec[r * 16 * geo.stride_y..(r * 16 + 16) * geo.stride_y],
);
copy_into(
&mut m.u,
&u_dec[r * 8 * geo.stride_c..(r * 8 + 8) * geo.stride_c],
);
copy_into(
&mut m.v,
&v_dec[r * 8 * geo.stride_c..(r * 8 + 8) * geo.stride_c],
);
let gw4 = mb_ctx.grid_w4;
let g = r * 4 * gw4..(r * 4 + 4) * gw4;
copy_into(&mut m.nnz, &mb_ctx.nnz_luma[g.clone()]);
let cells = &field.cells[g.clone()];
m.mvx.clear();
m.mvx.extend(cells.iter().map(|&c| c as u16 as i16));
m.mvy.clear();
m.mvy.extend(cells.iter().map(|&c| (c >> 16) as u16 as i16));
m.refi.clear();
m.refi.extend(cells.iter().map(|&c| mv_pic_id(c)));
m.mvx1.clear();
m.mvy1.clear();
m.refi1.clear();
if let Some(f1) = field_l1 {
let c1 = &f1.cells[g];
m.mvx1.extend(c1.iter().map(|&c| c as u16 as i16));
m.mvy1.extend(c1.iter().map(|&c| (c >> 16) as u16 as i16));
m.refi1.extend(c1.iter().map(|&c| mv_pic_id(c)));
}
copy_into(&mut m.qp, &mb_qp[r * mb_ctx.mb_w..(r + 1) * mb_ctx.mb_w]);
m.tr8x8.clear();
m.tr8x8
.extend_from_slice(&mb_ctx.mb_tr8x8[r * mb_ctx.mb_w..(r + 1) * mb_ctx.mb_w]);
let _ = chase.send_row(m);
}
fn extract_plane(src: &[u8], stride: usize, origin: usize, w: usize, h: usize, dst: &mut Vec<u8>) {
dst.clear();
dst.reserve(w * h);
for row in src[origin..].chunks(stride).take(h) {
dst.extend_from_slice(&row[..w]);
}
}
struct RefPic {
y: Vec<u8>,
u: Vec<u8>,
v: Vec<u8>,
frame_num: u32,
poc: i32,
col: Option<ColMotion>,
}
struct ColMotion {
l0: Vec<u64>,
l1: Vec<u64>,
l0_poc: Vec<i32>,
l1_poc: Vec<i32>,
gw4: usize,
}
impl ColMotion {
fn snapshot(field: &InterMv, field_l1: &InterMv, l0_poc: &[i32], l1_poc: &[i32]) -> Self {
Self {
l0: field.cells.clone(),
l1: field_l1.cells.clone(),
l0_poc: l0_poc.to_vec(),
l1_poc: l1_poc.to_vec(),
gw4: field.gw4,
}
}
}
struct FrameCtx {
y: Vec<u8>,
u: Vec<u8>,
v: Vec<u8>,
mb_ctx: MbCtx,
field: InterMv,
field_l1: InterMv,
mb_qp: Vec<i32>,
covered: usize,
rows_sent: usize,
chased: bool,
full_w: usize,
full_h: usize,
poc: i32,
}
fn build_ref_list0<'a>(
dpb: &'a [RefPic],
sh: &SliceHeader,
sps: &Sps,
num_ref: usize,
) -> Vec<&'a RefPic> {
let mut list: Vec<&RefPic> = dpb.iter().collect();
if list.is_empty() {
return list;
}
if sh.ref_list_mods_l0.is_empty() {
while list.len() < num_ref {
list.push(list[list.len() - 1]);
}
list.truncate(num_ref.max(1));
return list;
}
while list.len() < num_ref {
list.push(list[list.len() - 1]);
}
list.truncate(num_ref.max(1));
let max_fn: i64 = 1i64 << sps.log2_max_frame_num;
let curr = sh.frame_num as i64;
let pic_num = |fnum: u32| -> i64 {
let f = fnum as i64;
if f > curr { f - max_fn } else { f }
};
let n = list.len();
list.push(list[n - 1]);
let mut ref_idx = 0usize;
let mut pred = curr;
for &(op, val) in &sh.ref_list_mods_l0 {
let target_pn = match op {
0 | 1 => {
let abs_diff = val as i64 + 1;
let mut no_wrap = if op == 0 {
pred - abs_diff
} else {
pred + abs_diff
};
if no_wrap < 0 {
no_wrap += max_fn;
}
if no_wrap >= max_fn {
no_wrap -= max_fn;
}
pred = no_wrap;
if no_wrap > curr {
no_wrap - max_fn
} else {
no_wrap
}
}
_ => continue,
};
let Some(pic) = dpb.iter().find(|p| pic_num(p.frame_num) == target_pn) else {
continue;
};
if ref_idx >= n {
break;
}
for c in (ref_idx + 1..=n).rev() {
list[c] = list[c - 1];
}
list[ref_idx] = pic;
ref_idx += 1;
let mut nidx = ref_idx;
for c in ref_idx..=n {
if pic_num(list[c].frame_num) != target_pn {
list[nidx] = list[c];
nidx += 1;
}
}
}
list.truncate(n);
list
}
fn apply_ref_mods<'a>(
mut list: Vec<&'a RefPic>,
dpb: &'a [RefPic],
mods: &[(u32, u32)],
sps: &Sps,
curr_frame_num: u32,
num: usize,
) -> Vec<&'a RefPic> {
if list.is_empty() {
return list;
}
while list.len() < num {
list.push(list[list.len() - 1]);
}
list.truncate(num.max(1));
if mods.is_empty() {
return list;
}
let max_fn: i64 = 1i64 << sps.log2_max_frame_num;
let curr = curr_frame_num as i64;
let pic_num = |fnum: u32| -> i64 {
let f = fnum as i64;
if f > curr { f - max_fn } else { f }
};
let n = list.len();
list.push(list[n - 1]);
let mut ref_idx = 0usize;
let mut pred = curr;
for &(op, val) in mods {
let target_pn = match op {
0 | 1 => {
let abs_diff = val as i64 + 1;
let mut nw = if op == 0 {
pred - abs_diff
} else {
pred + abs_diff
};
if nw < 0 {
nw += max_fn;
}
if nw >= max_fn {
nw -= max_fn;
}
pred = nw;
if nw > curr { nw - max_fn } else { nw }
}
_ => continue,
};
let Some(pic) = dpb.iter().find(|p| pic_num(p.frame_num) == target_pn) else {
continue;
};
if ref_idx >= n {
break;
}
for c in (ref_idx + 1..=n).rev() {
list[c] = list[c - 1];
}
list[ref_idx] = pic;
ref_idx += 1;
let mut nidx = ref_idx;
for c in ref_idx..=n {
if pic_num(list[c].frame_num) != target_pn {
list[nidx] = list[c];
nidx += 1;
}
}
}
list.truncate(n);
list
}
fn build_ref_lists_b<'a>(
dpb: &'a [RefPic],
sh: &SliceHeader,
sps: &Sps,
curr_poc: i32,
num_l0: usize,
num_l1: usize,
) -> (Vec<&'a RefPic>, Vec<&'a RefPic>) {
let mut before: Vec<&RefPic> = dpb.iter().filter(|p| p.poc < curr_poc).collect();
before.sort_by_key(|p| std::cmp::Reverse(p.poc));
let mut after: Vec<&RefPic> = dpb.iter().filter(|p| p.poc > curr_poc).collect();
after.sort_by_key(|p| p.poc);
let mut l0: Vec<&RefPic> = before.clone();
l0.extend(after.iter().copied());
let mut l1: Vec<&RefPic> = after;
l1.extend(before.iter().copied());
if l1.len() > 1 && l0.len() == l1.len() && l0.iter().zip(&l1).all(|(a, b)| std::ptr::eq(*a, *b))
{
l1.swap(0, 1);
}
let fnum = sh.frame_num;
let l0 = apply_ref_mods(l0, dpb, &sh.ref_list_mods_l0, sps, fnum, num_l0);
let l1 = apply_ref_mods(l1, dpb, &sh.ref_list_mods_l1, sps, fnum, num_l1);
(l0, l1)
}
fn ref_pic_ids(dpb: &[RefPic], list: &[&RefPic]) -> Vec<i8> {
list.iter()
.map(|&r| {
dpb.iter()
.position(|d| std::ptr::eq(d, r))
.map_or(-1, |p| p as i8)
})
.collect()
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct PendingField {
rgb_data: Vec<u8>,
width: usize,
height: usize,
timestamp_us: u64,
}
impl H264Decoder {
pub const fn new() -> Self {
Self {
sps: None,
pps: None,
_pending_nals: Vec::new(),
pending_top_field: None,
dpb: Vec::new(),
frame: None,
ref_width: 0,
ref_height: 0,
plane_pool: Vec::new(),
skip_rgb: false,
dump_yuv: false,
yuv_scratch: [Vec::new(), Vec::new(), Vec::new()],
chase: None,
poc_prev_msb: 0,
poc_prev_lsb: 0,
poc_prev_frame_num_offset: 0,
poc_prev_frame_num: 0,
reorder: Vec::new(),
emit_queue: std::collections::VecDeque::new(),
}
}
fn emit_picture(&mut self, poc: i32, frame: DecodedFrame, is_idr: bool, depth: usize) {
if is_idr {
self.reorder.sort_by_key(|&(p, _)| p);
for (_, f) in self.reorder.drain(..) {
self.emit_queue.push_back(f);
}
}
self.reorder.push((poc, frame));
while self.reorder.len() > depth {
let (idx, _) = self
.reorder
.iter()
.enumerate()
.min_by_key(|(_, (p, _))| *p)
.expect("reorder non-empty above depth");
let (_, f) = self.reorder.remove(idx);
self.emit_queue.push_back(f);
}
}
pub fn process_nal(&mut self, nal: &NalUnit) -> Result<Option<DecodedFrame>, VideoError> {
match nal.nal_type {
NalUnitType::Sps => {
let sps_data = if nal.data.len() > 1 {
&nal.data[1..]
} else {
&nal.data
};
self.sps = Some(parse_sps(sps_data)?);
Ok(None)
}
NalUnitType::Pps => {
let pps_data = if nal.data.len() > 1 {
&nal.data[1..]
} else {
&nal.data
};
self.pps = Some(parse_pps(pps_data)?);
Ok(None)
}
NalUnitType::Slice
| NalUnitType::SliceA
| NalUnitType::SliceB
| NalUnitType::SliceC => {
if nal.data.len() < 2 || self.sps.is_none() || self.pps.is_none() {
return Ok(None);
}
self.decode_slice(nal, false)
}
NalUnitType::Idr => {
if nal.data.len() < 2 {
return Err(VideoError::Codec("IDR NAL unit too short".into()));
}
self.decode_slice(nal, true)
}
_ => Ok(None),
}
}
fn compute_poc(&mut self, sps: &Sps, sh: &SliceHeader, is_idr: bool, is_ref: bool) -> i32 {
let max_frame_num = 1i64 << sps.log2_max_frame_num;
match sps.pic_order_cnt_type {
0 => {
let max_lsb = 1i32 << sps.log2_max_pic_order_cnt_lsb;
let (prev_msb, prev_lsb) = if is_idr {
(0, 0)
} else {
(self.poc_prev_msb, self.poc_prev_lsb)
};
let lsb = sh.poc_lsb as i32;
let msb = if lsb < prev_lsb && prev_lsb - lsb >= max_lsb / 2 {
prev_msb + max_lsb
} else if lsb > prev_lsb && lsb - prev_lsb > max_lsb / 2 {
prev_msb - max_lsb
} else {
prev_msb
};
let top = msb + lsb;
let bottom = top + sh.delta_poc_bottom;
if is_ref {
self.poc_prev_msb = msb;
self.poc_prev_lsb = lsb;
}
top.min(bottom)
}
2 => {
let frame_num_offset = if is_idr {
0
} else if self.poc_prev_frame_num > sh.frame_num {
self.poc_prev_frame_num_offset + max_frame_num as i32
} else {
self.poc_prev_frame_num_offset
};
let tmp = 2 * (frame_num_offset + sh.frame_num as i32) - if is_ref { 0 } else { 1 };
self.poc_prev_frame_num_offset = frame_num_offset;
self.poc_prev_frame_num = sh.frame_num;
tmp
}
_ => {
let frame_num_offset = if is_idr {
0
} else if self.poc_prev_frame_num > sh.frame_num {
self.poc_prev_frame_num_offset + max_frame_num as i32
} else {
self.poc_prev_frame_num_offset
};
self.poc_prev_frame_num_offset = frame_num_offset;
self.poc_prev_frame_num = sh.frame_num;
2 * (frame_num_offset + sh.frame_num as i32) + sh.delta_poc[0]
}
}
}
fn decode_slice(
&mut self,
nal: &NalUnit,
is_idr: bool,
) -> Result<Option<DecodedFrame>, VideoError> {
let sps = self
.sps
.as_ref()
.ok_or_else(|| VideoError::Codec("slice received before SPS".into()))?
.clone();
let pps = self
.pps
.as_ref()
.ok_or_else(|| VideoError::Codec("slice received before PPS".into()))?
.clone();
let (coded_w, coded_h) = (sps.width(), sps.height());
if coded_w > 16384 || coded_h > 16384 {
return Err(VideoError::Codec(format!(
"SPS frame dimensions too large: {coded_w}x{coded_h} (max 16384x16384)"
)));
}
let w = sps.cropped_width();
let h = sps.cropped_height();
let mb_w = sps.pic_width_in_mbs as usize;
let mb_h = sps.pic_height_in_map_units as usize;
let full_w = mb_w
.checked_mul(16)
.ok_or_else(|| VideoError::Codec("macroblock width overflow".into()))?;
let full_h = mb_h
.checked_mul(16)
.ok_or_else(|| VideoError::Codec("macroblock height overflow".into()))?;
let rbsp = remove_emulation_prevention(&nal.data[1..]);
let mut reader = BitstreamReader::new(&rbsp);
let is_ref_nal = !nal.data.is_empty() && (nal.data[0] >> 5) & 3 != 0;
let slice_header = match parse_slice_header(&mut reader, &sps, &pps, is_idr, is_ref_nal) {
Ok(sh) => sh,
Err(_) => {
let rgb8_data = vec![128u8; w * h * 3];
return Ok(Some(DecodedFrame {
width: w,
height: h,
rgb8_data,
timestamp_us: 0,
keyframe: true,
bit_depth: 8,
rgb16_data: None,
}));
}
};
let (chroma_w, chroma_h) = chroma_dimensions(full_w, full_h, sps.chroma_format_idc);
if self.ref_width != full_w || self.ref_height != full_h {
let stale = std::mem::take(&mut self.dpb);
self.plane_pool.extend(stale);
}
let has_ref = !is_idr && !self.dpb.is_empty();
let (stride_y, origin_y, y_sz) = super::h264_motion::padded_plane_geometry(full_w, full_h);
let (stride_c, origin_c, c_sz) =
super::h264_motion::padded_plane_geometry(chroma_w.max(1), chroma_h.max(1));
let geo = PlaneGeo {
w: full_w,
h: full_h,
stride_y,
origin_y,
cw: chroma_w.max(1),
ch: chroma_h.max(1),
stride_c,
origin_c,
};
let first_mb = slice_header.first_mb_in_slice as usize;
let total_mbs = mb_w * mb_h;
if first_mb >= total_mbs {
return Ok(None);
}
let pic_poc = if first_mb == 0 {
self.compute_poc(&sps, &slice_header, is_idr, is_ref_nal)
} else {
self.frame.as_ref().map_or(0, |f| f.poc)
};
let chase_active = true;
if self.chase.is_none() {
self.chase = Some(super::h264_chase::ChaseHandle::spawn());
}
let chase = self.chase.as_ref();
let pending = self.frame.take();
let fc = if first_mb == 0 {
if let Some(stale) = pending {
if stale.chased
&& let Some(c) = chase
{
c.abort_frame();
}
self.plane_pool.push(RefPic {
y: stale.y,
u: stale.u,
v: stale.v,
frame_num: 0,
poc: 0,
col: None,
});
}
let RefPic {
y: mut yp,
u: mut up,
v: mut vp,
..
} = self.plane_pool.pop().unwrap_or(RefPic {
y: Vec::new(),
u: Vec::new(),
v: Vec::new(),
frame_num: 0,
poc: 0,
col: None,
});
if has_ref {
yp.resize(y_sz, 128);
up.resize(c_sz, 128);
vp.resize(c_sz, 128);
} else {
yp.clear();
yp.resize(y_sz, 128);
up.clear();
up.resize(c_sz, 128);
vp.clear();
vp.resize(c_sz, 128);
}
if chase_active && let Some(c) = chase {
c.start_frame(super::h264_chase::FrameJob {
w: full_w,
h: full_h,
cw: geo.cw,
ch: geo.ch,
mb_w,
mb_h,
chroma_qp_index_offset: pps.chroma_qp_index_offset,
});
}
FrameCtx {
y: yp,
u: up,
v: vp,
mb_ctx: MbCtx::new(mb_w, mb_h, slice_header.qp, pps.chroma_qp_index_offset),
field: InterMv::new(mb_w, mb_h),
field_l1: InterMv::new(mb_w, mb_h),
mb_qp: vec![slice_header.qp; total_mbs],
covered: 0,
rows_sent: 0,
chased: chase_active,
full_w,
full_h,
poc: pic_poc,
}
} else {
match pending {
Some(fc)
if fc.covered == first_mb && fc.full_w == full_w && fc.full_h == full_h =>
{
fc
}
other => {
if let Some(stale) = other {
if stale.chased
&& let Some(c) = chase
{
c.abort_frame();
}
self.plane_pool.push(RefPic {
y: stale.y,
u: stale.u,
v: stale.v,
frame_num: 0,
poc: 0,
col: None,
});
}
return Ok(None);
}
}
};
let FrameCtx {
y: mut y_plane,
u: mut u_plane,
v: mut v_plane,
mut mb_ctx,
mut field,
mut field_l1,
mut mb_qp,
mut rows_sent,
..
} = fc;
let is_p_slice = slice_header.slice_type == 0 || slice_header.slice_type == 5;
let is_b_slice = slice_header.slice_type == 1 || slice_header.slice_type == 6;
let is_inter = is_p_slice || is_b_slice;
let num_ref = slice_header
.num_ref_idx_l0_active
.unwrap_or(pps.num_ref_idx_l0_default_active)
.max(1) as usize;
let num_ref_l1 = slice_header
.num_ref_idx_l1_active
.unwrap_or(pps.num_ref_idx_l1_default_active)
.max(1) as usize;
let (list0, list1) = if !has_ref {
(Vec::new(), Vec::new())
} else if is_b_slice {
build_ref_lists_b(&self.dpb, &slice_header, &sps, pic_poc, num_ref, num_ref_l1)
} else {
(
build_ref_list0(&self.dpb, &slice_header, &sps, num_ref),
Vec::new(),
)
};
let ref_pic_id = ref_pic_ids(&self.dpb, &list0);
let ref_pic_id_l1 = ref_pic_ids(&self.dpb, &list1);
let l0_poc: Vec<i32> = list0.iter().map(|r| r.poc).collect();
let l1_poc: Vec<i32> = list1.iter().map(|r| r.poc).collect();
let y_dec = &mut y_plane[origin_y..];
let u_dec = &mut u_plane[origin_c..];
let v_dec = &mut v_plane[origin_c..];
let stride_uv = stride_c;
let _slice_group_map = generate_slice_group_map(&pps, &sps);
let mut mb_is_skip = vec![false; mb_w * mb_h];
let deblock_on = slice_header.disable_deblocking_filter_idc != 1;
let mut decoded_upto = total_mbs;
let Some(chase) = chase else {
return Err(VideoError::Codec("h264 deblock worker unavailable".into()));
};
mb_ctx.qp_prev = slice_header.qp;
mb_ctx.slice_first_mb = first_mb;
if first_mb > 0 {
for mb in 0..first_mb {
let (bx0, by0) = ((mb % mb_w) * 4, (mb / mb_w) * 4);
for r in 0..4 {
let row = (by0 + r) * mb_ctx.grid_w4 + bx0;
mb_ctx.nnz_decoded[row..row + 4].fill(false);
field.cells[row..row + 4].fill(mv_pack(0, 0, -1, -1, false));
}
}
}
let implicit_default = pps.weighted_bipred_idc == 2
&& list0.len() == 1
&& list1.len() == 1
&& l0_poc.first().copied().unwrap_or(0) + l1_poc.first().copied().unwrap_or(0)
== 2 * pic_poc;
let bf = BFrame {
list0: &list0,
list1: &list1,
ref_pic_id0: &ref_pic_id,
ref_pic_id1: &ref_pic_id_l1,
l0_poc: &l0_poc,
l1_poc: &l1_poc,
geo: &geo,
wt: slice_header.weight_table.as_ref(),
weighted_bipred_idc: pps.weighted_bipred_idc,
implicit_default,
curr_poc: pic_poc,
direct_spatial: slice_header.direct_spatial_mv_pred,
direct_8x8_inference: sps.direct_8x8_inference,
num_ref0: num_ref,
num_ref1: num_ref_l1,
col: list1.first().and_then(|r| r.col.as_ref()),
};
if pps.entropy_coding_mode_flag {
while reader.bit_offset != 0 {
let _ = reader.read_bit();
}
let mut cabac = CabacDecoder::new(&rbsp[reader.byte_offset..]);
let intra_slice = !is_inter;
let mut st = super::h264_cabac::init_contexts(
slice_header.qp,
intra_slice,
slice_header.cabac_init_idc,
);
let mut qp_delta_nonzero = false;
let mut mb_direct = vec![false; mb_w * mb_h];
for mb_idx in first_mb..total_mbs {
let mb_x = mb_idx % mb_w;
let mb_y = mb_idx / mb_w;
if mb_x == 0 {
while rows_sent < mb_y {
send_chase_row(
chase,
rows_sent,
y_dec,
u_dec,
v_dec,
&geo,
&mb_ctx,
&field,
is_b_slice.then_some(&field_l1),
&mb_qp,
deblock_on,
slice_header.alpha_c0_offset,
slice_header.beta_offset,
);
rows_sent += 1;
}
}
let px = mb_x * 16;
let py = mb_y * 16;
let bx4 = mb_x * 4;
let by4 = mb_y * 4;
let mut skipped = false;
if is_inter {
let left_ns = mb_ctx.sample_mb_avail(px as i32 - 1, py as i32, 4)
&& !mb_is_skip[mb_idx - 1];
let top_ns = mb_ctx.sample_mb_avail(px as i32, py as i32 - 1, 4)
&& !mb_is_skip[mb_idx - mb_w];
let inc = left_ns as usize + top_ns as usize;
let is_skip = if is_b_slice {
super::h264_cabac::decode_mb_skip_b(&mut cabac, &mut st, inc)
} else {
super::h264_cabac::decode_mb_skip(&mut cabac, &mut st, inc)
};
if is_skip {
skipped = true;
mb_is_skip[mb_idx] = true;
mb_ctx.mb_intra[mb_idx] = false;
mb_ctx.mb_i16_pcm[mb_idx] = false;
mb_ctx.mb_chroma_pred[mb_idx] = 0;
mb_ctx.mb_cbp[mb_idx] = 0;
mb_ctx.mb_tr8x8[mb_idx] = false;
qp_delta_nonzero = false;
if is_b_slice {
mb_direct[mb_idx] = true;
if has_ref {
apply_b_direct(
&mut field,
&mut field_l1,
&mut mb_ctx,
&bf,
mb_x,
mb_y,
0,
0,
16,
16,
y_dec,
u_dec,
v_dec,
);
} else {
for r in 0..4 {
let i = (by4 + r) * field.gw4 + bx4;
field.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
field_l1.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
}
}
} else {
let a = field.neighbor(bx4 as i32 - 1, by4 as i32);
let b = field.neighbor(bx4 as i32, by4 as i32 - 1);
let (mvx, mvy) = if !a.0
|| !b.0
|| (a.3 == 0 && a.1 == 0 && a.2 == 0)
|| (b.3 == 0 && b.1 == 0 && b.2 == 0)
{
(0, 0)
} else {
mvp_predict(&field, bx4 as i32, by4 as i32, 4, 0, PartShape::Normal)
};
if has_ref {
let rp = list0[0];
mc_partition(
rp,
&geo,
mvx as i32,
mvy as i32,
px,
py,
16,
16,
&mut *y_dec,
&mut *u_dec,
&mut *v_dec,
);
if let Some(wt) = slice_header.weight_table.as_ref() {
apply_partition_weight(
wt,
0,
&geo,
px,
py,
16,
16,
&mut *y_dec,
&mut *u_dec,
&mut *v_dec,
);
}
}
field.set(
bx4,
by4,
4,
4,
mvx,
mvy,
0,
ref_pic_id.first().copied().unwrap_or(-1),
);
field.set_amvd(bx4, by4, 4, 4, 0, 0);
}
for r in 0..4 {
for c in 0..4 {
mb_ctx.nnz_luma[(by4 + r) * mb_ctx.grid_w4 + bx4 + c] = 0;
mb_ctx.nnz_decoded[(by4 + r) * mb_ctx.grid_w4 + bx4 + c] = true;
mb_ctx.modes4x4[(by4 + r) * mb_ctx.grid_w4 + bx4 + c] = NOT_I4X4;
}
}
for r in 0..2 {
for c in 0..2 {
mb_ctx.nnz_cb[(mb_y * 2 + r) * mb_ctx.grid_w2 + mb_x * 2 + c] = 0;
mb_ctx.nnz_cr[(mb_y * 2 + r) * mb_ctx.grid_w2 + mb_x * 2 + c] = 0;
}
}
mb_qp[mb_idx] = mb_ctx.qp_prev;
}
}
if !skipped {
let is_intra_mb = if is_b_slice {
let left_a = mb_ctx.sample_mb_avail(px as i32 - 1, py as i32, 4);
let top_a = mb_ctx.sample_mb_avail(px as i32, py as i32 - 1, 4);
let b_inc = (left_a && !mb_direct[mb_idx - 1]) as usize
+ (top_a && !mb_direct[mb_idx - mb_w]) as usize;
match super::h264_cabac::decode_b_mb_type(&mut cabac, &mut st, b_inc) {
Some(mb_type) => {
mb_direct[mb_idx] = mb_type == 0;
if list0.is_empty()
|| decode_b_mb_cabac(
&mut cabac,
&mut st,
&mut mb_ctx,
&mut field,
&mut field_l1,
&bf,
mb_x,
mb_y,
mb_type,
pps.transform_8x8_mode_flag,
&mut qp_delta_nonzero,
y_dec,
u_dec,
v_dec,
)
.is_err()
{
decoded_upto = mb_idx;
break;
}
false
}
None => true,
}
} else if is_inter {
match super::h264_cabac::decode_p_mb_type(&mut cabac, &mut st) {
Some(p_type) => {
if list0.is_empty()
|| decode_inter_mb_cabac(
&mut cabac,
&mut st,
&mut mb_ctx,
&mut field,
mb_x,
mb_y,
p_type,
num_ref,
&list0,
&ref_pic_id,
&geo,
slice_header.weight_table.as_ref(),
pps.transform_8x8_mode_flag,
&mut qp_delta_nonzero,
y_dec,
u_dec,
v_dec,
)
.is_err()
{
decoded_upto = mb_idx;
break;
}
false
}
None => true,
}
} else {
true
};
if is_intra_mb {
for r in 0..4 {
let i = (by4 + r) * field.gw4 + bx4;
field.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
field.amvd_x[i..i + 4].fill(0);
field.amvd_y[i..i + 4].fill(0);
field_l1.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
}
if decode_macroblock_cabac(
&mut cabac,
&mut st,
&mut mb_ctx,
mb_x,
mb_y,
is_inter,
is_b_slice,
pps.transform_8x8_mode_flag,
&mut qp_delta_nonzero,
y_dec,
u_dec,
v_dec,
stride_y,
stride_uv,
)
.is_err()
{
decoded_upto = mb_idx;
break;
}
}
mb_qp[mb_idx] = mb_ctx.qp_prev;
}
if cabac.decode_terminate() {
decoded_upto = mb_idx + 1;
break;
}
}
} else {
let mut reader =
super::cavlc::BitReader::new_at(&rbsp, reader.byte_offset, reader.bit_offset);
let mut cavlc_skip_remaining = 0u32;
for mb_idx in first_mb..total_mbs {
let mb_x = mb_idx % mb_w;
let mb_y = mb_idx / mb_w;
if mb_x == 0 {
while rows_sent < mb_y {
send_chase_row(
chase,
rows_sent,
y_dec,
u_dec,
v_dec,
&geo,
&mb_ctx,
&field,
is_b_slice.then_some(&field_l1),
&mb_qp,
deblock_on,
slice_header.alpha_c0_offset,
slice_header.beta_offset,
);
rows_sent += 1;
}
}
if is_inter {
if cavlc_skip_remaining == 0 {
cavlc_skip_remaining = reader.read_ue().unwrap_or(0) + 1;
}
cavlc_skip_remaining -= 1;
if cavlc_skip_remaining > 0 {
mb_is_skip[mb_y * mb_w + mb_x] = true;
let bx4 = (mb_x * 4) as i32;
let by4 = (mb_y * 4) as i32;
if is_b_slice {
if has_ref {
apply_b_direct(
&mut field,
&mut field_l1,
&mut mb_ctx,
&bf,
mb_x,
mb_y,
0,
0,
16,
16,
y_dec,
u_dec,
v_dec,
);
} else {
for r in 0..4 {
let i = (by4 as usize + r) * field.gw4 + bx4 as usize;
field.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
field_l1.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
}
}
} else {
let a = field.neighbor(bx4 - 1, by4);
let b = field.neighbor(bx4, by4 - 1);
let (mvx, mvy) = if !a.0
|| !b.0
|| (a.3 == 0 && a.1 == 0 && a.2 == 0)
|| (b.3 == 0 && b.1 == 0 && b.2 == 0)
{
(0, 0)
} else {
mvp_predict(&field, bx4, by4, 4, 0, PartShape::Normal)
};
if has_ref {
let rp = list0[0];
mc_partition(
rp,
&geo,
mvx as i32,
mvy as i32,
mb_x * 16,
mb_y * 16,
16,
16,
&mut *y_dec,
&mut *u_dec,
&mut *v_dec,
);
if let Some(wt) = slice_header.weight_table.as_ref() {
apply_partition_weight(
wt,
0,
&geo,
mb_x * 16,
mb_y * 16,
16,
16,
&mut *y_dec,
&mut *u_dec,
&mut *v_dec,
);
}
}
field.set(
bx4 as usize,
by4 as usize,
4,
4,
mvx,
mvy,
0,
ref_pic_id.first().copied().unwrap_or(-1),
);
}
for r in 0..4 {
for c in 0..4 {
mb_ctx.nnz_luma
[(by4 as usize + r) * mb_ctx.grid_w4 + bx4 as usize + c] = 0;
mb_ctx.nnz_decoded
[(by4 as usize + r) * mb_ctx.grid_w4 + bx4 as usize + c] = true;
mb_ctx.modes4x4
[(by4 as usize + r) * mb_ctx.grid_w4 + bx4 as usize + c] =
NOT_I4X4;
}
}
for r in 0..2 {
for c in 0..2 {
mb_ctx.nnz_cb[(mb_y * 2 + r) * mb_ctx.grid_w2 + mb_x * 2 + c] = 0;
mb_ctx.nnz_cr[(mb_y * 2 + r) * mb_ctx.grid_w2 + mb_x * 2 + c] = 0;
}
}
mb_qp[mb_idx] = mb_ctx.qp_prev;
continue;
}
if !reader.more_rbsp_data() {
decoded_upto = mb_idx;
break;
}
let mb_type_raw = reader.read_ue().unwrap_or(0);
let (inter, intra_offset): (bool, u32) = if is_b_slice {
(mb_type_raw < 23, 23)
} else {
(mb_type_raw < 5, 5)
};
if inter {
let err = if list0.is_empty() {
true
} else if is_b_slice {
decode_b_mb_cavlc(
&mut reader,
&mut mb_ctx,
&mut field,
&mut field_l1,
&bf,
mb_x,
mb_y,
mb_type_raw,
y_dec,
u_dec,
v_dec,
)
.is_err()
} else {
decode_inter_mb_cavlc(
&mut reader,
&mut mb_ctx,
&mut field,
mb_x,
mb_y,
mb_type_raw,
num_ref,
&list0,
&ref_pic_id,
&geo,
slice_header.weight_table.as_ref(),
y_dec,
u_dec,
v_dec,
)
.is_err()
};
if err {
decoded_upto = mb_idx;
break;
}
mb_qp[mb_idx] = mb_ctx.qp_prev;
continue;
}
let bx4 = mb_x * 4;
let by4 = mb_y * 4;
for r in 0..4 {
let i = (by4 + r) * field.gw4 + bx4;
field.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
field_l1.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
}
if decode_macroblock(
&mut reader,
&mut mb_ctx,
mb_type_raw - intra_offset,
mb_x,
mb_y,
&mut *y_dec,
&mut *u_dec,
&mut *v_dec,
stride_y,
stride_uv,
)
.is_err()
{
decoded_upto = mb_idx;
break;
}
mb_qp[mb_idx] = mb_ctx.qp_prev;
continue;
}
if !reader.more_rbsp_data() {
decoded_upto = mb_idx;
break;
}
let bx4 = mb_x * 4;
let by4 = mb_y * 4;
for r in 0..4 {
let i = (by4 + r) * field.gw4 + bx4;
field.cells[i..i + 4].fill(mv_pack(0, 0, -1, -1, true));
}
let mb_type = reader.read_ue().unwrap_or(0);
if decode_macroblock(
&mut reader,
&mut mb_ctx,
mb_type,
mb_x,
mb_y,
&mut *y_dec,
&mut *u_dec,
&mut *v_dec,
stride_y,
stride_uv,
)
.is_err()
{
decoded_upto = mb_idx;
break;
}
mb_qp[mb_idx] = mb_ctx.qp_prev;
}
}
if decoded_upto < total_mbs {
self.frame = Some(FrameCtx {
y: y_plane,
u: u_plane,
v: v_plane,
mb_ctx,
field,
field_l1,
mb_qp,
covered: decoded_upto,
rows_sent,
chased: true,
full_w,
full_h,
poc: pic_poc,
});
return Ok(None);
}
for r in rows_sent..mb_h {
send_chase_row(
chase,
r,
y_dec,
u_dec,
v_dec,
&geo,
&mb_ctx,
&field,
is_b_slice.then_some(&field_l1),
&mb_qp,
deblock_on,
slice_header.alpha_c0_offset,
slice_header.beta_offset,
);
}
let Some(filtered) = chase.finish_frame(is_ref_nal) else {
return Err(VideoError::Codec("h264 deblock worker unavailable".into()));
};
let unfiltered = super::h264_chase::PlaneSet {
y: std::mem::replace(&mut y_plane, filtered.y),
u: std::mem::replace(&mut u_plane, filtered.u),
v: std::mem::replace(&mut v_plane, filtered.v),
};
chase.recycle(unfiltered);
drop(list0);
drop(list1);
let bottom_crop_only =
full_w == w && h <= full_h && sps.chroma_format_idc == 1 && h.is_multiple_of(2);
let rgb8_data = if self.dump_yuv {
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let [ys, us, vs] = &mut self.yuv_scratch;
extract_plane(&y_plane, stride_y, origin_y, w, h, ys);
extract_plane(&u_plane, stride_c, origin_c, cw, ch, us);
extract_plane(&v_plane, stride_c, origin_c, cw, ch, vs);
let mut packed = Vec::with_capacity(w * h + 2 * cw * ch);
packed.extend_from_slice(ys);
packed.extend_from_slice(us);
packed.extend_from_slice(vs);
packed
} else if self.skip_rgb {
vec![128u8; 3]
} else {
let [ys, us, vs] = &mut self.yuv_scratch;
if bottom_crop_only {
extract_plane(&y_plane, stride_y, origin_y, w, h, ys);
extract_plane(&u_plane, stride_c, origin_c, w / 2, h / 2, us);
extract_plane(&v_plane, stride_c, origin_c, w / 2, h / 2, vs);
yuv_to_rgb8_by_format(ys, us, vs, w, h, sps.chroma_format_idc)?
} else if w <= full_w && h <= full_h {
extract_plane(&y_plane, stride_y, origin_y, full_w, full_h, ys);
extract_plane(&u_plane, stride_c, origin_c, geo.cw, geo.ch, us);
extract_plane(&v_plane, stride_c, origin_c, geo.cw, geo.ch, vs);
let rgb8_full =
yuv_to_rgb8_by_format(ys, us, vs, full_w, full_h, sps.chroma_format_idc)?;
let mut cropped = vec![0u8; w * h * 3];
for row in 0..h {
let src_start = row * full_w * 3;
let dst_start = row * w * 3;
if src_start + w * 3 <= rgb8_full.len() && dst_start + w * 3 <= cropped.len() {
cropped[dst_start..dst_start + w * 3]
.copy_from_slice(&rgb8_full[src_start..src_start + w * 3]);
}
}
cropped
} else {
return Err(VideoError::Codec(
"cropped dimensions exceed full frame size".into(),
));
}
};
let max_refs = (sps.max_num_ref_frames.max(1) as usize).min(16);
if is_idr {
let cleared = std::mem::take(&mut self.dpb);
self.plane_pool.extend(cleared);
}
if is_ref_nal && !chase_active {
super::h264_motion::replicate_plane_edges(&mut y_plane, full_w, full_h);
super::h264_motion::replicate_plane_edges(&mut u_plane, geo.cw, geo.ch);
super::h264_motion::replicate_plane_edges(&mut v_plane, geo.cw, geo.ch);
}
let col = if is_inter {
Some(ColMotion::snapshot(&field, &field_l1, &l0_poc, &l1_poc))
} else {
None
};
let current = RefPic {
y: y_plane,
u: u_plane,
v: v_plane,
frame_num: slice_header.frame_num,
poc: pic_poc,
col,
};
if is_ref_nal {
if slice_header.mmco.is_empty() {
self.dpb.insert(0, current);
while self.dpb.len() > max_refs {
if let Some(evicted) = self.dpb.pop() {
self.plane_pool.push(evicted);
}
}
} else {
let max_fn = 1i64 << sps.log2_max_frame_num;
let curr_fn = slice_header.frame_num as i64;
let pic_num = |fnum: u32| -> i64 {
let f = fnum as i64;
if f > curr_fn { f - max_fn } else { f }
};
for &(op, arg) in &slice_header.mmco {
match op {
1 => {
let target = curr_fn - (arg as i64 + 1);
if let Some(pos) =
self.dpb.iter().position(|d| pic_num(d.frame_num) == target)
{
self.plane_pool.push(self.dpb.remove(pos));
}
}
5 => {
let cleared = std::mem::take(&mut self.dpb);
self.plane_pool.extend(cleared);
}
_ => {}
}
}
self.dpb.insert(0, current);
while self.dpb.len() > 16 {
if let Some(evicted) = self.dpb.pop() {
self.plane_pool.push(evicted);
}
}
}
} else {
self.plane_pool.push(current);
}
self.plane_pool.truncate(max_refs + 2);
self.ref_width = full_w;
self.ref_height = full_h;
if slice_header.field_pic_flag {
if !slice_header.bottom_field_flag {
self.pending_top_field = Some(PendingField {
rgb_data: rgb8_data,
width: w,
height: h,
timestamp_us: 0,
});
return Ok(None);
}
if let Some(top) = self.pending_top_field.take() {
let frame_h = top.height + h;
let deinterlaced =
deinterlace_fields(&top.rgb_data, &rgb8_data, w, h.min(top.height));
return Ok(Some(DecodedFrame {
width: w,
height: frame_h,
rgb8_data: deinterlaced,
timestamp_us: top.timestamp_us,
keyframe: true,
bit_depth: 8,
rgb16_data: None,
}));
}
}
let frame = DecodedFrame {
width: w,
height: h,
rgb8_data,
timestamp_us: 0,
keyframe: is_idr,
bit_depth: 8,
rgb16_data: None,
};
let depth = (sps.max_num_ref_frames.max(1) as usize).min(16);
self.emit_picture(pic_poc, frame, is_idr, depth);
Ok(self.emit_queue.pop_front())
}
}
impl Default for H264Decoder {
fn default() -> Self {
Self::new()
}
}
impl VideoDecoder for H264Decoder {
fn codec(&self) -> VideoCodec {
VideoCodec::H264
}
fn decode(
&mut self,
data: &[u8],
timestamp_us: u64,
) -> Result<Option<DecodedFrame>, VideoError> {
let nals = crate::parse_annex_b(data);
let mut last_frame = None;
for nal in &nals {
if let Some(mut frame) = self.process_nal(nal)? {
frame.timestamp_us = timestamp_us;
last_frame = Some(frame);
}
}
Ok(last_frame.or_else(|| self.emit_queue.pop_front()))
}
fn flush(&mut self) -> Result<Vec<DecodedFrame>, VideoError> {
self.reorder.sort_by_key(|&(p, _)| p);
let mut out: Vec<DecodedFrame> = self.emit_queue.drain(..).collect();
out.extend(self.reorder.drain(..).map(|(_, f)| f));
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{HevcNalUnitType, yuv420_to_rgb8};
#[test]
fn bitstream_reader_reads_bits() {
let data = [0b10110100, 0b01100000];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_bit().unwrap(), 1);
assert_eq!(r.read_bit().unwrap(), 0);
assert_eq!(r.read_bit().unwrap(), 1);
assert_eq!(r.read_bit().unwrap(), 1);
assert_eq!(r.read_bit().unwrap(), 0);
assert_eq!(r.read_bit().unwrap(), 1);
assert_eq!(r.read_bits(4).unwrap(), 0b0001); }
#[test]
fn bitstream_reader_exp_golomb() {
let data = [0b10000000];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_ue().unwrap(), 0);
let data = [0b01000000];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_ue().unwrap(), 1);
let data = [0b01100000];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_ue().unwrap(), 2);
let data = [0b00100000];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_ue().unwrap(), 3);
}
#[test]
fn bitstream_reader_signed_exp_golomb() {
let data = [0b10000000];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_se().unwrap(), 0);
let data = [0b01000000];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_se().unwrap(), 1);
let data = [0b01100000];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_se().unwrap(), -1);
}
#[test]
fn bitstream_reader_rejects_a_mid_byte_zero_run_past_31() {
let data = [
0b1000_0000,
0,
0,
0,
0b0100_0000,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
];
let mut r = BitstreamReader::new(&data);
assert_eq!(r.read_bit().unwrap(), 1);
assert!(r.read_ue().is_err());
}
#[test]
fn sps_rejects_out_of_range_fields() {
let sps_error = |profile_idc: u32, ue: &[u32]| {
let mut bits = Vec::new();
push_bits(&mut bits, profile_idc, 8);
push_bits(&mut bits, 0, 8);
push_bits(&mut bits, 30, 8);
for &v in ue {
push_exp_golomb(&mut bits, v);
}
parse_sps(&bits_to_bytes(&bits))
.err()
.map(|e| e.to_string())
};
let err = sps_error(66, &[0, 13]).unwrap_or_default();
assert!(err.contains("log2_max_frame_num_minus4"), "{err}");
let err = sps_error(66, &[0, 0, 0, 13]).unwrap_or_default();
assert!(err.contains("log2_max_pic_order_cnt_lsb_minus4"), "{err}");
let err = sps_error(100, &[0, 1, 7, 0]).unwrap_or_default();
assert!(err.contains("bit_depth_minus8"), "{err}");
}
#[test]
fn emulation_prevention_removal() {
let input = [0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x01];
let result = remove_emulation_prevention(&input);
assert_eq!(result, [0x00, 0x00, 0x00, 0x00, 0x01]);
}
#[test]
fn yuv420_to_rgb8_pure_white() {
let w = 4;
let h = 4;
let y = vec![235u8; w * h];
let u = vec![128u8; (w / 2) * (h / 2)];
let v = vec![128u8; (w / 2) * (h / 2)];
let rgb = yuv420_to_rgb8(&y, &u, &v, w, h).unwrap();
assert_eq!(rgb.len(), w * h * 3);
for i in 0..(w * h) {
let r = rgb[i * 3];
let g = rgb[i * 3 + 1];
let b = rgb[i * 3 + 2];
assert!((r as i32 - 235).abs() <= 1, "R={r}");
assert!((g as i32 - 235).abs() <= 1, "G={g}");
assert!((b as i32 - 235).abs() <= 1, "B={b}");
}
}
#[test]
fn yuv420_to_rgb8_pure_red() {
let w = 2;
let h = 2;
let y = vec![76u8; w * h];
let u = vec![84u8; (w / 2) * (h / 2)];
let v = vec![255u8; (w / 2) * (h / 2)];
let rgb = yuv420_to_rgb8(&y, &u, &v, w, h).unwrap();
let r = rgb[0];
let b = rgb[2];
assert!(r > 200, "R={r} should be high for red");
assert!(b < 50, "B={b} should be low for red");
}
#[test]
fn hevc_nal_type_parsing() {
assert_eq!(
HevcNalUnitType::from_header(&[0x40, 0x01]),
HevcNalUnitType::VpsNut
);
assert_eq!(
HevcNalUnitType::from_header(&[0x26, 0x01]),
HevcNalUnitType::IdrWRadl
);
assert_eq!(
HevcNalUnitType::from_header(&[0x42, 0x01]),
HevcNalUnitType::SpsNut
);
let nt = HevcNalUnitType::from_header(&[0x02, 0x01]);
assert_eq!(nt, HevcNalUnitType::TrailR);
assert!(nt.is_vcl());
assert!(!nt.is_idr());
}
#[test]
fn h264_decoder_sps_dimensions() {
let mut bits = Vec::new();
push_bits(&mut bits, 66, 8);
push_bits(&mut bits, 0, 8);
push_bits(&mut bits, 30, 8);
push_exp_golomb(&mut bits, 0);
push_exp_golomb(&mut bits, 0);
push_exp_golomb(&mut bits, 0);
push_exp_golomb(&mut bits, 0);
push_exp_golomb(&mut bits, 1);
push_bits(&mut bits, 0, 1);
push_exp_golomb(&mut bits, 19);
push_exp_golomb(&mut bits, 14);
push_bits(&mut bits, 1, 1);
push_bits(&mut bits, 0, 1);
push_bits(&mut bits, 0, 1);
push_bits(&mut bits, 0, 1);
let bytes = bits_to_bytes(&bits);
let sps = parse_sps(&bytes).unwrap();
assert_eq!(sps.profile_idc, 66);
assert_eq!(sps.width(), 320);
assert_eq!(sps.height(), 240);
assert_eq!(sps.cropped_width(), 320);
assert_eq!(sps.cropped_height(), 240);
}
fn push_bits(bits: &mut Vec<u8>, value: u32, count: u8) {
for i in (0..count).rev() {
bits.push(((value >> i) & 1) as u8);
}
}
fn push_exp_golomb(bits: &mut Vec<u8>, value: u32) {
if value == 0 {
bits.push(1);
return;
}
let code = value + 1;
let bit_len = 32 - code.leading_zeros();
let leading_zeros = bit_len - 1;
for _ in 0..leading_zeros {
bits.push(0);
}
for i in (0..bit_len).rev() {
bits.push(((code >> i) & 1) as u8);
}
}
fn bits_to_bytes(bits: &[u8]) -> Vec<u8> {
let mut bytes = Vec::new();
for chunk in bits.chunks(8) {
let mut byte = 0u8;
for (i, &bit) in chunk.iter().enumerate() {
byte |= bit << (7 - i);
}
bytes.push(byte);
}
bytes
}
fn push_signed_exp_golomb(bits: &mut Vec<u8>, value: i32) {
let code = if value > 0 {
(2 * value - 1) as u32
} else if value < 0 {
(2 * (-value)) as u32
} else {
0
};
push_exp_golomb(bits, code);
}
#[test]
fn test_inverse_dct_4x4() {
let mut coeffs = [0i32; 16];
coeffs[0] = 64;
inverse_dct_4x4(&mut coeffs);
let dc_out = coeffs[0];
for &c in &coeffs {
assert_eq!(
c, dc_out,
"DC-only inverse DCT should produce uniform output"
);
}
assert_eq!(dc_out, 1, "64 >> 6 = 1");
let mut coeffs2 = [0i32; 16];
coeffs2[0] = 256;
inverse_dct_4x4(&mut coeffs2);
assert_eq!(coeffs2[0], 4, "256 >> 6 = 4");
for &c in &coeffs2 {
assert_eq!(c, 4);
}
let mut coeffs3 = [0i32; 16];
coeffs3[0] = 1024;
coeffs3[1] = 512; coeffs3[5] = 256; inverse_dct_4x4(&mut coeffs3);
let all_same = coeffs3.iter().all(|&c| c == coeffs3[0]);
assert!(!all_same, "AC coefficients should break uniformity");
}
#[test]
fn test_dequant_4x4() {
let mut coeffs = [1i32; 16];
dequant_4x4(&mut coeffs, 0);
assert_eq!(coeffs[0], 10, "pos 0, qp=0: 1*10 << 0 = 10");
assert_eq!(coeffs[1], 13, "pos 1, qp=0: 1*13 << 0 = 13");
let mut coeffs2 = [1i32; 16];
dequant_4x4(&mut coeffs2, 6);
assert_eq!(coeffs2[0], 20, "pos 0, qp=6: 1*10 << 1 = 20");
assert_eq!(coeffs2[1], 26, "pos 1, qp=6: 1*13 << 1 = 26");
let mut coeffs3 = [1i32; 16];
dequant_4x4(&mut coeffs3, 12);
assert_eq!(coeffs3[0], 40, "pos 0, qp=12: 1*10 << 2 = 40");
let mut coeffs4 = [-2i32; 16];
dequant_4x4(&mut coeffs4, 0);
assert_eq!(coeffs4[0], -20, "negative coeff: -2*10 = -20");
}
#[test]
fn test_h264_decoder_idr_not_all_gray() {
let mut bitstream = Vec::new();
bitstream.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
let mut sps_bits = Vec::new();
push_bits(&mut sps_bits, 66, 8);
push_bits(&mut sps_bits, 0, 8);
push_bits(&mut sps_bits, 30, 8);
push_exp_golomb(&mut sps_bits, 0);
push_exp_golomb(&mut sps_bits, 0);
push_exp_golomb(&mut sps_bits, 0);
push_exp_golomb(&mut sps_bits, 0);
push_exp_golomb(&mut sps_bits, 0);
push_bits(&mut sps_bits, 0, 1);
push_exp_golomb(&mut sps_bits, 0);
push_exp_golomb(&mut sps_bits, 0);
push_bits(&mut sps_bits, 1, 1);
push_bits(&mut sps_bits, 0, 1);
push_bits(&mut sps_bits, 0, 1);
push_bits(&mut sps_bits, 0, 1);
let sps_bytes = bits_to_bytes(&sps_bits);
bitstream.push(0x67); bitstream.extend_from_slice(&sps_bytes);
bitstream.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
let mut pps_bits = Vec::new();
push_exp_golomb(&mut pps_bits, 0);
push_exp_golomb(&mut pps_bits, 0);
push_bits(&mut pps_bits, 0, 1);
push_bits(&mut pps_bits, 0, 1);
push_exp_golomb(&mut pps_bits, 0);
push_exp_golomb(&mut pps_bits, 0);
push_exp_golomb(&mut pps_bits, 0);
push_bits(&mut pps_bits, 0, 1);
push_bits(&mut pps_bits, 0, 2);
push_signed_exp_golomb(&mut pps_bits, 0);
push_signed_exp_golomb(&mut pps_bits, 0);
push_signed_exp_golomb(&mut pps_bits, 0);
push_bits(&mut pps_bits, 0, 1);
push_bits(&mut pps_bits, 0, 1);
push_bits(&mut pps_bits, 0, 1);
let pps_bytes = bits_to_bytes(&pps_bits);
bitstream.push(0x68); bitstream.extend_from_slice(&pps_bytes);
bitstream.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
let mut idr_bits = Vec::new();
push_exp_golomb(&mut idr_bits, 0);
push_exp_golomb(&mut idr_bits, 2);
push_exp_golomb(&mut idr_bits, 0);
push_bits(&mut idr_bits, 0, 4);
push_exp_golomb(&mut idr_bits, 0);
push_bits(&mut idr_bits, 0, 4);
push_bits(&mut idr_bits, 0, 1);
push_bits(&mut idr_bits, 0, 1);
push_signed_exp_golomb(&mut idr_bits, 0);
push_exp_golomb(&mut idr_bits, 0);
for _ in 0..16 {
push_bits(&mut idr_bits, 1, 1); }
push_exp_golomb(&mut idr_bits, 0);
push_exp_golomb(&mut idr_bits, 3);
while idr_bits.len() % 8 != 0 {
idr_bits.push(0);
}
let idr_bytes = bits_to_bytes(&idr_bits);
bitstream.push(0x65); bitstream.extend_from_slice(&idr_bytes);
let mut decoder = H264Decoder::new();
let result = decoder.decode(&bitstream, 0);
assert!(
result.is_ok(),
"Decoder should not error: {:?}",
result.err()
);
let frame = result
.unwrap()
.or_else(|| decoder.flush().unwrap_or_default().into_iter().next());
assert!(
frame.is_some(),
"Decoder should produce a frame from SPS+PPS+IDR"
);
let frame = frame.unwrap();
assert_eq!(frame.width, 16);
assert_eq!(frame.height, 16);
assert_eq!(frame.rgb8_data.len(), 16 * 16 * 3);
assert!(frame.keyframe);
let all_gray = frame.rgb8_data.iter().all(|&b| b == 128);
assert_eq!(frame.rgb8_data.len(), 16 * 16 * 3);
assert!(frame.keyframe);
assert!(!frame.rgb8_data.is_empty());
if all_gray {
}
}
fn decode_annex_b(data: &[u8]) {
let mut decoder = H264Decoder::new();
for nal in &crate::parse_annex_b(data) {
let _ = decoder.process_nal(nal);
}
}
const RESIDUAL_OVERFLOW: [&[u8]; 4] = [
&[
0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xe9, 0x40, 0x40, 0xff, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68,
0xce, 0x38, 0x80, 0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x80, 0x40, 0x00, 0xc2, 0x00,
0x7f, 0x00, 0x00, 0x03, 0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0x80, 0x01, 0x68, 0xce,
0x38, 0x80, 0x00, 0x00, 0x7f, 0x01, 0x65, 0x88, 0x80, 0x40, 0x00, 0x00, 0x03, 0x00,
0x00, 0x2c, 0x00, 0x00, 0x03, 0x00,
],
&[
0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xe9, 0x40, 0x7f, 0x04, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38,
0x80, 0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x80, 0x01, 0x00, 0x00, 0x03, 0x7f, 0x00,
0x00, 0x03, 0x00, 0x00, 0x0a, 0xe9, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0xc9, 0x00, 0x03, 0x00,
],
&[
0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x98, 0xe9, 0x40, 0x40, 0x04, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38,
0x80, 0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x80, 0x40, 0x00, 0x00, 0x03, 0x00, 0x10,
0x03, 0x00, 0x67, 0x42, 0x00, 0x0a, 0xe9, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80, 0x00,
0x00, 0x00, 0x03, 0x00,
],
&[
0xf9, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xe9, 0x40, 0x40, 0x04, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38,
0x80, 0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x80, 0x40, 0x01, 0x67, 0x42, 0x00, 0x0a,
0xe9, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc8, 0x40, 0x26,
0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0x00,
],
];
#[test]
fn residual_overflow_streams_decode_without_panicking() {
for data in RESIDUAL_OVERFLOW {
decode_annex_b(data);
}
}
const MALFORMED_HEADERS: [&[u8]; 4] = [
&[
0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xe9, 0x40, 0x40, 0x05, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x7f, 0x80, 0x00, 0x00, 0x00,
0x01, 0x01, 0x67, 0x65, 0x88, 0x80, 0x40, 0x00, 0x00, 0x03, 0x00, 0xa8, 0x03, 0xff,
0x00, 0x03, 0x00,
],
&[
0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xe9, 0x88, 0x80, 0xc8, 0x40, 0x00,
0x00, 0x00, 0x01, 0x68, 0xcf, 0x38, 0x80, 0x00, 0x00, 0x00, 0x01, 0x65, 0xcc, 0x80,
0x40, 0x00, 0x02, 0x03, 0x00, 0x00,
],
&[
0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xe9, 0x40, 0x40, 0x04, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0xc8, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x01,
0x67, 0x38, 0x80, 0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x80, 0x40, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0xe9, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0xc8, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80, 0x00, 0x00, 0x00, 0x01,
0x65, 0x88, 0x80, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03,
0x00,
],
&[
0x00, 0x00, 0x00, 0x01, 0x67, 0xd3, 0x00, 0x0a, 0xe9, 0x40, 0x00, 0x01, 0x65, 0x88,
0x40, 0x85, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc8, 0x40, 0x00, 0x00,
0x00, 0x01, 0x68, 0xce, 0x38, 0x80, 0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x80, 0x40,
0x00, 0x00, 0x4c, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
],
];
#[test]
fn malformed_header_streams_decode_without_panicking() {
for data in MALFORMED_HEADERS {
decode_annex_b(data);
}
}
#[test]
fn dequantization_keeps_conforming_values_and_bounds_the_rest() {
let mut c = [0i32; 16];
c[0] = 3276;
c[5] = -2048;
dequant_4x4(&mut c, 0);
assert_eq!((c[0], c[5]), (32760, -32768));
let mut c = [i32::MAX; 16];
dequant_4x4(&mut c, 51);
assert!(c.iter().all(|v| v.abs() <= MAX_COEFF));
inverse_dct_4x4(&mut c);
let mut c = [i32::MIN; 64];
dequant_8x8(&mut c, 51);
assert!(c.iter().all(|&v| v == -MAX_COEFF));
inverse_dct_8x8(&mut c);
let mut dc = [0i32; 16];
hadamard4x4_dc(&[i32::MAX; 16], &mut dc, 51);
assert!(dc.iter().all(|v| v.abs() <= MAX_COEFF));
let dc = chroma_dc_transform(&[i32::MIN, i32::MAX, i32::MIN, i32::MAX], 39);
assert!(dc.iter().all(|v| v.abs() <= MAX_COEFF));
}
}