#![cfg(test)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::print_stderr,
reason = "unit tests"
)]
use crate::isobmff::{FourCc, parse_header, parse_moof};
use crate::mux::Muxer;
use crate::types::{Bytes, Codec, Rational, Sample, Track};
fn track(id: u32) -> Track {
Track {
id,
codec: Codec::H264,
time_base: Rational::new(1, 30),
width: 16,
height: 16,
extra_data: Bytes::new(),
}
}
fn sample(stream_id: u32, dts: i64, duration: u64, key: bool) -> Sample {
Sample {
stream_id,
pts: dts,
dts,
duration,
is_keyframe: key,
is_discard: false,
payload: Bytes::from_static(&[1, 2, 3]),
}
}
fn mux(samples: &[Sample]) -> Vec<u8> {
let mut m = Muxer::new();
m.add_track(track(0)).unwrap();
let mut live = m.begin();
for s in samples {
live.push_packet(s).unwrap();
}
live.flush();
let mut out = Vec::new();
live.poll_bytes(&mut out);
out
}
fn moofs(out: &[u8]) -> Vec<(u64, Vec<(u32, bool)>)> {
let mut res = Vec::new();
let mut pos = 0usize;
while pos + 8 <= out.len() {
let Some(hdr) = parse_header(&out[pos..]) else {
break;
};
if hdr.typ == FourCc(*b"moof") {
let info = parse_moof(&out[pos + hdr.header_len..pos + hdr.size]);
res.push((
info.base_dts,
info.samples.iter().map(|s| (s.duration, s.key)).collect(),
));
}
pos += hdr.size;
}
res
}
fn durations(frag: &(u64, Vec<(u32, bool)>)) -> Vec<u32> {
frag.1.iter().map(|(d, _)| *d).collect()
}
#[test]
fn durations_derive_from_dts_deltas() {
let samples = [
sample(0, 0, 0, true),
sample(0, 30, 0, false),
sample(0, 60, 0, false),
];
let frags = moofs(&mux(&samples));
assert_eq!(frags.len(), 1);
assert_eq!(frags[0].0, 0, "fragment base dts");
assert_eq!(durations(&frags[0]), vec![30, 30, 30]);
}
#[test]
fn caller_duration_trusted_for_last_sample_only() {
let samples = [
sample(0, 0, 999, true),
sample(0, 30, 999, false),
sample(0, 60, 45, false),
];
let frags = moofs(&mux(&samples));
assert_eq!(durations(&frags[0]), vec![30, 30, 45]);
}
#[test]
fn lone_sample_fragment_defaults_to_one_tick() {
let samples = [sample(0, 42, 0, true)];
let frags = moofs(&mux(&samples));
assert_eq!(durations(&frags[0]), vec![1]);
}
#[test]
fn non_monotonic_dts_clamps_to_one_tick() {
let samples = [
sample(0, 0, 0, true),
sample(0, 30, 0, false),
sample(0, 10, 0, false), sample(0, 40, 0, false),
];
let frags = moofs(&mux(&samples));
assert_eq!(durations(&frags[0]), vec![30, 1, 30, 30]);
}
#[test]
fn fragment_bases_advance_with_dts() {
let samples: Vec<Sample> = (0..60).map(|i| sample(0, i * 30, 0, i % 30 == 0)).collect();
let frags = moofs(&mux(&samples));
assert_eq!(frags.len(), 2);
assert_eq!(
frags.iter().map(|f| f.0).collect::<Vec<_>>(),
vec![0, 900],
"fragment bases"
);
for frag in &frags {
assert!(durations(frag).iter().all(|&d| d == 30));
}
}