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 {
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);
static 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,
) {
let is_420 = matches!(
chroma,
crate::fmt::ChromaFormat::Yuv420 | crate::fmt::ChromaFormat::Monochrome
);
let bits = bit_depth.bits();
let is_rext = !is_420 || bits > 10;
let (profile_idc, compat): (u32, u32) = if is_rext {
(4, 0x0800_0000) } else if bits <= 8 {
(3, 0x7000_0000) } else {
(2, 0x2000_0000) };
bw.write_bits(0, 2); bw.write_bit(false); bw.write_bits(profile_idc, 5); bw.write_bits(compat, 32);
bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.write_bit(true);
if is_rext {
let is_444 = matches!(chroma, crate::fmt::ChromaFormat::Yuv444);
let is_mono = matches!(chroma, crate::fmt::ChromaFormat::Monochrome);
bw.write_bit(bits <= 12); bw.write_bit(bits <= 10); bw.write_bit(bits <= 8); 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_bit(bits <= 14); bw.write_bits(0, 32);
bw.write_bits(0, 2); } else {
bw.write_bits(0, 32);
bw.write_bits(0, 12);
}
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(0); 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,
color: Option<&crate::color::ColorEncoding>,
) -> 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(0); 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, color);
let need_range_ext = bit_depth.bits() > 8;
bw.write_bit(need_range_ext); if need_range_ext {
bw.write_bit(true); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bits(0, 4); 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); bw.write_bit(false); bw.write_bit(false); }
bw.rbsp_trailing_bits();
Nalu {
_nal_type: 33,
data: bw.finish(),
}
}
fn write_vui(bw: &mut BitWriter, color: Option<&crate::color::ColorEncoding>) {
bw.write_bit(false); bw.write_bit(false);
bw.write_bit(true);
bw.write_bits(5, 3); bw.write_bit(color.map(|c| c.full_range).unwrap_or(true)); match color {
Some(c) => {
bw.write_bit(true); bw.write_bits(c.primaries as u32, 8); bw.write_bits(c.transfer as u32, 8); bw.write_bits(c.matrix as u32, 8); }
None => {
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); bw.write_bit(false); }
pub(crate) fn build_pps(qp: u8, lossless: bool) -> 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(lossless); 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,
lossless: bool,
color: Option<crate::color::ColorEncoding>,
) -> 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, color.as_ref());
let qp_val: u8 = ((100 - quality.clamp(1, 100) as u32) * 41 / 99 + 10).min(51) as u8;
let pps = build_pps(qp_val, lossless);
let (idr, _ry, _rcb, _rcr) = build_idr_slice(yuv, width, height, quality, lossless)?;
Ok(NaluStream {
nalus: vec![vps, sps, pps, idr],
})
}
#[allow(clippy::type_complexity)]
fn build_idr_slice(
yuv: &Yuv,
width: u32,
height: u32,
quality: u8,
lossless: bool,
) -> 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 rd16 = matches!(
yuv.chroma,
crate::fmt::ChromaFormat::Yuv420 | crate::fmt::ChromaFormat::Monochrome
);
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.write_bit(true); 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 lambda = 0.57_f64 * 2f64.powf((qp as f64 - 12.0) / 3.0);
let mut cu_depth = vec![0u8; (w / 8) * (h / 8)];
let blk_stride = w / 8;
let mut mode_map = vec![0u8; (w / 8) * (h / 8)];
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
};
let _ = (cl2, ca2, l2_ch_r, l2_ch_c, cu_size_c); let bx = w / cu_size_y;
let cond_l = l2_lu_c > 0
&& cu_depth[(l2_lu_r / cu_size_y) * bx + (l2_lu_c - 1) / cu_size_y] > 2;
let cond_a = l2_lu_r > 0
&& cu_depth[((l2_lu_r - 1) / cu_size_y) * bx + l2_lu_c / cu_size_y] > 2;
let split_ctx = cond_l as usize + cond_a as usize;
let strides8 = PlaneStrides {
w,
src_yw,
src_yh,
cw,
src_cw,
src_ch,
sub_w,
sub_h,
};
let l2_ch_rr = l2_lu_r / sub_h;
let l2_ch_cc = l2_lu_c / sub_w;
let mut chose_16 = false;
if rd16 {
let base_bits = cab.flushed_bits();
let base_snap = cab.snapshot();
let base_ctx = ctx.clone();
let base_ictx = ictx.clone();
cab.encode_bin(0, &mut ctx.split_cu_flag[split_ctx]);
code_one_cu(
Entropy {
enc: &mut cab,
ctx: &mut ctx,
ictx: &mut ictx,
},
yuv,
&mut rec_y,
&mut rec_cb,
&mut rec_cr,
l2_lu_r,
l2_lu_c,
16,
strides8,
qp,
&mut mode_map,
blk_stride,
lossless,
);
let d_a = region_sse(
yuv, &rec_y, &rec_cb, &rec_cr, l2_lu_r, l2_lu_c, l2_ch_rr, l2_ch_cc,
strides8,
);
let bits_a = cab.flushed_bits().saturating_sub(base_bits) as f64;
let a_snap = cab.snapshot();
let a_ctx = ctx.clone();
let a_ictx = ictx.clone();
let a_tail: Vec<u8> = cab.output[base_snap.output_len()..].to_vec();
let mut sa_y = [0u16; 256];
let mut sa_cb = [0u16; 64];
let mut sa_cr = [0u16; 64];
let mut sa_mode = [0u8; 4];
for r in 0..16 {
let o = (l2_lu_r + r) * w + l2_lu_c;
sa_y[r * 16..r * 16 + 16].copy_from_slice(&rec_y[o..o + 16]);
}
if !yuv.chroma.is_monochrome() {
for r in 0..8 {
let o = (l2_ch_rr + r) * cw + l2_ch_cc;
sa_cb[r * 8..r * 8 + 8].copy_from_slice(&rec_cb[o..o + 8]);
sa_cr[r * 8..r * 8 + 8].copy_from_slice(&rec_cr[o..o + 8]);
}
}
for br in 0..2 {
for bc in 0..2 {
sa_mode[br * 2 + bc] = mode_map
[((l2_lu_r / 8) + br) * blk_stride + (l2_lu_c / 8) + bc];
}
}
cab.restore(&base_snap);
ctx = base_ctx;
ictx = base_ictx;
cab.encode_bin(1, &mut ctx.split_cu_flag[split_ctx]);
for (dy, dx) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)] {
code_one_cu(
Entropy {
enc: &mut cab,
ctx: &mut ctx,
ictx: &mut ictx,
},
yuv,
&mut rec_y,
&mut rec_cb,
&mut rec_cr,
l2_lu_r + dy * cu_size_y,
l2_lu_c + dx * cu_size_y,
8,
strides8,
qp,
&mut mode_map,
blk_stride,
lossless,
);
}
let d_b = region_sse(
yuv, &rec_y, &rec_cb, &rec_cr, l2_lu_r, l2_lu_c, l2_ch_rr, l2_ch_cc,
strides8,
);
let bits_b = cab.flushed_bits().saturating_sub(base_bits) as f64;
chose_16 = d_a + lambda * bits_a <= d_b + lambda * bits_b;
if chose_16 {
for r in 0..16 {
let o = (l2_lu_r + r) * w + l2_lu_c;
rec_y[o..o + 16].copy_from_slice(&sa_y[r * 16..r * 16 + 16]);
}
if !yuv.chroma.is_monochrome() {
for r in 0..8 {
let o = (l2_ch_rr + r) * cw + l2_ch_cc;
rec_cb[o..o + 8].copy_from_slice(&sa_cb[r * 8..r * 8 + 8]);
rec_cr[o..o + 8].copy_from_slice(&sa_cr[r * 8..r * 8 + 8]);
}
}
for br in 0..2 {
for bc in 0..2 {
mode_map
[((l2_lu_r / 8) + br) * blk_stride + (l2_lu_c / 8) + bc] =
sa_mode[br * 2 + bc];
cu_depth[((l2_lu_r / cu_size_y) + br) * bx
+ (l2_lu_c / cu_size_y)
+ bc] = 2;
}
}
cab.reinstate_tail(&base_snap, &a_tail);
cab.restore(&a_snap);
ctx = a_ctx;
ictx = a_ictx;
} else {
for br in 0..2 {
for bc in 0..2 {
cu_depth[((l2_lu_r / cu_size_y) + br) * bx
+ (l2_lu_c / cu_size_y)
+ bc] = 3;
}
}
}
} else {
cab.encode_bin(1, &mut ctx.split_cu_flag[split_ctx]);
for (dy, dx) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)] {
code_one_cu(
Entropy {
enc: &mut cab,
ctx: &mut ctx,
ictx: &mut ictx,
},
yuv,
&mut rec_y,
&mut rec_cb,
&mut rec_cr,
l2_lu_r + dy * cu_size_y,
l2_lu_c + dx * cu_size_y,
8,
strides8,
qp,
&mut mode_map,
blk_stride,
lossless,
);
}
for br in 0..2 {
for bc in 0..2 {
cu_depth[((l2_lu_r / cu_size_y) + br) * bx
+ (l2_lu_c / cu_size_y)
+ bc] = 3;
}
}
}
let _ = chose_16;
}
}
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 !lossless {
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);
let src_row = &src[sr * src_w..sr * src_w + src_w];
let dst_row = &mut out[r * dst_w..r * dst_w + dst_w];
dst_row[..src_w].copy_from_slice(src_row);
let edge = src_row[src_w - 1];
dst_row[src_w..].fill(edge);
}
out
}
#[allow(clippy::too_many_arguments)]
fn satd_block(orig: &[u16], pred: &[u16], n: usize) -> u32 {
let mut total = 0u32;
let mut d = [0i32; 16];
for by in (0..n).step_by(4) {
for bx in (0..n).step_by(4) {
for r in 0..4 {
let row = (by + r) * n + bx;
let o = &orig[row..row + 4];
let p = &pred[row..row + 4];
let dr = &mut d[r * 4..r * 4 + 4];
for c in 0..4 {
dr[c] = o[c] as i32 - p[c] as i32;
}
}
for r in 0..4 {
let o = r * 4;
let a0 = d[o] + d[o + 2];
let a1 = d[o + 1] + d[o + 3];
let a2 = d[o] - d[o + 2];
let a3 = d[o + 1] - d[o + 3];
d[o] = a0 + a1;
d[o + 1] = a0 - a1;
d[o + 2] = a2 + a3;
d[o + 3] = a2 - a3;
}
for c in 0..4 {
let a0 = d[c] + d[8 + c];
let a1 = d[4 + c] + d[12 + c];
let a2 = d[c] - d[8 + c];
let a3 = d[4 + c] - d[12 + c];
d[c] = a0 + a1;
d[4 + c] = a0 - a1;
d[8 + c] = a2 + a3;
d[12 + c] = a2 - a3;
}
let mut s = 0u32;
for &v in d.iter() {
s += v.unsigned_abs();
}
total += s.div_ceil(2);
}
}
total
}
static MODE_422_MAP: [u8; 35] = [
0, 1, 2, 2, 2, 2, 3, 5, 7, 8, 10, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 23, 24, 24, 25, 25,
26, 27, 27, 28, 28, 29, 29, 30, 31,
];
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,
blk_stride: usize,
}
struct Entropy<'a> {
enc: &'a mut CabacEncoder,
ctx: &'a mut ContextSet,
ictx: &'a mut IntraModeContexts,
}
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,
lu: usize,
lossless: bool,
}
#[derive(Clone, Copy)]
struct PlaneStrides {
w: usize,
src_yw: usize,
src_yh: usize,
cw: usize,
src_cw: usize,
src_ch: usize,
sub_w: usize,
sub_h: usize,
}
fn encode_cu(
ent: Entropy<'_>,
src: &CuSrcPlanes<'_>,
rec: &mut CuRecPlanes<'_>,
geo: &CuGeometry,
par: &CuParams,
mode_map: &mut [u8],
) {
let Entropy { enc, ctx, ictx } = ent;
let CuGeometry {
lu_row,
lu_col,
ch_row,
ch_col,
yw_stride,
src_yh,
cw_stride,
src_cw,
src_ch,
blk_stride,
} = *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,
lu,
lossless,
} = *par;
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
};
const PLANAR: u8 = 0;
const DC: u8 = 1;
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);
let mode_at = |r: usize, c: usize| mode_map[(r / 8) * blk_stride + c / 8];
let cand_a = if avail_left {
mode_at(lu_row, lu_col - 1)
} else {
DC
};
let cand_b = if avail_above {
mode_at(lu_row - 1, lu_col)
} else {
DC
};
let mpm = mpm_list(cand_a, cand_b);
let (yc0, ya, yl) = intra::get_reference_samples(
rec_y,
intra::LumaRefGeometry {
stride: yw_stride,
block_row: lu_row,
block_col: lu_col,
height: coded_yh,
n: lu,
ctu: 64,
ctus_x: yw_stride / 64,
neutral,
},
);
let y_orig_rmd = extract_block_dyn(src_y, src_yw, src_yh, lu_row, lu_col, lu);
let lambda = 0.57_f64 * 2f64.powf((qp_slice as f64 - 12.0) / 3.0);
let lambda_mode = lambda.sqrt();
let mut best_mode = PLANAR;
let mut best_cost = f64::INFINITY;
let (fa, fl) = intra::filter_references(yc0, &ya, &yl, lu);
let cf = ((ya[0] as i32 + 2 * yc0 as i32 + yl[0] as i32 + 2) >> 2) as u16;
for mode in 0u8..35 {
let pred = if intra::should_filter_refs(mode, lu) {
match mode {
PLANAR => intra::predict_planar(&fa, &fl, lu),
DC => intra::predict_dc(&fa, &fl, lu, true),
_ => intra::predict_angular(cf, &fa, &fl, lu, mode, true, max_val as i32),
}
} else {
match mode {
PLANAR => intra::predict_planar(&ya, &yl, lu),
DC => intra::predict_dc(&ya, &yl, lu, true),
_ => intra::predict_angular(yc0, &ya, &yl, lu, mode, true, max_val as i32),
}
};
let satd = satd_block(&y_orig_rmd, &pred, lu) as f64;
let mode_bits = if let Some(i) = mpm.iter().position(|&m| m == mode) {
(1 + i + 1) as f64 } else {
6.0 };
let cost = satd + lambda_mode * mode_bits;
if cost < best_cost {
best_cost = cost;
best_mode = mode;
}
}
let luma_mode = best_mode;
let y_pred = if intra::should_filter_refs(luma_mode, lu) {
match luma_mode {
PLANAR => intra::predict_planar(&fa, &fl, lu),
DC => intra::predict_dc(&fa, &fl, lu, true),
_ => intra::predict_angular(cf, &fa, &fl, lu, luma_mode, true, max_val as i32),
}
} else {
match luma_mode {
PLANAR => intra::predict_planar(&ya, &yl, lu),
DC => intra::predict_dc(&ya, &yl, lu, true),
_ => intra::predict_angular(yc0, &ya, &yl, lu, luma_mode, true, max_val as i32),
}
};
if lossless {
enc.encode_bin(1, &mut ctx.cu_transquant_bypass_flag);
}
if lu == 8 {
enc.encode_bin(1, &mut ictx.part_mode);
}
let _ = &ictx.part_mode;
if let Some(idx) = mpm.iter().position(|&m| m == luma_mode) {
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 rem = luma_mode as i32;
for &m in mpm.iter() {
if (m as i32) < luma_mode as i32 {
rem -= 1;
}
}
for i in (0..5).rev() {
enc.encode_bypass(((rem >> i) & 1) as u8);
}
}
for br in 0..(lu / 8) {
for bc in 0..(lu / 8) {
mode_map[((lu_row / 8) + br) * blk_stride + (lu_col / 8) + bc] = luma_mode;
}
}
let chroma_mode = if chroma.sub_w() == 2 && chroma.sub_h() == 1 {
MODE_422_MAP[luma_mode as usize]
} else {
luma_mode
};
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 = lu / sub_w; let log2_ctb = ctb.trailing_zeros(); let is_444 = matches!(chroma, crate::fmt::ChromaFormat::Yuv444);
let chroma_tb_scan_idx = dct::scan_idx_for(chroma_mode, log2_ctb, false, is_444);
let chroma_scan: &[(usize, usize)] = dct::coeff_scan(log2_ctb, chroma_tb_scan_idx);
#[derive(Clone, Copy)]
struct ChromaTb {
cb_zz: [i16; 64],
cb_nz: bool,
cr_zz: [i16; 64],
cr_nz: bool,
}
let mut tbs = [ChromaTb {
cb_zz: [0i16; 64],
cb_nz: false,
cr_zz: [0i16; 64],
cr_nz: false,
}; 2];
for (t, tbs) in tbs[..n_chroma_tb].iter_mut().enumerate() {
let sub_ch_row = ch_row + t * ctb;
let filt = ctb > 4 && intra::should_filter_refs(chroma_mode, ctb);
let ((bc0, ba, bl), (rc0, ra, rl)) = intra::get_reference_samples_chroma_pair(
rec_cb,
rec_cr,
intra::ChromaRefGeometry {
stride: cw_stride,
block_row: sub_ch_row,
block_col: ch_col,
chroma_h: coded_ch_h,
n: ctb,
sub_w,
sub_h,
luma_w: yw_stride,
luma_h: coded_yh,
luma_ctus_x,
cur_luma_row: lu_row,
cur_luma_col: lu_col,
neutral,
},
);
let (baf, blf) = if filt {
intra::filter_references(bc0, &ba, &bl, ctb)
} else {
(ba, bl)
};
let bcf = if filt {
((ba[0] as i32 + 2 * bc0 as i32 + bl[0] as i32 + 2) >> 2) as u16
} else {
bc0
};
let cb_pred = intra::predict_chroma_tb(chroma_mode, bcf, &baf, &blf, ctb, max_val as i32);
let (raf, rlf) = if filt {
intra::filter_references(rc0, &ra, &rl, ctb)
} else {
(ra, rl)
};
let rcf = if filt {
((ra[0] as i32 + 2 * rc0 as i32 + rl[0] as i32 + 2) >> 2) as u16
} else {
rc0
};
let cr_pred = intra::predict_chroma_tb(chroma_mode, rcf, &raf, &rlf, ctb, max_val as i32);
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 n_ch = ctb * ctb;
let mut b_res = [0i32; 64];
let mut r_res = [0i32; 64];
for (d, (&o, &p)) in b_res[..n_ch]
.iter_mut()
.zip(b_orig[..n_ch].iter().zip(&cb_pred[..n_ch]))
{
*d = o as i32 - p as i32;
}
for (d, (&o, &p)) in r_res[..n_ch]
.iter_mut()
.zip(r_orig[..n_ch].iter().zip(&cr_pred[..n_ch]))
{
*d = o as i32 - p as i32;
}
let (cb_level, cr_level): ([i16; 256], [i16; 256]) = if lossless {
let mut cbl = [0i16; 256];
let mut crl = [0i16; 256];
for i in 0..n_ch {
cbl[i] = b_res[i] as i16;
crl[i] = r_res[i] as i16;
}
(cbl, crl)
} else {
(
crate::hevc_transform::quantize(
&crate::hevc_transform::fwd_transform(&b_res, ctb, bit_depth.bits()),
ctb,
chroma_qp,
bit_depth.bits(),
),
crate::hevc_transform::quantize(
&crate::hevc_transform::fwd_transform(&r_res, ctb, bit_depth.bits()),
ctb,
chroma_qp,
bit_depth.bits(),
),
)
};
let mut cb_zz = [0i16; 64];
let mut cr_zz = [0i16; 64];
for (&(r, c), dst) in chroma_scan.iter().zip(cb_zz.iter_mut()) {
*dst = cb_level[r * ctb + c];
}
for (&(r, c), dst) in chroma_scan.iter().zip(cr_zz.iter_mut()) {
*dst = cr_level[r * ctb + c];
}
let cb_nz = cb_zz.iter().any(|&x| x != 0);
let cr_nz = cr_zz.iter().any(|&x| x != 0);
let (b_rec, r_rec) = if lossless {
let mut b_res_rec = [0i32; 64];
let mut r_res_rec = [0i32; 64];
for i in 0..n_ch {
b_res_rec[i] = cb_level[i] as i32;
r_res_rec[i] = cr_level[i] as i32;
}
(
intra::reconstruct(&cb_pred, &b_res_rec, ctb, max_val),
intra::reconstruct(&cr_pred, &r_res_rec, ctb, max_val),
)
} else {
let b_dq =
crate::hevc_transform::dequantize(&cb_level, ctb, chroma_qp, bit_depth.bits());
let b_inv = crate::hevc_transform::inv_transform(&b_dq, ctb, bit_depth.bits());
let r_dq =
crate::hevc_transform::dequantize(&cr_level, ctb, chroma_qp, bit_depth.bits());
let r_inv = crate::hevc_transform::inv_transform(&r_dq, ctb, bit_depth.bits());
(
intra::reconstruct(&cb_pred, &b_inv, ctb, max_val),
intra::reconstruct(&cr_pred, &r_inv, 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 = ChromaTb {
cb_zz,
cb_nz,
cr_zz,
cr_nz,
};
}
let y_orig = match lu {
16 => extract_block_n::<16>(src_y, src_yw, src_yh, lu_row, lu_col),
_ => extract_block_n::<8>(src_y, src_yw, src_yh, lu_row, lu_col),
};
let y_res = intra::compute_residual_i32(&y_orig, &y_pred, lu);
let luma_log2_ts: u32 = if lu == 16 { 4 } else { 3 };
let luma_scan_idx = dct::scan_idx_for(luma_mode, luma_log2_ts, true, false);
let luma_scan = dct::coeff_scan(luma_log2_ts, luma_scan_idx);
let y_level: [i16; 256] = if lossless {
let mut lv = [0i16; 256];
for (d, &r) in lv[..lu * lu].iter_mut().zip(y_res[..lu * lu].iter()) {
*d = r as i16;
}
lv
} else {
let y_tcoeff =
crate::hevc_transform::fwd_transform(&y_res[..lu * lu], lu, bit_depth.bits());
crate::hevc_transform::quantize(&y_tcoeff, lu, qp, bit_depth.bits()) };
let y_zigzag: Vec<i16> = luma_scan
.iter()
.map(|&(r, c)| y_level[r * lu + c])
.collect();
let y_nz = y_zigzag.iter().any(|&x| x != 0);
for t in &tbs[..n_chroma_tb] {
encode_cbf_chroma(enc, ctx, t.cb_nz, 0);
}
for t in &tbs[..n_chroma_tb] {
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, luma_log2_ts, true, luma_scan_idx);
}
let chroma_scan_idx = chroma_tb_scan_idx;
for t in &tbs[..n_chroma_tb] {
if t.cb_nz {
encode_residual(enc, ctx, &t.cb_zz, log2_ctb, false, chroma_scan_idx);
}
}
for t in &tbs[..n_chroma_tb] {
if t.cr_nz {
encode_residual(enc, ctx, &t.cr_zz, log2_ctb, false, chroma_scan_idx);
}
}
let y_rec = if lossless {
let mut y_res_rec = [0i32; 256];
for (d, &l) in y_res_rec[..lu * lu]
.iter_mut()
.zip(y_level[..lu * lu].iter())
{
*d = l as i32;
}
intra::reconstruct(&y_pred, &y_res_rec, lu, max_val)
} else {
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());
intra::reconstruct(&y_pred, &y_res_rec, 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];
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn code_one_cu(
ent: Entropy<'_>,
yuv: &Yuv,
rec_y: &mut [u16],
rec_cb: &mut [u16],
rec_cr: &mut [u16],
lu_row: usize,
lu_col: usize,
lu: usize,
strides: PlaneStrides,
qp: u8,
mode_map: &mut [u8],
blk_stride: usize,
lossless: bool,
) {
let PlaneStrides {
w,
src_yw,
src_yh,
cw,
src_cw,
src_ch,
sub_w,
sub_h,
} = strides;
let ch_row = lu_row / sub_h;
let ch_col = lu_col / sub_w;
let geo = CuGeometry {
lu_row,
lu_col,
ch_row,
ch_col,
yw_stride: w,
src_yh,
cw_stride: cw,
src_cw,
src_ch,
blk_stride,
};
let src = CuSrcPlanes {
y: &yuv.y,
cb: &yuv.cb,
cr: &yuv.cr,
src_yw,
};
let mut rec = CuRecPlanes {
y: rec_y,
cb: rec_cb,
cr: rec_cr,
};
let par = CuParams {
qp,
chroma: yuv.chroma,
bit_depth: yuv.bit_depth,
lu,
lossless,
};
encode_cu(ent, &src, &mut rec, &geo, &par, mode_map);
}
#[allow(clippy::too_many_arguments)]
fn region_sse(
yuv: &Yuv,
rec_y: &[u16],
rec_cb: &[u16],
rec_cr: &[u16],
lu_row: usize,
lu_col: usize,
ch_row: usize,
ch_col: usize,
strides: PlaneStrides,
) -> f64 {
let PlaneStrides {
w,
src_yw,
src_yh,
cw,
src_cw,
src_ch,
..
} = strides;
let mut sse: i64 = 0;
for r in 0..16 {
let sy = (lu_row + r).min(src_yh - 1);
for c in 0..16 {
let sx = (lu_col + c).min(src_yw - 1);
let s = yuv.y[sy * src_yw + sx] as i64;
let d = rec_y[(lu_row + r) * w + (lu_col + c)] as i64;
let e = s - d;
sse += e * e;
}
}
if !yuv.chroma.is_monochrome() {
for r in 0..8 {
let sy = (ch_row + r).min(src_ch - 1);
for c in 0..8 {
let sx = (ch_col + c).min(src_cw - 1);
let sb = yuv.cb[sy * src_cw + sx] as i64;
let db = rec_cb[(ch_row + r) * cw + (ch_col + c)] as i64;
let eb = sb - db;
sse += eb * eb;
let sr = yuv.cr[sy * src_cw + sx] as i64;
let dr = rec_cr[(ch_row + r) * cw + (ch_col + c)] as i64;
let er = sr - dr;
sse += er * er;
}
}
}
sse as f64
}
fn extract_block_n<const N: usize>(
plane: &[u16],
src_w: usize,
src_h: usize,
row: usize,
col: usize,
) -> [u16; 256] {
let mut out = [128u16; 256];
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,
) -> [u16; 256] {
let mut out = [128u16; 256];
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,
Some(&crate::color::ColorEncoding::srgb()),
);
assert!(sps.data.len() > 10);
}
#[test]
fn pps_builds_cleanly() {
let pps = build_pps(30, false);
assert_eq!(pps.data[0], 0x44, "PPS first byte should be 0x44");
}
}