use crate::{
cabac::{
CabacEncoder, ContextSet, IntraModeContexts, encode_cbf_chroma, encode_cbf_luma,
encode_residual,
},
dct,
error::EncodeError,
intra,
yuv::Yuv,
};
#[derive(Clone, Debug)]
pub(crate) struct Nalu {
#[allow(unused)]
pub(crate) nal_type: u8,
pub(crate) data: Vec<u8>,
}
pub(crate) struct NaluStream {
pub(crate) nalus: Vec<Nalu>,
}
impl NaluStream {
pub(crate) fn to_length_prefixed_slices(&self) -> Vec<u8> {
let mut out = Vec::new();
for nalu in &self.nalus {
let nal_type = (nalu.data[0] >> 1) & 0x3f;
if matches!(nal_type, 32..=34) {
continue;
}
let mut escaped: Vec<u8> = Vec::with_capacity(nalu.data.len() + 8);
let mut prev = [0xffu8; 2];
for &b in &nalu.data {
if prev[0] == 0 && prev[1] == 0 && b <= 3 {
escaped.push(0x03);
prev = [prev[1], 0x03];
}
escaped.push(b);
prev = [prev[1], b];
}
out.extend_from_slice(&(escaped.len() as u32).to_be_bytes());
out.extend_from_slice(&escaped);
}
out
}
}
pub(crate) struct BitWriter {
buf: Vec<u8>,
bit_pos: u32,
cur_byte: u8,
}
impl BitWriter {
pub(crate) fn new() -> Self {
Self {
buf: Vec::new(),
bit_pos: 0,
cur_byte: 0,
}
}
pub(crate) fn write_bits(&mut self, v: u32, n: u32) {
for i in (0..n).rev() {
let bit = ((v >> i) & 1) as u8;
self.cur_byte = (self.cur_byte << 1) | bit;
self.bit_pos += 1;
if self.bit_pos == 8 {
self.buf.push(self.cur_byte);
self.cur_byte = 0;
self.bit_pos = 0;
}
}
}
pub(crate) fn write_bit(&mut self, v: bool) {
self.write_bits(v as u32, 1);
}
pub(crate) fn write_ue(&mut self, mut v: u32) {
v += 1;
let bits = 32 - v.leading_zeros();
self.write_bits(0, bits - 1);
self.write_bits(v, bits);
}
pub(crate) fn write_se(&mut self, v: i32) {
let u = if v > 0 {
2 * v as u32 - 1
} else {
(-2 * v) as u32
};
self.write_ue(u);
}
pub(crate) fn rbsp_trailing_bits(&mut self) {
self.write_bit(true);
while self.bit_pos != 0 {
self.write_bit(false);
}
}
pub(crate) fn finish(mut self) -> Vec<u8> {
if self.bit_pos > 0 {
self.buf.push(self.cur_byte << (8 - self.bit_pos));
}
self.buf
}
}
fn nalu_header(bw: &mut BitWriter, nal_type: u8) {
bw.write_bit(false); bw.write_bits(nal_type as u32, 6); bw.write_bits(0, 6); bw.write_bits(1, 3); }
pub(crate) fn level_idc_for(w: u32, h: u32) -> u8 {
let ps = (w as u64) * (h as u64);
const TABLE: &[(u64, u8)] = &[
(36864, 30),
(122880, 60),
(245760, 63),
(552960, 90),
(983040, 93),
(2228224, 120),
(8912896, 150),
(35651584, 180),
];
for &(maxps, lvl) in TABLE {
if ps <= maxps {
return lvl;
}
}
186 }
fn write_profile_tier_level(
bw: &mut BitWriter,
level_idc: u8,
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
) {
bw.write_bits(0, 2); bw.write_bit(false); bw.write_bits(4, 5);
bw.write_bits(0x0800_0000, 32);
bw.write_bit(true); bw.write_bit(false); bw.write_bit(false); bw.write_bit(true); let bits = bit_depth.bits();
bw.write_bit(bits <= 12); bw.write_bit(bits <= 10); bw.write_bit(bits <= 8); let is_444 = matches!(chroma, crate::fmt::ChromaFormat::Yuv444);
let is_420 = matches!(chroma, crate::fmt::ChromaFormat::Yuv420);
let is_mono = matches!(chroma, crate::fmt::ChromaFormat::Monochrome);
bw.write_bit(!is_444 || is_mono); bw.write_bit(is_420 || is_mono); bw.write_bit(is_mono); bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.write_bits(0, 32); bw.write_bits(0, 3);
bw.write_bits(level_idc as u32, 8);
}
pub(crate) fn build_vps(
width: u32,
height: u32,
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
) -> Nalu {
let coded_w = (width + 63) & !63;
let coded_h = (height + 63) & !63;
let level = level_idc_for(coded_w, coded_h);
let mut bw = BitWriter::new();
nalu_header(&mut bw, 32);
bw.write_bits(0, 4); bw.write_bit(true); bw.write_bit(true); bw.write_bits(0, 6); bw.write_bits(0, 3); bw.write_bit(true); bw.write_bits(0xFFFF, 16);
write_profile_tier_level(&mut bw, level, chroma, bit_depth);
bw.write_bit(false);
bw.write_ue(2); bw.write_ue(0); bw.write_ue(0);
bw.write_bits(0, 6); bw.write_ue(0);
bw.write_bit(false); bw.write_bit(false);
bw.rbsp_trailing_bits();
Nalu {
nal_type: 32,
data: bw.finish(),
}
}
pub(crate) fn build_sps(
width: u32,
height: u32,
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
) -> Nalu {
let mut bw = BitWriter::new();
nalu_header(&mut bw, 33);
bw.write_bits(0, 4); bw.write_bits(0, 3); bw.write_bit(true);
let sps_level = level_idc_for((width + 63) & !63, (height + 63) & !63);
write_profile_tier_level(&mut bw, sps_level, chroma, bit_depth);
bw.write_ue(0);
bw.write_ue(chroma.idc()); if chroma.idc() == 3 {
bw.write_bit(false); }
let coded_w = (width + 63) & !63;
let coded_h = (height + 63) & !63;
bw.write_ue(coded_w);
bw.write_ue(coded_h);
let sub_w = chroma.sub_w() as u32;
let sub_h = chroma.sub_h() as u32;
let crop_right = (coded_w - width) / sub_w;
let crop_bottom = (coded_h - height) / sub_h;
let need_window = crop_right > 0 || crop_bottom > 0;
bw.write_bit(need_window);
if need_window {
bw.write_ue(0); bw.write_ue(crop_right); bw.write_ue(0); bw.write_ue(crop_bottom); }
bw.write_ue(bit_depth.minus8() as u32); bw.write_ue(bit_depth.minus8() as u32);
bw.write_ue(4);
bw.write_bit(false);
bw.write_ue(2); bw.write_ue(0); bw.write_ue(0);
bw.write_ue(0);
bw.write_ue(3);
bw.write_ue(0);
bw.write_ue(3);
bw.write_ue(0);
bw.write_ue(0);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(true); bw.write_bit(false);
bw.write_ue(0); bw.write_bit(false); bw.write_bit(true); bw.write_bit(true);
bw.write_bit(true); write_vui(&mut bw);
bw.write_bit(false);
bw.rbsp_trailing_bits();
Nalu {
nal_type: 33,
data: bw.finish(),
}
}
fn write_vui(bw: &mut BitWriter) {
bw.write_bit(false); bw.write_bit(false);
bw.write_bit(true);
bw.write_bits(5, 3); bw.write_bit(true); bw.write_bit(true); bw.write_bits(1, 8); bw.write_bits(13, 8); bw.write_bits(1, 8);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); }
pub(crate) fn build_pps(qp: u8) -> Nalu {
let mut bw = BitWriter::new();
nalu_header(&mut bw, 34);
bw.write_ue(0); bw.write_ue(0); bw.write_bit(false); bw.write_bit(false); bw.write_bits(0, 3); bw.write_bit(false); bw.write_bit(false); bw.write_ue(0); bw.write_ue(0); bw.write_se(qp as i32 - 26); bw.write_bit(false); bw.write_bit(false);
bw.write_bit(false);
bw.write_se(0); bw.write_se(0);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(true);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_ue(0); bw.write_bit(false); bw.write_bit(false);
bw.rbsp_trailing_bits();
Nalu {
nal_type: 34,
data: bw.finish(),
}
}
pub(crate) fn encode_intra(
yuv: &Yuv,
width: u32,
height: u32,
quality: u8,
) -> Result<NaluStream, EncodeError> {
let vps = build_vps(width, height, yuv.chroma, yuv.bit_depth);
let sps = build_sps(width, height, yuv.chroma, yuv.bit_depth);
let qp_val: u8 = ((100 - quality.clamp(1, 100) as u32) * 41 / 99 + 10).min(51) as u8;
let pps = build_pps(qp_val);
let (idr, _ry, _rcb, _rcr) = build_idr_slice(yuv, width, height, quality)?;
Ok(NaluStream {
nalus: vec![vps, sps, pps, idr],
})
}
#[allow(clippy::type_complexity)]
fn build_idr_slice(
yuv: &Yuv,
width: u32,
height: u32,
quality: u8,
) -> Result<(Nalu, Vec<u16>, Vec<u16>, Vec<u16>), EncodeError> {
let qp_val: u8 = ((100 - quality.clamp(1, 100) as u32) * 41 / 99 + 10).min(51) as u8;
let _ = quality;
let sub_w = yuv.chroma.sub_w();
let sub_h = yuv.chroma.sub_h();
let w = ((width + 63) & !63) as usize;
let h = ((height + 63) & !63) as usize;
let cw = w / sub_w;
let ch = h / sub_h;
let src_yw = yuv.width as usize;
let src_yh = yuv.height as usize;
let src_cw = (yuv.width as usize).div_ceil(sub_w);
let src_ch = (yuv.height as usize).div_ceil(sub_h);
let mut hdr = BitWriter::new();
nalu_header(&mut hdr, 20);
hdr.write_bit(true); hdr.write_bit(false); hdr.write_ue(0); hdr.write_ue(2); hdr.write_bit(true); if !yuv.chroma.is_monochrome() {
hdr.write_bit(true); }
hdr.write_se(0); hdr.rbsp_trailing_bits();
let header_bytes = hdr.finish();
let qp: u8 = qp_val;
let mut cab = CabacEncoder::new();
let mut ctx = ContextSet::init_islice(qp);
let mut ictx = IntraModeContexts::init_islice(qp);
let mut rec_y = pad_plane(&yuv.y, src_yw, src_yh, w, h);
let (mut rec_cb, mut rec_cr) = if yuv.chroma.is_monochrome() {
(Vec::new(), Vec::new())
} else {
(
pad_plane(&yuv.cb, src_cw, src_ch, cw, ch),
pad_plane(&yuv.cr, src_cw, src_ch, cw, ch),
)
};
let ctb_size_y = 64usize;
let ctb_size_c = 32usize;
let cu_size_y = 8usize;
let cu_size_c = 4usize;
let ctus_x = w / ctb_size_y;
let ctus_y = h / ctb_size_y;
let total_ctus = ctus_x * ctus_y;
let mut ctu_idx = 0usize;
for ctu_row in 0..ctus_y {
for ctu_col in 0..ctus_x {
let lu_row0 = ctu_row * ctb_size_y;
let lu_col0 = ctu_col * ctb_size_y;
let ch_row0 = ctu_row * ctb_size_c;
let ch_col0 = ctu_col * ctb_size_c;
if ctu_col > 0 {
cab.encode_bin(0, &mut ctx.sao_merge_flag); }
if ctu_row > 0 {
cab.encode_bin(0, &mut ctx.sao_merge_flag); }
cab.encode_bin(0, &mut ctx.sao_type_idx);
if !yuv.chroma.is_monochrome() {
cab.encode_bin(0, &mut ctx.sao_type_idx); }
let cl0 = if ctu_col > 0 { 1 } else { 0 };
let ca0 = if ctu_row > 0 { 1 } else { 0 };
cab.encode_bin(1, &mut ctx.split_cu_flag[cl0 + ca0]);
for (q1y, q1x) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)] {
let l1_lu_r = lu_row0 + q1y * 32;
let l1_lu_c = lu_col0 + q1x * 32;
let l1_ch_r = ch_row0 + q1y * 16;
let l1_ch_c = ch_col0 + q1x * 16;
let cl1 = if q1x > 0 || ctu_col > 0 { 1 } else { 0 };
let ca1 = if q1y > 0 || ctu_row > 0 { 1 } else { 0 };
cab.encode_bin(1, &mut ctx.split_cu_flag[cl1 + ca1]);
for (q2y, q2x) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)] {
let l2_lu_r = l1_lu_r + q2y * 16;
let l2_lu_c = l1_lu_c + q2x * 16;
let l2_ch_r = l1_ch_r + q2y * 8;
let l2_ch_c = l1_ch_c + q2x * 8;
let cl2 = if q2x > 0 || q1x > 0 || ctu_col > 0 {
1
} else {
0
};
let ca2 = if q2y > 0 || q1y > 0 || ctu_row > 0 {
1
} else {
0
};
cab.encode_bin(1, &mut ctx.split_cu_flag[cl2 + ca2]);
for (dy, dx) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)] {
let lu_row = l2_lu_r + dy * cu_size_y;
let lu_col = l2_lu_c + dx * cu_size_y;
let ch_row = lu_row / sub_h;
let ch_col = lu_col / sub_w;
let _ = (l2_ch_r, l2_ch_c, cu_size_c);
let geo = CuGeometry {
lu_row,
lu_col,
ch_row,
ch_col,
yw_stride: w,
src_yh,
cw_stride: cw,
src_cw,
src_ch,
};
let src = CuSrcPlanes {
y: &yuv.y,
cb: &yuv.cb,
cr: &yuv.cr,
src_yw,
};
let mut rec = CuRecPlanes {
y: &mut rec_y,
cb: &mut rec_cb,
cr: &mut rec_cr,
};
let par = CuParams {
qp,
chroma: yuv.chroma,
bit_depth: yuv.bit_depth,
};
encode_cu(&mut cab, &mut ctx, &mut ictx, &src, &mut rec, &geo, &par);
}
}
}
let is_last_ctu = ctu_idx == total_ctus - 1;
cab.encode_terminate(if is_last_ctu { 1 } else { 0 });
ctu_idx += 1;
}
}
let cabac_bytes = cab.finish();
let mut nalu_data = header_bytes;
nalu_data.extend_from_slice(&cabac_bytes);
if yuv.chroma.is_monochrome() {
crate::deblock::deblock_luma_only(&mut rec_y, w, h, qp_val, yuv.bit_depth);
} else {
crate::deblock::deblock(
&mut rec_y,
w,
h,
&mut rec_cb,
&mut rec_cr,
cw,
ch,
qp_val,
yuv.bit_depth,
);
}
Ok((
Nalu {
nal_type: 20,
data: nalu_data,
},
rec_y,
rec_cb,
rec_cr,
))
}
fn pad_plane(src: &[u16], src_w: usize, src_h: usize, dst_w: usize, dst_h: usize) -> Vec<u16> {
let mut out = vec![128u16; dst_w * dst_h];
for r in 0..dst_h {
let sr = r.min(src_h - 1);
for c in 0..dst_w {
let sc = c.min(src_w - 1);
out[r * dst_w + c] = src[sr * src_w + sc];
}
}
out
}
#[allow(clippy::too_many_arguments)]
fn mpm_list(cand_a: u8, cand_b: u8) -> [u8; 3] {
const PLANAR: u8 = 0;
const DC: u8 = 1;
const ANG26: u8 = 26;
if cand_a == cand_b {
if cand_a < 2 {
[PLANAR, DC, ANG26]
} else {
let m1 = 2 + ((cand_a as i32 - 2 - 1 + 32) % 32) as u8;
let m2 = 2 + ((cand_a as i32 - 2 + 1) % 32) as u8;
[cand_a, m1, m2]
}
} else {
let third = if cand_a != PLANAR && cand_b != PLANAR {
PLANAR
} else if cand_a != DC && cand_b != DC {
DC
} else {
ANG26
};
[cand_a, cand_b, third]
}
}
fn is_block_decoded(
nr: usize,
nc: usize,
cur_r: usize,
cur_c: usize,
ctb: usize,
width: usize,
) -> bool {
if nc >= width {
return false;
}
let blk = 8usize;
let ctus_x = width / ctb;
let grid = ctb / blk; let order = |r: usize, c: usize| -> i64 {
let ci = (r / ctb) * ctus_x + (c / ctb);
let mut sr = ((r % ctb) / blk) as u64;
let mut sc = ((c % ctb) / blk) as u64;
let mut z: u64 = 0;
let mut bit = 0;
let mut g = grid;
while g > 1 {
z |= (sc & 1) << (2 * bit);
z |= (sr & 1) << (2 * bit + 1);
sr >>= 1;
sc >>= 1;
bit += 1;
g >>= 1;
}
let cells = (grid * grid) as i64;
ci as i64 * cells + z as i64
};
order(nr, nc) < order(cur_r, cur_c)
}
fn chroma_qp_for(qp: u8, chroma: crate::fmt::ChromaFormat) -> u8 {
let qpi = (qp as i32).clamp(0, 57);
match chroma {
crate::fmt::ChromaFormat::Yuv420 => {
static QP_C: [u8; 14] = [29, 30, 31, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37];
if qpi < 30 {
qpi as u8
} else if qpi > 43 {
(qpi - 6) as u8
} else {
QP_C[(qpi - 30) as usize]
}
}
crate::fmt::ChromaFormat::Monochrome => qpi.min(51) as u8,
crate::fmt::ChromaFormat::Yuv422 | crate::fmt::ChromaFormat::Yuv444 => qpi.min(51) as u8,
}
}
struct CuGeometry {
lu_row: usize,
lu_col: usize,
ch_row: usize,
ch_col: usize,
yw_stride: usize,
src_yh: usize,
cw_stride: usize,
src_cw: usize,
src_ch: usize,
}
struct CuSrcPlanes<'a> {
y: &'a [u16],
cb: &'a [u16],
cr: &'a [u16],
src_yw: usize,
}
struct CuRecPlanes<'a> {
y: &'a mut [u16],
cb: &'a mut [u16],
cr: &'a mut [u16],
}
struct CuParams {
qp: u8,
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
}
fn encode_cu(
enc: &mut CabacEncoder,
ctx: &mut ContextSet,
ictx: &mut IntraModeContexts,
src: &CuSrcPlanes<'_>,
rec: &mut CuRecPlanes<'_>,
geo: &CuGeometry,
par: &CuParams,
) {
let CuGeometry {
lu_row,
lu_col,
ch_row,
ch_col,
yw_stride,
src_yh,
cw_stride,
src_cw,
src_ch,
} = *geo;
let CuSrcPlanes {
y: src_y,
cb: src_cb,
cr: src_cr,
src_yw,
} = *src;
let CuRecPlanes {
y: rec_y,
cb: rec_cb,
cr: rec_cr,
} = rec;
let CuParams {
qp,
chroma,
bit_depth,
} = *par;
const LU: usize = 8; let neutral: u16 = bit_depth.neutral(); let max_val: u16 = bit_depth.max_val(); let qp_bd_offset = bit_depth.qp_bd_offset();
let qp_slice = qp; let qp = qp_slice + qp_bd_offset; let n_chroma_tb = chroma.chroma_tbs_per_cu();
let coded_yh = rec_y.len() / yw_stride;
let coded_ch_h = if cw_stride > 0 {
rec_cb.len() / cw_stride.max(1)
} else {
0
};
let (yc0, ya, yl) = intra::get_reference_samples(
rec_y,
yw_stride,
lu_row,
lu_col,
coded_yh,
LU,
64,
yw_stride / 64,
neutral,
);
let (yaf, ylf) = intra::filter_references(yc0, &ya, &yl, LU);
let y_pred = intra::predict_planar(&yaf, &ylf, LU);
enc.encode_bin(1, &mut ictx.part_mode);
let _ = &ictx.part_mode;
let ctb = 64usize;
let avail_left =
lu_col > 0 && is_block_decoded(lu_row, lu_col - 1, lu_row, lu_col, ctb, yw_stride);
let above_in_same_ctb = lu_row > 0 && ((lu_row - 1) >= (lu_row / ctb) * ctb);
let avail_above = lu_row > 0
&& above_in_same_ctb
&& is_block_decoded(lu_row - 1, lu_col, lu_row, lu_col, ctb, yw_stride);
const PLANAR: u8 = 0;
const DC: u8 = 1;
let cand_a = if avail_left { PLANAR } else { DC };
let cand_b = if avail_above { PLANAR } else { DC };
let mpm = mpm_list(cand_a, cand_b);
let planar_idx = mpm.iter().position(|&m| m == PLANAR);
if let Some(idx) = planar_idx {
enc.encode_bin(1, &mut ictx.prev_intra_luma_pred_flag);
match idx {
0 => {
enc.encode_bypass(0);
}
1 => {
enc.encode_bypass(1);
enc.encode_bypass(0);
}
_ => {
enc.encode_bypass(1);
enc.encode_bypass(1);
}
}
} else {
enc.encode_bin(0, &mut ictx.prev_intra_luma_pred_flag);
let mut sorted = mpm;
sorted.sort_unstable();
let mut rem = PLANAR as i32;
for &m in sorted.iter() {
if (m as i32) <= rem {
rem += 1;
}
}
for i in (0..5).rev() {
enc.encode_bypass(((rem >> i) & 1) as u8);
}
}
if !chroma.is_monochrome() {
enc.encode_bin(0, &mut ictx.intra_chroma_pred_mode);
}
let chroma_qp = chroma_qp_for(qp_slice, chroma) + qp_bd_offset;
let sub_w = chroma.sub_w();
let sub_h = chroma.sub_h();
let luma_ctus_x = yw_stride / 64;
let ctb = chroma.chroma_tb_size(); let log2_ctb = ctb.trailing_zeros(); let chroma_scan: &[(usize, usize)] = if ctb == 4 {
&dct::DIAG_SCAN_4X4
} else {
&dct::ZIGZAG
};
struct ChromaTb {
cb_zz: Vec<i16>,
cb_nz: bool,
cr_zz: Vec<i16>,
cr_nz: bool,
}
let mut tbs: Vec<ChromaTb> = Vec::with_capacity(n_chroma_tb);
for t in 0..n_chroma_tb {
let sub_ch_row = ch_row + t * ctb;
let filt = ctb > 4; let (bc0, ba, bl) = intra::get_reference_samples_chroma(
rec_cb,
cw_stride,
sub_ch_row,
ch_col,
coded_ch_h,
ctb,
sub_w,
sub_h,
yw_stride,
coded_yh,
luma_ctus_x,
lu_row,
lu_col,
neutral,
);
let (baf, blf) = if filt {
intra::filter_references(bc0, &ba, &bl, ctb)
} else {
(ba, bl)
};
let cb_pred = intra::predict_planar(&baf, &blf, ctb);
let (rc0, ra, rl) = intra::get_reference_samples_chroma(
rec_cr,
cw_stride,
sub_ch_row,
ch_col,
coded_ch_h,
ctb,
sub_w,
sub_h,
yw_stride,
coded_yh,
luma_ctus_x,
lu_row,
lu_col,
neutral,
);
let (raf, rlf) = if filt {
intra::filter_references(rc0, &ra, &rl, ctb)
} else {
(ra, rl)
};
let cr_pred = intra::predict_planar(&raf, &rlf, ctb);
let b_orig = extract_block_dyn(src_cb, src_cw, src_ch, sub_ch_row, ch_col, ctb);
let r_orig = extract_block_dyn(src_cr, src_cw, src_ch, sub_ch_row, ch_col, ctb);
let b_res: Vec<i32> = b_orig
.iter()
.zip(&cb_pred)
.map(|(&o, &p)| o as i32 - p as i32)
.collect();
let r_res: Vec<i32> = r_orig
.iter()
.zip(&cr_pred)
.map(|(&o, &p)| o as i32 - p as i32)
.collect();
let cb_level = crate::hevc_transform::quantize(
&crate::hevc_transform::fwd_transform(&b_res, ctb, bit_depth.bits()),
ctb,
chroma_qp,
bit_depth.bits(),
);
let cr_level = crate::hevc_transform::quantize(
&crate::hevc_transform::fwd_transform(&r_res, ctb, bit_depth.bits()),
ctb,
chroma_qp,
bit_depth.bits(),
);
let cb_zz: Vec<i16> = chroma_scan
.iter()
.map(|&(r, c)| cb_level[r * ctb + c])
.collect();
let cr_zz: Vec<i16> = chroma_scan
.iter()
.map(|&(r, c)| cr_level[r * ctb + c])
.collect();
let cb_nz = cb_zz.iter().any(|&x| x != 0);
let cr_nz = cr_zz.iter().any(|&x| x != 0);
let b_dq = crate::hevc_transform::dequantize(&cb_level, ctb, chroma_qp, bit_depth.bits());
let b_rec_f: Vec<f32> = crate::hevc_transform::inv_transform(&b_dq, ctb, bit_depth.bits())
.iter()
.map(|&v| v as f32)
.collect();
let b_rec = intra::reconstruct(&cb_pred, &b_rec_f, ctb, max_val);
let r_dq = crate::hevc_transform::dequantize(&cr_level, ctb, chroma_qp, bit_depth.bits());
let r_rec_f: Vec<f32> = crate::hevc_transform::inv_transform(&r_dq, ctb, bit_depth.bits())
.iter()
.map(|&v| v as f32)
.collect();
let r_rec = intra::reconstruct(&cr_pred, &r_rec_f, ctb, max_val);
for r in 0..ctb {
for c in 0..ctb {
let (row, col) = (sub_ch_row + r, ch_col + c);
if row < coded_ch_h && col < cw_stride {
rec_cb[row * cw_stride + col] = b_rec[r * ctb + c];
rec_cr[row * cw_stride + col] = r_rec[r * ctb + c];
}
}
}
tbs.push(ChromaTb {
cb_zz,
cb_nz,
cr_zz,
cr_nz,
});
}
let y_orig = extract_block_n::<LU>(src_y, src_yw, src_yh, lu_row, lu_col);
let y_res = intra::compute_residual(&y_orig, &y_pred, LU);
let y_res_i: Vec<i32> = y_res.iter().map(|&v| v as i32).collect();
let y_tcoeff = crate::hevc_transform::fwd_transform(&y_res_i, LU, bit_depth.bits());
let y_level = crate::hevc_transform::quantize(&y_tcoeff, LU, qp, bit_depth.bits()); let y_zigzag: Vec<i16> = dct::ZIGZAG
.iter()
.map(|&(r, c)| y_level[r * LU + c])
.collect();
let y_nz = y_zigzag.iter().any(|&x| x != 0);
for t in &tbs {
encode_cbf_chroma(enc, ctx, t.cb_nz, 0);
}
for t in &tbs {
encode_cbf_chroma(enc, ctx, t.cr_nz, 0);
}
encode_cbf_luma(enc, ctx, y_nz, 0);
if y_nz {
encode_residual(enc, ctx, &y_zigzag, 3, true);
}
for t in &tbs {
if t.cb_nz {
encode_residual(enc, ctx, &t.cb_zz, log2_ctb, false);
}
}
for t in &tbs {
if t.cr_nz {
encode_residual(enc, ctx, &t.cr_zz, log2_ctb, false);
}
}
let y_dq = crate::hevc_transform::dequantize(&y_level, LU, qp, bit_depth.bits());
let y_res_rec = crate::hevc_transform::inv_transform(&y_dq, LU, bit_depth.bits());
let y_res_rec_f: Vec<f32> = y_res_rec.iter().map(|&v| v as f32).collect();
let y_rec = intra::reconstruct(&y_pred, &y_res_rec_f, LU, max_val);
for r in 0..LU {
for c in 0..LU {
let (row, col) = (lu_row + r, lu_col + c);
if row < coded_yh && col < yw_stride {
rec_y[row * yw_stride + col] = y_rec[r * LU + c];
}
}
}
}
fn extract_block_n<const N: usize>(
plane: &[u16],
src_w: usize,
src_h: usize,
row: usize,
col: usize,
) -> Vec<u16> {
let mut out = vec![128u16; N * N];
for r in 0..N {
for c in 0..N {
out[r * N + c] = plane[(row + r).min(src_h - 1) * src_w + (col + c).min(src_w - 1)];
}
}
out
}
fn extract_block_dyn(
plane: &[u16],
src_w: usize,
src_h: usize,
row: usize,
col: usize,
n: usize,
) -> Vec<u16> {
let mut out = vec![128u16; n * n];
for r in 0..n {
for c in 0..n {
out[r * n + c] = plane[(row + r).min(src_h - 1) * src_w + (col + c).min(src_w - 1)];
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bit_writer_basic() {
let mut bw = BitWriter::new();
bw.write_bits(0b10110, 5);
bw.rbsp_trailing_bits();
assert_eq!(bw.finish()[0], 0b1011_0100);
}
#[test]
fn ue_coding() {
let mut bw = BitWriter::new();
bw.write_ue(0); bw.rbsp_trailing_bits();
assert_eq!(bw.finish()[0] >> 7, 1);
}
#[test]
fn vps_starts_with_nalu_header() {
let vps = build_vps(
256,
256,
crate::fmt::ChromaFormat::Yuv420,
crate::fmt::BitDepth::Eight,
);
assert_eq!(vps.data[0], 0x40, "VPS first byte should be 0x40");
}
#[test]
fn sps_conformance_window() {
let sps = build_sps(
64,
48,
crate::fmt::ChromaFormat::Yuv420,
crate::fmt::BitDepth::Eight,
);
assert!(sps.data.len() > 10);
}
#[test]
fn pps_builds_cleanly() {
let pps = build_pps(30);
assert_eq!(pps.data[0], 0x44, "PPS first byte should be 0x44");
}
}