use av1_obu_parser::{
IvfReader,
buffer::Buffer,
obu::{Obu, ObuParser},
};
struct BitWriter {
bytes: Vec<u8>,
current_byte: u8,
bit_pos: usize, }
impl BitWriter {
fn new() -> Self {
Self {
bytes: Vec::new(),
current_byte: 0,
bit_pos: 0,
}
}
fn write_bit(&mut self, bit: bool) {
if bit {
self.current_byte |= 1 << (7 - self.bit_pos);
}
self.bit_pos += 1;
if self.bit_pos == 8 {
self.bytes.push(self.current_byte);
self.current_byte = 0;
self.bit_pos = 0;
}
}
fn write_bits(&mut self, value: u32, count: usize) {
for i in (0..count).rev() {
self.write_bit((value >> i) & 1 != 0);
}
}
fn finish(mut self) -> Vec<u8> {
if self.bit_pos > 0 {
self.bytes.push(self.current_byte);
}
self.bytes
}
fn write_leb128(&mut self, mut value: u64) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
if value == 0 {
self.write_bits(byte as u32, 8);
break;
} else {
self.write_bits((byte | 0x80) as u32, 8);
}
}
}
}
fn build_sequence_header_obu_640x480() -> Vec<u8> {
let mut payload = BitWriter::new();
payload.write_bits(0, 3);
payload.write_bit(true);
payload.write_bit(true);
payload.write_bits(0, 5);
payload.write_bits(9, 4);
payload.write_bits(8, 4);
payload.write_bits(639, 10);
payload.write_bits(479, 9);
payload.write_bit(false);
payload.write_bit(true);
payload.write_bit(true);
payload.write_bit(false);
payload.write_bit(true);
payload.write_bit(true);
payload.write_bit(false);
payload.write_bit(false);
payload.write_bit(false);
payload.write_bit(false);
payload.write_bits(0, 2);
payload.write_bit(false);
payload.write_bit(false);
let payload_bytes = payload.finish();
let obu_header = 0x0Au8;
let mut obu = vec![obu_header];
let size = payload_bytes.len() as u64;
let mut size_writer = BitWriter::new();
size_writer.write_leb128(size);
obu.extend(size_writer.finish());
obu.extend(payload_bytes);
obu
}
#[test]
fn test_parse_sequence_header_640x480() {
let obu_bytes = build_sequence_header_obu_640x480();
let mut parser = ObuParser::default();
let mut buf = Buffer::from_slice(&obu_bytes);
let result = parser
.parse(&mut buf)
.expect("sequence header should parse successfully");
match result {
Obu::SequenceHeader(seq) => {
use av1_obu_parser::obu::sequence_header::SequenceProfile;
assert_eq!(seq.seq_profile, SequenceProfile::Main);
assert_eq!(seq.max_frame_width, 640);
assert_eq!(seq.max_frame_height, 480);
assert!(seq.still_picture);
assert!(seq.reduced_still_picture_header);
assert!(!seq.use_128x128_superblock);
assert!(seq.enable_filter_intra);
assert!(seq.enable_intra_edge_filter);
assert!(!seq.enable_superres);
assert!(seq.enable_cdef);
assert!(seq.enable_restoration);
assert!(!seq.color_config.high_bitdepth);
assert!(!seq.color_config.mono_chrome);
assert!(seq.color_config.subsampling_x);
assert!(seq.color_config.subsampling_y);
assert!(!seq.film_grain_params_present);
}
other => panic!("expected SequenceHeader, got {:?}", other),
}
}
#[test]
fn test_parse_obu_header_types() {
let temporal_delimiter_obu = vec![
0x12u8, 0x00, ];
let mut parser = ObuParser::default();
let mut buf = Buffer::from_slice(&temporal_delimiter_obu);
let result = parser
.parse(&mut buf)
.expect("temporal delimiter should parse successfully");
assert!(
matches!(result, Obu::TemporalDelimiter),
"expected TemporalDelimiter"
);
}
#[test]
fn test_buffer_basic_read() {
let data = [0xB2u8];
let mut buf = Buffer::from_slice(&data);
assert_eq!(buf.get_bit(), true, "bit 7 should be 1");
assert_eq!(buf.get_bit(), false, "bit 6 should be 0");
assert_eq!(buf.get_bit(), true, "bit 5 should be 1");
assert_eq!(buf.get_bit(), true, "bit 4 should be 1");
assert_eq!(buf.get_bit(), false, "bit 3 should be 0");
assert_eq!(buf.get_bit(), false, "bit 2 should be 0");
assert_eq!(buf.get_bit(), true, "bit 1 should be 1");
assert_eq!(buf.get_bit(), false, "bit 0 should be 0");
}
#[test]
fn test_buffer_get_bits_crossbyte() {
let data = [0xABu8, 0xCDu8];
let mut buf = Buffer::from_slice(&data);
assert_eq!(buf.get_bits(12), 0xABC);
assert_eq!(buf.get_bits(4), 0xD);
}
#[test]
fn test_buffer_leb128() {
let data1 = [0x05u8];
let mut buf1 = Buffer::from_slice(&data1);
assert_eq!(buf1.get_leb128(), 5);
let data2 = [0x80u8, 0x01u8];
let mut buf2 = Buffer::from_slice(&data2);
assert_eq!(buf2.get_leb128(), 128);
let data3 = [0xACu8, 0x02u8];
let mut buf3 = Buffer::from_slice(&data3);
assert_eq!(buf3.get_leb128(), 300);
}
#[test]
fn test_buffer_get_su() {
let data = [0b1100_0000u8];
let mut buf = Buffer::from_slice(&data);
assert_eq!(buf.get_su(4), -4);
let data2 = [0b0011_0000u8];
let mut buf2 = Buffer::from_slice(&data2);
assert_eq!(buf2.get_su(4), 3);
let data3 = [0xFFu8];
let mut buf3 = Buffer::from_slice(&data3);
assert_eq!(buf3.get_su(8), -1);
}
#[test]
fn test_buffer_get_ns() {
let data = [0b00_01_10_11u8, 0b0_11_1_0000u8];
let mut buf = Buffer::from_slice(&data);
assert_eq!(buf.get_ns(5), 0);
assert_eq!(buf.get_ns(5), 1);
assert_eq!(buf.get_ns(5), 2);
assert_eq!(buf.get_ns(5), 3);
assert_eq!(buf.get_ns(5), 4);
}
#[test]
fn test_buffer_byte_align() {
let data = [0xFFu8, 0xAAu8];
let mut buf = Buffer::from_slice(&data);
buf.get_bits(3);
assert!(!buf.is_byte_aligned());
buf.byte_align();
assert!(buf.is_byte_aligned());
assert_eq!(buf.get_bits(8), 0xAA);
}
#[test]
fn test_parse_sequence_then_temporal_delimiter() {
let mut stream = build_sequence_header_obu_640x480();
stream.extend_from_slice(&[0x12u8, 0x00u8]);
let mut parser = ObuParser::default();
let mut buf = Buffer::from_slice(&stream);
let obu1 = parser
.parse(&mut buf)
.expect("sequence header should parse");
assert!(matches!(obu1, Obu::SequenceHeader(_)));
let obu2 = parser
.parse(&mut buf)
.expect("temporal delimiter should parse");
assert!(matches!(obu2, Obu::TemporalDelimiter));
}
#[test]
fn test_parse_ivf_file_if_available() {
let test_file_path = "DEMO.ivf";
if !std::path::Path::new(test_file_path).exists() {
eprintln!(
"Skipping IVF file test: {} not found.\n\
Place DEMO.ivf at the repository root.",
test_file_path
);
return;
}
let file_data = std::fs::read(&test_file_path).expect("cannot read test file");
let ivf = IvfReader::new(&file_data).expect("test file should be a valid IVF container");
let header = ivf.header();
println!(
"IVF file: codec={}, resolution={}x{}",
header.codec_string(),
header.width,
header.height
);
let mut frame_count = 0usize;
let mut seq_header_found = false;
let mut parser = ObuParser::default();
for ivf_frame in ivf.frames().take(10) {
let ivf_frame = ivf_frame.expect("IVF frame should be well-formed");
let mut buf = Buffer::from_slice(ivf_frame.data);
loop {
match parser.parse(&mut buf) {
Ok(Obu::SequenceHeader(seq)) => {
println!(
"frame {}: SequenceHeader {}x{} profile={:?}",
ivf_frame.index, seq.max_frame_width, seq.max_frame_height, seq.seq_profile
);
seq_header_found = true;
}
Ok(Obu::Frame(frame)) => {
println!(
"frame {}: Frame type={:?} {}x{} Q={} tiles={}x{}",
ivf_frame.index,
frame.header.frame_type,
frame.header.frame_width,
frame.header.frame_height,
frame.header.quantization_params.base_q_idx,
frame.header.tile_info.tile_cols,
frame.header.tile_info.tile_rows,
);
}
Ok(Obu::FrameHeader(fh)) => {
println!(
"frame {}: FrameHeader type={:?} {}x{} Q={}",
ivf_frame.index,
fh.frame_type,
fh.frame_width,
fh.frame_height,
fh.quantization_params.base_q_idx,
);
}
Ok(Obu::TemporalDelimiter) | Ok(Obu::Drop) => {}
Ok(other) => {
println!("frame {}: other OBU: {:?}", ivf_frame.index, other);
}
Err(e) => {
eprintln!("frame {}: parse error: {:?}", ivf_frame.index, e);
break;
}
}
if buf.bytes_remaining() == 0 {
break;
}
}
frame_count += 1;
}
println!("parsed {} frames total", frame_count);
assert!(
seq_header_found,
"should find a sequence header in the bitstream"
);
assert!(frame_count > 0, "should parse at least one frame");
}