From ca400ee05c052b10e5a740ab6b137ad884662a68 Mon Sep 17 00:00:00 2001 From: Jerzy Wilczek Date: Wed, 21 Feb 2024 11:54:29 +0100 Subject: [PATCH] Support multiple `trun` boxes in a single `traf` box MPEG-4 part 12 section 8.8.8 states that a `traf` box may contain multiple `trun` boxes. This patch allows parsing such files correctly. Previously, the library would only parse the last `trun` box for each `traf` box, which caused the tracks to be unplayable. --- examples/mp4dump.rs | 2 +- src/mp4box/traf.rs | 13 +++++----- src/track.rs | 61 ++++++++++++++++++++++----------------------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/examples/mp4dump.rs b/examples/mp4dump.rs index 6a97d9a..6c5d22f 100644 --- a/examples/mp4dump.rs +++ b/examples/mp4dump.rs @@ -123,7 +123,7 @@ fn get_boxes(file: File) -> Result> { for traf in moof.trafs.iter() { boxes.push(build_box(traf)); boxes.push(build_box(&traf.tfhd)); - if let Some(ref trun) = &traf.trun { + for trun in &traf.truns { boxes.push(build_box(trun)); } } diff --git a/src/mp4box/traf.rs b/src/mp4box/traf.rs index d53d713..0a998f7 100644 --- a/src/mp4box/traf.rs +++ b/src/mp4box/traf.rs @@ -8,7 +8,7 @@ use crate::mp4box::{tfdt::TfdtBox, tfhd::TfhdBox, trun::TrunBox}; pub struct TrafBox { pub tfhd: TfhdBox, pub tfdt: Option, - pub trun: Option, + pub truns: Vec, } impl TrafBox { @@ -22,9 +22,10 @@ impl TrafBox { if let Some(ref tfdt) = self.tfdt { size += tfdt.box_size(); } - if let Some(ref trun) = self.trun { + for trun in &self.truns { size += trun.box_size(); } + size } } @@ -54,7 +55,7 @@ impl ReadBox<&mut R> for TrafBox { let mut tfhd = None; let mut tfdt = None; - let mut trun = None; + let mut truns = Vec::new(); let mut current = reader.stream_position()?; let end = start + size; @@ -76,7 +77,7 @@ impl ReadBox<&mut R> for TrafBox { tfdt = Some(TfdtBox::read_box(reader, s)?); } BoxType::TrunBox => { - trun = Some(TrunBox::read_box(reader, s)?); + truns.push(TrunBox::read_box(reader, s)?); } _ => { // XXX warn!() @@ -96,7 +97,7 @@ impl ReadBox<&mut R> for TrafBox { Ok(TrafBox { tfhd: tfhd.unwrap(), tfdt, - trun, + truns, }) } } @@ -110,7 +111,7 @@ impl WriteBox<&mut W> for TrafBox { if let Some(ref tfdt) = self.tfdt { tfdt.write_box(writer)?; } - if let Some(ref trun) = self.trun { + for trun in &self.truns { trun.write_box(writer)?; } diff --git a/src/track.rs b/src/track.rs index 7eada83..868dd9c 100644 --- a/src/track.rs +++ b/src/track.rs @@ -236,7 +236,7 @@ impl Mp4Track { if !self.trafs.is_empty() { let mut sample_count = 0u32; for traf in self.trafs.iter() { - if let Some(ref trun) = traf.trun { + for trun in &traf.truns { sample_count = sample_count .checked_add(trun.sample_count) .expect("attempt to sum trun sample_count with overflow"); @@ -369,14 +369,14 @@ impl Mp4Track { } /// return `(traf_idx, sample_idx_in_trun)` - fn find_traf_idx_and_sample_idx(&self, sample_id: u32) -> Option<(usize, usize)> { + fn find_traf_trun_and_sample_idx(&self, sample_id: u32) -> Option<(usize, usize, usize)> { let global_idx = sample_id - 1; let mut offset = 0; for traf_idx in 0..self.trafs.len() { - if let Some(trun) = &self.trafs[traf_idx].trun { + for (trun_idx, trun) in self.trafs[traf_idx].truns.iter().enumerate() { let sample_count = trun.sample_count; if sample_count > (global_idx - offset) { - return Some((traf_idx, (global_idx - offset) as _)); + return Some((traf_idx, trun_idx, (global_idx - offset) as _)); } offset = offset .checked_add(sample_count) @@ -388,11 +388,10 @@ impl Mp4Track { fn sample_size(&self, sample_id: u32) -> Result { if !self.trafs.is_empty() { - if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) { - if let Some(size) = self.trafs[traf_idx] - .trun - .as_ref() - .unwrap() + if let Some((traf_idx, trun_idx, sample_idx)) = + self.find_traf_trun_and_sample_idx(sample_id) + { + if let Some(size) = self.trafs[traf_idx].truns[trun_idx] .sample_sizes .get(sample_idx) { @@ -439,17 +438,15 @@ impl Mp4Track { pub fn sample_offset(&self, sample_id: u32) -> Result { if !self.trafs.is_empty() { - if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) { + if let Some((traf_idx, trun_idx, sample_idx)) = + self.find_traf_trun_and_sample_idx(sample_id) + { let mut sample_offset = self.trafs[traf_idx] .tfhd .base_data_offset .unwrap_or(self.moof_offsets[traf_idx]); - if let Some(data_offset) = self.trafs[traf_idx] - .trun - .as_ref() - .and_then(|trun| trun.data_offset) - { + if let Some(data_offset) = self.trafs[traf_idx].truns[trun_idx].data_offset { sample_offset = sample_offset.checked_add_signed(data_offset as i64).ok_or( Error::InvalidData("attempt to calculate trun sample offset with overflow"), )?; @@ -503,7 +500,9 @@ impl Mp4Track { if !self.trafs.is_empty() { let mut base_start_time = 0; let mut default_sample_duration = self.default_sample_duration; - if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) { + if let Some((traf_idx, trun_idx, sample_idx)) = + self.find_traf_trun_and_sample_idx(sample_id) + { let traf = &self.trafs[traf_idx]; if let Some(tfdt) = &traf.tfdt { base_start_time = tfdt.base_media_decode_time; @@ -511,17 +510,16 @@ impl Mp4Track { if let Some(duration) = traf.tfhd.default_sample_duration { default_sample_duration = duration; } - if let Some(trun) = &traf.trun { - if TrunBox::FLAG_SAMPLE_DURATION & trun.flags != 0 { - let mut start_offset = 0u64; - for duration in &trun.sample_durations[..sample_idx] { - start_offset = start_offset.checked_add(*duration as u64).ok_or( - Error::InvalidData("attempt to sum sample durations with overflow"), - )?; - } - let duration = trun.sample_durations[sample_idx]; - return Ok((base_start_time + start_offset, duration)); + let trun = &traf.truns[trun_idx]; + if TrunBox::FLAG_SAMPLE_DURATION & trun.flags != 0 { + let mut start_offset = 0u64; + for duration in &trun.sample_durations[..sample_idx] { + start_offset = start_offset.checked_add(*duration as u64).ok_or( + Error::InvalidData("attempt to sum sample durations with overflow"), + )?; } + let duration = trun.sample_durations[sample_idx]; + return Ok((base_start_time + start_offset, duration)); } } let start_offset = ((sample_id - 1) * default_sample_duration) as u64; @@ -559,11 +557,12 @@ impl Mp4Track { fn sample_rendering_offset(&self, sample_id: u32) -> i32 { if !self.trafs.is_empty() { - if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) { - if let Some(cts) = self.trafs[traf_idx] - .trun - .as_ref() - .and_then(|trun| trun.sample_cts.get(sample_idx)) + if let Some((traf_idx, trun_idx, sample_idx)) = + self.find_traf_trun_and_sample_idx(sample_id) + { + if let Some(cts) = self.trafs[traf_idx].truns[trun_idx] + .sample_cts + .get(sample_idx) { return *cts as i32; }