use super::bitstream::BitWriter;
use super::cavlc::encode_residual_block_cavlc;
use super::macroblock::{
inter_cbp_to_codenum_420_422, intra_cbp_to_codenum_420_422, write_i_nxn_mb,
write_p_l0_16x16_mb, INxNMcbConfig, PL016x16McbConfig,
};
use super::nal::build_nal_unit;
use super::pps::{build_baseline_pps_rbsp, BaselinePpsConfig};
use super::sps::{build_baseline_sps_rbsp, BaselineSpsConfig};
use super::transform::{zigzag_scan_4x4, zigzag_scan_4x4_ac};
use crate::cavlc::CoeffTokenContext;
use crate::intra_pred::{
predict_4x4, predict_chroma, ChromaArrayType, Intra4x4Mode, IntraChromaMode,
Neighbour4x4Availability, Samples4x4, SamplesChroma,
};
use crate::macroblock_layer::{derive_nc_luma, CavlcNcGrid, LumaNcKind};
use crate::nal::NalUnitType;
use crate::sp_transform::{
chroma_dc_switch_scale, chroma_pred_transform, encoder_quant_8_416, encoder_quant_8_428,
hadamard_2x2, quant_qs_dc, sp_chroma_non_switching, sp_chroma_switching, sp_luma_non_switching,
sp_luma_switching,
};
use crate::transform::{inverse_transform_4x4, inverse_transform_4x4_dc_preserved, qp_y_to_qp_c};
const FLAT: [i32; 16] = [16; 16];
const BLK4_XY: [(usize, usize); 16] = [
(0, 0),
(4, 0),
(0, 4),
(4, 4),
(8, 0),
(12, 0),
(8, 4),
(12, 4),
(0, 8),
(4, 8),
(0, 12),
(4, 12),
(8, 8),
(12, 8),
(8, 12),
(12, 12),
];
#[derive(Clone, Copy, Debug)]
pub struct SpConfig {
pub width: u32,
pub height: u32,
pub qp: i32,
pub qs: i32,
pub deblock: bool,
}
impl SpConfig {
fn width_mbs(&self) -> usize {
(self.width / 16) as usize
}
fn height_mbs(&self) -> usize {
(self.height / 16) as usize
}
fn disable_deblocking_filter_idc(&self) -> u32 {
if self.deblock {
0
} else {
1
}
}
}
#[derive(Clone)]
pub struct SpMbTargets {
pub luma_c: [[i32; 16]; 16],
pub chroma_c: [[[i32; 16]; 4]; 2],
}
pub struct SpTargets {
pub mbs: Vec<SpMbTargets>,
pub recon_y: Vec<u8>,
pub recon_u: Vec<u8>,
pub recon_v: Vec<u8>,
}
pub struct SpEncodedPicture {
pub annex_b: Vec<u8>,
pub targets: SpTargets,
}
pub fn sp_parameter_sets(cfg: &SpConfig) -> Vec<u8> {
let sps = build_baseline_sps_rbsp(&BaselineSpsConfig {
seq_parameter_set_id: 0,
level_idc: 20,
width_in_mbs: cfg.width / 16,
height_in_mbs: cfg.height / 16,
log2_max_frame_num_minus4: 4,
log2_max_poc_lsb_minus4: 4,
max_num_ref_frames: 1,
profile_idc: 88,
chroma_format_idc: 1,
separate_colour_plane: false,
seq_scaling_lists: None,
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
interlaced_fields: false,
mbaff: false,
vui: None,
});
let pps = build_baseline_pps_rbsp(&BaselinePpsConfig {
redundant_pic_cnt_present_flag: false,
slice_groups: None,
constrained_intra_pred_flag: false,
pic_parameter_set_id: 0,
seq_parameter_set_id: 0,
pic_init_qp_minus26: 0,
chroma_qp_index_offset: 0,
weighted_pred_flag: false,
weighted_bipred_idc: 0,
entropy_coding_mode_flag: false,
transform_8x8_mode_flag: false,
pic_scaling_lists: None,
chroma_format_idc: 1,
});
let mut out = build_nal_unit(3, NalUnitType::Sps, &sps);
out.extend_from_slice(&build_nal_unit(3, NalUnitType::Pps, &pps));
out
}
pub fn encode_ipcm_idr(cfg: &SpConfig, y: &[u8], u: &[u8], v: &[u8]) -> Vec<u8> {
let (w_mbs, h_mbs) = (cfg.width_mbs(), cfg.height_mbs());
let width = cfg.width as usize;
let cw = width / 2;
let mut bw = BitWriter::new();
bw.ue(0); bw.ue(7); bw.ue(0); bw.u(8, 0); bw.ue(0); bw.u(8, 0); bw.u(1, 0); bw.u(1, 0); bw.se(cfg.qp - 26); bw.ue(cfg.disable_deblocking_filter_idc());
if cfg.deblock {
bw.se(0); bw.se(0); }
for mby in 0..h_mbs {
for mbx in 0..w_mbs {
bw.ue(25);
bw.align_to_byte_zero();
for row in 0..16 {
for col in 0..16 {
bw.u(8, y[(mby * 16 + row) * width + mbx * 16 + col] as u32);
}
}
for plane in [u, v] {
for row in 0..8 {
for col in 0..8 {
bw.u(8, plane[(mby * 8 + row) * cw + mbx * 8 + col] as u32);
}
}
}
}
}
bw.rbsp_trailing_bits();
build_nal_unit(3, NalUnitType::SliceIdr, &bw.into_bytes())
}
#[allow(clippy::too_many_arguments)]
fn write_sp_slice_header(
bw: &mut BitWriter,
cfg: &SpConfig,
frame_num: u32,
poc_lsb: u32,
nal_ref_idc: u32,
sp_for_switch_flag: bool,
) {
bw.ue(0); bw.ue(8); bw.ue(0); bw.u(8, frame_num);
bw.u(8, poc_lsb);
bw.u(1, 0); bw.u(1, 0); if nal_ref_idc != 0 {
bw.u(1, 0); }
bw.se(cfg.qp - 26); bw.u(1, if sp_for_switch_flag { 1 } else { 0 });
bw.se(cfg.qs - 26); bw.ue(cfg.disable_deblocking_filter_idc());
if cfg.deblock {
bw.se(0);
bw.se(0);
}
}
fn write_si_slice_header(bw: &mut BitWriter, cfg: &SpConfig, frame_num: u32, poc_lsb: u32) {
bw.ue(0); bw.ue(9); bw.ue(0); bw.u(8, frame_num);
bw.u(8, poc_lsb);
bw.se(cfg.qp - 26); bw.se(cfg.qs - 26); bw.ue(cfg.disable_deblocking_filter_idc());
if cfg.deblock {
bw.se(0);
bw.se(0);
}
}
fn gather16(plane: &[u8], stride: usize, x0: usize, y0: usize) -> [i32; 256] {
let mut out = [0i32; 256];
for row in 0..16 {
for col in 0..16 {
out[row * 16 + col] = plane[(y0 + row) * stride + x0 + col] as i32;
}
}
out
}
fn gather8(plane: &[u8], stride: usize, x0: usize, y0: usize) -> [i32; 64] {
let mut out = [0i32; 64];
for row in 0..8 {
for col in 0..8 {
out[row * 8 + col] = plane[(y0 + row) * stride + x0 + col] as i32;
}
}
out
}
fn blk4(mb: &[i32; 256], bx: usize, by: usize) -> [i32; 16] {
let mut out = [0i32; 16];
for row in 0..4 {
for col in 0..4 {
out[row * 4 + col] = mb[(by + row) * 16 + bx + col];
}
}
out
}
fn chroma_blk4(mb: &[i32; 64], blk: usize) -> [i32; 16] {
let (bx, by) = ((blk % 2) * 4, (blk / 2) * 4);
let mut out = [0i32; 16];
for row in 0..4 {
for col in 0..4 {
out[row * 4 + col] = mb[(by + row) * 8 + bx + col];
}
}
out
}
#[allow(clippy::too_many_arguments)]
fn mirror_recon_mb(
targets: &SpMbTargets,
qs_y: i32,
qs_c: i32,
mbx: usize,
mby: usize,
width: usize,
recon_y: &mut [u8],
recon_u: &mut [u8],
recon_v: &mut [u8],
) {
for (blk, c) in targets.luma_c.iter().enumerate() {
let (bx, by) = BLK4_XY[blk];
let r = inverse_transform_4x4(c, qs_y, &FLAT, 8).expect("QSY in range");
for row in 0..4 {
for col in 0..4 {
let v = r[row * 4 + col].clamp(0, 255) as u8;
recon_y[(mby * 16 + by + row) * width + mbx * 16 + bx + col] = v;
}
}
}
let cw = width / 2;
for (plane_idx, plane_c) in targets.chroma_c.iter().enumerate() {
let plane = if plane_idx == 0 {
&mut *recon_u
} else {
&mut *recon_v
};
for (blk, c) in plane_c.iter().enumerate() {
let (bx, by) = ((blk % 2) * 4, (blk / 2) * 4);
let r = inverse_transform_4x4_dc_preserved(c, qs_c, &FLAT, 8).expect("QSC in range");
for row in 0..4 {
for col in 0..4 {
let v = r[row * 4 + col].clamp(0, 255) as u8;
plane[(mby * 8 + by + row) * cw + mbx * 8 + bx + col] = v;
}
}
}
}
}
struct MbLevels {
luma_scan: [[i32; 16]; 16],
cbp_luma: u8,
dc_cb: [i32; 4],
dc_cr: [i32; 4],
ac_cb_scan: [[i32; 16]; 4],
ac_cr_scan: [[i32; 16]; 4],
cbp_chroma: u8,
}
fn pack_levels(
luma_cr: &[[i32; 16]; 16],
dc_levels: &[[i32; 4]; 2],
ac_cr: &[[[i32; 16]; 4]; 2],
) -> MbLevels {
let mut luma_scan = [[0i32; 16]; 16];
let mut cbp_luma = 0u8;
for blk in 0..16 {
luma_scan[blk] = zigzag_scan_4x4(&luma_cr[blk]);
if luma_scan[blk].iter().any(|&v| v != 0) {
cbp_luma |= 1 << (blk / 4);
}
}
let mut ac_scan = [[[0i32; 16]; 4]; 2];
let mut any_ac = false;
for plane in 0..2 {
for blk in 0..4 {
let mut raster = ac_cr[plane][blk];
raster[0] = 0;
ac_scan[plane][blk] = zigzag_scan_4x4_ac(&raster);
if ac_scan[plane][blk].iter().any(|&v| v != 0) {
any_ac = true;
}
}
}
let any_dc = dc_levels.iter().flatten().any(|&v| v != 0);
let cbp_chroma = if any_ac {
2
} else if any_dc {
1
} else {
0
};
MbLevels {
luma_scan,
cbp_luma,
dc_cb: dc_levels[0],
dc_cr: dc_levels[1],
ac_cb_scan: ac_scan[0],
ac_cr_scan: ac_scan[1],
cbp_chroma,
}
}
fn luma_nc_and_commit(
nc_grid: &mut CavlcNcGrid,
mb_addr: u32,
is_intra: bool,
levels: &MbLevels,
) -> [i32; 16] {
{
let cur = &mut nc_grid.mbs[mb_addr as usize];
cur.is_available = true;
cur.is_intra = is_intra;
cur.is_skip = false;
cur.is_i_pcm = false;
cur.luma_total_coeff = [0u8; 16];
}
let mut nc = [0i32; 16];
let mut own = [0u8; 16];
for blk in 0..16usize {
nc_grid.mbs[mb_addr as usize].luma_total_coeff = own;
nc[blk] = derive_nc_luma(nc_grid, mb_addr, blk as u8, LumaNcKind::Ac, is_intra, false);
if (levels.cbp_luma >> (blk / 4)) & 1 == 1 {
own[blk] = levels.luma_scan[blk].iter().filter(|&&v| v != 0).count() as u8;
}
}
nc_grid.mbs[mb_addr as usize].luma_total_coeff = own;
nc
}
#[allow(clippy::too_many_arguments)]
pub fn encode_sp_picture(
cfg: &SpConfig,
src: (&[u8], &[u8], &[u8]),
refp: (&[u8], &[u8], &[u8]),
frame_num: u32,
poc_lsb: u32,
nal_ref_idc: u8,
) -> SpEncodedPicture {
assert!((0..=51).contains(&cfg.qp) && (0..=51).contains(&cfg.qs));
let (w_mbs, h_mbs) = (cfg.width_mbs(), cfg.height_mbs());
let width = cfg.width as usize;
let cw = width / 2;
let qp_c = qp_y_to_qp_c(cfg.qp, 0);
let qs_c = qp_y_to_qp_c(cfg.qs, 0);
let mut bw = BitWriter::new();
write_sp_slice_header(
&mut bw,
cfg,
frame_num,
poc_lsb,
u32::from(nal_ref_idc),
false,
);
let mut nc_grid = CavlcNcGrid::new(w_mbs as u32, h_mbs as u32);
let mut mbs = Vec::with_capacity(w_mbs * h_mbs);
let mut recon_y = vec![0u8; width * cfg.height as usize];
let mut recon_u = vec![0u8; cw * (cfg.height as usize / 2)];
let mut recon_v = vec![0u8; cw * (cfg.height as usize / 2)];
for mby in 0..h_mbs {
for mbx in 0..w_mbs {
let mb_addr = (mby * w_mbs + mbx) as u32;
let pred_mb = gather16(refp.0, width, mbx * 16, mby * 16);
let src_mb = gather16(src.0, width, mbx * 16, mby * 16);
let mut luma_cr = [[0i32; 16]; 16];
let mut luma_c = [[0i32; 16]; 16];
for blk in 0..16 {
let (bx, by) = BLK4_XY[blk];
let p = blk4(&pred_mb, bx, by);
let s = blk4(&src_mb, bx, by);
let cp = crate::sp_transform::forward_core_4x4(&p);
let cwc = crate::sp_transform::forward_core_4x4(&s);
let mut cr = [0i32; 16];
for i in 0..4 {
for j in 0..4 {
let idx = i * 4 + j;
cr[idx] =
encoder_quant_8_416((cwc[idx] - cp[idx]) as i64, cfg.qp, &FLAT, i, j);
}
}
luma_cr[blk] = cr;
luma_c[blk] = sp_luma_non_switching(&p, &cr, cfg.qp, cfg.qs, &FLAT);
}
let mut dc_levels = [[0i32; 4]; 2];
let mut ac_levels = [[[0i32; 16]; 4]; 2];
let mut chroma_c = [[[0i32; 16]; 4]; 2];
for plane in 0..2 {
let (sp_, rp_) = if plane == 0 {
(src.1, refp.1)
} else {
(src.2, refp.2)
};
let pred8 = gather8(rp_, cw, mbx * 8, mby * 8);
let src8 = gather8(sp_, cw, mbx * 8, mby * 8);
let cp_blocks = chroma_pred_transform(&pred8);
let cw_blocks = chroma_pred_transform(&src8);
for blk in 0..4 {
for i in 0..4 {
for j in 0..4 {
if i == 0 && j == 0 {
continue;
}
let idx = i * 4 + j;
ac_levels[plane][blk][idx] = encoder_quant_8_416(
(cw_blocks[blk][idx] - cp_blocks[blk][idx]) as i64,
qp_c,
&FLAT,
i,
j,
);
}
}
}
let dcp = hadamard_2x2(&[
cp_blocks[0][0] as i64,
cp_blocks[1][0] as i64,
cp_blocks[2][0] as i64,
cp_blocks[3][0] as i64,
]);
let dcw = hadamard_2x2(&[
cw_blocks[0][0] as i64,
cw_blocks[1][0] as i64,
cw_blocks[2][0] as i64,
cw_blocks[3][0] as i64,
]);
for pos in 0..4 {
dc_levels[plane][pos] = encoder_quant_8_428(dcw[pos] - dcp[pos], qp_c, &FLAT);
}
chroma_c[plane] = sp_chroma_non_switching(
&pred8,
&dc_levels[plane],
&ac_levels[plane],
qp_c,
qs_c,
&FLAT,
);
}
let targets = SpMbTargets { luma_c, chroma_c };
mirror_recon_mb(
&targets,
cfg.qs,
qs_c,
mbx,
mby,
width,
&mut recon_y,
&mut recon_u,
&mut recon_v,
);
emit_sp_inter_mb(
&mut bw,
&mut nc_grid,
mb_addr,
&luma_cr,
&dc_levels,
&ac_levels,
);
mbs.push(targets);
}
}
bw.rbsp_trailing_bits();
let annex_b = build_nal_unit(nal_ref_idc, NalUnitType::SliceNonIdr, &bw.into_bytes());
SpEncodedPicture {
annex_b,
targets: SpTargets {
mbs,
recon_y,
recon_u,
recon_v,
},
}
}
fn emit_sp_inter_mb(
bw: &mut BitWriter,
nc_grid: &mut CavlcNcGrid,
mb_addr: u32,
luma_cr: &[[i32; 16]; 16],
dc_levels: &[[i32; 4]; 2],
ac_levels: &[[[i32; 16]; 4]; 2],
) {
let levels = pack_levels(luma_cr, dc_levels, ac_levels);
let luma_nc = luma_nc_and_commit(nc_grid, mb_addr, false, &levels);
let (nc_cb, nc_cr) = super::derive_chroma_ac_nc_and_commit_totals(
nc_grid,
mb_addr,
false,
levels.cbp_chroma,
&levels.ac_cb_scan,
&levels.ac_cr_scan,
1,
);
bw.ue(0); write_p_l0_16x16_mb(
bw,
&PL016x16McbConfig {
ref_idx_l0: 0,
transform_size_8x8_flag: None,
mvd_l0_x: 0,
mvd_l0_y: 0,
cbp_luma: levels.cbp_luma,
cbp_chroma: levels.cbp_chroma,
mb_qp_delta: 0,
luma_4x4_levels: levels.luma_scan,
luma_4x4_nc: luma_nc,
chroma_dc_cb: levels.dc_cb,
chroma_dc_cr: levels.dc_cr,
chroma_ac_cb: levels.ac_cb_scan,
chroma_ac_cr: levels.ac_cr_scan,
chroma_ac_nc_cb: nc_cb,
chroma_ac_nc_cr: nc_cr,
},
0,
)
.expect("SP P_L0_16x16 emit");
}
pub fn encode_sp_switch_picture(
cfg: &SpConfig,
targets: &SpTargets,
refp: (&[u8], &[u8], &[u8]),
frame_num: u32,
poc_lsb: u32,
) -> Vec<u8> {
let (w_mbs, h_mbs) = (cfg.width_mbs(), cfg.height_mbs());
let width = cfg.width as usize;
let cw = width / 2;
let qs_c = qp_y_to_qp_c(cfg.qs, 0);
let _k = chroma_dc_switch_scale(qs_c, &FLAT)
.expect("switching construction requires QSC >= 6 (choose qs >= 6)");
let mut bw = BitWriter::new();
write_sp_slice_header(&mut bw, cfg, frame_num, poc_lsb, 0, true);
let mut nc_grid = CavlcNcGrid::new(w_mbs as u32, h_mbs as u32);
for mby in 0..h_mbs {
for mbx in 0..w_mbs {
let mb_addr = (mby * w_mbs + mbx) as u32;
let mb_targets = &targets.mbs[mb_addr as usize];
let pred_mb = gather16(refp.0, width, mbx * 16, mby * 16);
let mut luma_cr = [[0i32; 16]; 16];
for blk in 0..16 {
let (bx, by) = BLK4_XY[blk];
let p = blk4(&pred_mb, bx, by);
let cs = sp_luma_switching(&p, &[0i32; 16], cfg.qs);
for idx in 0..16 {
luma_cr[blk][idx] = mb_targets.luma_c[blk][idx] - cs[idx];
}
}
let (dc_levels, ac_levels) =
switch_chroma_levels(mb_targets, refp.1, refp.2, cw, mbx, mby, qs_c);
emit_sp_inter_mb(
&mut bw,
&mut nc_grid,
mb_addr,
&luma_cr,
&dc_levels,
&ac_levels,
);
}
}
bw.rbsp_trailing_bits();
build_nal_unit(0, NalUnitType::SliceNonIdr, &bw.into_bytes())
}
#[allow(clippy::too_many_arguments)]
fn switch_chroma_levels(
mb_targets: &SpMbTargets,
ref_u: &[u8],
ref_v: &[u8],
cw: usize,
mbx: usize,
mby: usize,
qs_c: i32,
) -> ([[i32; 4]; 2], [[[i32; 16]; 4]; 2]) {
let mut dc_levels = [[0i32; 4]; 2];
let mut ac_levels = [[[0i32; 16]; 4]; 2];
for plane in 0..2 {
let rp_ = if plane == 0 { ref_u } else { ref_v };
let pred8 = gather8(rp_, cw, mbx * 8, mby * 8);
switch_chroma_levels_from_pred(
&pred8,
&mb_targets.chroma_c[plane],
qs_c,
&mut dc_levels[plane],
&mut ac_levels[plane],
);
}
(dc_levels, ac_levels)
}
fn switch_chroma_levels_from_pred(
pred8: &[i32; 64],
target_c: &[[i32; 16]; 4],
qs_c: i32,
dc_levels: &mut [i32; 4],
ac_levels: &mut [[i32; 16]; 4],
) {
let cs = sp_chroma_switching(pred8, &[0i32; 4], &[[0i32; 16]; 4], qs_c);
for blk in 0..4 {
for idx in 1..16 {
ac_levels[blk][idx] = target_c[blk][idx] - cs[blk][idx];
}
}
let kf = [
target_c[0][0] as i64,
target_c[1][0] as i64,
target_c[2][0] as i64,
target_c[3][0] as i64,
];
let h_kf = hadamard_2x2(&kf); let cp_blocks = chroma_pred_transform(pred8);
let dcp = hadamard_2x2(&[
cp_blocks[0][0] as i64,
cp_blocks[1][0] as i64,
cp_blocks[2][0] as i64,
cp_blocks[3][0] as i64,
]);
for pos in 0..4 {
debug_assert_eq!(h_kf[pos] % 4, 0);
let k_dcr = h_kf[pos] / 4; let dcs = quant_qs_dc(dcp[pos], qs_c);
let level = k_dcr - dcs;
dc_levels[pos] = i32::try_from(level).expect("chroma DC level fits i32");
}
}
pub fn encode_si_picture(
cfg: &SpConfig,
targets: &SpTargets,
frame_num: u32,
poc_lsb: u32,
) -> Vec<u8> {
let (w_mbs, h_mbs) = (cfg.width_mbs(), cfg.height_mbs());
let width = cfg.width as usize;
let cw = width / 2;
let qs_c = qp_y_to_qp_c(cfg.qs, 0);
let _k = chroma_dc_switch_scale(qs_c, &FLAT)
.expect("switching construction requires QSC >= 6 (choose qs >= 6)");
let mut bw = BitWriter::new();
write_si_slice_header(&mut bw, cfg, frame_num, poc_lsb);
let mut nc_grid = CavlcNcGrid::new(w_mbs as u32, h_mbs as u32);
for mby in 0..h_mbs {
for mbx in 0..w_mbs {
let mb_addr = (mby * w_mbs + mbx) as u32;
let (luma_cr, dc_levels, ac_levels) =
si_mb_levels(cfg, targets, mbx, mby, width, cw, qs_c);
let levels = pack_levels(&luma_cr, &dc_levels, &ac_levels);
let luma_nc = luma_nc_and_commit(&mut nc_grid, mb_addr, true, &levels);
let (nc_cb, nc_cr) = super::derive_chroma_ac_nc_and_commit_totals(
&mut nc_grid,
mb_addr,
true,
levels.cbp_chroma,
&levels.ac_cb_scan,
&levels.ac_cr_scan,
1,
);
write_i_nxn_mb(
&mut bw,
&INxNMcbConfig {
emit_transform_size_8x8_zero: false,
prev_intra4x4_pred_mode_flag: [true; 16],
rem_intra4x4_pred_mode: [0u8; 16],
intra_chroma_pred_mode: 0,
cbp_luma: levels.cbp_luma,
cbp_chroma: levels.cbp_chroma,
mb_qp_delta: 0,
luma_4x4_levels: levels.luma_scan,
luma_4x4_nc: luma_nc,
chroma_dc_cb: levels.dc_cb,
chroma_dc_cr: levels.dc_cr,
chroma_ac_cb: levels.ac_cb_scan,
chroma_ac_cr: levels.ac_cr_scan,
chroma_ac_nc_cb: nc_cb,
chroma_ac_nc_cr: nc_cr,
},
)
.expect("SI macroblock emit");
}
}
bw.rbsp_trailing_bits();
build_nal_unit(0, NalUnitType::SliceNonIdr, &bw.into_bytes())
}
struct PreppedMbLevels {
levels: MbLevels,
luma_nc: [i32; 16],
nc_cb: [i32; 8],
nc_cr: [i32; 8],
}
fn prep_mb_levels(
nc_grid: &mut CavlcNcGrid,
mb_addr: u32,
is_intra: bool,
luma_cr: &[[i32; 16]; 16],
dc_levels: &[[i32; 4]; 2],
ac_levels: &[[[i32; 16]; 4]; 2],
) -> PreppedMbLevels {
let levels = pack_levels(luma_cr, dc_levels, ac_levels);
let luma_nc = luma_nc_and_commit(nc_grid, mb_addr, is_intra, &levels);
let (nc_cb, nc_cr) = super::derive_chroma_ac_nc_and_commit_totals(
nc_grid,
mb_addr,
is_intra,
levels.cbp_chroma,
&levels.ac_cb_scan,
&levels.ac_cr_scan,
1,
);
PreppedMbLevels {
levels,
luma_nc,
nc_cb,
nc_cr,
}
}
fn write_mb_residual(res: &mut BitWriter, p: &PreppedMbLevels) {
for blk8 in 0..4u8 {
if (p.levels.cbp_luma >> blk8) & 1 == 1 {
for sub in 0..4u8 {
let blk = (blk8 * 4 + sub) as usize;
encode_residual_block_cavlc(
res,
CoeffTokenContext::Numeric(p.luma_nc[blk]),
16,
&p.levels.luma_scan[blk],
)
.expect("luma residual emit");
}
}
}
if p.levels.cbp_chroma > 0 {
encode_residual_block_cavlc(res, CoeffTokenContext::ChromaDc420, 4, &p.levels.dc_cb)
.expect("chroma DC emit");
encode_residual_block_cavlc(res, CoeffTokenContext::ChromaDc420, 4, &p.levels.dc_cr)
.expect("chroma DC emit");
}
if p.levels.cbp_chroma == 2 {
for (k, blk) in p.levels.ac_cb_scan.iter().enumerate() {
encode_residual_block_cavlc(
res,
CoeffTokenContext::Numeric(p.nc_cb[k]),
15,
&blk[..15],
)
.expect("chroma AC emit");
}
for (k, blk) in p.levels.ac_cr_scan.iter().enumerate() {
encode_residual_block_cavlc(
res,
CoeffTokenContext::Numeric(p.nc_cr[k]),
15,
&blk[..15],
)
.expect("chroma AC emit");
}
}
}
fn write_inter_mb_cat2(a: &mut BitWriter, p: &PreppedMbLevels) {
a.ue(0); a.ue(0); a.se(0); a.se(0); a.ue(inter_cbp_to_codenum_420_422(
p.levels.cbp_luma,
p.levels.cbp_chroma,
));
if p.levels.cbp_luma > 0 || p.levels.cbp_chroma > 0 {
a.se(0); }
}
fn write_si_mb_cat2(a: &mut BitWriter, p: &PreppedMbLevels) {
a.ue(0); for _ in 0..16 {
a.u(1, 1); }
a.ue(0); a.ue(intra_cbp_to_codenum_420_422(
p.levels.cbp_luma,
p.levels.cbp_chroma,
));
if p.levels.cbp_luma > 0 || p.levels.cbp_chroma > 0 {
a.se(0); }
}
fn write_ipcm_payload(b: &mut BitWriter, y_mb: &[u8; 256], cb_mb: &[u8; 64], cr_mb: &[u8; 64]) {
b.align_to_byte_zero();
for v in y_mb {
b.u(8, u32::from(*v));
}
for v in cb_mb {
b.u(8, u32::from(*v));
}
for v in cr_mb {
b.u(8, u32::from(*v));
}
}
pub struct SpDualFormPicture {
pub single_annex_b: Vec<u8>,
pub partitioned_annex_b: Vec<u8>,
pub recon_y: Vec<u8>,
pub recon_u: Vec<u8>,
pub recon_v: Vec<u8>,
}
#[allow(clippy::too_many_arguments)]
pub fn encode_sp_dual_form_picture(
cfg: &SpConfig,
src: (&[u8], &[u8], &[u8]),
refp: (&[u8], &[u8], &[u8]),
frame_num: u32,
poc_lsb: u32,
nal_ref_idc: u8,
pcm_period: usize,
) -> SpDualFormPicture {
assert!(pcm_period >= 1);
let (w_mbs, h_mbs) = (cfg.width_mbs(), cfg.height_mbs());
let width = cfg.width as usize;
let cw = width / 2;
let qp_c = qp_y_to_qp_c(cfg.qp, 0);
let qs_c = qp_y_to_qp_c(cfg.qs, 0);
let mut w_single = BitWriter::new();
write_sp_slice_header(
&mut w_single,
cfg,
frame_num,
poc_lsb,
u32::from(nal_ref_idc),
false,
);
let mut w_a = BitWriter::new();
write_sp_slice_header(
&mut w_a,
cfg,
frame_num,
poc_lsb,
u32::from(nal_ref_idc),
false,
);
w_a.ue(0); let mut w_b = BitWriter::new();
w_b.ue(0); let mut w_c = BitWriter::new();
w_c.ue(0); let (mut b_used, mut c_used) = (false, false);
let mut nc_grid = CavlcNcGrid::new(w_mbs as u32, h_mbs as u32);
let mut recon_y = vec![0u8; width * cfg.height as usize];
let mut recon_u = vec![0u8; cw * (cfg.height as usize / 2)];
let mut recon_v = vec![0u8; cw * (cfg.height as usize / 2)];
for mby in 0..h_mbs {
for mbx in 0..w_mbs {
let mb_addr = (mby * w_mbs + mbx) as u32;
if (mb_addr as usize) % pcm_period == pcm_period - 1 {
let mut y_mb = [0u8; 256];
let mut cb_mb = [0u8; 64];
let mut cr_mb = [0u8; 64];
for row in 0..16 {
for col in 0..16 {
let v = src.0[(mby * 16 + row) * width + mbx * 16 + col];
y_mb[row * 16 + col] = v;
recon_y[(mby * 16 + row) * width + mbx * 16 + col] = v;
}
}
for row in 0..8 {
for col in 0..8 {
let u = src.1[(mby * 8 + row) * cw + mbx * 8 + col];
let v = src.2[(mby * 8 + row) * cw + mbx * 8 + col];
cb_mb[row * 8 + col] = u;
cr_mb[row * 8 + col] = v;
recon_u[(mby * 8 + row) * cw + mbx * 8 + col] = u;
recon_v[(mby * 8 + row) * cw + mbx * 8 + col] = v;
}
}
{
let cur = &mut nc_grid.mbs[mb_addr as usize];
cur.is_available = true;
cur.is_intra = true;
cur.is_skip = false;
cur.is_i_pcm = true;
}
w_single.ue(0); w_single.ue(30); write_ipcm_payload(&mut w_single, &y_mb, &cb_mb, &cr_mb);
w_a.ue(0);
w_a.ue(30);
write_ipcm_payload(&mut w_b, &y_mb, &cb_mb, &cr_mb);
b_used = true;
continue;
}
let pred_mb = gather16(refp.0, width, mbx * 16, mby * 16);
let src_mb = gather16(src.0, width, mbx * 16, mby * 16);
let mut luma_cr = [[0i32; 16]; 16];
let mut luma_c = [[0i32; 16]; 16];
for blk in 0..16 {
let (bx, by) = BLK4_XY[blk];
let p = blk4(&pred_mb, bx, by);
let s_blk = blk4(&src_mb, bx, by);
let cp = crate::sp_transform::forward_core_4x4(&p);
let cwc = crate::sp_transform::forward_core_4x4(&s_blk);
let mut cr = [0i32; 16];
for i in 0..4 {
for j in 0..4 {
let idx = i * 4 + j;
cr[idx] =
encoder_quant_8_416((cwc[idx] - cp[idx]) as i64, cfg.qp, &FLAT, i, j);
}
}
luma_cr[blk] = cr;
luma_c[blk] = sp_luma_non_switching(&p, &cr, cfg.qp, cfg.qs, &FLAT);
}
let mut dc_levels = [[0i32; 4]; 2];
let mut ac_levels = [[[0i32; 16]; 4]; 2];
let mut chroma_c = [[[0i32; 16]; 4]; 2];
for plane in 0..2 {
let (sp_, rp_) = if plane == 0 {
(src.1, refp.1)
} else {
(src.2, refp.2)
};
let pred8 = gather8(rp_, cw, mbx * 8, mby * 8);
let src8 = gather8(sp_, cw, mbx * 8, mby * 8);
let cp_blocks = chroma_pred_transform(&pred8);
let cw_blocks = chroma_pred_transform(&src8);
for blk in 0..4 {
for i in 0..4 {
for j in 0..4 {
if i == 0 && j == 0 {
continue;
}
let idx = i * 4 + j;
ac_levels[plane][blk][idx] = encoder_quant_8_416(
(cw_blocks[blk][idx] - cp_blocks[blk][idx]) as i64,
qp_c,
&FLAT,
i,
j,
);
}
}
}
let dcp = hadamard_2x2(&[
cp_blocks[0][0] as i64,
cp_blocks[1][0] as i64,
cp_blocks[2][0] as i64,
cp_blocks[3][0] as i64,
]);
let dcw = hadamard_2x2(&[
cw_blocks[0][0] as i64,
cw_blocks[1][0] as i64,
cw_blocks[2][0] as i64,
cw_blocks[3][0] as i64,
]);
for pos in 0..4 {
dc_levels[plane][pos] = encoder_quant_8_428(dcw[pos] - dcp[pos], qp_c, &FLAT);
}
chroma_c[plane] = sp_chroma_non_switching(
&pred8,
&dc_levels[plane],
&ac_levels[plane],
qp_c,
qs_c,
&FLAT,
);
}
let targets = SpMbTargets { luma_c, chroma_c };
mirror_recon_mb(
&targets,
cfg.qs,
qs_c,
mbx,
mby,
width,
&mut recon_y,
&mut recon_u,
&mut recon_v,
);
let prepped = prep_mb_levels(
&mut nc_grid,
mb_addr,
false,
&luma_cr,
&dc_levels,
&ac_levels,
);
write_inter_mb_cat2(&mut w_single, &prepped);
write_mb_residual(&mut w_single, &prepped);
write_inter_mb_cat2(&mut w_a, &prepped);
if prepped.levels.cbp_luma > 0 || prepped.levels.cbp_chroma > 0 {
write_mb_residual(&mut w_c, &prepped);
c_used = true;
}
}
}
w_single.rbsp_trailing_bits();
let single_annex_b = build_nal_unit(
nal_ref_idc,
NalUnitType::SliceNonIdr,
&w_single.into_bytes(),
);
w_a.rbsp_trailing_bits();
let mut partitioned_annex_b = build_nal_unit(
nal_ref_idc,
NalUnitType::SliceDataPartitionA,
&w_a.into_bytes(),
);
if b_used {
let mut body = w_b;
body.rbsp_trailing_bits();
partitioned_annex_b.extend_from_slice(&build_nal_unit(
nal_ref_idc,
NalUnitType::SliceDataPartitionB,
&body.into_bytes(),
));
}
if c_used {
let mut body = w_c;
body.rbsp_trailing_bits();
partitioned_annex_b.extend_from_slice(&build_nal_unit(
nal_ref_idc,
NalUnitType::SliceDataPartitionC,
&body.into_bytes(),
));
}
SpDualFormPicture {
single_annex_b,
partitioned_annex_b,
recon_y,
recon_u,
recon_v,
}
}
#[allow(clippy::type_complexity)]
fn si_mb_levels(
cfg: &SpConfig,
targets: &SpTargets,
mbx: usize,
mby: usize,
width: usize,
cw: usize,
qs_c: i32,
) -> ([[i32; 16]; 16], [[i32; 4]; 2], [[[i32; 16]; 4]; 2]) {
let mb_addr = (mby * (width / 16) + mbx) as u32;
let mb_targets = &targets.mbs[mb_addr as usize];
let ry = &targets.recon_y;
let ru = &targets.recon_u;
let rv = &targets.recon_v;
let mut luma_cr = [[0i32; 16]; 16];
for blk in 0..16 {
let (bx, by) = BLK4_XY[blk];
let (gx, gy) = (mbx * 16 + bx, mby * 16 + by);
let av = Neighbour4x4Availability {
top_left: gx > 0 && gy > 0,
top: gy > 0,
top_right: false,
left: gx > 0,
};
let samples = Samples4x4 {
top_left: if av.top_left {
ry[(gy - 1) * width + gx - 1] as i32
} else {
0
},
top: core::array::from_fn(|i| {
if av.top {
ry[(gy - 1) * width + gx + i] as i32
} else {
0
}
}),
top_right: [0i32; 4],
left: core::array::from_fn(|i| {
if av.left {
ry[(gy + i) * width + gx - 1] as i32
} else {
0
}
}),
availability: av,
};
let mut pred = [0i32; 16];
predict_4x4(Intra4x4Mode::Dc, &samples, 8, &mut pred);
let cs = sp_luma_switching(&pred, &[0i32; 16], cfg.qs);
for idx in 0..16 {
luma_cr[blk][idx] = mb_targets.luma_c[blk][idx] - cs[idx];
}
}
let mut dc_levels = [[0i32; 4]; 2];
let mut ac_levels = [[[0i32; 16]; 4]; 2];
for plane in 0..2 {
let rp_: &[u8] = if plane == 0 { ru } else { rv };
let (cx, cy) = (mbx * 8, mby * 8);
let av = Neighbour4x4Availability {
top_left: cx > 0 && cy > 0,
top: cy > 0,
top_right: false,
left: cx > 0,
};
let samples = SamplesChroma {
top_left: if av.top_left {
rp_[(cy - 1) * cw + cx - 1] as i32
} else {
0
},
top: (0..8)
.map(|i| {
if av.top {
rp_[(cy - 1) * cw + cx + i] as i32
} else {
0
}
})
.collect(),
left: (0..8)
.map(|i| {
if av.left {
rp_[(cy + i) * cw + cx - 1] as i32
} else {
0
}
})
.collect(),
availability: av,
};
let mut pred = vec![0i32; 64];
predict_chroma(
IntraChromaMode::Dc,
&samples,
ChromaArrayType::Yuv420,
8,
&mut pred,
);
let mut pred8 = [0i32; 64];
pred8.copy_from_slice(&pred);
switch_chroma_levels_from_pred(
&pred8,
&mb_targets.chroma_c[plane],
qs_c,
&mut dc_levels[plane],
&mut ac_levels[plane],
);
}
(luma_cr, dc_levels, ac_levels)
}
pub fn encode_si_dual_form_picture(
cfg: &SpConfig,
targets: &SpTargets,
frame_num: u32,
poc_lsb: u32,
) -> SpDualFormPicture {
let (w_mbs, h_mbs) = (cfg.width_mbs(), cfg.height_mbs());
let width = cfg.width as usize;
let cw = width / 2;
let qs_c = qp_y_to_qp_c(cfg.qs, 0);
let _k = chroma_dc_switch_scale(qs_c, &FLAT)
.expect("switching construction requires QSC >= 6 (choose qs >= 6)");
let mut w_single = BitWriter::new();
write_si_slice_header(&mut w_single, cfg, frame_num, poc_lsb);
let mut w_a = BitWriter::new();
write_si_slice_header(&mut w_a, cfg, frame_num, poc_lsb);
w_a.ue(0); let mut w_b = BitWriter::new();
w_b.ue(0); let mut b_used = false;
let mut nc_grid = CavlcNcGrid::new(w_mbs as u32, h_mbs as u32);
for mby in 0..h_mbs {
for mbx in 0..w_mbs {
let mb_addr = (mby * w_mbs + mbx) as u32;
let (luma_cr, dc_levels, ac_levels) =
si_mb_levels(cfg, targets, mbx, mby, width, cw, qs_c);
let prepped = prep_mb_levels(
&mut nc_grid,
mb_addr,
true,
&luma_cr,
&dc_levels,
&ac_levels,
);
write_si_mb_cat2(&mut w_single, &prepped);
write_mb_residual(&mut w_single, &prepped);
write_si_mb_cat2(&mut w_a, &prepped);
if prepped.levels.cbp_luma > 0 || prepped.levels.cbp_chroma > 0 {
write_mb_residual(&mut w_b, &prepped);
b_used = true;
}
}
}
w_single.rbsp_trailing_bits();
let single_annex_b = build_nal_unit(0, NalUnitType::SliceNonIdr, &w_single.into_bytes());
w_a.rbsp_trailing_bits();
let mut partitioned_annex_b =
build_nal_unit(0, NalUnitType::SliceDataPartitionA, &w_a.into_bytes());
if b_used {
let mut body = w_b;
body.rbsp_trailing_bits();
partitioned_annex_b.extend_from_slice(&build_nal_unit(
0,
NalUnitType::SliceDataPartitionB,
&body.into_bytes(),
));
}
SpDualFormPicture {
single_annex_b,
partitioned_annex_b,
recon_y: targets.recon_y.clone(),
recon_u: targets.recon_u.clone(),
recon_v: targets.recon_v.clone(),
}
}