use bytes::Bytes;
use codec::encode::EncodedPacket;
use container::mux::Av1Mp4Muxer;
fn minimal_av1_first_packet() -> Bytes {
let header: u8 = (1 << 3) | (1 << 1);
let payload = [0u8; 5];
let mut out = Vec::with_capacity(2 + payload.len());
out.push(header);
out.push(payload.len() as u8); out.extend_from_slice(&payload);
Bytes::from(out)
}
fn opaque_packet(size: usize) -> Bytes {
Bytes::from(vec![0xAA; size])
}
fn find_fourcc(data: &[u8], tag: &[u8; 4]) -> Option<usize> {
data.windows(4).position(|w| w == tag)
}
fn find_box(data: &[u8], tag: &[u8; 4]) -> Option<(usize, usize)> {
let pos = find_fourcc(data, tag)?;
if pos < 4 {
return None;
}
let size =
u32::from_be_bytes([data[pos - 4], data[pos - 3], data[pos - 2], data[pos - 1]]) as usize;
Some((pos - 4, size))
}
fn parse_co64(data: &[u8]) -> Vec<u64> {
let (pos, size) = find_box(data, b"co64").expect("co64 box present");
let body = &data[pos..pos + size];
let count = u32::from_be_bytes([body[12], body[13], body[14], body[15]]) as usize;
let mut out = Vec::with_capacity(count);
for i in 0..count {
let p = 16 + i * 8;
out.push(u64::from_be_bytes([
body[p],
body[p + 1],
body[p + 2],
body[p + 3],
body[p + 4],
body[p + 5],
body[p + 6],
body[p + 7],
]));
}
out
}
fn parse_stsc(data: &[u8]) -> Vec<(u32, u32, u32)> {
let (pos, size) = find_box(data, b"stsc").expect("stsc box present");
let body = &data[pos..pos + size];
let count = u32::from_be_bytes([body[12], body[13], body[14], body[15]]) as usize;
let mut out = Vec::with_capacity(count);
for i in 0..count {
let p = 16 + i * 12;
let fc = u32::from_be_bytes([body[p], body[p + 1], body[p + 2], body[p + 3]]);
let spc = u32::from_be_bytes([body[p + 4], body[p + 5], body[p + 6], body[p + 7]]);
let sdi = u32::from_be_bytes([body[p + 8], body[p + 9], body[p + 10], body[p + 11]]);
out.push((fc, spc, sdi));
}
out
}
fn build_muxer_with_packets(count: u32, fps: f64, other_packet_size: usize) -> Bytes {
let mut muxer = Av1Mp4Muxer::new(1280, 720, fps).expect("muxer");
muxer
.add_packet(EncodedPacket {
data: minimal_av1_first_packet(),
pts: 0,
is_keyframe: true,
})
.expect("add first packet");
for i in 1..count {
muxer
.add_packet(EncodedPacket {
data: opaque_packet(other_packet_size),
pts: i as u64,
is_keyframe: false,
})
.expect("add packet");
}
muxer.finalize().expect("finalize")
}
#[test]
fn mux_emits_stco_when_under_u32() {
let out = build_muxer_with_packets(60, 30.0, 1024);
assert!(
find_fourcc(&out, b"stco").is_some(),
"stco missing in small output"
);
assert!(
find_fourcc(&out, b"co64").is_none(),
"co64 present in small output"
);
}
#[test]
fn mux_stsc_chunks_fps_rounded_capped_120() {
let out = build_muxer_with_packets(120, 24.0, 512);
let entries = parse_stsc(&out);
assert_eq!(
entries,
vec![(1, 24, 1)],
"24 fps should chunk by 24 with no tail"
);
}
#[test]
fn mux_stsc_last_partial_chunk_emits_tail_entry_real_muxer() {
let out = build_muxer_with_packets(121, 24.0, 512);
let entries = parse_stsc(&out);
assert_eq!(entries, vec![(1, 24, 1), (6, 1, 1)]);
}
#[test]
fn mux_stsc_caps_samples_per_chunk_at_120() {
let out = build_muxer_with_packets(300, 240.0, 256);
let entries = parse_stsc(&out);
assert_eq!(entries, vec![(1, 120, 1), (3, 60, 1)]);
}
#[test]
fn mux_emits_co64_when_upper_bound_exceeds_u32() {
let out = build_muxer_with_packets(30, 30.0, 2048);
assert!(
find_fourcc(&out, b"stco").is_some(),
"small output should use stco"
);
assert!(
find_fourcc(&out, b"co64").is_none(),
"small output should not emit co64"
);
let crafted_co64 = {
let offs: [u64; 3] = [
(u32::MAX as u64) + 1,
(u32::MAX as u64) + 1_000_000,
(u32::MAX as u64) + 2_000_000,
];
let body_len = 16 + 8 * offs.len();
let mut v = Vec::with_capacity(body_len);
v.extend_from_slice(&(body_len as u32).to_be_bytes());
v.extend_from_slice(b"co64");
v.push(0); v.extend_from_slice(&[0, 0, 0]); v.extend_from_slice(&(offs.len() as u32).to_be_bytes());
for &o in &offs {
v.extend_from_slice(&o.to_be_bytes());
}
v
};
let parsed = parse_co64(&crafted_co64);
assert_eq!(parsed.len(), 3);
let mut prev = 0u64;
for v in &parsed {
assert!(*v > prev, "co64 entries not monotonic BE");
assert!(*v > u32::MAX as u64, "co64 entries should be >u32");
prev = *v;
}
}