use super::*;
use super::bitreader::BitReader;
use super::h264::detect_h264;
use crate::frame::PixelFormat;
#[test]
fn detects_h264_baseline_yuv420p() {
let sps_rbsp = vec![
66, 0, 30, 0b1000_0000,
];
let mut sample = vec![0, 0, 0, 1, 0x27]; sample.extend_from_slice(&sps_rbsp);
let pf = detect_h264(&sample).unwrap();
assert_eq!(pf, PixelFormat::Yuv420p);
}
#[test]
fn empty_samples_returns_default() {
let pf = detect("h264", &[]);
assert_eq!(pf, PixelFormat::Yuv420p);
}
#[test]
fn unknown_codec_returns_default() {
let pf = detect("prores", &[vec![1, 2, 3]]);
assert_eq!(pf, PixelFormat::Yuv420p);
}
#[test]
fn from_chroma_and_depth_420_8bit() {
assert_eq!(
PixelFormat::from_chroma_and_depth(1, 8),
PixelFormat::Yuv420p
);
assert_eq!(
PixelFormat::from_chroma_and_depth(1, 10),
PixelFormat::Yuv420p10le
);
assert_eq!(
PixelFormat::from_chroma_and_depth(2, 8),
PixelFormat::Yuv422p
);
assert_eq!(
PixelFormat::from_chroma_and_depth(3, 8),
PixelFormat::Yuv444p
);
}
#[test]
fn as_ffmpeg_str_matches_python_names() {
assert_eq!(PixelFormat::Yuv420p.as_ffmpeg_str(), "yuv420p");
assert_eq!(PixelFormat::Yuv420p10le.as_ffmpeg_str(), "yuv420p10le");
assert_eq!(PixelFormat::Yuv444p.as_ffmpeg_str(), "yuv444p");
}
struct BitWriter {
bytes: Vec<u8>,
bit_pos: usize, }
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
bit_pos: 8,
}
}
fn write_bit(&mut self, b: u8) {
if self.bit_pos == 8 {
self.bytes.push(0);
self.bit_pos = 0;
}
if b != 0 {
let idx = self.bytes.len() - 1;
self.bytes[idx] |= 1 << (7 - self.bit_pos);
}
self.bit_pos += 1;
}
fn write_bits(&mut self, val: u64, n: usize) {
for i in 0..n {
let bit = ((val >> (n - 1 - i)) & 1) as u8;
self.write_bit(bit);
}
}
fn write_ue(&mut self, v: u32) {
let z = if v == 0 { 0 } else { (v + 1).ilog2() as usize };
for _ in 0..z {
self.write_bit(0);
}
self.write_bit(1);
if z > 0 {
let suffix = (v + 1) - (1u32 << z);
self.write_bits(suffix as u64, z);
}
}
fn bytes(self) -> Vec<u8> {
self.bytes
}
}
fn build_h264_baseline_sps(width_in_mbs: u32, height_in_mbs: u32) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_bits(66, 8); w.write_bits(0, 8); w.write_bits(30, 8); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_ue(1); w.write_bit(0); w.write_ue(width_in_mbs - 1); w.write_ue(height_in_mbs - 1); w.write_bit(1); w.write_bit(1); w.write_bit(0); w.write_bit(0); w.write_bit(1); let mut sample = vec![0x00, 0x00, 0x00, 0x01, 0x67]; sample.extend_from_slice(&w.bytes());
sample
}
#[test]
fn parse_h264_sps_baseline_1280x720() {
let sample = build_h264_baseline_sps(1280 / 16, 720 / 16);
let info = parse_h264_sps(&sample).expect("parse");
assert_eq!(info.profile_idc, 66);
assert_eq!(info.chroma_format_idc, 1); assert_eq!(info.width, Some(1280));
assert_eq!(info.height, Some(720));
assert!(info.frame_mbs_only);
}
#[test]
fn parse_h264_sps_baseline_640x480() {
let sample = build_h264_baseline_sps(640 / 16, 480 / 16);
let info = parse_h264_sps(&sample).expect("parse");
assert_eq!(info.width, Some(640));
assert_eq!(info.height, Some(480));
}
#[test]
fn parse_h264_sps_with_cropping_1920x1080() {
let mut w = BitWriter::new();
w.write_bits(66, 8);
w.write_bits(0, 8);
w.write_bits(40, 8);
w.write_ue(0);
w.write_ue(0);
w.write_ue(0);
w.write_ue(0);
w.write_ue(1);
w.write_bit(0);
w.write_ue(1920 / 16 - 1); w.write_ue(1088 / 16 - 1); w.write_bit(1); w.write_bit(1); w.write_bit(1); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_ue(4); w.write_bit(0); w.write_bit(1); let mut sample = vec![0, 0, 0, 1, 0x67];
sample.extend_from_slice(&w.bytes());
let info = parse_h264_sps(&sample).expect("parse");
assert_eq!(info.width, Some(1920));
assert_eq!(info.height, Some(1080));
}
#[test]
fn parse_h264_sps_high_profile_422_returns_chroma_even_without_dims() {
let mut w = BitWriter::new();
w.write_bits(122, 8); w.write_bits(0, 8);
w.write_bits(40, 8);
w.write_ue(0); w.write_ue(2); w.write_ue(0); w.write_ue(0); w.write_bit(0); w.write_bit(0); let mut sample = vec![0, 0, 0, 1, 0x67];
sample.extend_from_slice(&w.bytes());
let info = parse_h264_sps(&sample).expect("parse");
assert_eq!(info.profile_idc, 122);
assert_eq!(info.chroma_format_idc, 2);
}
fn build_hevc_sps(pic_width: u32, pic_height: u32) -> Vec<u8> {
build_hevc_sps_full(pic_width, pic_height, false, 0, 0, 0, 0)
}
fn build_hevc_sps_full(
pic_width: u32,
pic_height: u32,
conformance_window: bool,
cwl: u32,
cwr: u32,
cwt: u32,
cwb: u32,
) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_bits(0, 4); w.write_bits(0, 3); w.write_bits(1, 1); w.write_bits(0b0_0_00001, 8); w.write_bits(0x40000000, 32); w.write_bits(0, 48); w.write_bits(93, 8);
w.write_ue(0); w.write_ue(1); w.write_ue(pic_width);
w.write_ue(pic_height);
if conformance_window {
w.write_bit(1);
w.write_ue(cwl);
w.write_ue(cwr);
w.write_ue(cwt);
w.write_ue(cwb);
} else {
w.write_bit(0); }
w.write_ue(0); w.write_ue(0); w.write_ue(4); w.write_bit(1); w.write_ue(4); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_ue(3); w.write_ue(0); w.write_ue(3); w.write_ue(2); w.write_ue(2); w.write_bit(0); w.write_bit(1); w.write_bit(1); w.write_bit(0); w.write_ue(0); w.write_bit(0); w.write_bit(1); w.write_bit(0); w.write_bit(0); w.write_bit(0); w.write_bit(1); let mut sample = vec![0, 0, 0, 1, 0x42, 0x01];
sample.extend_from_slice(&w.bytes());
sample
}
#[test]
fn parse_hevc_sps_1920x1080_no_crop() {
let sample = build_hevc_sps(1920, 1080);
let info = parse_hevc_sps(&sample).expect("parse");
assert_eq!(info.chroma_format_idc, 1);
assert_eq!(info.bit_depth_luma, 8);
assert_eq!(info.width, Some(1920));
assert_eq!(info.height, Some(1080));
}
#[test]
fn parse_hevc_sps_with_conformance_window() {
let sample = build_hevc_sps_full(1920, 1088, true, 0, 0, 0, 4);
let info = parse_hevc_sps(&sample).expect("parse");
assert_eq!(info.width, Some(1920));
assert_eq!(info.height, Some(1080));
}
#[test]
fn parse_mpeg2_sequence_header_no_extension_640x480() {
let sample = vec![0x00, 0x00, 0x01, 0xB3, 0x28, 0x01, 0xE0, 0x13, 0xFF, 0xFF];
let info = parse_mpeg2_sequence_header(&sample).expect("parse");
assert_eq!(info.width, 640);
assert_eq!(info.height, 480);
}
#[test]
fn parse_mpeg2_sequence_header_with_extension_upgrades_to_14bit() {
let mut bytes = vec![0x00, 0x00, 0x01, 0xB3, 0x78, 0x04, 0x38, 0x13, 0xFF, 0xFF];
let mut w = BitWriter::new();
w.write_bits(1, 4); w.write_bits(0, 8); w.write_bit(1); w.write_bits(1, 2); w.write_bits(1, 2); w.write_bits(2, 2); w.write_bits(0, 1); bytes.extend_from_slice(&[0x00, 0x00, 0x01, 0xB5]);
bytes.extend_from_slice(&w.bytes());
let info = parse_mpeg2_sequence_header(&bytes).expect("parse");
assert_eq!(info.width, 1920 | (1 << 12)); assert_eq!(info.height, 1080 | (2 << 12)); }
#[test]
fn parse_mpeg2_sequence_header_none_when_no_start_code() {
let sample = vec![0xFFu8; 128];
assert!(parse_mpeg2_sequence_header(&sample).is_none());
}
#[test]
fn detect_dims_dispatches_by_codec() {
let h264 = build_h264_baseline_sps(1280 / 16, 720 / 16);
let hevc = build_hevc_sps(1920, 1080);
let mpeg2 = vec![0x00, 0x00, 0x01, 0xB3, 0x28, 0x01, 0xE0, 0x13, 0xFF, 0xFF];
assert_eq!(detect_dims("h264", &[h264.clone()]), Some((1280, 720)));
assert_eq!(detect_dims("avc1", &[h264]), Some((1280, 720)));
assert_eq!(detect_dims("h265", &[hevc.clone()]), Some((1920, 1080)));
assert_eq!(detect_dims("hevc", &[hevc]), Some((1920, 1080)));
assert_eq!(detect_dims("mpeg2", &[mpeg2]), Some((640, 480)));
assert_eq!(detect_dims("unknown", &[vec![0u8; 8]]), None);
assert_eq!(detect_dims("h264", &[]), None);
}
fn build_h264_baseline_pps(pps_id: u32, sps_id: u32) -> Vec<u8> {
let mut w = BitWriter::new();
w.write_ue(pps_id); w.write_ue(sps_id); w.write_bit(0); w.write_bit(0); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_bit(0); w.write_bits(0, 2); w.write_ue(0); w.write_ue(0); w.write_ue(0); w.write_bit(1); w.write_bit(0); w.write_bit(0); w.write_bit(1); let mut sample = vec![0x00, 0x00, 0x00, 0x01, 0x68]; sample.extend_from_slice(&w.bytes());
sample
}
#[test]
fn parse_h264_pps_baseline_roundtrip() {
let sample = build_h264_baseline_pps(0, 0);
let info = parse_h264_pps(&sample).expect("PPS parses");
assert_eq!(info.pic_parameter_set_id, 0);
assert_eq!(info.seq_parameter_set_id, 0);
assert!(!info.entropy_coding_mode_flag);
assert!(!info.bottom_field_pic_order_in_frame_present_flag);
assert_eq!(info.num_slice_groups_minus1, 0);
assert_eq!(info.num_ref_idx_l0_default_active_minus1, 0);
assert_eq!(info.num_ref_idx_l1_default_active_minus1, 0);
assert!(!info.weighted_pred_flag);
assert_eq!(info.weighted_bipred_idc, 0);
assert_eq!(info.pic_init_qp_minus26, 0);
assert_eq!(info.pic_init_qs_minus26, 0);
assert_eq!(info.chroma_qp_index_offset, 0);
assert!(info.deblocking_filter_control_present_flag);
assert!(!info.constrained_intra_pred_flag);
assert!(!info.redundant_pic_cnt_present_flag);
}
#[test]
fn parse_h264_pps_nonzero_ids_and_flags() {
let mut w = BitWriter::new();
w.write_ue(3); w.write_ue(7); w.write_bit(1); w.write_bit(1); w.write_ue(0); w.write_ue(2); w.write_ue(1); w.write_bit(1); w.write_bits(2, 2); w.write_ue(10);
w.write_ue(5);
w.write_ue(0);
w.write_bit(0); w.write_bit(1); w.write_bit(1); w.write_bit(1); let mut sample = vec![0x00, 0x00, 0x00, 0x01, 0x68];
sample.extend_from_slice(&w.bytes());
let info = parse_h264_pps(&sample).expect("parse");
assert_eq!(info.pic_parameter_set_id, 3);
assert_eq!(info.seq_parameter_set_id, 7);
assert!(info.entropy_coding_mode_flag);
assert!(info.bottom_field_pic_order_in_frame_present_flag);
assert_eq!(info.num_ref_idx_l0_default_active_minus1, 2);
assert_eq!(info.num_ref_idx_l1_default_active_minus1, 1);
assert!(info.weighted_pred_flag);
assert_eq!(info.weighted_bipred_idc, 2);
assert_eq!(info.pic_init_qp_minus26, -5);
assert_eq!(info.pic_init_qs_minus26, 3);
assert!(!info.deblocking_filter_control_present_flag);
assert!(info.constrained_intra_pred_flag);
assert!(info.redundant_pic_cnt_present_flag);
}
#[test]
fn parse_h264_pps_returns_none_when_no_pps_in_sample() {
let sample = build_h264_baseline_sps(80, 45); assert!(parse_h264_pps(&sample).is_none());
}
fn build_h264_idr_slice_header_rbsp() -> Vec<u8> {
let mut w = BitWriter::new();
w.write_ue(0); w.write_ue(7); w.write_ue(0); w.write_bits(0, 4); w.write_ue(0); w.write_bits(0, 4); w.bytes()
}
#[test]
fn parse_h264_slice_header_idr_i_slice() {
let sps = parse_h264_sps(&build_h264_baseline_sps(1280 / 16, 720 / 16)).expect("sps");
let pps = parse_h264_pps(&build_h264_baseline_pps(0, 0)).expect("pps");
let rbsp = build_h264_idr_slice_header_rbsp();
let mut sample = vec![0x00, 0x00, 0x00, 0x01, 0x65];
sample.extend_from_slice(&rbsp);
let sh = parse_h264_slice_header(&sample, &sps, &pps).expect("slice");
assert_eq!(sh.first_mb_in_slice, 0);
assert_eq!(sh.slice_type, H264SliceType::I);
assert_eq!(sh.pic_parameter_set_id, 0);
assert!(sh.is_idr);
assert_eq!(sh.frame_num, 0);
assert!(!sh.field_pic_flag);
assert_eq!(sh.idr_pic_id, Some(0));
assert_eq!(sh.pic_order_cnt_lsb, Some(0));
}
#[test]
fn parse_h264_slice_header_returns_none_without_sps_context() {
let mut w = BitWriter::new();
w.write_bits(122, 8);
w.write_bits(0, 8);
w.write_bits(40, 8);
w.write_ue(0); w.write_ue(2); w.write_ue(0);
w.write_ue(0);
w.write_bit(0); w.write_bit(0); let mut sample = vec![0, 0, 0, 1, 0x67];
sample.extend_from_slice(&w.bytes());
let sps = parse_h264_sps(&sample).expect("sps parses");
assert!(sps.pic_order_cnt_type.is_none());
let pps = parse_h264_pps(&build_h264_baseline_pps(0, 0)).expect("pps");
let rbsp = build_h264_idr_slice_header_rbsp();
let mut slice_sample = vec![0x00, 0x00, 0x00, 0x01, 0x65];
slice_sample.extend_from_slice(&rbsp);
assert!(parse_h264_slice_header(&slice_sample, &sps, &pps).is_none());
}
#[test]
fn parse_h264_slice_type_ue_mapping_covers_both_halves() {
for (code, expected) in [
(0, H264SliceType::P),
(5, H264SliceType::P),
(1, H264SliceType::B),
(6, H264SliceType::B),
(2, H264SliceType::I),
(7, H264SliceType::I),
(3, H264SliceType::SP),
(8, H264SliceType::SP),
(4, H264SliceType::SI),
(9, H264SliceType::SI),
] {
assert_eq!(
H264SliceType::from_ue(code),
Some(expected),
"code {}",
code
);
}
}
#[test]
fn bit_reader_read_se_exp_golomb_mapping() {
for (code, expected) in [(0u32, 0i32), (1, 1), (2, -1), (3, 2), (4, -2), (5, 3)] {
let mut w = BitWriter::new();
w.write_ue(code);
let bytes = w.bytes();
let mut br = BitReader::new(&bytes);
assert_eq!(
br.read_se(),
Some(expected),
"codeNum={} expected={}",
code,
expected
);
}
}