#![allow(clippy::needless_return)]
use proptest::prelude::*;
use h264_parser::AnnexBParser;
#[test]
fn eof_semantics_next_access_unit_is_none_after_drain() {
let mut parser = AnnexBParser::new();
parser.push(&[]);
assert!(matches!(parser.next_access_unit(), Ok(None)));
assert!(matches!(parser.next_access_unit(), Ok(None)));
assert!(matches!(parser.next_access_unit(), Ok(None)));
}
proptest! {
#[test]
fn keyframe_flag_matches_idr(idr in any::<bool>()) {
let sps = build_min_sps_pic_order_cnt_type2(0);
let pps = build_min_pps(0, 0);
let first = build_min_slice(idr, 0, 0, 0);
let second = build_min_slice(false, 0, 1, 0);
let mut stream = Vec::new();
push_start_code(&mut stream); stream.extend_from_slice(&sps);
push_start_code(&mut stream); stream.extend_from_slice(&pps);
push_start_code(&mut stream); stream.extend_from_slice(&first);
push_start_code(&mut stream); stream.extend_from_slice(&second);
let mut parser = AnnexBParser::new();
parser.push(&stream);
parser.push(&[]);
let mut aus = Vec::new();
while let Ok(Some(au)) = parser.next_access_unit() {
aus.push(au);
}
while let Ok(Some(au)) = parser.next_access_unit_final() {
aus.push(au);
}
let slice_aus: Vec<_> = aus.into_iter()
.filter(|au| au.nals.iter().any(|n| n.is_vcl()))
.collect();
assert_eq!(slice_aus.len(), 2, "Expected exactly 2 AUs with slices, got {}", slice_aus.len());
assert_eq!(slice_aus[0].is_keyframe(), idr, "First slice AU keyframe mismatch");
assert_eq!(slice_aus[1].is_keyframe(), false, "Second slice AU must be non-keyframe");
}
}
proptest! {
#[test]
fn chunking_yields_same_au_count(splits in proptest::collection::vec(1usize..50usize, 0..50)) {
let sps = build_min_sps_pic_order_cnt_type2(0);
let pps = build_min_pps(0, 0);
let idr = build_min_slice(true, 0, 0, 0);
let p = build_min_slice(false, 0, 1, 0);
let mut stream = Vec::new();
push_start_code(&mut stream); stream.extend_from_slice(&sps);
push_start_code(&mut stream); stream.extend_from_slice(&pps);
push_start_code(&mut stream); stream.extend_from_slice(&idr);
push_start_code(&mut stream); stream.extend_from_slice(&p);
let mut p1 = AnnexBParser::new();
p1.push(&stream);
let mut count_all_at_once = 0;
while let Ok(Some(_)) = p1.next_access_unit() {
count_all_at_once += 1;
}
while let Ok(Some(_)) = p1.next_access_unit_final() {
count_all_at_once += 1;
}
let mut p2 = AnnexBParser::new();
let mut i = 0usize;
for step in splits {
if i >= stream.len() { break; }
let end = (i + step).min(stream.len());
p2.push(&stream[i..end]);
i = end;
}
if i < stream.len() {
p2.push(&stream[i..]);
}
let mut count_chunked = 0;
while let Ok(Some(_)) = p2.next_access_unit() {
count_chunked += 1;
}
while let Ok(Some(_)) = p2.next_access_unit_final() {
count_chunked += 1;
}
assert_eq!(count_all_at_once, count_chunked, "AU count differs with chunking");
}
}
fn push_start_code(dst: &mut Vec<u8>) {
dst.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
}
fn nal_header(ref_idc: u8, nal_unit_type: u8) -> u8 {
((ref_idc & 0b11) << 5) | (nal_unit_type & 0b1_1111)
}
fn build_min_sps_pic_order_cnt_type2(sps_id: u32) -> Vec<u8> {
let mut rbsp = BitWriter::new();
rbsp.write_u8(66); rbsp.write_u8(0); rbsp.write_u8(30);
ue(&mut rbsp, sps_id); ue(&mut rbsp, 0); ue(&mut rbsp, 2); ue(&mut rbsp, 0); rbsp.write_flag(false);
ue(&mut rbsp, 0); ue(&mut rbsp, 0); rbsp.write_flag(true); rbsp.write_flag(false); rbsp.write_flag(false); rbsp.write_flag(false);
let mut out = Vec::new();
out.push(nal_header(3, 7)); out.extend_from_slice(&rbsp.finish_trailing_bits());
out
}
fn build_min_pps(pps_id: u32, sps_id: u32) -> Vec<u8> {
let mut rbsp = BitWriter::new();
ue(&mut rbsp, pps_id); ue(&mut rbsp, sps_id); rbsp.write_flag(false); rbsp.write_flag(false); ue(&mut rbsp, 0); ue(&mut rbsp, 0); ue(&mut rbsp, 0); rbsp.write_flag(false); rbsp.write_bits(2, 0); se(&mut rbsp, 0); se(&mut rbsp, 0); se(&mut rbsp, 0); rbsp.write_flag(false); rbsp.write_flag(false); rbsp.write_flag(false);
let mut out = Vec::new();
out.push(nal_header(3, 8)); out.extend_from_slice(&rbsp.finish_trailing_bits());
out
}
fn build_min_slice(idr: bool, pps_id: u32, frame_num: u32, idr_pic_id: u32) -> Vec<u8> {
let mut rbsp = BitWriter::new();
ue(&mut rbsp, 0); ue(&mut rbsp, if idr { 2 } else { 0 }); ue(&mut rbsp, pps_id); rbsp.write_bits(4, frame_num); if idr {
ue(&mut rbsp, idr_pic_id); }
if !idr {
rbsp.write_flag(false);
}
if idr {
ue(&mut rbsp, 0); } else {
ue(&mut rbsp, 1); }
let mut out = Vec::new();
out.push(nal_header(3, if idr { 5 } else { 1 })); out.extend_from_slice(&rbsp.finish_trailing_bits());
out
}
struct BitWriter {
bytes: Vec<u8>,
cur: u8,
nbits: u8,
}
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
cur: 0,
nbits: 0,
}
}
fn write_bit(&mut self, bit: bool) {
self.cur <<= 1;
if bit {
self.cur |= 1;
}
self.nbits += 1;
if self.nbits == 8 {
self.bytes.push(self.cur);
self.cur = 0;
self.nbits = 0;
}
}
fn write_bits(&mut self, n: u32, val: u32) {
for i in (0..n).rev() {
let b = ((val >> i) & 1) != 0;
self.write_bit(b);
}
}
fn write_flag(&mut self, b: bool) {
self.write_bit(b);
}
fn write_u8(&mut self, v: u8) {
for i in (0..8).rev() {
self.write_bit(((v >> i) & 1) != 0);
}
}
fn finish_trailing_bits(mut self) -> Vec<u8> {
self.write_bit(true);
while self.nbits != 0 {
self.write_bit(false);
}
self.bytes
}
}
fn ue(w: &mut BitWriter, v: u32) {
if v == 0 {
w.write_bit(true); return;
}
let code_num = v + 1;
let bits = 32 - code_num.leading_zeros();
let prefix_zeros = (bits - 1) as usize;
for _ in 0..prefix_zeros {
w.write_bit(false);
}
for i in (0..bits).rev() {
w.write_bit(((code_num >> i) & 1) != 0);
}
}
fn se(w: &mut BitWriter, v: i32) {
let k = if v > 0 {
(v as u32) * 2 - 1
} else {
(-v as u32) * 2
};
ue(w, k);
}