#![cfg(test)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test modules may unwrap"
)]
use super::*;
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 build_baseline_sps_rbsp(
profile_idc: u8,
level_idc: u8,
pic_width_in_mbs_minus1: u32,
pic_height_in_map_units_minus1: u32,
crop: Option<(u32, u32, u32, u32)>,
) -> Vec<u8> {
let mut writer = BitWriter::new();
writer.write_ue(0); writer.write_ue(0); writer.write_ue(0); writer.write_ue(0); writer.write_ue(1); writer.push_bit(0); writer.write_ue(pic_width_in_mbs_minus1);
writer.write_ue(pic_height_in_map_units_minus1);
writer.push_bit(1); writer.push_bit(1); match crop {
Some((left, right, top, bottom)) => {
writer.push_bit(1); writer.write_ue(left);
writer.write_ue(right);
writer.write_ue(top);
writer.write_ue(bottom);
}
None => writer.push_bit(0), }
let mut rbsp = vec![profile_idc, 0, level_idc];
rbsp.extend(writer.finish());
rbsp
}
#[test]
fn parse_extracts_width_height_profile_level_for_baseline_no_crop() {
let rbsp = build_baseline_sps_rbsp(66, 30, 19, 14, None);
let sps = Sps::parse(&rbsp).unwrap();
assert_eq!(sps.profile_idc, 66);
assert_eq!(sps.level_idc, 30);
assert_eq!(sps.constraint_flags, 0);
assert_eq!(sps.width, 320);
assert_eq!(sps.height, 240);
assert!(sps.frame_mbs_only);
assert_eq!(sps.chroma_format_idc, 1);
assert_eq!(sps.log2_max_frame_num, 4);
assert_eq!(sps.pic_order_cnt_type, 0);
assert_eq!(sps.log2_max_pic_order_cnt_lsb, 4);
assert_eq!(sps.pic_width_in_mbs, 20);
assert_eq!(sps.pic_height_in_mbs, 15);
}
#[test]
fn parse_extracts_log2_frame_num_and_poc_lsb_with_nonzero_minus4_values() {
let mut writer = BitWriter::new();
writer.write_ue(0); writer.write_ue(2); writer.write_ue(0); writer.write_ue(3); writer.write_ue(1); writer.push_bit(0); writer.write_ue(0); writer.write_ue(0); writer.push_bit(1); writer.push_bit(1); writer.push_bit(0);
let mut rbsp = vec![66u8, 0, 30];
rbsp.extend(writer.finish());
let sps = Sps::parse(&rbsp).unwrap();
assert_eq!(sps.log2_max_frame_num, 6);
assert_eq!(sps.pic_order_cnt_type, 0);
assert_eq!(sps.log2_max_pic_order_cnt_lsb, 7);
assert_eq!(sps.pic_width_in_mbs, 1);
assert_eq!(sps.pic_height_in_mbs, 1);
}
#[test]
fn parse_applies_frame_cropping_rectangle() {
let rbsp = build_baseline_sps_rbsp(66, 30, 0, 0, Some((1, 0, 1, 0)));
let sps = Sps::parse(&rbsp).unwrap();
assert_eq!(sps.width, 16 - 2);
assert_eq!(sps.height, 16 - 2);
}
#[test]
fn parse_high_profile_skips_chroma_and_bit_depth_fields() {
let mut writer = BitWriter::new();
writer.write_ue(0); writer.write_ue(1); writer.write_ue(0); writer.write_ue(0); writer.push_bit(0); writer.push_bit(0); writer.write_ue(0); writer.write_ue(0); writer.write_ue(0); writer.write_ue(1); writer.push_bit(0); writer.write_ue(9); writer.write_ue(9); writer.push_bit(1); writer.push_bit(1); writer.push_bit(0);
let mut rbsp = vec![100u8, 0, 40]; rbsp.extend(writer.finish());
let sps = Sps::parse(&rbsp).unwrap();
assert_eq!(sps.profile_idc, 100);
assert_eq!(sps.width, 160);
assert_eq!(sps.height, 160);
}
#[test]
fn parse_high_profile_with_scaling_list_stops_early_and_stays_bit_aligned() {
let mut writer = BitWriter::new();
writer.write_ue(0); writer.write_ue(1); writer.write_ue(0); writer.write_ue(0); writer.push_bit(0); writer.push_bit(1); writer.push_bit(1); writer.write_se(0); writer.write_se(-8); for _ in 1..8 {
writer.push_bit(0);
}
writer.write_ue(0); writer.write_ue(0); writer.write_ue(0); writer.write_ue(1); writer.push_bit(0); writer.write_ue(4); writer.write_ue(4); writer.push_bit(1); writer.push_bit(1); writer.push_bit(0);
let mut rbsp = vec![100u8, 0, 40];
rbsp.extend(writer.finish());
let sps = Sps::parse(&rbsp).unwrap();
assert_eq!(sps.width, 80);
assert_eq!(sps.height, 80);
}
#[test]
fn parse_errors_on_truncated_input_with_no_body_bits() {
let rbsp = [66u8, 0, 30]; assert_eq!(Sps::parse(&rbsp), Err(H264Error::UnexpectedEof));
}
#[test]
fn parse_errors_on_truncated_input_missing_header_bytes() {
let rbsp = [66u8]; assert_eq!(Sps::parse(&rbsp), Err(H264Error::UnexpectedEof));
}
#[test]
fn parse_errors_when_chroma_format_idc_exceeds_valid_range() {
let mut writer = BitWriter::new();
writer.write_ue(0); writer.write_ue(4); writer.write_ue(0); writer.write_ue(0); writer.push_bit(0); writer.push_bit(0);
let mut rbsp = vec![100u8, 0, 40];
rbsp.extend(writer.finish());
assert_eq!(Sps::parse(&rbsp), Err(H264Error::InvalidChromaFormat));
}