use oxideav_core::bits::BitWriter;
use oxideav_core::{Error, Result};
use crate::fdct::fdct_intra;
use crate::picture::SourceFormat;
use crate::quant::{quant_ac, quant_intra_dc};
use crate::tables::{
encode_cbp, encode_mba_diff, lookup_tcoeff, MBA_STUFFING, MTYPE_INTRA, MTYPE_INTRA_MQUANT,
ZIGZAG,
};
pub const DEFAULT_QUANT: u32 = 8;
pub fn encode_intra_picture(
fmt: SourceFormat,
y: &[u8],
y_stride: usize,
cb: &[u8],
cb_stride: usize,
cr: &[u8],
cr_stride: usize,
quant: u32,
temporal_reference: u8,
) -> Result<Vec<u8>> {
if !(1..=31).contains(&quant) {
return Err(Error::invalid(format!(
"h261 encode: QUANT out of range: {quant}"
)));
}
if temporal_reference > 31 {
return Err(Error::invalid(format!(
"h261 encode: TR out of range: {temporal_reference}"
)));
}
let (_w, h) = fmt.dimensions();
let h = h as usize;
if y.len() < y_stride * h || cb.len() < cb_stride * (h / 2) || cr.len() < cr_stride * (h / 2) {
return Err(Error::invalid("h261 encode: input plane too short"));
}
let mut bw = BitWriter::with_capacity(4096);
write_picture_header(&mut bw, fmt, temporal_reference);
for &gn in fmt.gob_numbers() {
write_gob_header(&mut bw, gn, quant);
let (gob_x, gob_y) = gob_origin_luma(fmt, gn);
encode_gob_intra(
&mut bw, y, y_stride, cb, cb_stride, cr, cr_stride, gob_x, gob_y, quant,
);
}
bw.align_to_byte();
Ok(bw.finish())
}
pub fn write_picture_header(bw: &mut BitWriter, fmt: SourceFormat, tr: u8) {
bw.write_u32(0x00010, 20); bw.write_u32(tr as u32, 5); bw.write_u32(0, 1);
bw.write_u32(0, 1);
bw.write_u32(0, 1);
let fmt_bit = match fmt {
SourceFormat::Qcif => 0,
SourceFormat::Cif => 1,
};
bw.write_u32(fmt_bit, 1);
bw.write_u32(1, 1);
bw.write_u32(1, 1);
bw.write_u32(0, 1);
}
pub fn write_gob_header(bw: &mut BitWriter, gn: u8, gquant: u32) {
debug_assert!((1..=12).contains(&gn));
debug_assert!((1..=31).contains(&gquant));
bw.write_u32(0x0001, 16); bw.write_u32(gn as u32, 4);
bw.write_u32(gquant, 5);
bw.write_u32(0, 1);
}
fn gob_origin_luma(fmt: SourceFormat, gn: u8) -> (usize, usize) {
match fmt {
SourceFormat::Cif => crate::gob::cif_gob_origin_luma(gn),
SourceFormat::Qcif => crate::gob::qcif_gob_origin_luma(gn),
}
}
#[allow(clippy::too_many_arguments)]
fn encode_gob_intra(
bw: &mut BitWriter,
y: &[u8],
y_stride: usize,
cb: &[u8],
cb_stride: usize,
cr: &[u8],
cr_stride: usize,
gob_x: usize,
gob_y: usize,
quant: u32,
) {
let mut prev_mba: u8 = 0;
for mba in 1u8..=33 {
let diff = mba - prev_mba;
let (bits, code) = encode_mba_diff(diff);
bw.write_u32(code, bits as u32);
bw.write_u32(MTYPE_INTRA.1, MTYPE_INTRA.0 as u32);
let mb_col = (mba - 1) as usize % 11;
let mb_row = (mba - 1) as usize / 11;
let luma_x = gob_x + mb_col * 16;
let luma_y = gob_y + mb_row * 16;
encode_intra_mb_blocks(
bw, y, y_stride, cb, cb_stride, cr, cr_stride, luma_x, luma_y, quant,
);
prev_mba = mba;
}
}
#[allow(clippy::too_many_arguments)]
fn encode_intra_mb_blocks(
bw: &mut BitWriter,
y: &[u8],
y_stride: usize,
cb: &[u8],
cb_stride: usize,
cr: &[u8],
cr_stride: usize,
luma_x: usize,
luma_y: usize,
quant: u32,
) {
for (sub_x, sub_y) in [(0, 0), (8, 0), (0, 8), (8, 8)] {
let mut pels = [0u8; 64];
extract_block(y, y_stride, luma_x + sub_x, luma_y + sub_y, &mut pels);
encode_intra_block(bw, &pels, quant);
}
let cx = luma_x / 2;
let cy = luma_y / 2;
let mut cb_pels = [0u8; 64];
extract_block(cb, cb_stride, cx, cy, &mut cb_pels);
encode_intra_block(bw, &cb_pels, quant);
let mut cr_pels = [0u8; 64];
extract_block(cr, cr_stride, cx, cy, &mut cr_pels);
encode_intra_block(bw, &cr_pels, quant);
}
fn extract_block(plane: &[u8], stride: usize, x: usize, y: usize, out: &mut [u8; 64]) {
for j in 0..8 {
for i in 0..8 {
let px = (y + j) * stride + (x + i);
out[j * 8 + i] = plane.get(px).copied().unwrap_or(0);
}
}
}
fn encode_intra_block(bw: &mut BitWriter, pels: &[u8; 64], quant: u32) {
let mut coeffs = [0i32; 64];
fdct_intra(pels, &mut coeffs);
let dc_code = quant_intra_dc(coeffs[0]);
bw.write_u32(dc_code as u32, 8);
let mut zz_levels = [0i32; 63];
for i in 1..64 {
zz_levels[i - 1] = quant_ac(coeffs[ZIGZAG[i]], quant);
}
let mut run: u32 = 0;
for &lvl in zz_levels.iter() {
if lvl == 0 {
run += 1;
continue;
}
emit_runlevel(bw, run as u8, lvl, false);
run = 0;
}
bw.write_u32(0b10, 2);
}
fn emit_runlevel(bw: &mut BitWriter, run: u8, level: i32, is_first_inter: bool) {
debug_assert_ne!(level, 0);
let abs = level.unsigned_abs() as u8;
let sign = if level < 0 { 1 } else { 0 };
if run == 0 && abs == 1 {
if is_first_inter {
bw.write_u32(1, 1); } else {
bw.write_u32(0b11, 2); }
bw.write_u32(sign, 1);
return;
}
if let Some((bits, code)) = lookup_tcoeff(run, abs) {
bw.write_u32(code, bits as u32);
bw.write_u32(sign, 1);
return;
}
bw.write_u32(0b0000_01, 6);
bw.write_u32(run as u32 & 0x3F, 6);
let enc = if level < 0 {
(level + 256) as u32
} else {
level as u32
};
bw.write_u32(enc & 0xFF, 8);
}
#[allow(dead_code)]
fn _unused_refs() {
let _ = encode_cbp(1);
let _ = MBA_STUFFING;
let _ = MTYPE_INTRA_MQUANT;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decoder::{decode_picture_body, pic_to_video_frame, H261Decoder};
use crate::picture::parse_picture_header;
use oxideav_core::bits::BitReader;
use oxideav_core::packet::PacketFlags;
use oxideav_core::Decoder;
use oxideav_core::{CodecId, Frame, Packet, TimeBase};
fn neutral_qcif() -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let y = vec![128u8; 176 * 144];
let cb = vec![128u8; 88 * 72];
let cr = vec![128u8; 88 * 72];
(y, cb, cr)
}
#[test]
fn picture_header_roundtrip() {
let mut bw = BitWriter::new();
write_picture_header(&mut bw, SourceFormat::Qcif, 7);
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let hdr = parse_picture_header(&mut br).expect("parse");
assert_eq!(hdr.temporal_reference, 7);
assert_eq!(hdr.source_format, SourceFormat::Qcif);
assert_eq!(hdr.width, 176);
assert_eq!(hdr.height, 144);
}
#[test]
fn gob_header_roundtrip() {
let mut bw = BitWriter::new();
write_gob_header(&mut bw, 3, 8);
let bytes = bw.finish();
let mut br = BitReader::new(&bytes);
let hdr = crate::gob::parse_gob_header(&mut br).expect("parse GOB");
assert_eq!(hdr.gn, 3);
assert_eq!(hdr.gquant, 8);
}
#[test]
fn encode_qcif_grey_roundtrips_through_our_decoder() {
let (y, cb, cr) = neutral_qcif();
let bytes = encode_intra_picture(
SourceFormat::Qcif,
&y,
176,
&cb,
88,
&cr,
88,
8,
0,
)
.expect("encode");
assert!(!bytes.is_empty());
let codec_id = CodecId::new(crate::CODEC_ID_STR);
let mut decoder = H261Decoder::new(codec_id);
let pkt = Packet {
stream_index: 0,
data: bytes,
pts: Some(0),
dts: Some(0),
duration: None,
time_base: TimeBase::new(1, 30_000),
flags: PacketFlags {
keyframe: true,
..Default::default()
},
};
decoder.send_packet(&pkt).expect("send");
decoder.flush().ok();
let frame = decoder.receive_frame().expect("frame");
let vf = match frame {
Frame::Video(v) => v,
_ => panic!("expected video"),
};
assert_eq!(vf.width, 176);
assert_eq!(vf.height, 144);
let y_plane = &vf.planes[0].data;
let mut max_err = 0i32;
for &p in y_plane {
max_err = max_err.max((p as i32 - 128).abs());
}
assert!(max_err <= 2, "max Y error was {max_err}");
for &p in &vf.planes[1].data {
assert!((p as i32 - 128).abs() <= 2);
}
for &p in &vf.planes[2].data {
assert!((p as i32 - 128).abs() <= 2);
}
}
#[test]
fn encode_cif_grey_roundtrips() {
let y = vec![128u8; 352 * 288];
let cb = vec![128u8; 176 * 144];
let cr = vec![128u8; 176 * 144];
let bytes = encode_intra_picture(SourceFormat::Cif, &y, 352, &cb, 176, &cr, 176, 8, 0)
.expect("encode cif");
assert!(!bytes.is_empty());
let mut br = BitReader::new(&bytes);
let hdr = parse_picture_header(&mut br).expect("pic header");
let pic = decode_picture_body(&mut br, &hdr, &bytes, None).expect("body");
let vf = pic_to_video_frame(&pic, Some(0), TimeBase::new(1, 30_000));
assert_eq!(vf.width, 352);
assert_eq!(vf.height, 288);
for &p in &vf.planes[0].data {
assert!((p as i32 - 128).abs() <= 2, "Y pel {p} too far from 128");
}
}
#[test]
fn encode_qcif_gradient_plausible_decode() {
let w = 176usize;
let h = 144usize;
let mut y = vec![0u8; w * h];
for j in 0..h {
for i in 0..w {
y[j * w + i] = (32 + (i * 192) / w) as u8;
}
}
let cb = vec![128u8; (w / 2) * (h / 2)];
let cr = vec![128u8; (w / 2) * (h / 2)];
let bytes = encode_intra_picture(SourceFormat::Qcif, &y, w, &cb, w / 2, &cr, w / 2, 8, 0)
.expect("encode gradient");
let mut decoder = H261Decoder::new(CodecId::new(crate::CODEC_ID_STR));
let pkt = Packet {
stream_index: 0,
data: bytes,
pts: Some(0),
dts: Some(0),
duration: None,
time_base: TimeBase::new(1, 30_000),
flags: PacketFlags {
keyframe: true,
..Default::default()
},
};
decoder.send_packet(&pkt).expect("send");
decoder.flush().ok();
let frame = decoder.receive_frame().expect("frame");
let vf = match frame {
Frame::Video(v) => v,
_ => panic!("video"),
};
let y = &vf.planes[0].data;
let sample = |x: usize, yy: usize| y[yy * w + x] as i32;
let expected = |x: usize| 32 + (x * 192) as i32 / w as i32;
for &x in &[24usize, 80, 152] {
let got = sample(x, 72);
let want = expected(x);
assert!(
(got - want).abs() <= 40,
"gradient at x={x}: got {got}, want ~{want}"
);
}
}
}