#![cfg(test)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
reason = "test modules may unwrap/panic on an assertion failure"
)]
use super::*;
use crate::h264::nal::NalUnitType;
use mediaway_common::Bytes;
struct BitWriter {
bytes: Vec<u8>,
cur: u8,
nbits: u8,
}
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
cur: 0,
nbits: 0,
}
}
fn push_bit(&mut self, bit: u32) {
let bit_u8 = u8::from(bit & 1 == 1);
self.cur = (self.cur << 1) | bit_u8;
self.nbits += 1;
if self.nbits == 8 {
self.bytes.push(self.cur);
self.cur = 0;
self.nbits = 0;
}
}
fn write_bits(&mut self, value: u32, count: u32) {
for i in (0..count).rev() {
self.push_bit(value >> i);
}
}
fn write_ue(&mut self, value: u32) {
let code = value + 1;
let len = u32::BITS - code.leading_zeros();
for _ in 0..(len - 1) {
self.push_bit(0);
}
self.write_bits(code, len);
}
fn write_se(&mut self, value: i32) {
let magnitude = value.unsigned_abs();
let code = if value <= 0 {
magnitude * 2
} else {
magnitude * 2 - 1
};
self.write_ue(code);
}
fn finish(mut self) -> Vec<u8> {
while self.nbits != 0 {
self.push_bit(0);
}
self.bytes
}
}
fn one_mb_sps() -> Sps {
Sps {
profile_idc: 66,
constraint_flags: 0,
level_idc: 30,
seq_parameter_set_id: 0,
width: 16,
height: 16,
frame_mbs_only: true,
chroma_format_idc: 1,
log2_max_frame_num: 4,
pic_order_cnt_type: 0,
log2_max_pic_order_cnt_lsb: 4,
pic_width_in_mbs: 1,
pic_height_in_mbs: 1,
}
}
fn one_mb_pps(entropy_coding_mode: bool) -> Pps {
Pps {
pic_parameter_set_id: 0,
seq_parameter_set_id: 0,
entropy_coding_mode,
num_ref_idx_l0_default_active: 1,
num_ref_idx_l1_default_active: 1,
pic_init_qp: 28,
chroma_qp_index_offset: 0,
deblocking_filter_control_present: false,
constrained_intra_pred: false,
}
}
fn write_idr_i_slice_header(writer: &mut BitWriter, first_mb_in_slice: u32, slice_type: u32) {
writer.write_ue(first_mb_in_slice);
writer.write_ue(slice_type);
writer.write_ue(0); writer.write_bits(0, 4); writer.write_ue(0); writer.write_bits(0, 4); writer.push_bit(0); writer.push_bit(0); writer.write_se(0); }
fn idr_nal(rbsp: Vec<u8>) -> NalUnit {
NalUnit {
ref_idc: 1,
unit_type: NalUnitType::IdrSlice,
rbsp: Bytes::from(rbsp),
}
}
#[test]
fn decode_i_frame_reconstructs_a_solid_color_one_macroblock_picture() {
let mut writer = BitWriter::new();
write_idr_i_slice_header(&mut writer, 0, 2);
writer.write_ue(3);
writer.write_ue(0); writer.write_se(0);
writer.write_bits(0b00_0101, 6); for _ in 0..6 {
writer.push_bit(0); }
writer.push_bit(1); writer.push_bit(1);
let nal = idr_nal(writer.finish());
let sps = one_mb_sps();
let pps = one_mb_pps(false);
let frame = decode_i_frame(&sps, &pps, &nal).unwrap();
assert_eq!(frame.width, 16);
assert_eq!(frame.height, 16);
assert_eq!(frame.format, mediaway_common::PixelFormat::I420);
let VideoFrameStorage::Cpu { data } = &frame.storage else {
panic!("expected CPU storage");
};
assert_eq!(data.len(), 16 * 16 + 2 * 8 * 8);
let y = &data[0..256];
assert!(
y.iter().all(|&v| v == 133),
"luma should be uniformly 133, got {y:?}"
);
let u = &data[256..256 + 64];
let v = &data[256 + 64..256 + 128];
assert!(
u.iter().all(|&val| val == 128),
"chroma U should be uniformly 128, got {u:?}"
);
assert!(
v.iter().all(|&val| val == 128),
"chroma V should be uniformly 128, got {v:?}"
);
}
#[test]
fn decode_i_frame_rejects_cabac() {
let mut writer = BitWriter::new();
write_idr_i_slice_header(&mut writer, 0, 2);
let nal = idr_nal(writer.finish());
let sps = one_mb_sps();
let pps = one_mb_pps(true);
assert_eq!(
decode_i_frame(&sps, &pps, &nal),
Err(H264Error::UnsupportedEntropyCoding)
);
}
#[test]
fn decode_i_frame_rejects_non_4_2_0_chroma_format() {
let mut sps = one_mb_sps();
sps.chroma_format_idc = 2;
let pps = one_mb_pps(false);
let mut writer = BitWriter::new();
write_idr_i_slice_header(&mut writer, 0, 2);
let nal = idr_nal(writer.finish());
assert_eq!(
decode_i_frame(&sps, &pps, &nal),
Err(H264Error::UnsupportedChromaFormat)
);
}
#[test]
fn decode_i_frame_rejects_p_slice() {
let mut writer = BitWriter::new();
write_idr_i_slice_header(&mut writer, 0, 0); let nal = idr_nal(writer.finish());
let sps = one_mb_sps();
let pps = one_mb_pps(false);
assert_eq!(
decode_i_frame(&sps, &pps, &nal),
Err(H264Error::UnsupportedSliceType)
);
}
#[test]
fn decode_i_frame_rejects_non_zero_first_mb_in_slice() {
let mut writer = BitWriter::new();
write_idr_i_slice_header(&mut writer, 1, 2);
let nal = idr_nal(writer.finish());
let sps = one_mb_sps();
let pps = one_mb_pps(false);
assert_eq!(
decode_i_frame(&sps, &pps, &nal),
Err(H264Error::MultiSliceUnsupported)
);
}
#[test]
fn decode_i_frame_rejects_i_nxn_macroblocks() {
let mut writer = BitWriter::new();
write_idr_i_slice_header(&mut writer, 0, 2);
writer.write_ue(0); let nal = idr_nal(writer.finish());
let sps = one_mb_sps();
let pps = one_mb_pps(false);
assert_eq!(
decode_i_frame(&sps, &pps, &nal),
Err(H264Error::UnsupportedMbType)
);
}