#![cfg(test)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test modules may unwrap"
)]
use super::*;
struct Writer {
bytes: Vec<u8>,
buf: u8,
n: u8,
}
impl Writer {
const fn new() -> Self {
Self {
bytes: Vec::new(),
buf: 0,
n: 0,
}
}
fn bit(&mut self, b: bool) {
self.buf = (self.buf << 1) | u8::from(b);
self.n += 1;
if self.n == 8 {
self.bytes.push(self.buf);
self.buf = 0;
self.n = 0;
}
}
fn bits(&mut self, value: u32, n: u32) {
for i in (0..n).rev() {
self.bit((value >> i) & 1 == 1);
}
}
fn trailing(&mut self) {
self.bit(true);
while self.n != 0 {
self.bit(false);
}
}
fn finish(self) -> Vec<u8> {
self.bytes
}
}
#[derive(Default)]
#[allow(
clippy::struct_excessive_bools,
reason = "one bool per sequence-header optional-tool flag under test; a state machine \
would obscure which flag each rejection test actually flips"
)]
struct SeqFlags {
use_128x128_superblock: bool,
enable_filter_intra: bool,
enable_intra_edge_filter: bool,
enable_interintra_compound: bool,
enable_masked_compound: bool,
enable_warped_motion: bool,
enable_dual_filter: bool,
enable_order_hint: bool,
enable_jnt_comp: bool,
enable_ref_frame_mvs: bool,
enable_superres: bool,
enable_cdef: bool,
enable_restoration: bool,
high_bitdepth: bool,
mono_chrome: bool,
film_grain_params_present: bool,
}
fn build_sequence_header(width: u32, height: u32, flags: &SeqFlags) -> Vec<u8> {
let mut w = Writer::new();
w.bits(0, 3); w.bit(false); w.bit(false); w.bit(false); w.bit(false); w.bits(0, 5); w.bits(0, 12); w.bits(0, 5);
let width_bits = (u32::BITS - (width - 1).leading_zeros()).max(1);
let height_bits = (u32::BITS - (height - 1).leading_zeros()).max(1);
w.bits(width_bits - 1, 4);
w.bits(height_bits - 1, 4);
w.bits(width - 1, width_bits);
w.bits(height - 1, height_bits);
w.bit(false); w.bit(flags.use_128x128_superblock);
w.bit(flags.enable_filter_intra);
w.bit(flags.enable_intra_edge_filter);
w.bit(flags.enable_interintra_compound);
w.bit(flags.enable_masked_compound);
w.bit(flags.enable_warped_motion);
w.bit(flags.enable_dual_filter);
w.bit(flags.enable_order_hint);
if flags.enable_order_hint {
w.bit(flags.enable_jnt_comp);
w.bit(flags.enable_ref_frame_mvs);
}
w.bit(false); w.bit(false); if flags.enable_order_hint {
w.bits(0, 3); }
w.bit(flags.enable_superres);
w.bit(flags.enable_cdef);
w.bit(flags.enable_restoration);
w.bit(flags.high_bitdepth);
w.bit(flags.mono_chrome);
w.bit(false); w.bit(false); w.bits(0, 2); w.bit(false);
w.bit(flags.film_grain_params_present);
w.trailing();
w.finish()
}
#[test]
fn accepted_minimal_sequence_header_round_trips() {
let bytes = build_sequence_header(64, 64, &SeqFlags::default());
let seq = SequenceHeader::parse(&bytes).unwrap();
assert_eq!(seq.seq_profile, 0);
assert!(!seq.use_128x128_superblock);
assert!(!seq.enable_order_hint);
assert_eq!(seq.order_hint_bits, 0);
assert_eq!(seq.frame_width_bits_minus_1, 5);
assert_eq!(seq.frame_height_bits_minus_1, 5);
assert_eq!(seq.max_frame_width_minus_1, 63);
assert_eq!(seq.max_frame_height_minus_1, 63);
assert_eq!(seq.width(), 64);
assert_eq!(seq.height(), 64);
assert_eq!(seq.seq_force_screen_content_tools, 0);
assert_eq!(seq.seq_force_integer_mv, SELECT_VALUE);
assert!(!seq.color_range);
assert_eq!(seq.matrix_coefficients, u8::try_from(UNSPECIFIED).unwrap());
assert_eq!(seq.chroma_sample_position, 0);
}
#[test]
fn accepted_with_order_hint_enabled() {
let flags = SeqFlags {
enable_order_hint: true,
..SeqFlags::default()
};
let bytes = build_sequence_header(1920, 1080, &flags);
let seq = SequenceHeader::parse(&bytes).unwrap();
assert!(seq.enable_order_hint);
assert_eq!(seq.order_hint_bits, 1); assert_eq!(seq.width(), 1920);
assert_eq!(seq.height(), 1080);
}
#[test]
fn rejects_non_main_profile() {
assert_eq!(
SequenceHeader::parse(&[0b0010_0000]),
Err(DecodeError::Unsupported)
);
}
#[test]
fn rejects_reduced_still_picture_header() {
assert_eq!(
SequenceHeader::parse(&[0b0000_1000]),
Err(DecodeError::Unsupported)
);
}
#[test]
fn rejects_timing_info_present() {
assert_eq!(
SequenceHeader::parse(&[0b0000_0100]),
Err(DecodeError::Unsupported)
);
}
fn assert_flag_rejected(mutate: impl FnOnce(&mut SeqFlags)) {
let mut flags = SeqFlags::default();
mutate(&mut flags);
let bytes = build_sequence_header(64, 64, &flags);
assert_eq!(SequenceHeader::parse(&bytes), Err(DecodeError::Unsupported));
}
#[test]
fn rejects_every_disabled_optional_tool_when_signaled() {
assert_flag_rejected(|f| f.enable_filter_intra = true);
assert_flag_rejected(|f| f.enable_intra_edge_filter = true);
assert_flag_rejected(|f| f.enable_interintra_compound = true);
assert_flag_rejected(|f| f.enable_masked_compound = true);
assert_flag_rejected(|f| f.enable_warped_motion = true);
assert_flag_rejected(|f| f.enable_dual_filter = true);
assert_flag_rejected(|f| f.enable_superres = true);
assert_flag_rejected(|f| f.enable_cdef = true);
assert_flag_rejected(|f| f.enable_restoration = true);
assert_flag_rejected(|f| f.high_bitdepth = true);
assert_flag_rejected(|f| f.mono_chrome = true);
assert_flag_rejected(|f| f.film_grain_params_present = true);
assert_flag_rejected(|f| {
f.enable_order_hint = true;
f.enable_jnt_comp = true;
});
assert_flag_rejected(|f| {
f.enable_order_hint = true;
f.enable_ref_frame_mvs = true;
});
}
#[test]
fn truncated_input_is_invalid() {
assert_eq!(SequenceHeader::parse(&[]), Err(DecodeError::InvalidInput));
}