From 19e4eaa3c8d3143427526e0d9d0c5131377c842a Mon Sep 17 00:00:00 2001 From: emkman99 Date: Thu, 3 Aug 2023 00:13:58 -0400 Subject: [PATCH] Extract esds box from wave box (#96) * Extract esds from wave box * Allow empty, multi-byte, and arbitrary NUL terminated strings * Skip unsupported avc1 sub-boxes * Fixed non-integer framerates * Fixed bitrate calculation * Fixed format issue * Public read sample offset * Fix lint warning. --------- Co-authored-by: Alfred Gutierrez --- src/mp4box/avc1.rs | 51 ++++++++++++++++++++---------------- src/mp4box/dinf.rs | 26 +++++++------------ src/mp4box/hdlr.rs | 65 ++++++++++++++++++++++++++++++++++++++-------- src/mp4box/mod.rs | 3 ++- src/mp4box/mp4a.rs | 31 +++++++++++++++++----- src/reader.rs | 8 ++++++ src/track.rs | 20 +++++++------- tests/lib.rs | 4 +-- 8 files changed, 140 insertions(+), 68 deletions(-) diff --git a/src/mp4box/avc1.rs b/src/mp4box/avc1.rs index 7174122..f386f9a 100644 --- a/src/mp4box/avc1.rs +++ b/src/mp4box/avc1.rs @@ -101,30 +101,37 @@ impl ReadBox<&mut R> for Avc1Box { let depth = reader.read_u16::()?; reader.read_i16::()?; // pre-defined - let header = BoxHeader::read(reader)?; - let BoxHeader { name, size: s } = header; - if s > size { - return Err(Error::InvalidData( - "avc1 box contains a box with a larger size than it", - )); - } - if name == BoxType::AvcCBox { - let avcc = AvcCBox::read_box(reader, s)?; + let end = start + size; + loop { + let current = reader.stream_position()?; + if current >= end { + return Err(Error::InvalidData("avcc not found")); + } + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + if s > size { + return Err(Error::InvalidData( + "avc1 box contains a box with a larger size than it", + )); + } + if name == BoxType::AvcCBox { + let avcc = AvcCBox::read_box(reader, s)?; - skip_bytes_to(reader, start + size)?; + skip_bytes_to(reader, start + size)?; - Ok(Avc1Box { - data_reference_index, - width, - height, - horizresolution, - vertresolution, - frame_count, - depth, - avcc, - }) - } else { - Err(Error::InvalidData("avcc not found")) + return Ok(Avc1Box { + data_reference_index, + width, + height, + horizresolution, + vertresolution, + frame_count, + depth, + avcc, + }); + } else { + skip_bytes_to(reader, current + s)?; + } } } } diff --git a/src/mp4box/dinf.rs b/src/mp4box/dinf.rs index 46698c9..e365e4a 100644 --- a/src/mp4box/dinf.rs +++ b/src/mp4box/dinf.rs @@ -264,22 +264,16 @@ impl ReadBox<&mut R> for UrlBox { let (version, flags) = read_box_header_ext(reader)?; - let location = if size.saturating_sub(HEADER_SIZE + HEADER_EXT_SIZE) > 0 { - let buf_size = size - HEADER_SIZE - HEADER_EXT_SIZE - 1; - let mut buf = vec![0u8; buf_size as usize]; - reader.read_exact(&mut buf)?; - match String::from_utf8(buf) { - Ok(t) => { - if t.len() != buf_size as usize { - return Err(Error::InvalidData("string too small")); - } - t - } - _ => String::default(), - } - } else { - String::default() - }; + let buf_size = size + .checked_sub(HEADER_SIZE + HEADER_EXT_SIZE) + .ok_or(Error::InvalidData("url size too small"))?; + + let mut buf = vec![0u8; buf_size as usize]; + reader.read_exact(&mut buf)?; + if let Some(end) = buf.iter().position(|&b| b == b'\0') { + buf.truncate(end); + } + let location = String::from_utf8(buf).unwrap_or_default(); skip_bytes_to(reader, start + size)?; diff --git a/src/mp4box/hdlr.rs b/src/mp4box/hdlr.rs index 1ff559e..b9d86a9 100644 --- a/src/mp4box/hdlr.rs +++ b/src/mp4box/hdlr.rs @@ -53,20 +53,15 @@ impl ReadBox<&mut R> for HdlrBox { skip_bytes(reader, 12)?; // reserved let buf_size = size - .checked_sub(HEADER_SIZE + HEADER_EXT_SIZE + 20 + 1) + .checked_sub(HEADER_SIZE + HEADER_EXT_SIZE + 20) .ok_or(Error::InvalidData("hdlr size too small"))?; + let mut buf = vec![0u8; buf_size as usize]; reader.read_exact(&mut buf)?; - - let handler_string = match String::from_utf8(buf) { - Ok(t) => { - if t.len() != buf_size as usize { - return Err(Error::InvalidData("string too small")); - } - t - } - _ => String::from("null"), - }; + if let Some(end) = buf.iter().position(|&b| b == b'\0') { + buf.truncate(end); + } + let handler_string = String::from_utf8(buf).unwrap_or_default(); skip_bytes_to(reader, start + size)?; @@ -127,4 +122,52 @@ mod tests { let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); assert_eq!(src_box, dst_box); } + + #[test] + fn test_hdlr_empty() { + let src_box = HdlrBox { + version: 0, + flags: 0, + handler_type: str::parse::("vide").unwrap(), + name: String::new(), + }; + let mut buf = Vec::new(); + src_box.write_box(&mut buf).unwrap(); + assert_eq!(buf.len(), src_box.box_size() as usize); + + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::HdlrBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } + + #[test] + fn test_hdlr_extra() { + let real_src_box = HdlrBox { + version: 0, + flags: 0, + handler_type: str::parse::("vide").unwrap(), + name: String::from("Good"), + }; + let src_box = HdlrBox { + version: 0, + flags: 0, + handler_type: str::parse::("vide").unwrap(), + name: String::from_utf8(b"Good\0Bad".to_vec()).unwrap(), + }; + let mut buf = Vec::new(); + src_box.write_box(&mut buf).unwrap(); + assert_eq!(buf.len(), src_box.box_size() as usize); + + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::HdlrBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(real_src_box, dst_box); + } } diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 2346ef1..4bbdd41 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -237,7 +237,8 @@ boxtype! { DayBox => 0xa9646179, CovrBox => 0x636f7672, DescBox => 0x64657363, - WideBox => 0x77696465 + WideBox => 0x77696465, + WaveBox => 0x77617665 } pub trait Mp4Box: Sized { diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 89ad9f5..a80c6c4 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -82,16 +82,28 @@ impl ReadBox<&mut R> for Mp4aBox { reader.read_u32::()?; // reserved reader.read_u16::()?; // reserved let data_reference_index = reader.read_u16::()?; - - reader.read_u64::()?; // reserved + let version = reader.read_u16::()?; + reader.read_u16::()?; // reserved + reader.read_u32::()?; // reserved let channelcount = reader.read_u16::()?; let samplesize = reader.read_u16::()?; reader.read_u32::()?; // pre-defined, reserved let samplerate = FixedPointU16::new_raw(reader.read_u32::()?); + if version == 1 { + // Skip QTFF + reader.read_u64::()?; + reader.read_u64::()?; + } + + // Find esds in mp4a or wave let mut esds = None; - let current = reader.stream_position()?; - if current < start + size { + let end = start + size; + loop { + let current = reader.stream_position()?; + if current >= end { + break; + } let header = BoxHeader::read(reader)?; let BoxHeader { name, size: s } = header; if s > size { @@ -99,13 +111,20 @@ impl ReadBox<&mut R> for Mp4aBox { "mp4a box contains a box with a larger size than it", )); } - if name == BoxType::EsdsBox { esds = Some(EsdsBox::read_box(reader, s)?); + break; + } else if name == BoxType::WaveBox { + // Typically contains frma, mp4a, esds, and a terminator atom + } else { + // Skip boxes + let skip_to = current + s; + skip_bytes_to(reader, skip_to)?; } - skip_bytes_to(reader, start + size)?; } + skip_bytes_to(reader, end)?; + Ok(Mp4aBox { data_reference_index, channelcount, diff --git a/src/reader.rs b/src/reader.rs index 4a285c4..e5ac296 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -262,6 +262,14 @@ impl Mp4Reader { Err(Error::TrakNotFound(track_id)) } } + + pub fn sample_offset(&mut self, track_id: u32, sample_id: u32) -> Result { + if let Some(track) = self.tracks.get(&track_id) { + track.sample_offset(sample_id) + } else { + Err(Error::TrakNotFound(track_id)) + } + } } impl Mp4Reader { diff --git a/src/track.rs b/src/track.rs index 3a99c3e..7eada83 100644 --- a/src/track.rs +++ b/src/track.rs @@ -167,11 +167,11 @@ impl Mp4Track { } pub fn frame_rate(&self) -> f64 { - let dur_msec = self.duration().as_millis() as u64; - if dur_msec > 0 { - ((self.sample_count() as u64 * 1000) / dur_msec) as f64 - } else { + let dur = self.duration(); + if dur.is_zero() { 0.0 + } else { + self.sample_count() as f64 / dur.as_secs_f64() } } @@ -222,12 +222,12 @@ impl Mp4Track { } // mp4a.esds.es_desc.dec_config.avg_bitrate } else { - let dur_sec = self.duration().as_secs(); - if dur_sec > 0 { - let bitrate = self.total_sample_size() * 8 / dur_sec; - bitrate as u32 - } else { + let dur = self.duration(); + if dur.is_zero() { 0 + } else { + let bitrate = self.total_sample_size() as f64 * 8.0 / dur.as_secs_f64(); + bitrate as u32 } } } @@ -437,7 +437,7 @@ impl Mp4Track { } } - fn sample_offset(&self, sample_id: u32) -> Result { + 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) { let mut sample_offset = self.trafs[traf_idx] diff --git a/tests/lib.rs b/tests/lib.rs index 896efb1..7c81f95 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -99,8 +99,8 @@ fn test_read_mp4() { assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh); assert_eq!(track1.width(), 320); assert_eq!(track1.height(), 240); - assert_eq!(track1.bitrate(), 0); // XXX - assert_eq!(track1.frame_rate(), 25.00); // XXX + assert_eq!(track1.bitrate(), 150200); + assert_eq!(track1.frame_rate(), 25.00); // track #2 let track2 = mp4.tracks().get(&2).unwrap();