use broadcast_common::{Parse, Serialize};
use transmux::{CleanApertureBox, ColourInformationBox, PixelAspectRatioBox};
const COLR_BODY_ORACLE: [u8; 11] = [
0x6E, 0x63, 0x6C, 0x78, 0x00, 0x02, 0x00, 0x02, 0x00, 0x09, 0x00, ];
const PASP_BODY_ORACLE: [u8; 8] = [
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, ];
fn load_fixture() -> Vec<u8> {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../fixtures/mp4/colr_hdr.mp4");
std::fs::read(path).expect("fixture must exist")
}
fn find_child_box<'a>(region: &'a [u8], fourcc: &[u8; 4]) -> Option<&'a [u8]> {
let mut off = 0usize;
while off + 8 <= region.len() {
let sz = u32::from_be_bytes([
region[off],
region[off + 1],
region[off + 2],
region[off + 3],
]) as usize;
if sz < 8 || off + sz > region.len() {
break;
}
if ®ion[off + 4..off + 8] == fourcc {
return Some(®ion[off..off + sz]);
}
off += sz;
}
None
}
fn find_visual_ext_box<'a>(data: &'a [u8], child_fourcc: &[u8; 4]) -> &'a [u8] {
let moov = find_child_box(data, b"moov").expect("moov");
let moov_body = &moov[8..];
let trak = find_child_box(moov_body, b"trak").expect("trak");
let trak_body = &trak[8..];
let mdia = find_child_box(trak_body, b"mdia").expect("mdia");
let mdia_body = &mdia[8..];
let minf = find_child_box(mdia_body, b"minf").expect("minf");
let minf_body = &minf[8..];
let stbl = find_child_box(minf_body, b"stbl").expect("stbl");
let stbl_body = &stbl[8..];
let stsd = find_child_box(stbl_body, b"stsd").expect("stsd");
let entry_count = u32::from_be_bytes([stsd[12], stsd[13], stsd[14], stsd[15]]) as usize;
assert!(entry_count >= 1, "stsd must have at least 1 entry");
let entries_start = 16usize;
let mut off = entries_start;
for _ in 0..entry_count {
let sz =
u32::from_be_bytes([stsd[off], stsd[off + 1], stsd[off + 2], stsd[off + 3]]) as usize;
let ty = &stsd[off + 4..off + 8];
if ty == b"avc1" || ty == b"hvc1" || ty == b"encv" {
let config_start = off + 8 + 78;
let entry_end = off + sz;
let config_region = &stsd[config_start..entry_end];
let mut cfg_off = 0usize;
while cfg_off + 8 <= config_region.len() {
let cfg_sz = u32::from_be_bytes([
config_region[cfg_off],
config_region[cfg_off + 1],
config_region[cfg_off + 2],
config_region[cfg_off + 3],
]) as usize;
if cfg_sz < 8 || cfg_off + cfg_sz > config_region.len() {
break;
}
if &config_region[cfg_off + 4..cfg_off + 8] == child_fourcc {
return &config_region[cfg_off..cfg_off + cfg_sz];
}
cfg_off += cfg_sz;
}
panic!(
"box {:?} not found in visual sample entry config region",
std::str::from_utf8(child_fourcc).unwrap()
);
}
off += sz;
}
panic!("visual sample entry not found in stsd");
}
#[test]
fn pasp_from_fixture_round_trip() {
let data = load_fixture();
let pasp_box = find_visual_ext_box(&data, b"pasp");
let pasp_body = &pasp_box[8..];
assert_eq!(
pasp_body, PASP_BODY_ORACLE,
"pasp body must match oracle (ffmpeg output)"
);
let pasp = PixelAspectRatioBox::parse(pasp_body).expect("pasp parse");
assert_eq!(pasp.h_spacing, 1);
assert_eq!(pasp.v_spacing, 1);
let bytes = pasp.to_bytes();
assert_eq!(
&bytes, &PASP_BODY_ORACLE,
"pasp round-trip must be byte-exact"
);
}
#[test]
fn colr_nclx_from_fixture_round_trip() {
let data = load_fixture();
let colr_box = find_visual_ext_box(&data, b"colr");
let colr_body = &colr_box[8..];
assert_eq!(
colr_body, COLR_BODY_ORACLE,
"colr body must match oracle (ffmpeg output)"
);
let colr = ColourInformationBox::parse(colr_body).expect("colr parse");
assert_eq!(&colr.colour_type, b"nclx", "colr must be nclx type");
let nclx = colr.nclx.as_ref().expect("colr must have nclx params");
assert_eq!(nclx.colour_primaries, 2, "colour_primaries oracle");
assert_eq!(
nclx.transfer_characteristics, 2,
"transfer_characteristics oracle"
);
assert_eq!(
nclx.matrix_coefficients, 9,
"matrix_coefficients must be 9 (bt2020nc)"
);
assert!(!nclx.full_range_flag, "full_range_flag must be false");
let bytes = colr.to_bytes();
assert_eq!(
&bytes, &COLR_BODY_ORACLE,
"colr round-trip must be byte-exact"
);
}
#[test]
fn colr_mutate_field_changes_bytes() {
let colr = ColourInformationBox::parse(&COLR_BODY_ORACLE).unwrap();
let mut colr2 = colr.clone();
colr2.nclx.as_mut().unwrap().matrix_coefficients = 1;
assert_ne!(
colr.to_bytes(),
colr2.to_bytes(),
"mutating matrix_coefficients must change serialized bytes"
);
}
#[test]
fn pasp_mutate_field_changes_bytes() {
let pasp = PixelAspectRatioBox::parse(&PASP_BODY_ORACLE).unwrap();
let mut pasp2 = pasp;
pasp2.h_spacing = 2;
assert_ne!(
pasp.to_bytes(),
pasp2.to_bytes(),
"mutating h_spacing must change serialized bytes"
);
}
#[test]
fn clap_spec_vector_round_trip() {
let clap = CleanApertureBox {
clean_aperture_width_n: 1920,
clean_aperture_width_d: 1,
clean_aperture_height_n: 1080,
clean_aperture_height_d: 1,
horiz_off_n: 0,
horiz_off_d: 1,
vert_off_n: 0,
vert_off_d: 1,
};
assert_eq!(clap.clean_aperture_width_n, 1920);
assert_eq!(clap.clean_aperture_height_n, 1080);
let bytes = clap.to_bytes();
assert_eq!(bytes.len(), 32);
let clap2 = CleanApertureBox::parse(&bytes).expect("clap re-parse");
assert_eq!(clap2.clean_aperture_width_n, 1920);
assert_eq!(clap2.clean_aperture_height_n, 1080);
assert_eq!(clap2.horiz_off_n, 0);
assert_eq!(clap2.vert_off_n, 0);
assert_eq!(clap2.to_bytes(), bytes);
let clap3 = CleanApertureBox {
clean_aperture_width_n: 1280,
..clap
};
assert_ne!(clap3.to_bytes(), bytes, "mutation must change bytes");
}