use super::{
fourcc, iterate_boxes, parse_box_header, read_u16, read_u32, read_u64, AvccData, HvccData,
Mp4Error, Mp4File, Sample, Track,
};
pub fn demux(data: &[u8]) -> Result<Mp4File, Mp4Error> {
let mut ftyp: Option<Vec<u8>> = None;
let mut moov_range: Option<(usize, usize)> = None;
let mut has_moof = false;
let file_end = data.len();
let mut pos = 0usize;
while pos < file_end {
if pos + 8 > file_end {
break;
}
let header = parse_box_header(data, pos)?;
if header.size < 8 {
break;
}
let box_end = pos + header.size as usize;
let _content_start = pos + header.header_len as usize;
match &header.box_type {
b"ftyp" => {
let safe_end = box_end.min(file_end);
ftyp = Some(data[pos..safe_end].to_vec());
}
b"moov" => {
let safe_end = box_end.min(file_end);
moov_range = Some((pos, safe_end));
}
b"moof" => {
has_moof = true;
}
_ => {} }
if box_end > file_end {
break;
}
pos = box_end;
}
if has_moof {
return Err(Mp4Error::FragmentedMp4);
}
let ftyp = ftyp.ok_or_else(|| Mp4Error::InvalidBox("missing ftyp box".into()))?;
let (moov_start, moov_end) = match moov_range {
Some(r) => r,
None => {
return recover_truncated(data, &ftyp);
}
};
let moov_header = parse_box_header(data, moov_start)?;
let moov_content_start = moov_start + moov_header.header_len as usize;
let tracks = parse_moov(data, moov_content_start, moov_end)?;
let video_track_idx = tracks.iter().position(|t| {
t.handler_type == *b"vide" && (t.is_hevc() || t.is_h264())
});
Ok(Mp4File {
ftyp,
tracks,
video_track_idx,
})
}
fn parse_moov(data: &[u8], start: usize, end: usize) -> Result<Vec<Track>, Mp4Error> {
let mut tracks = Vec::new();
iterate_boxes(data, start, end, |header, content_start, box_data| {
if header.box_type == *b"trak" {
let trak_start = content_start - header.header_len as usize;
let trak_end = trak_start + header.size as usize;
let track = parse_trak(data, content_start, trak_end, box_data)?;
tracks.push(track);
}
Ok(())
})?;
Ok(tracks)
}
fn parse_trak(
data: &[u8],
start: usize,
end: usize,
trak_raw_data: &[u8],
) -> Result<Track, Mp4Error> {
let mut track = Track {
track_id: 0,
handler_type: [0; 4],
codec: [0; 4],
width: 0,
height: 0,
timescale: 0,
duration: 0,
samples: Vec::new(),
hvcc_data: None,
avcc_data: None,
stsd_raw: Vec::new(),
trak_raw: trak_raw_data.to_vec(),
};
let mut sample_sizes: Vec<u32> = Vec::new();
let mut sample_offsets: Vec<u64> = Vec::new();
let mut sync_samples: Option<Vec<u32>> = None; let mut stsc_entries: Vec<(u32, u32, u32)> = Vec::new();
parse_trak_children(
data,
start,
end,
&mut track,
&mut sample_sizes,
&mut sample_offsets,
&mut sync_samples,
&mut stsc_entries,
)?;
build_samples(
data,
&mut track,
&sample_sizes,
&sample_offsets,
&sync_samples,
&stsc_entries,
)?;
Ok(track)
}
#[allow(clippy::too_many_arguments)]
fn parse_trak_children(
data: &[u8],
start: usize,
end: usize,
track: &mut Track,
sample_sizes: &mut Vec<u32>,
sample_offsets: &mut Vec<u64>,
sync_samples: &mut Option<Vec<u32>>,
stsc_entries: &mut Vec<(u32, u32, u32)>,
) -> Result<(), Mp4Error> {
iterate_boxes(data, start, end, |header, content_start, _box_data| {
match &header.box_type {
b"tkhd" => parse_tkhd(data, content_start, track)?,
b"mdia" => {
let child_end = content_start - header.header_len as usize + header.size as usize;
parse_trak_children(
data,
content_start,
child_end,
track,
sample_sizes,
sample_offsets,
sync_samples,
stsc_entries,
)?;
}
b"mdhd" => parse_mdhd(data, content_start, track)?,
b"hdlr" => parse_hdlr(data, content_start, track)?,
b"minf" | b"stbl" => {
let child_end = content_start - header.header_len as usize + header.size as usize;
let saved_handler = track.handler_type;
parse_trak_children(
data,
content_start,
child_end,
track,
sample_sizes,
sample_offsets,
sync_samples,
stsc_entries,
)?;
if saved_handler != [0; 4] {
track.handler_type = saved_handler;
}
}
b"stsd" => {
let box_start = content_start - header.header_len as usize;
let box_end = box_start + header.size as usize;
track.stsd_raw = data[box_start..box_end].to_vec();
parse_stsd(data, content_start, track)?;
}
b"stsz" => *sample_sizes = parse_stsz(data, content_start)?,
b"stco" => *sample_offsets = parse_stco(data, content_start)?,
b"co64" => *sample_offsets = parse_co64(data, content_start)?,
b"stss" => *sync_samples = Some(parse_stss(data, content_start)?),
b"stsc" => *stsc_entries = parse_stsc(data, content_start)?,
b"stts" => {} _ => {} }
Ok(())
})
}
fn parse_tkhd(data: &[u8], start: usize, track: &mut Track) -> Result<(), Mp4Error> {
if start >= data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let version = data[start];
let offset = if version == 1 {
start + 4 } else {
start + 4
};
if version == 1 {
track.track_id = read_u32(data, offset + 16)?;
let wh_offset = start + 4 + 8 + 8 + 4 + 4 + 8 + 8 + 2 + 2 + 2 + 2 + 36;
track.width = read_u32(data, wh_offset)? >> 16;
track.height = read_u32(data, wh_offset + 4)? >> 16;
} else {
track.track_id = read_u32(data, offset + 8)?;
let wh_offset = start + 4 + 4 + 4 + 4 + 4 + 4 + 8 + 2 + 2 + 2 + 2 + 36;
track.width = read_u32(data, wh_offset)? >> 16;
track.height = read_u32(data, wh_offset + 4)? >> 16;
}
Ok(())
}
fn parse_mdhd(data: &[u8], start: usize, track: &mut Track) -> Result<(), Mp4Error> {
if start >= data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let version = data[start];
if version == 1 {
track.timescale = read_u32(data, start + 4 + 8 + 8)?;
track.duration = read_u64(data, start + 4 + 8 + 8 + 4)?;
} else {
track.timescale = read_u32(data, start + 4 + 4 + 4)?;
track.duration = read_u32(data, start + 4 + 4 + 4 + 4)? as u64;
}
Ok(())
}
fn parse_hdlr(data: &[u8], start: usize, track: &mut Track) -> Result<(), Mp4Error> {
track.handler_type = fourcc(data, start + 4 + 4)?;
Ok(())
}
fn parse_stsd(data: &[u8], start: usize, track: &mut Track) -> Result<(), Mp4Error> {
let entry_count = read_u32(data, start + 4)?;
if entry_count == 0 {
return Ok(());
}
let entry_start = start + 8;
let entry_header = parse_box_header(data, entry_start)?;
track.codec = entry_header.box_type;
let is_video = track.codec == *b"hev1"
|| track.codec == *b"hvc1"
|| track.codec == *b"avc1"
|| track.codec == *b"avc3";
if is_video {
let children_start = entry_start + entry_header.header_len as usize + 78;
let entry_end = entry_start + entry_header.size as usize;
let dim_offset = entry_start + entry_header.header_len as usize + 6 + 2 + 2 + 2 + 12;
if dim_offset + 4 <= data.len() {
track.width = read_u16(data, dim_offset)? as u32;
track.height = read_u16(data, dim_offset + 2)? as u32;
}
if children_start < entry_end {
iterate_boxes(data, children_start, entry_end, |h, cs, _| {
if h.box_type == *b"hvcC" {
track.hvcc_data = Some(parse_hvcc(data, cs)?);
} else if h.box_type == *b"avcC" {
track.avcc_data = Some(parse_avcc(data, cs)?);
}
Ok(())
})?;
}
}
Ok(())
}
fn parse_hvcc(data: &[u8], start: usize) -> Result<HvccData, Mp4Error> {
if start + 23 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let configuration_version = data[start];
let length_size_minus1 = data[start + 21] & 0x03;
let num_arrays = data[start + 22];
let mut vps_nalus = Vec::new();
let mut sps_nalus = Vec::new();
let mut pps_nalus = Vec::new();
let mut pos = start + 23;
for _ in 0..num_arrays {
if pos + 3 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let nal_type = data[pos] & 0x3F;
let num_nalus = read_u16(data, pos + 1)? as usize;
pos += 3;
for _ in 0..num_nalus {
if pos + 2 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let nalu_len = read_u16(data, pos)? as usize;
pos += 2;
if pos + nalu_len > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let nalu_data = data[pos..pos + nalu_len].to_vec();
pos += nalu_len;
match nal_type {
32 => vps_nalus.push(nalu_data), 33 => sps_nalus.push(nalu_data), 34 => pps_nalus.push(nalu_data), _ => {} }
}
}
Ok(HvccData {
configuration_version,
length_size_minus1,
vps_nalus,
sps_nalus,
pps_nalus,
})
}
fn parse_avcc(data: &[u8], start: usize) -> Result<AvccData, Mp4Error> {
if start + 6 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let configuration_version = data[start];
let profile = data[start + 1];
let profile_compat = data[start + 2];
let level = data[start + 3];
let length_size_minus1 = data[start + 4] & 0x03;
let num_sps = (data[start + 5] & 0x1F) as usize;
let mut sps_nalus = Vec::new();
let mut pps_nalus = Vec::new();
let mut pos = start + 6;
for _ in 0..num_sps {
if pos + 2 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let nalu_len = read_u16(data, pos)? as usize;
pos += 2;
if pos + nalu_len > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
sps_nalus.push(data[pos..pos + nalu_len].to_vec());
pos += nalu_len;
}
if pos >= data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let num_pps = data[pos] as usize;
pos += 1;
for _ in 0..num_pps {
if pos + 2 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let nalu_len = read_u16(data, pos)? as usize;
pos += 2;
if pos + nalu_len > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
pps_nalus.push(data[pos..pos + nalu_len].to_vec());
pos += nalu_len;
}
Ok(AvccData {
configuration_version,
profile,
profile_compat,
level,
length_size_minus1,
sps_nalus,
pps_nalus,
})
}
fn parse_stsz(data: &[u8], start: usize) -> Result<Vec<u32>, Mp4Error> {
let sample_size = read_u32(data, start + 4)?;
let sample_count = read_u32(data, start + 8)? as usize;
if sample_size != 0 {
Ok(vec![sample_size; sample_count])
} else {
let mut sizes = Vec::with_capacity(sample_count);
for i in 0..sample_count {
sizes.push(read_u32(data, start + 12 + i * 4)?);
}
Ok(sizes)
}
}
fn parse_stco(data: &[u8], start: usize) -> Result<Vec<u64>, Mp4Error> {
let count = read_u32(data, start + 4)? as usize;
let mut offsets = Vec::with_capacity(count);
for i in 0..count {
offsets.push(read_u32(data, start + 8 + i * 4)? as u64);
}
Ok(offsets)
}
fn parse_co64(data: &[u8], start: usize) -> Result<Vec<u64>, Mp4Error> {
let count = read_u32(data, start + 4)? as usize;
let mut offsets = Vec::with_capacity(count);
for i in 0..count {
offsets.push(read_u64(data, start + 8 + i * 8)?);
}
Ok(offsets)
}
fn parse_stss(data: &[u8], start: usize) -> Result<Vec<u32>, Mp4Error> {
let count = read_u32(data, start + 4)? as usize;
let mut syncs = Vec::with_capacity(count);
for i in 0..count {
syncs.push(read_u32(data, start + 8 + i * 4)?);
}
Ok(syncs)
}
pub(super) fn parse_stsc(data: &[u8], start: usize) -> Result<Vec<(u32, u32, u32)>, Mp4Error> {
let count = read_u32(data, start + 4)? as usize;
let mut entries = Vec::with_capacity(count);
for i in 0..count {
let offset = start + 8 + i * 12;
let first_chunk = read_u32(data, offset)?;
let samples_per_chunk = read_u32(data, offset + 4)?;
let desc_index = read_u32(data, offset + 8)?;
entries.push((first_chunk, samples_per_chunk, desc_index));
}
Ok(entries)
}
fn build_samples(
data: &[u8],
track: &mut Track,
sample_sizes: &[u32],
chunk_offsets: &[u64],
sync_samples: &Option<Vec<u32>>,
stsc_entries: &[(u32, u32, u32)],
) -> Result<(), Mp4Error> {
if sample_sizes.is_empty() || chunk_offsets.is_empty() {
return Ok(());
}
let sync_set: Option<std::collections::HashSet<u32>> =
sync_samples.as_ref().map(|s| s.iter().copied().collect());
let num_chunks = chunk_offsets.len();
let mut samples_per_chunk = vec![0u32; num_chunks];
if stsc_entries.is_empty() {
samples_per_chunk.fill(1);
} else {
for (i, entry) in stsc_entries.iter().enumerate() {
let first_chunk = entry.0 as usize; let spc = entry.1;
let next_first = if i + 1 < stsc_entries.len() {
stsc_entries[i + 1].0 as usize
} else {
num_chunks + 1
};
for chunk_idx in first_chunk..next_first {
if chunk_idx <= num_chunks && chunk_idx >= 1 {
samples_per_chunk[chunk_idx - 1] = spc;
}
}
}
}
let mut samples = Vec::with_capacity(sample_sizes.len());
let mut sample_idx = 0usize;
for (chunk_idx, &chunk_offset) in chunk_offsets.iter().enumerate() {
let spc = samples_per_chunk[chunk_idx] as usize;
let mut offset_in_chunk = 0u64;
for _ in 0..spc {
if sample_idx >= sample_sizes.len() {
break;
}
let size = sample_sizes[sample_idx];
let abs_offset = chunk_offset + offset_in_chunk;
let sample_num = (sample_idx + 1) as u32;
let is_sync = match &sync_set {
Some(set) => set.contains(&sample_num),
None => true, };
let start = abs_offset as usize;
let end = start + size as usize;
let sample_data = if end <= data.len() {
data[start..end].to_vec()
} else {
return Err(Mp4Error::TruncatedFile);
};
samples.push(Sample {
offset: abs_offset,
size,
is_sync,
data: sample_data,
});
offset_in_chunk += size as u64;
sample_idx += 1;
}
}
track.samples = samples;
Ok(())
}
fn recover_truncated(data: &[u8], ftyp: &[u8]) -> Result<Mp4File, Mp4Error> {
let (mdat_start, mdat_end) = find_mdat_region(data)?;
let mdat_data = &data[mdat_start..mdat_end];
let length_size: u8 = 4;
if validate_nal_lengths(mdat_data, length_size) {
return recover_from_length_prefixed(ftyp, mdat_data, mdat_start, length_size);
}
recover_from_annexb(ftyp, mdat_data, mdat_start)
}
fn recover_from_annexb(
ftyp: &[u8],
mdat_data: &[u8],
mdat_start: usize,
) -> Result<Mp4File, Mp4Error> {
let length_size: u8 = 4;
let mut sc_positions: Vec<usize> = Vec::new();
let mut i = 0;
while i + 3 < mdat_data.len() {
if mdat_data[i] == 0 && mdat_data[i + 1] == 0 {
if mdat_data[i + 2] == 1 {
sc_positions.push(i + 3);
i += 3;
continue;
} else if i + 3 < mdat_data.len() && mdat_data[i + 2] == 0 && mdat_data[i + 3] == 1 {
sc_positions.push(i + 4);
i += 4;
continue;
}
}
i += 1;
}
if sc_positions.len() < 2 {
return Err(Mp4Error::TruncatedFile);
}
let mut vps_nalus: Vec<Vec<u8>> = Vec::new();
let mut sps_nalus: Vec<Vec<u8>> = Vec::new();
let mut pps_nalus: Vec<Vec<u8>> = Vec::new();
let mut samples: Vec<Sample> = Vec::new();
let mut au_buffer: Vec<u8> = Vec::new();
let mut au_buffer_start: usize = 0;
let mut au_is_irap: bool = false;
let mut au_has_vcl: bool = false;
for (idx, &nal_start) in sc_positions.iter().enumerate() {
let nal_end = if idx + 1 < sc_positions.len() {
let next_sc_nal = sc_positions[idx + 1];
let mut end = next_sc_nal;
while end > nal_start && mdat_data[end - 1] == 0 {
end -= 1;
}
end
} else {
mdat_data.len()
};
if nal_end <= nal_start || nal_end - nal_start < 2 {
continue;
}
let nal_data = &mdat_data[nal_start..nal_end];
let forbidden_zero = (nal_data[0] >> 7) & 1;
if forbidden_zero != 0 {
continue;
}
let nal_type_val = (nal_data[0] >> 1) & 0x3F;
match nal_type_val {
32 => {
if !vps_nalus.iter().any(|v| v.as_slice() == nal_data) {
vps_nalus.push(nal_data.to_vec());
}
}
33 => {
if !sps_nalus.iter().any(|v| v.as_slice() == nal_data) {
sps_nalus.push(nal_data.to_vec());
}
}
34 => {
if !pps_nalus.iter().any(|v| v.as_slice() == nal_data) {
pps_nalus.push(nal_data.to_vec());
}
}
_ => {}
}
let is_vcl = nal_type_val <= 31;
let is_irap = (16..=23).contains(&nal_type_val);
let is_first_slice = if is_vcl && nal_data.len() >= 3 {
(nal_data[2] >> 7) & 1 == 1
} else {
is_vcl
};
if is_vcl && is_first_slice && au_has_vcl {
if !au_buffer.is_empty() {
let file_offset = (mdat_start + au_buffer_start) as u64;
samples.push(Sample {
offset: file_offset,
size: au_buffer.len() as u32,
is_sync: au_is_irap,
data: au_buffer.clone(),
});
}
au_buffer.clear();
au_buffer_start = nal_start;
au_is_irap = false;
au_has_vcl = false;
}
if au_buffer.is_empty() {
au_buffer_start = nal_start;
}
let nal_len = nal_data.len() as u32;
au_buffer.extend_from_slice(&nal_len.to_be_bytes());
au_buffer.extend_from_slice(nal_data);
if is_vcl {
au_has_vcl = true;
}
if is_irap {
au_is_irap = true;
}
}
if !au_buffer.is_empty() && au_has_vcl {
let file_offset = (mdat_start + au_buffer_start) as u64;
samples.push(Sample {
offset: file_offset,
size: au_buffer.len() as u32,
is_sync: au_is_irap,
data: au_buffer,
});
}
build_recovered_mp4(ftyp, vps_nalus, sps_nalus, pps_nalus, samples, length_size)
}
fn recover_from_length_prefixed(
ftyp: &[u8],
mdat_data: &[u8],
mdat_start: usize,
length_size: u8,
) -> Result<Mp4File, Mp4Error> {
let _ls = length_size as usize;
let mut vps_nalus: Vec<Vec<u8>> = Vec::new();
let mut sps_nalus: Vec<Vec<u8>> = Vec::new();
let mut pps_nalus: Vec<Vec<u8>> = Vec::new();
let mut samples: Vec<Sample> = Vec::new();
let mut au_start: Option<usize> = None; let mut au_size: usize = 0;
let mut au_is_irap: bool = false;
let ls = length_size as usize;
let mut pos: usize = 0;
while pos + ls <= mdat_data.len() {
let nal_len = read_nal_length(mdat_data, pos, ls);
if nal_len == 0 || pos + ls + nal_len > mdat_data.len() {
break;
}
let nal_data_start = pos + ls;
let nal_data = &mdat_data[nal_data_start..nal_data_start + nal_len];
if nal_len < 2 {
break;
}
let forbidden_zero = (nal_data[0] >> 7) & 1;
if forbidden_zero != 0 {
break; }
let nal_type_val = (nal_data[0] >> 1) & 0x3F;
if nal_type_val > 63 {
break;
}
let total_nal_size = ls + nal_len;
match nal_type_val {
32 => {
if !vps_nalus.iter().any(|v| v.as_slice() == nal_data) {
vps_nalus.push(nal_data.to_vec());
}
if au_start.is_some() {
flush_access_unit(
&mut samples, mdat_start, au_start.unwrap(), au_size, au_is_irap,
mdat_data,
);
au_start = None;
au_size = 0;
au_is_irap = false;
}
if au_start.is_none() {
au_start = Some(pos);
}
au_size += total_nal_size;
}
33 => {
if !sps_nalus.iter().any(|v| v.as_slice() == nal_data) {
sps_nalus.push(nal_data.to_vec());
}
if au_start.is_none() {
au_start = Some(pos);
}
au_size += total_nal_size;
}
34 => {
if !pps_nalus.iter().any(|v| v.as_slice() == nal_data) {
pps_nalus.push(nal_data.to_vec());
}
if au_start.is_none() {
au_start = Some(pos);
}
au_size += total_nal_size;
}
0..=31 => {
let is_first_slice = if nal_len >= 3 {
(nal_data[2] >> 7) & 1 == 1
} else {
true };
let is_irap = (16..=23).contains(&nal_type_val);
if is_first_slice {
if au_start.is_some() && au_size > 0 {
let prev_has_vcl = samples_has_vcl_data(
mdat_data, au_start.unwrap(), au_size, ls,
);
if prev_has_vcl {
flush_access_unit(
&mut samples, mdat_start, au_start.unwrap(), au_size,
au_is_irap, mdat_data,
);
au_start = Some(pos);
au_size = 0;
au_is_irap = false;
}
}
if au_start.is_none() {
au_start = Some(pos);
}
if is_irap {
au_is_irap = true;
}
} else if au_start.is_none() {
pos += total_nal_size;
continue;
}
au_size += total_nal_size;
}
_ => {
if nal_type_val == 35
&& au_start.is_some() && au_size > 0 {
let prev_has_vcl = samples_has_vcl_data(
mdat_data, au_start.unwrap(), au_size, ls,
);
if prev_has_vcl {
flush_access_unit(
&mut samples, mdat_start, au_start.unwrap(), au_size,
au_is_irap, mdat_data,
);
au_start = None;
au_size = 0;
au_is_irap = false;
}
}
if au_start.is_none() {
au_start = Some(pos);
}
au_size += total_nal_size;
}
}
pos += total_nal_size;
}
if let Some(start) = au_start
&& au_size > 0 && samples_has_vcl_data(mdat_data, start, au_size, ls) {
flush_access_unit(&mut samples, mdat_start, start, au_size, au_is_irap, mdat_data);
}
build_recovered_mp4(ftyp, vps_nalus, sps_nalus, pps_nalus, samples, length_size)
}
fn build_recovered_mp4(
ftyp: &[u8],
vps_nalus: Vec<Vec<u8>>,
sps_nalus: Vec<Vec<u8>>,
pps_nalus: Vec<Vec<u8>>,
samples: Vec<Sample>,
length_size: u8,
) -> Result<Mp4File, Mp4Error> {
if vps_nalus.is_empty() || sps_nalus.is_empty() || pps_nalus.is_empty() {
return Err(Mp4Error::TruncatedFile);
}
if samples.is_empty() {
return Err(Mp4Error::TruncatedFile);
}
let (width, height) = extract_dimensions_from_sps(&sps_nalus[0]).unwrap_or((0, 0));
let hvcc = HvccData {
configuration_version: 1,
length_size_minus1: length_size - 1,
vps_nalus,
sps_nalus,
pps_nalus,
};
let track = Track {
track_id: 1,
handler_type: *b"vide",
codec: *b"hvc1",
width,
height,
timescale: 30000,
duration: (samples.len() as u64) * 1000,
samples,
hvcc_data: Some(hvcc),
avcc_data: None,
stsd_raw: Vec::new(),
trak_raw: Vec::new(),
};
Ok(Mp4File {
ftyp: ftyp.to_vec(),
tracks: vec![track],
video_track_idx: Some(0),
})
}
fn find_mdat_region(data: &[u8]) -> Result<(usize, usize), Mp4Error> {
let file_end = data.len();
let mut pos = 0usize;
while pos < file_end {
if pos + 8 > file_end {
break;
}
let header = parse_box_header(data, pos)?;
if header.size < 8 {
break;
}
if header.box_type == *b"mdat" {
let content_start = pos + header.header_len as usize;
let content_end = (pos + header.size as usize).min(file_end);
return Ok((content_start, content_end));
}
let box_end = pos + header.size as usize;
if box_end > file_end {
break;
}
pos = box_end;
}
Err(Mp4Error::TruncatedFile)
}
fn read_nal_length(data: &[u8], pos: usize, length_size: usize) -> usize {
let mut len = 0usize;
for i in 0..length_size {
len = (len << 8) | data[pos + i] as usize;
}
len
}
fn validate_nal_lengths(mdat_data: &[u8], length_size: u8) -> bool {
let ls = length_size as usize;
let mut pos = 0usize;
let mut valid_count = 0u32;
for _ in 0..8 {
if pos + ls > mdat_data.len() {
break;
}
let len = read_nal_length(mdat_data, pos, ls);
if len == 0 || len > 50_000_000 || pos + ls + len > mdat_data.len() {
break;
}
let nal_start = pos + ls;
if nal_start >= mdat_data.len() {
break;
}
let byte0 = mdat_data[nal_start];
if (byte0 >> 7) & 1 != 0 {
break;
}
let nal_type = (byte0 >> 1) & 0x3F;
if nal_type > 63 {
break;
}
valid_count += 1;
pos += ls + len;
}
valid_count >= 2
}
fn samples_has_vcl_data(mdat_data: &[u8], start: usize, size: usize, length_size: usize) -> bool {
let end = start + size;
let mut pos = start;
while pos + length_size <= end {
let len = read_nal_length(mdat_data, pos, length_size);
if len == 0 || pos + length_size + len > end {
break;
}
let nal_byte0 = mdat_data[pos + length_size];
let nal_type = (nal_byte0 >> 1) & 0x3F;
if nal_type <= 31 {
return true;
}
pos += length_size + len;
}
false
}
fn flush_access_unit(
samples: &mut Vec<Sample>,
mdat_file_start: usize,
au_offset_in_mdat: usize,
au_size: usize,
is_irap: bool,
mdat_data: &[u8],
) {
let file_offset = (mdat_file_start + au_offset_in_mdat) as u64;
let sample_data = mdat_data[au_offset_in_mdat..au_offset_in_mdat + au_size].to_vec();
samples.push(Sample {
offset: file_offset,
size: au_size as u32,
is_sync: is_irap,
data: sample_data,
});
}
fn extract_dimensions_from_sps(sps_nal_data: &[u8]) -> Option<(u32, u32)> {
use crate::codec::h264::bitstream::{remove_emulation_prevention, RbspReader};
if sps_nal_data.len() < 4 {
return None;
}
let rbsp = remove_emulation_prevention(&sps_nal_data[2..]);
let mut r = RbspReader::new(&rbsp);
r.read_bits(4).ok()?;
let max_sub_layers_minus1 = r.read_bits(3).ok()? as u8;
r.read_bits(1).ok()?;
skip_profile_tier_level(&mut r, max_sub_layers_minus1).ok()?;
r.read_ue().ok()?;
let chroma = r.read_ue().ok()?;
if chroma == 3 {
r.read_bits(1).ok()?;
}
let width = r.read_ue().ok()?;
let height = r.read_ue().ok()?;
Some((width, height))
}
fn skip_profile_tier_level(
r: &mut crate::codec::h264::bitstream::RbspReader<'_>,
max_sub_layers_minus1: u8,
) -> Result<(), Mp4Error> {
r.read_bits(8).map_err(|_| Mp4Error::UnexpectedEof)?;
r.read_bits(32).map_err(|_| Mp4Error::UnexpectedEof)?;
r.read_bits(32).map_err(|_| Mp4Error::UnexpectedEof)?;
r.read_bits(16).map_err(|_| Mp4Error::UnexpectedEof)?;
r.read_bits(8).map_err(|_| Mp4Error::UnexpectedEof)?;
let mut sub_layer_profile_present = [false; 8];
let mut sub_layer_level_present = [false; 8];
for i in 0..max_sub_layers_minus1 as usize {
sub_layer_profile_present[i] = r.read_bit().map_err(|_| Mp4Error::UnexpectedEof)?;
sub_layer_level_present[i] = r.read_bit().map_err(|_| Mp4Error::UnexpectedEof)?;
}
if max_sub_layers_minus1 > 0 {
for _ in max_sub_layers_minus1..8 {
r.read_bits(2).map_err(|_| Mp4Error::UnexpectedEof)?;
}
}
for i in 0..max_sub_layers_minus1 as usize {
if sub_layer_profile_present[i] {
r.skip_bits(88).map_err(|_| Mp4Error::UnexpectedEof)?;
}
if sub_layer_level_present[i] {
r.read_bits(8).map_err(|_| Mp4Error::UnexpectedEof)?;
}
}
Ok(())
}
use std::io::{Read, Seek, SeekFrom};
pub fn demux_streaming<R: Read + Seek>(reader: &mut R) -> Result<Mp4File, Mp4Error> {
let start_pos = reader.seek(SeekFrom::Start(0)).map_err(|_| Mp4Error::UnexpectedEof)?;
let file_size = reader.seek(SeekFrom::End(0)).map_err(|_| Mp4Error::UnexpectedEof)?;
reader.seek(SeekFrom::Start(start_pos)).map_err(|_| Mp4Error::UnexpectedEof)?;
let mut ftyp: Option<Vec<u8>> = None;
let mut moov_data: Option<Vec<u8>> = None;
let mut pos = 0u64;
while pos < file_size {
reader.seek(SeekFrom::Start(pos)).map_err(|_| Mp4Error::UnexpectedEof)?;
let mut header_buf = [0u8; 16];
let n = reader.read(&mut header_buf[..8]).map_err(|_| Mp4Error::UnexpectedEof)?;
if n < 8 {
break;
}
let size32 = u32::from_be_bytes([header_buf[0], header_buf[1], header_buf[2], header_buf[3]]);
let box_type = [header_buf[4], header_buf[5], header_buf[6], header_buf[7]];
let (box_size, header_len): (u64, u64) = if size32 == 1 {
let n2 = reader.read(&mut header_buf[8..16]).map_err(|_| Mp4Error::UnexpectedEof)?;
if n2 < 8 {
return Err(Mp4Error::UnexpectedEof);
}
let s64 = u64::from_be_bytes([
header_buf[8], header_buf[9], header_buf[10], header_buf[11],
header_buf[12], header_buf[13], header_buf[14], header_buf[15],
]);
(s64, 16)
} else if size32 == 0 {
(file_size - pos, 8)
} else {
(size32 as u64, 8)
};
if box_size < 8 {
break;
}
let _content_size = (box_size - header_len) as usize;
match &box_type {
b"ftyp" => {
let mut buf = vec![0u8; box_size as usize];
reader.seek(SeekFrom::Start(pos)).map_err(|_| Mp4Error::UnexpectedEof)?;
reader.read_exact(&mut buf).map_err(|_| Mp4Error::UnexpectedEof)?;
ftyp = Some(buf);
}
b"moov" => {
let mut buf = vec![0u8; box_size as usize];
reader.seek(SeekFrom::Start(pos)).map_err(|_| Mp4Error::UnexpectedEof)?;
reader.read_exact(&mut buf).map_err(|_| Mp4Error::UnexpectedEof)?;
moov_data = Some(buf);
}
_ => {} }
pos += box_size;
}
let ftyp = ftyp.ok_or_else(|| Mp4Error::InvalidBox("missing ftyp box".into()))?;
let moov_buf = moov_data.ok_or_else(|| Mp4Error::InvalidBox("missing moov box".into()))?;
let moov_header = parse_box_header(&moov_buf, 0)?;
let moov_content_start = moov_header.header_len as usize;
let moov_end = moov_header.size as usize;
let tracks = parse_moov_metadata(&moov_buf, moov_content_start, moov_end)?;
let video_track_idx = tracks.iter().position(|t| {
t.handler_type == *b"vide" && (t.is_hevc() || t.is_h264())
});
Ok(Mp4File {
ftyp,
tracks,
video_track_idx,
})
}
pub fn read_sample_data<R: Read + Seek>(
reader: &mut R,
sample: &Sample,
) -> Result<Vec<u8>, Mp4Error> {
reader.seek(SeekFrom::Start(sample.offset)).map_err(|_| Mp4Error::UnexpectedEof)?;
let mut buf = vec![0u8; sample.size as usize];
reader.read_exact(&mut buf).map_err(|_| Mp4Error::UnexpectedEof)?;
Ok(buf)
}
fn parse_moov_metadata(data: &[u8], start: usize, end: usize) -> Result<Vec<Track>, Mp4Error> {
let mut tracks = Vec::new();
iterate_boxes(data, start, end, |header, content_start, box_data| {
if header.box_type == *b"trak" {
let trak_end = content_start - header.header_len as usize + header.size as usize;
let track = parse_trak_metadata(data, content_start, trak_end, box_data)?;
tracks.push(track);
}
Ok(())
})?;
Ok(tracks)
}
fn parse_trak_metadata(
data: &[u8],
start: usize,
end: usize,
trak_raw_data: &[u8],
) -> Result<Track, Mp4Error> {
let mut track = Track {
track_id: 0,
handler_type: [0; 4],
codec: [0; 4],
width: 0,
height: 0,
timescale: 0,
duration: 0,
samples: Vec::new(),
hvcc_data: None,
avcc_data: None,
stsd_raw: Vec::new(),
trak_raw: trak_raw_data.to_vec(),
};
let mut sample_sizes: Vec<u32> = Vec::new();
let mut sample_offsets: Vec<u64> = Vec::new();
let mut sync_samples: Option<Vec<u32>> = None;
let mut stsc_entries: Vec<(u32, u32, u32)> = Vec::new();
parse_trak_children(
data, start, end, &mut track,
&mut sample_sizes, &mut sample_offsets, &mut sync_samples, &mut stsc_entries,
)?;
build_samples_metadata(
&mut track, &sample_sizes, &sample_offsets, &sync_samples, &stsc_entries,
)?;
Ok(track)
}
fn build_samples_metadata(
track: &mut Track,
sample_sizes: &[u32],
chunk_offsets: &[u64],
sync_samples: &Option<Vec<u32>>,
stsc_entries: &[(u32, u32, u32)],
) -> Result<(), Mp4Error> {
if sample_sizes.is_empty() || chunk_offsets.is_empty() {
return Ok(());
}
let sync_set: Option<std::collections::HashSet<u32>> =
sync_samples.as_ref().map(|s| s.iter().copied().collect());
let num_chunks = chunk_offsets.len();
let mut samples_per_chunk = vec![0u32; num_chunks];
if stsc_entries.is_empty() {
samples_per_chunk.fill(1);
} else {
for (i, entry) in stsc_entries.iter().enumerate() {
let first_chunk = entry.0 as usize;
let spc = entry.1;
let next_first = if i + 1 < stsc_entries.len() {
stsc_entries[i + 1].0 as usize
} else {
num_chunks + 1
};
for chunk_idx in first_chunk..next_first {
if chunk_idx <= num_chunks && chunk_idx >= 1 {
samples_per_chunk[chunk_idx - 1] = spc;
}
}
}
}
let mut samples = Vec::with_capacity(sample_sizes.len());
let mut sample_idx = 0usize;
for (chunk_idx, &chunk_offset) in chunk_offsets.iter().enumerate() {
let spc = samples_per_chunk[chunk_idx] as usize;
let mut offset_in_chunk = 0u64;
for _ in 0..spc {
if sample_idx >= sample_sizes.len() {
break;
}
let size = sample_sizes[sample_idx];
let abs_offset = chunk_offset + offset_in_chunk;
let sample_num = (sample_idx + 1) as u32;
let is_sync = match &sync_set {
Some(set) => set.contains(&sample_num),
None => true,
};
samples.push(Sample {
offset: abs_offset,
size,
is_sync,
data: Vec::new(), });
offset_in_chunk += size as u64;
sample_idx += 1;
}
}
track.samples = samples;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn make_box(box_type: &[u8; 4], content: &[u8]) -> Vec<u8> {
let size = (8 + content.len()) as u32;
let mut buf = Vec::new();
buf.extend_from_slice(&size.to_be_bytes());
buf.extend_from_slice(box_type);
buf.extend_from_slice(content);
buf
}
fn make_fullbox(box_type: &[u8; 4], version: u8, flags: u32, content: &[u8]) -> Vec<u8> {
let mut inner = Vec::new();
inner.push(version);
inner.extend_from_slice(&(flags & 0x00FF_FFFF).to_be_bytes()[1..]);
inner.extend_from_slice(content);
make_box(box_type, &inner)
}
#[test]
fn test_parse_stsz_variable() {
let mut content = Vec::new();
content.extend_from_slice(&[0, 0, 0, 0]); content.extend_from_slice(&0u32.to_be_bytes()); content.extend_from_slice(&3u32.to_be_bytes()); content.extend_from_slice(&100u32.to_be_bytes());
content.extend_from_slice(&200u32.to_be_bytes());
content.extend_from_slice(&150u32.to_be_bytes());
let sizes = parse_stsz(&content, 0).unwrap();
assert_eq!(sizes, vec![100, 200, 150]);
}
#[test]
fn test_parse_stsz_fixed() {
let mut content = Vec::new();
content.extend_from_slice(&[0, 0, 0, 0]); content.extend_from_slice(&512u32.to_be_bytes()); content.extend_from_slice(&4u32.to_be_bytes());
let sizes = parse_stsz(&content, 0).unwrap();
assert_eq!(sizes, vec![512, 512, 512, 512]);
}
#[test]
fn test_parse_stco() {
let mut content = Vec::new();
content.extend_from_slice(&[0, 0, 0, 0]); content.extend_from_slice(&2u32.to_be_bytes()); content.extend_from_slice(&1000u32.to_be_bytes());
content.extend_from_slice(&5000u32.to_be_bytes());
let offsets = parse_stco(&content, 0).unwrap();
assert_eq!(offsets, vec![1000, 5000]);
}
#[test]
fn test_parse_co64() {
let mut content = Vec::new();
content.extend_from_slice(&[0, 0, 0, 0]); content.extend_from_slice(&1u32.to_be_bytes()); content.extend_from_slice(&0x0000_0001_0000_0000u64.to_be_bytes());
let offsets = parse_co64(&content, 0).unwrap();
assert_eq!(offsets, vec![0x0000_0001_0000_0000]);
}
#[test]
fn test_parse_stss() {
let mut content = Vec::new();
content.extend_from_slice(&[0, 0, 0, 0]); content.extend_from_slice(&3u32.to_be_bytes()); content.extend_from_slice(&1u32.to_be_bytes()); content.extend_from_slice(&31u32.to_be_bytes()); content.extend_from_slice(&61u32.to_be_bytes());
let syncs = parse_stss(&content, 0).unwrap();
assert_eq!(syncs, vec![1, 31, 61]);
}
#[test]
fn test_parse_stsc() {
let mut content = Vec::new();
content.extend_from_slice(&[0, 0, 0, 0]); content.extend_from_slice(&2u32.to_be_bytes()); content.extend_from_slice(&1u32.to_be_bytes());
content.extend_from_slice(&10u32.to_be_bytes());
content.extend_from_slice(&1u32.to_be_bytes());
content.extend_from_slice(&5u32.to_be_bytes());
content.extend_from_slice(&5u32.to_be_bytes());
content.extend_from_slice(&1u32.to_be_bytes());
let entries = parse_stsc(&content, 0).unwrap();
assert_eq!(entries, vec![(1, 10, 1), (5, 5, 1)]);
}
#[test]
fn test_parse_hvcc() {
let mut content = Vec::new();
content.push(1); content.push(0); content.extend_from_slice(&[0; 4]); content.extend_from_slice(&[0; 6]); content.push(0); content.extend_from_slice(&[0xF0, 0x00]); content.push(0xFC); content.push(0xFC); content.push(0xF8); content.push(0xF8); content.extend_from_slice(&[0x00, 0x00]); content.push(0x0F);
content.push(3);
content.push(0x20 | 32); content.extend_from_slice(&1u16.to_be_bytes()); let vps_data = [0x40, 0x01, 0x0C, 0x01]; content.extend_from_slice(&(vps_data.len() as u16).to_be_bytes());
content.extend_from_slice(&vps_data);
content.push(0x20 | 33); content.extend_from_slice(&1u16.to_be_bytes());
let sps_data = [0x42, 0x01, 0x01, 0x02, 0x20];
content.extend_from_slice(&(sps_data.len() as u16).to_be_bytes());
content.extend_from_slice(&sps_data);
content.push(0x20 | 34); content.extend_from_slice(&1u16.to_be_bytes());
let pps_data = [0x44, 0x01, 0xC1];
content.extend_from_slice(&(pps_data.len() as u16).to_be_bytes());
content.extend_from_slice(&pps_data);
let hvcc = parse_hvcc(&content, 0).unwrap();
assert_eq!(hvcc.configuration_version, 1);
assert_eq!(hvcc.length_size_minus1, 3);
assert_eq!(hvcc.vps_nalus.len(), 1);
assert_eq!(hvcc.sps_nalus.len(), 1);
assert_eq!(hvcc.pps_nalus.len(), 1);
assert_eq!(hvcc.vps_nalus[0], vps_data);
assert_eq!(hvcc.sps_nalus[0], sps_data);
assert_eq!(hvcc.pps_nalus[0], pps_data);
}
#[test]
fn test_build_samples_simple() {
let sample_data = [0xAA; 10]; let mut file_data = vec![0u8; 200];
file_data[100..110].copy_from_slice(&sample_data);
file_data[110..115].fill(0xBB); file_data[115..120].fill(0xCC);
let mut track = Track {
track_id: 1,
handler_type: *b"vide",
codec: *b"hvc1",
width: 1920,
height: 1080,
timescale: 30000,
duration: 90000,
samples: Vec::new(),
hvcc_data: None,
avcc_data: None,
stsd_raw: Vec::new(),
trak_raw: Vec::new(),
};
let sizes = vec![10, 5, 5];
let offsets = vec![100u64];
let stsc = vec![(1u32, 3u32, 1u32)];
build_samples(&file_data, &mut track, &sizes, &offsets, &None, &stsc).unwrap();
assert_eq!(track.samples.len(), 3);
assert_eq!(track.samples[0].offset, 100);
assert_eq!(track.samples[0].size, 10);
assert!(track.samples[0].is_sync); assert_eq!(track.samples[0].data, vec![0xAA; 10]);
assert_eq!(track.samples[1].offset, 110);
assert_eq!(track.samples[1].size, 5);
assert_eq!(track.samples[2].offset, 115);
}
#[test]
fn test_build_samples_with_stss() {
let mut file_data = vec![0u8; 200];
file_data[50..54].fill(0x11);
file_data[54..58].fill(0x22);
file_data[58..62].fill(0x33);
let mut track = Track {
track_id: 1,
handler_type: *b"vide",
codec: *b"hvc1",
width: 0,
height: 0,
timescale: 0,
duration: 0,
samples: Vec::new(),
hvcc_data: None,
avcc_data: None,
stsd_raw: Vec::new(),
trak_raw: Vec::new(),
};
let sizes = vec![4, 4, 4];
let offsets = vec![50u64];
let sync = Some(vec![1u32]); let stsc = vec![(1, 3, 1)];
build_samples(&file_data, &mut track, &sizes, &offsets, &sync, &stsc).unwrap();
assert!(track.samples[0].is_sync);
assert!(!track.samples[1].is_sync);
assert!(!track.samples[2].is_sync);
}
fn build_test_mp4() -> Vec<u8> {
let mut mp4 = Vec::new();
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00isom");
mp4.extend_from_slice(&ftyp);
let sample1 = [0x00, 0x00, 0x00, 0x04, 0x28, 0x01, 0xAF, 0x09]; let sample2 = [0x00, 0x00, 0x00, 0x04, 0x02, 0x01, 0xD0, 0x10];
let sample3 = [0x00, 0x00, 0x00, 0x04, 0x02, 0x01, 0xD0, 0x11];
let mut tkhd_content = Vec::new();
tkhd_content.extend_from_slice(&[0, 0, 0, 0]); tkhd_content.extend_from_slice(&[0, 0, 0, 0]); tkhd_content.extend_from_slice(&1u32.to_be_bytes()); tkhd_content.extend_from_slice(&[0; 4]); tkhd_content.extend_from_slice(&90u32.to_be_bytes()); tkhd_content.extend_from_slice(&[0; 8]); tkhd_content.extend_from_slice(&[0; 2]); tkhd_content.extend_from_slice(&[0; 2]); tkhd_content.extend_from_slice(&[0; 2]); tkhd_content.extend_from_slice(&[0; 2]); tkhd_content.extend_from_slice(&[ 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00, 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00,
]);
tkhd_content.extend_from_slice(&((1920u32) << 16).to_be_bytes()); tkhd_content.extend_from_slice(&((1080u32) << 16).to_be_bytes()); let tkhd = make_fullbox(b"tkhd", 0, 3, &tkhd_content);
let mut mdhd_content = Vec::new();
mdhd_content.extend_from_slice(&[0; 4]); mdhd_content.extend_from_slice(&[0; 4]); mdhd_content.extend_from_slice(&30000u32.to_be_bytes()); mdhd_content.extend_from_slice(&90000u32.to_be_bytes()); mdhd_content.extend_from_slice(&[0x55, 0xC4]); mdhd_content.extend_from_slice(&[0; 2]); let mdhd = make_fullbox(b"mdhd", 0, 0, &mdhd_content);
let mut hdlr_content = Vec::new();
hdlr_content.extend_from_slice(&[0; 4]); hdlr_content.extend_from_slice(b"vide"); hdlr_content.extend_from_slice(&[0; 12]); hdlr_content.push(0); let hdlr = make_fullbox(b"hdlr", 0, 0, &hdlr_content);
let mut hvcc_content = Vec::new();
hvcc_content.push(1); hvcc_content.push(0); hvcc_content.extend_from_slice(&[0; 4]); hvcc_content.extend_from_slice(&[0; 6]); hvcc_content.push(0); hvcc_content.extend_from_slice(&[0xF0, 0x00]); hvcc_content.push(0xFC); hvcc_content.push(0xFC); hvcc_content.push(0xF8); hvcc_content.push(0xF8); hvcc_content.extend_from_slice(&[0, 0]); hvcc_content.push(0x0F); hvcc_content.push(1); hvcc_content.push(0x20); hvcc_content.extend_from_slice(&1u16.to_be_bytes()); let vps = [0x40, 0x01, 0x0C];
hvcc_content.extend_from_slice(&(vps.len() as u16).to_be_bytes());
hvcc_content.extend_from_slice(&vps);
let hvcc_box = make_box(b"hvcC", &hvcc_content);
let mut vse_content = Vec::new();
vse_content.extend_from_slice(&[0; 6]); vse_content.extend_from_slice(&1u16.to_be_bytes()); vse_content.extend_from_slice(&[0; 2]); vse_content.extend_from_slice(&[0; 2]); vse_content.extend_from_slice(&[0; 12]); vse_content.extend_from_slice(&1920u16.to_be_bytes()); vse_content.extend_from_slice(&1080u16.to_be_bytes()); vse_content.extend_from_slice(&0x00480000u32.to_be_bytes()); vse_content.extend_from_slice(&0x00480000u32.to_be_bytes()); vse_content.extend_from_slice(&[0; 4]); vse_content.extend_from_slice(&1u16.to_be_bytes()); vse_content.extend_from_slice(&[0; 32]); vse_content.extend_from_slice(&0x0018u16.to_be_bytes()); vse_content.extend_from_slice(&[0xFF, 0xFF]); vse_content.extend_from_slice(&hvcc_box);
let vse = make_box(b"hvc1", &vse_content);
let mut stsd_content = Vec::new();
stsd_content.extend_from_slice(&1u32.to_be_bytes()); stsd_content.extend_from_slice(&vse);
let stsd = make_fullbox(b"stsd", 0, 0, &stsd_content);
let mut stts_content = Vec::new();
stts_content.extend_from_slice(&1u32.to_be_bytes()); stts_content.extend_from_slice(&3u32.to_be_bytes()); stts_content.extend_from_slice(&1000u32.to_be_bytes()); let stts = make_fullbox(b"stts", 0, 0, &stts_content);
let mut stsc_content = Vec::new();
stsc_content.extend_from_slice(&1u32.to_be_bytes()); stsc_content.extend_from_slice(&1u32.to_be_bytes()); stsc_content.extend_from_slice(&3u32.to_be_bytes()); stsc_content.extend_from_slice(&1u32.to_be_bytes()); let stsc = make_fullbox(b"stsc", 0, 0, &stsc_content);
let mut stsz_content = Vec::new();
stsz_content.extend_from_slice(&0u32.to_be_bytes()); stsz_content.extend_from_slice(&3u32.to_be_bytes()); stsz_content.extend_from_slice(&8u32.to_be_bytes());
stsz_content.extend_from_slice(&8u32.to_be_bytes());
stsz_content.extend_from_slice(&8u32.to_be_bytes());
let stsz = make_fullbox(b"stsz", 0, 0, &stsz_content);
let mut stco_content = Vec::new();
stco_content.extend_from_slice(&1u32.to_be_bytes()); stco_content.extend_from_slice(&0u32.to_be_bytes()); let stco = make_fullbox(b"stco", 0, 0, &stco_content);
let mut stss_content = Vec::new();
stss_content.extend_from_slice(&1u32.to_be_bytes()); stss_content.extend_from_slice(&1u32.to_be_bytes()); let stss = make_fullbox(b"stss", 0, 0, &stss_content);
let mut stbl_content = Vec::new();
stbl_content.extend_from_slice(&stsd);
stbl_content.extend_from_slice(&stts);
stbl_content.extend_from_slice(&stsc);
stbl_content.extend_from_slice(&stsz);
stbl_content.extend_from_slice(&stco);
stbl_content.extend_from_slice(&stss);
let stbl = make_box(b"stbl", &stbl_content);
let dref = make_fullbox(b"dref", 0, 0, &{
let mut d = Vec::new();
d.extend_from_slice(&1u32.to_be_bytes()); let url = make_fullbox(b"url ", 0, 1, &[]); d.extend_from_slice(&url);
d
});
let dinf = make_box(b"dinf", &dref);
let vmhd = make_fullbox(b"vmhd", 0, 1, &[0; 8]);
let mut minf_content = Vec::new();
minf_content.extend_from_slice(&vmhd);
minf_content.extend_from_slice(&dinf);
minf_content.extend_from_slice(&stbl);
let minf = make_box(b"minf", &minf_content);
let mut mdia_content = Vec::new();
mdia_content.extend_from_slice(&mdhd);
mdia_content.extend_from_slice(&hdlr);
mdia_content.extend_from_slice(&minf);
let mdia = make_box(b"mdia", &mdia_content);
let mut trak_content = Vec::new();
trak_content.extend_from_slice(&tkhd);
trak_content.extend_from_slice(&mdia);
let trak = make_box(b"trak", &trak_content);
let mut mvhd_content = Vec::new();
mvhd_content.extend_from_slice(&[0; 4]); mvhd_content.extend_from_slice(&[0; 4]); mvhd_content.extend_from_slice(&1000u32.to_be_bytes()); mvhd_content.extend_from_slice(&3000u32.to_be_bytes()); mvhd_content.extend_from_slice(&0x00010000u32.to_be_bytes()); mvhd_content.extend_from_slice(&0x0100u16.to_be_bytes()); mvhd_content.extend_from_slice(&[0; 10]); mvhd_content.extend_from_slice(&[ 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00, 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x40,0x00,0x00,0x00,
]);
mvhd_content.extend_from_slice(&[0; 24]); mvhd_content.extend_from_slice(&2u32.to_be_bytes()); let mvhd = make_fullbox(b"mvhd", 0, 0, &mvhd_content);
let mut moov_content = Vec::new();
moov_content.extend_from_slice(&mvhd);
moov_content.extend_from_slice(&trak);
let moov = make_box(b"moov", &moov_content);
mp4.extend_from_slice(&moov);
let mut mdat_content = Vec::new();
mdat_content.extend_from_slice(&sample1);
mdat_content.extend_from_slice(&sample2);
mdat_content.extend_from_slice(&sample3);
let mdat = make_box(b"mdat", &mdat_content);
let mdat_data_offset = (ftyp.len() + moov.len() + 8) as u32;
mp4.extend_from_slice(&mdat);
let stco_needle = b"stco";
for i in 0..mp4.len() - 4 {
if &mp4[i..i + 4] == stco_needle {
let offset_pos = i + 4 + 4 + 4; mp4[offset_pos..offset_pos + 4]
.copy_from_slice(&mdat_data_offset.to_be_bytes());
break;
}
}
mp4
}
#[test]
fn test_demux_minimal_mp4() {
let mp4_data = build_test_mp4();
let mp4 = demux(&mp4_data).unwrap();
assert!(!mp4.ftyp.is_empty());
assert!(mp4.video_track_idx.is_some());
let idx = mp4.video_track_idx.unwrap();
let track = &mp4.tracks[idx];
assert_eq!(track.track_id, 1);
assert_eq!(track.handler_type, *b"vide");
assert_eq!(track.codec, *b"hvc1");
assert_eq!(track.width, 1920);
assert_eq!(track.height, 1080);
assert_eq!(track.timescale, 30000);
assert_eq!(track.duration, 90000);
assert_eq!(track.samples.len(), 3);
assert_eq!(track.samples[0].size, 8);
assert!(track.samples[0].is_sync);
assert!(!track.samples[1].is_sync);
assert!(!track.samples[2].is_sync);
assert_eq!(
track.samples[0].data,
[0x00, 0x00, 0x00, 0x04, 0x28, 0x01, 0xAF, 0x09]
);
let hvcc = track.hvcc_data.as_ref().unwrap();
assert_eq!(hvcc.configuration_version, 1);
assert_eq!(hvcc.length_size_minus1, 3);
assert_eq!(hvcc.vps_nalus.len(), 1);
assert_eq!(hvcc.vps_nalus[0], [0x40, 0x01, 0x0C]);
}
#[test]
fn test_demux_no_video_track() {
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00");
let mvhd_content = vec![0u8; 100]; let mvhd = make_fullbox(b"mvhd", 0, 0, &mvhd_content);
let moov = make_box(b"moov", &mvhd);
let mdat = make_box(b"mdat", &[]);
let mut mp4 = Vec::new();
mp4.extend_from_slice(&ftyp);
mp4.extend_from_slice(&moov);
mp4.extend_from_slice(&mdat);
let result = demux(&mp4).unwrap();
assert!(result.video_track_idx.is_none());
assert!(result.tracks.is_empty());
}
fn make_nal(nal_type: u8, payload: &[u8]) -> Vec<u8> {
let byte0 = (nal_type << 1) & 0x7E; let byte1 = 0x01u8; let nal_body_len = 2 + payload.len();
let mut buf = Vec::new();
buf.extend_from_slice(&(nal_body_len as u32).to_be_bytes()); buf.push(byte0);
buf.push(byte1);
buf.extend_from_slice(payload);
buf
}
#[test]
#[ignore] fn test_debug_dji_structure() {
let path = "/Users/cgaffga/Desktop/HEVC-H.265-samples/hevc_4k60P_main_dji_mavic3.mov";
let data = if let Ok(d) = std::fs::read(path) { d } else { eprintln!("[SKIP] file not found"); return; };
eprintln!("File size: {} bytes ({:.1} MB)", data.len(), data.len() as f64 / 1048576.0);
let mut pos = 0usize;
while pos + 8 <= data.len() {
let header = parse_box_header(&data, pos).unwrap();
let box_type = std::str::from_utf8(&header.box_type).unwrap_or("????");
let box_end = pos as u64 + header.size;
eprintln!(" pos={pos}: box={box_type:?} size={} header_len={} end={}", header.size, header.header_len, box_end);
if header.size < 8 { break; }
let next = pos + header.size as usize;
if next > data.len() {
eprintln!(" (overshoots file by {} bytes)", next - data.len());
if header.box_type == *b"mdat" {
let content_start = pos + header.header_len as usize;
let content_end = data.len().min(pos + header.size as usize);
let mdat_data = &data[content_start..content_end];
eprintln!(" mdat content: {} bytes", mdat_data.len());
let show = mdat_data.len().min(32);
eprint!(" first {} bytes: ", show);
for b in &mdat_data[..show] {
eprint!("{:02x} ", b);
}
eprintln!();
eprintln!(" Trying Annex B scan...");
let mut sc_count = 0;
for i in 0..mdat_data.len().min(100_000_000).saturating_sub(3) {
if mdat_data[i] == 0 && mdat_data[i+1] == 0 && mdat_data[i+2] == 1 {
let nal_pos = i + 3;
if nal_pos < mdat_data.len() {
let nal_type = (mdat_data[nal_pos] >> 1) & 0x3F;
if sc_count < 20 {
let prefix = if i > 0 && mdat_data[i-1] == 0 { "00 00 00 01" } else { "00 00 01" };
eprintln!(" SC[{sc_count}] pos={i}: {prefix} -> NAL type={nal_type}");
}
}
sc_count += 1;
}
}
eprintln!(" Total start codes found (first 100MB): {sc_count}");
}
break;
}
pos = next;
}
}
#[test]
fn test_recover_truncated_basic() {
let mut data = Vec::new();
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00");
data.extend_from_slice(&ftyp);
let mut mdat_content = Vec::new();
mdat_content.extend_from_slice(&make_nal(32, &[0x0C, 0x01, 0x00, 0x00]));
mdat_content.extend_from_slice(&make_nal(33, &[0x01, 0x02, 0x20, 0x00, 0x00]));
mdat_content.extend_from_slice(&make_nal(34, &[0xC1, 0x00]));
mdat_content.extend_from_slice(&make_nal(19, &[0x80, 0x00, 0xAA, 0xBB]));
mdat_content.extend_from_slice(&make_nal(1, &[0x80, 0x00, 0xCC, 0xDD]));
let mdat = make_box(b"mdat", &mdat_content);
data.extend_from_slice(&mdat);
let mp4 = demux(&data).unwrap();
assert!(mp4.video_track_idx.is_some());
let idx = mp4.video_track_idx.unwrap();
let track = &mp4.tracks[idx];
assert_eq!(track.handler_type, *b"vide");
assert_eq!(track.codec, *b"hvc1");
assert_eq!(track.samples.len(), 2);
assert!(track.samples[0].is_sync); assert!(!track.samples[1].is_sync);
let hvcc = track.hvcc_data.as_ref().unwrap();
assert_eq!(hvcc.length_size_minus1, 3);
assert_eq!(hvcc.vps_nalus.len(), 1);
assert_eq!(hvcc.sps_nalus.len(), 1);
assert_eq!(hvcc.pps_nalus.len(), 1);
}
#[test]
fn test_recover_truncated_no_parameter_sets() {
let mut data = Vec::new();
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00");
data.extend_from_slice(&ftyp);
let mut mdat_content = Vec::new();
mdat_content.extend_from_slice(&make_nal(19, &[0x80, 0x00, 0xAA])); mdat_content.extend_from_slice(&make_nal(1, &[0x80, 0x00, 0xBB]));
let mdat = make_box(b"mdat", &mdat_content);
data.extend_from_slice(&mdat);
let result = demux(&data);
assert!(result.is_err());
}
#[test]
fn test_recover_truncated_oversized_mdat() {
let mut data = Vec::new();
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00");
data.extend_from_slice(&ftyp);
let mut nal_data = Vec::new();
nal_data.extend_from_slice(&make_nal(32, &[0x0C, 0x01, 0x00, 0x00])); nal_data.extend_from_slice(&make_nal(33, &[0x01, 0x02, 0x20, 0x00, 0x00])); nal_data.extend_from_slice(&make_nal(34, &[0xC1, 0x00])); nal_data.extend_from_slice(&make_nal(19, &[0x80, 0x00, 0xAA, 0xBB]));
let fake_mdat_size: u32 = 1_000_000;
data.extend_from_slice(&fake_mdat_size.to_be_bytes());
data.extend_from_slice(b"mdat");
data.extend_from_slice(&nal_data);
let mp4 = demux(&data).unwrap();
assert!(mp4.video_track_idx.is_some());
let track = &mp4.tracks[mp4.video_track_idx.unwrap()];
assert_eq!(track.samples.len(), 1);
assert!(track.samples[0].is_sync);
}
fn make_annexb_nal(nal_type: u8, payload: &[u8]) -> Vec<u8> {
let byte0 = (nal_type << 1) & 0x7E;
let byte1 = 0x01u8;
let mut buf = Vec::new();
buf.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); buf.push(byte0);
buf.push(byte1);
buf.extend_from_slice(payload);
buf
}
#[test]
fn test_recover_truncated_annexb() {
let mut data = Vec::new();
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00");
data.extend_from_slice(&ftyp);
let mut mdat_content = Vec::new();
mdat_content.extend_from_slice(&make_annexb_nal(32, &[0x0C, 0x01, 0x00, 0x00])); mdat_content.extend_from_slice(&make_annexb_nal(33, &[0x01, 0x02, 0x20, 0x00, 0x00])); mdat_content.extend_from_slice(&make_annexb_nal(34, &[0xC1, 0x00])); mdat_content.extend_from_slice(&make_annexb_nal(19, &[0x80, 0x00, 0xAA, 0xBB]));
mdat_content.extend_from_slice(&make_annexb_nal(1, &[0x80, 0x00, 0xCC, 0xDD]));
let mdat = make_box(b"mdat", &mdat_content);
data.extend_from_slice(&mdat);
let mp4 = demux(&data).unwrap();
assert!(mp4.video_track_idx.is_some());
let track = &mp4.tracks[mp4.video_track_idx.unwrap()];
assert_eq!(track.handler_type, *b"vide");
assert_eq!(track.codec, *b"hvc1");
assert_eq!(track.samples.len(), 2);
assert!(track.samples[0].is_sync); assert!(!track.samples[1].is_sync);
let hvcc = track.hvcc_data.as_ref().unwrap();
assert_eq!(hvcc.vps_nalus.len(), 1);
assert_eq!(hvcc.sps_nalus.len(), 1);
assert_eq!(hvcc.pps_nalus.len(), 1);
}
#[test]
fn test_recover_truncated_annexb_3byte_startcode() {
let mut data = Vec::new();
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00");
data.extend_from_slice(&ftyp);
let mut mdat_content = Vec::new();
let make_3byte_nal = |nal_type: u8, payload: &[u8]| -> Vec<u8> {
let byte0 = (nal_type << 1) & 0x7E;
let mut buf = vec![0x00, 0x00, 0x01];
buf.push(byte0);
buf.push(0x01); buf.extend_from_slice(payload);
buf
};
mdat_content.extend_from_slice(&make_3byte_nal(32, &[0x0C, 0x01, 0x00, 0x00]));
mdat_content.extend_from_slice(&make_3byte_nal(33, &[0x01, 0x02, 0x20, 0x00, 0x00]));
mdat_content.extend_from_slice(&make_3byte_nal(34, &[0xC1, 0x00]));
mdat_content.extend_from_slice(&make_3byte_nal(19, &[0x80, 0x00, 0xAA, 0xBB]));
let mdat = make_box(b"mdat", &mdat_content);
data.extend_from_slice(&mdat);
let mp4 = demux(&data).unwrap();
assert!(mp4.video_track_idx.is_some());
let track = &mp4.tracks[mp4.video_track_idx.unwrap()];
assert_eq!(track.samples.len(), 1);
assert!(track.samples[0].is_sync);
}
#[test]
fn test_recover_truncated_multiple_irap() {
let mut data = Vec::new();
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00");
data.extend_from_slice(&ftyp);
let mut mdat_content = Vec::new();
mdat_content.extend_from_slice(&make_nal(32, &[0x0C, 0x01, 0x00, 0x00]));
mdat_content.extend_from_slice(&make_nal(33, &[0x01, 0x02, 0x20, 0x00, 0x00]));
mdat_content.extend_from_slice(&make_nal(34, &[0xC1, 0x00]));
mdat_content.extend_from_slice(&make_nal(19, &[0x80, 0x00, 0xAA, 0xBB]));
mdat_content.extend_from_slice(&make_nal(1, &[0x80, 0x00, 0xCC, 0xDD]));
mdat_content.extend_from_slice(&make_nal(1, &[0x80, 0x00, 0xEE, 0xFF]));
mdat_content.extend_from_slice(&make_nal(32, &[0x0C, 0x01, 0x00, 0x00]));
mdat_content.extend_from_slice(&make_nal(33, &[0x01, 0x02, 0x20, 0x00, 0x00]));
mdat_content.extend_from_slice(&make_nal(34, &[0xC1, 0x00]));
mdat_content.extend_from_slice(&make_nal(19, &[0x80, 0x00, 0x11, 0x22]));
let mdat = make_box(b"mdat", &mdat_content);
data.extend_from_slice(&mdat);
let mp4 = demux(&data).unwrap();
let track = &mp4.tracks[mp4.video_track_idx.unwrap()];
assert_eq!(track.samples.len(), 4);
assert!(track.samples[0].is_sync); assert!(!track.samples[1].is_sync); assert!(!track.samples[2].is_sync); assert!(track.samples[3].is_sync); }
#[test]
fn test_recover_no_mdat() {
let ftyp = make_box(b"ftyp", b"isom\x00\x00\x02\x00");
let result = demux(&ftyp);
assert!(result.is_err());
}
#[test]
fn test_validate_nal_lengths() {
let mut valid = Vec::new();
valid.extend_from_slice(&make_nal(32, &[0x0C, 0x01])); valid.extend_from_slice(&make_nal(33, &[0x01, 0x02])); assert!(validate_nal_lengths(&valid, 4));
let garbage = [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00];
assert!(!validate_nal_lengths(&garbage, 4));
let mut annexb = Vec::new();
annexb.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); annexb.extend_from_slice(&[0x40, 0x01]); assert!(!validate_nal_lengths(&annexb, 4));
}
}