Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mp4parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,9 @@ pub struct SampleToChunk {
#[derive(Debug)]
pub struct SampleSizeBox {
pub sample_size: u32,
/// The number of samples in the track. When `sample_size` is zero, this
/// is also the length of `sample_sizes`.
pub sample_count: u32,
pub sample_sizes: TryVec<u32>,
}

Expand Down Expand Up @@ -4984,6 +4987,7 @@ fn read_stsz<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleSizeBox> {

Ok(SampleSizeBox {
sample_size,
sample_count,
sample_sizes,
})
}
Expand Down
20 changes: 18 additions & 2 deletions mp4parse/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,24 +150,40 @@ pub fn create_sample_table(

let mut sample_size_iter = stsz.sample_sizes.iter();

// 'stsz' is authoritative for the number of samples in the track. Some
// files declare more chunks in 'stco' than the samples described by
// 'stsz' can fill, leaving trailing chunks that no sample maps into.
// Those chunks are unreachable, so stop once every sample is accounted
// for rather than rejecting the track.
let stsz_sample_count = stsz.sample_count.to_usize();

// Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample
// offset address.

// With large numbers of samples, the cost of many allocations dominates,
// so it's worth iterating twice to allocate sample_table just once.
let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets)
.map(|(_, sample_counts)| sample_counts.to_usize())
.try_fold(0usize, usize::checked_add)?;
.try_fold(0usize, usize::checked_add)?
.min(stsz_sample_count);
let mut sample_table = TryVec::with_capacity(total_sample_count).ok()?;

for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) {
'chunks: for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) {
let chunk_id = i.0 as usize;
let sample_counts = i.1;
let mut cur_position = match stco.offsets.get(chunk_id) {
Some(&i) => i.into(),
_ => return None,
};
for _ in 0..sample_counts {
if sample_table.len() >= stsz_sample_count {
debug!(
"track {}: 'stco' declares more chunks than the {} samples \
in 'stsz' can fill, ignoring the surplus",
track.id, stsz_sample_count
);
break 'chunks;
}
let start_offset = cur_position;
let end_offset = match (stsz.sample_size, sample_size_iter.next()) {
(_, Some(t)) => (start_offset + *t)?,
Expand Down
Binary file added mp4parse_capi/tests/stco_extra_chunk.mp4
Binary file not shown.
60 changes: 60 additions & 0 deletions mp4parse_capi/tests/test_sample_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,63 @@ fn parse_sample_table_with_negative_ctts() {
mp4parse_free(parser);
}
}

/// A file whose 'stco' declares more chunks than the samples in 'stsz' can
/// fill leaves trailing chunks that no sample maps into. The surplus chunk
/// offset is bogus (past the end of the file), but since nothing references
/// it the track is still decodable, so parse it rather than rejecting it.
/// See https://bugzilla.mozilla.org/show_bug.cgi?id=2026607
#[test]
fn parse_sample_table_with_surplus_stco_entry() {
let mut file = std::fs::File::open("tests/stco_extra_chunk.mp4").expect("Unknown file");
let io = Mp4parseIo {
read: Some(buf_read),
userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
};

unsafe {
let mut parser = std::ptr::null_mut();
let rv = mp4parse_new(&io, &mut parser);
assert_eq!(rv, Mp4parseStatus::Ok);
assert!(!parser.is_null());

let mut track_info = Mp4parseTrackInfo::default();
let rv = mp4parse_get_track_info(parser, 0, &mut track_info);
assert_eq!(rv, Mp4parseStatus::Ok);
assert_eq!(track_info.track_type, Mp4parseTrackType::Video);

let mut indice = Mp4parseByteData::default();
let rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
assert_eq!(rv, Mp4parseStatus::Ok);

// 'stsc' maps 3 samples per chunk and 'stco' declares 2 chunks, but
// 'stsz' only describes 3 samples, so only the first chunk is used.
// The table matches the one from the well formed video_colr_nclx_hdr10.mp4
// this file was derived from.
assert_eq!(indice.length, 3);
assert_eq!(
*indice.indices.offset(0),
Indice {
start_offset: 48.into(),
end_offset: 757.into(),
start_composition: 0.into(),
end_composition: 512.into(),
start_decode: 0.into(),
sync: true,
}
);
assert_eq!(
*indice.indices.offset(2),
Indice {
start_offset: 769.into(),
end_offset: 781.into(),
start_composition: 512.into(),
end_composition: 1024.into(),
start_decode: 1024.into(),
sync: false,
}
);

mp4parse_free(parser);
}
}
Loading