use std::path::Path;
use container::demux;
fn box_(fourcc: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let size = 8 + payload.len();
let mut out = Vec::with_capacity(size);
out.extend_from_slice(&(size as u32).to_be_bytes());
out.extend_from_slice(fourcc);
out.extend_from_slice(payload);
out
}
fn full_box_(fourcc: &[u8; 4], version: u8, flags: u32, payload: &[u8]) -> Vec<u8> {
let mut body = Vec::with_capacity(4 + payload.len());
body.push(version);
let f = flags & 0x00FF_FFFF;
body.push(((f >> 16) & 0xFF) as u8);
body.push(((f >> 8) & 0xFF) as u8);
body.push((f & 0xFF) as u8);
body.extend_from_slice(payload);
box_(fourcc, &body)
}
fn build_avcc(sps: &[u8], pps: &[u8], length_size_minus_one: u8) -> Vec<u8> {
let mut out = Vec::new();
out.push(0x01); out.push(0x42); out.push(0x00); out.push(0x1E); out.push(0xFC | (length_size_minus_one & 0x03));
out.push(0xE1); out.extend_from_slice(&(sps.len() as u16).to_be_bytes());
out.extend_from_slice(sps);
out.push(0x01); out.extend_from_slice(&(pps.len() as u16).to_be_bytes());
out.extend_from_slice(pps);
out
}
fn lp4_sample(nalus: &[&[u8]]) -> Vec<u8> {
let mut s = Vec::new();
for n in nalus {
s.extend_from_slice(&(n.len() as u32).to_be_bytes());
s.extend_from_slice(n);
}
s
}
fn build_synthetic_mp4(width: u16, height: u16, avcc: &[u8], samples: &[Vec<u8>]) -> Vec<u8> {
let ftyp = box_(b"ftyp", &{
let mut p = Vec::new();
p.extend_from_slice(b"isom"); p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(b"isom"); p.extend_from_slice(b"mp41");
p.extend_from_slice(b"avc1");
p
});
let mvhd = full_box_(b"mvhd", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(&1000u32.to_be_bytes()); p.extend_from_slice(&1000u32.to_be_bytes()); p.extend_from_slice(&0x00010000u32.to_be_bytes()); p.extend_from_slice(&0x0100u16.to_be_bytes()); p.extend_from_slice(&0u16.to_be_bytes()); p.extend_from_slice(&[0u8; 8]); p.extend_from_slice(&0x00010000u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0x00010000u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0x40000000u32.to_be_bytes());
p.extend_from_slice(&[0u8; 24]); p.extend_from_slice(&2u32.to_be_bytes()); p
});
let tkhd = full_box_(b"tkhd", 0, 0x000007, &{
let mut p = Vec::new();
p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(&1u32.to_be_bytes()); p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(&1000u32.to_be_bytes()); p.extend_from_slice(&[0u8; 8]); p.extend_from_slice(&0u16.to_be_bytes()); p.extend_from_slice(&0u16.to_be_bytes()); p.extend_from_slice(&0u16.to_be_bytes()); p.extend_from_slice(&0u16.to_be_bytes()); p.extend_from_slice(&0x00010000u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0x00010000u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0x40000000u32.to_be_bytes());
p.extend_from_slice(&((width as u32) << 16).to_be_bytes());
p.extend_from_slice(&((height as u32) << 16).to_be_bytes());
p
});
let mdhd = full_box_(b"mdhd", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes());
p.extend_from_slice(&1000u32.to_be_bytes());
p.extend_from_slice(&1000u32.to_be_bytes());
p.extend_from_slice(&0x55C4u16.to_be_bytes()); p.extend_from_slice(&0u16.to_be_bytes());
p
});
let hdlr = full_box_(b"hdlr", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(b"vide");
p.extend_from_slice(&[0u8; 12]); p.push(0); p
});
let vmhd = full_box_(b"vmhd", 0, 0x000001, &{
let mut p = Vec::new();
p.extend_from_slice(&0u16.to_be_bytes()); p.extend_from_slice(&[0u8; 6]); p
});
let url = full_box_(b"url ", 0, 0x000001, &[]);
let dref = full_box_(b"dref", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&1u32.to_be_bytes()); p.extend_from_slice(&url);
p
});
let dinf = box_(b"dinf", &dref);
let avc1 = {
let mut p = Vec::new();
p.extend_from_slice(&[0u8; 6]);
p.extend_from_slice(&1u16.to_be_bytes());
p.extend_from_slice(&0u16.to_be_bytes());
p.extend_from_slice(&0u16.to_be_bytes());
p.extend_from_slice(&[0u8; 12]);
p.extend_from_slice(&width.to_be_bytes());
p.extend_from_slice(&height.to_be_bytes());
p.extend_from_slice(&0x00480000u32.to_be_bytes());
p.extend_from_slice(&0x00480000u32.to_be_bytes());
p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(&1u16.to_be_bytes()); p.extend_from_slice(&[0u8; 32]);
p.extend_from_slice(&0x0018u16.to_be_bytes()); p.extend_from_slice(&0xFFFFu16.to_be_bytes()); let avcc_box = box_(b"avcC", avcc);
p.extend_from_slice(&avcc_box);
box_(b"avc1", &p)
};
let stsd = full_box_(b"stsd", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&1u32.to_be_bytes()); p.extend_from_slice(&avc1);
p
});
let per_sample_dur = 1000u32 / (samples.len() as u32).max(1);
let stts = full_box_(b"stts", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&1u32.to_be_bytes()); p.extend_from_slice(&(samples.len() as u32).to_be_bytes());
p.extend_from_slice(&per_sample_dur.to_be_bytes());
p
});
let stsc = full_box_(b"stsc", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&1u32.to_be_bytes()); p.extend_from_slice(&1u32.to_be_bytes()); p.extend_from_slice(&1u32.to_be_bytes()); p.extend_from_slice(&1u32.to_be_bytes()); p
});
let stsz = full_box_(b"stsz", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(&(samples.len() as u32).to_be_bytes());
for s in samples {
p.extend_from_slice(&(s.len() as u32).to_be_bytes());
}
p
});
let build_moov = |stco_offsets: &[u32]| -> Vec<u8> {
let stco = full_box_(b"stco", 0, 0, &{
let mut p = Vec::new();
p.extend_from_slice(&(stco_offsets.len() as u32).to_be_bytes());
for off in stco_offsets {
p.extend_from_slice(&off.to_be_bytes());
}
p
});
let stbl = box_(
b"stbl",
&[stsd.clone(), stts.clone(), stsc.clone(), stsz.clone(), stco].concat(),
);
let minf = box_(b"minf", &[vmhd.clone(), dinf.clone(), stbl].concat());
let mdia = box_(b"mdia", &[mdhd.clone(), hdlr.clone(), minf].concat());
let trak = box_(b"trak", &[tkhd.clone(), mdia].concat());
box_(b"moov", &[mvhd.clone(), trak].concat())
};
let mut stco_offsets = vec![0u32; samples.len()];
let moov_v1 = build_moov(&stco_offsets);
let mdat_payload_start = ftyp.len() + moov_v1.len() + 8;
let mut cur = mdat_payload_start;
for (i, s) in samples.iter().enumerate() {
stco_offsets[i] = cur as u32;
cur += s.len();
}
let moov_v2 = build_moov(&stco_offsets);
assert_eq!(
moov_v1.len(),
moov_v2.len(),
"moov v1 and v2 must be same size — offset bytes are u32 in both"
);
let mdat_payload: Vec<u8> = samples.iter().flatten().copied().collect();
let mdat = box_(b"mdat", &mdat_payload);
let mut out = Vec::new();
out.extend_from_slice(&ftyp);
out.extend_from_slice(&moov_v2);
out.extend_from_slice(&mdat);
out
}
fn find_nal_after_start_code(buf: &[u8], nal_first_byte: u8) -> Option<usize> {
let mut i = 0;
while i + 4 < buf.len() {
let is_4_sc = buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 0 && buf[i + 3] == 1;
let is_3_sc = buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 1;
if is_4_sc {
if buf[i + 4] == nal_first_byte {
return Some(i + 4);
}
i += 4;
} else if is_3_sc {
if buf[i + 3] == nal_first_byte {
return Some(i + 3);
}
i += 3;
} else {
i += 1;
}
}
None
}
fn collect_nal_first_bytes(buf: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
let mut i = 0;
while i + 4 < buf.len() {
let is_4_sc = buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 0 && buf[i + 3] == 1;
let is_3_sc = buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 1;
if is_4_sc {
out.push(buf[i + 4]);
i += 4;
} else if is_3_sc {
out.push(buf[i + 3]);
i += 3;
} else {
i += 1;
}
}
out
}
#[test]
fn mp4_exoplayer_sps_only_then_idr_pps_prepended_on_irap() {
let sps: Vec<u8> = vec![0x67, 0x42, 0x00, 0x1E, 0xAA, 0xBB];
let pps: Vec<u8> = vec![0x68, 0xCE, 0x3C, 0x80];
let p_slice: Vec<u8> = vec![0x41, 0x9A, 0x00];
let idr: Vec<u8> = vec![0x65, 0x88, 0x84, 0x10];
let avcc = build_avcc(&sps, &pps, 3);
let samples = vec![
lp4_sample(&[&sps, &p_slice]), lp4_sample(&[&idr]), ];
let data = build_synthetic_mp4(320, 240, &avcc, &samples);
let demuxed = demux::demux(&data).expect("demux synthetic ExoPlayer-shape MP4");
assert_eq!(demuxed.codec, "h264");
assert_eq!(demuxed.samples.len(), 2);
let s0 = &demuxed.samples[0];
assert!(
find_nal_after_start_code(s0, 0x67).is_some(),
"sample 0 must still contain its inline SPS"
);
assert!(
find_nal_after_start_code(s0, 0x68).is_none(),
"sample 0 must NOT contain PPS (no IRAP yet — old buggy code prepended here)"
);
let s1 = &demuxed.samples[1];
let nals = collect_nal_first_bytes(s1);
let pps_count = nals.iter().filter(|b| **b == 0x68).count();
let sps_count = nals.iter().filter(|b| **b == 0x67).count();
let idr_count = nals.iter().filter(|b| **b == 0x65).count();
assert_eq!(
pps_count, 1,
"PPS must be prepended exactly once on the first IRAP"
);
assert_eq!(
sps_count, 0,
"SPS must not be re-emitted (already inline at sample 0)"
);
assert_eq!(idr_count, 1, "IDR must appear exactly once");
let pps_pos = find_nal_after_start_code(s1, 0x68).unwrap();
let idr_pos = find_nal_after_start_code(s1, 0x65).unwrap();
assert!(
pps_pos < idr_pos,
"PPS must come before IDR in the prepended output"
);
}
#[test]
fn mp4_avcc_only_first_sample_idr_prepends_both() {
let sps: Vec<u8> = vec![0x67, 0x42, 0x00, 0x1E, 0xAA];
let pps: Vec<u8> = vec![0x68, 0xCE, 0x3C, 0x80];
let idr: Vec<u8> = vec![0x65, 0x88, 0x84, 0x00];
let avcc = build_avcc(&sps, &pps, 3);
let samples = vec![lp4_sample(&[&idr])];
let data = build_synthetic_mp4(320, 240, &avcc, &samples);
let demuxed = demux::demux(&data).expect("demux");
assert_eq!(demuxed.samples.len(), 1);
let s0 = &demuxed.samples[0];
let sps_pos = find_nal_after_start_code(s0, 0x67).expect("SPS prepended");
let pps_pos = find_nal_after_start_code(s0, 0x68).expect("PPS prepended");
let idr_pos = find_nal_after_start_code(s0, 0x65).expect("IDR present");
assert!(sps_pos < pps_pos);
assert!(pps_pos < idr_pos);
}
#[test]
fn mp4_inline_sps_pps_idr_no_duplication() {
let sps: Vec<u8> = vec![0x67, 0x42, 0x00, 0x1E, 0xAA];
let pps: Vec<u8> = vec![0x68, 0xCE, 0x3C, 0x80];
let idr: Vec<u8> = vec![0x65, 0x88, 0x84, 0x00];
let avcc = build_avcc(&sps, &pps, 3);
let samples = vec![lp4_sample(&[&sps, &pps, &idr])];
let data = build_synthetic_mp4(320, 240, &avcc, &samples);
let demuxed = demux::demux(&data).expect("demux");
let s0 = &demuxed.samples[0];
let nals = collect_nal_first_bytes(s0);
let sps_count = nals.iter().filter(|b| **b == 0x67).count();
let pps_count = nals.iter().filter(|b| **b == 0x68).count();
assert_eq!(sps_count, 1, "SPS must appear exactly once");
assert_eq!(pps_count, 1, "PPS must appear exactly once");
}
fn test_media(name: &str) -> Option<Vec<u8>> {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("test_media")
.join(name);
std::fs::read(&path).ok()
}
#[test]
fn mp4_exoplayer_h264_main_720p_first_irap_has_pps() {
let Some(data) = test_media("exoplayer_h264_main_720p.mp4") else {
eprintln!("SKIP mp4_exoplayer_h264_main_720p_first_irap_has_pps: test_media not available");
return;
};
let demuxed = match demux::demux(&data) {
Ok(d) => d,
Err(e) => {
panic!("demux failed: {e}");
}
};
assert_eq!(demuxed.codec, "h264");
assert!(!demuxed.samples.is_empty());
let mut saw_sps = false;
let mut saw_pps = false;
let mut first_irap_idx: Option<usize> = None;
for (i, s) in demuxed.samples.iter().enumerate() {
let nals = collect_nal_first_bytes(s);
for n in &nals {
let t = n & 0x1F;
if t == 7 {
saw_sps = true;
}
if t == 8 {
saw_pps = true;
}
if t == 5 && first_irap_idx.is_none() {
first_irap_idx = Some(i);
}
}
if first_irap_idx.is_some() {
break;
}
}
if let Some(idx) = first_irap_idx {
assert!(
saw_sps && saw_pps,
"by the first IDR (sample {}), SPS and PPS must have been seen \
— saw_sps={}, saw_pps={}",
idx,
saw_sps,
saw_pps
);
} else {
assert!(
saw_sps && saw_pps,
"ExoPlayer stream lacks both an IDR and the SPS+PPS prefix \
— saw_sps={}, saw_pps={}",
saw_sps,
saw_pps
);
}
}