1
0
Fork 0
mirror of https://github.com/alfg/mp4-rust.git synced 2024-05-09 03:52:53 +00:00

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 <alfg@users.noreply.github.com>
This commit is contained in:
emkman99 2023-08-03 00:13:58 -04:00 committed by GitHub
parent b4fca8a199
commit 19e4eaa3c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 68 deletions

View file

@ -101,30 +101,37 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
let depth = reader.read_u16::<BigEndian>()?; let depth = reader.read_u16::<BigEndian>()?;
reader.read_i16::<BigEndian>()?; // pre-defined reader.read_i16::<BigEndian>()?; // pre-defined
let header = BoxHeader::read(reader)?; let end = start + size;
let BoxHeader { name, size: s } = header; loop {
if s > size { let current = reader.stream_position()?;
return Err(Error::InvalidData( if current >= end {
"avc1 box contains a box with a larger size than it", return Err(Error::InvalidData("avcc not found"));
)); }
} let header = BoxHeader::read(reader)?;
if name == BoxType::AvcCBox { let BoxHeader { name, size: s } = header;
let avcc = AvcCBox::read_box(reader, s)?; 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 { return Ok(Avc1Box {
data_reference_index, data_reference_index,
width, width,
height, height,
horizresolution, horizresolution,
vertresolution, vertresolution,
frame_count, frame_count,
depth, depth,
avcc, avcc,
}) });
} else { } else {
Err(Error::InvalidData("avcc not found")) skip_bytes_to(reader, current + s)?;
}
} }
} }
} }

View file

@ -264,22 +264,16 @@ impl<R: Read + Seek> ReadBox<&mut R> for UrlBox {
let (version, flags) = read_box_header_ext(reader)?; let (version, flags) = read_box_header_ext(reader)?;
let location = if size.saturating_sub(HEADER_SIZE + HEADER_EXT_SIZE) > 0 { let buf_size = size
let buf_size = size - HEADER_SIZE - HEADER_EXT_SIZE - 1; .checked_sub(HEADER_SIZE + HEADER_EXT_SIZE)
let mut buf = vec![0u8; buf_size as usize]; .ok_or(Error::InvalidData("url size too small"))?;
reader.read_exact(&mut buf)?;
match String::from_utf8(buf) { let mut buf = vec![0u8; buf_size as usize];
Ok(t) => { reader.read_exact(&mut buf)?;
if t.len() != buf_size as usize { if let Some(end) = buf.iter().position(|&b| b == b'\0') {
return Err(Error::InvalidData("string too small")); buf.truncate(end);
} }
t let location = String::from_utf8(buf).unwrap_or_default();
}
_ => String::default(),
}
} else {
String::default()
};
skip_bytes_to(reader, start + size)?; skip_bytes_to(reader, start + size)?;

View file

@ -53,20 +53,15 @@ impl<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
skip_bytes(reader, 12)?; // reserved skip_bytes(reader, 12)?; // reserved
let buf_size = size 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"))?; .ok_or(Error::InvalidData("hdlr size too small"))?;
let mut buf = vec![0u8; buf_size as usize]; let mut buf = vec![0u8; buf_size as usize];
reader.read_exact(&mut buf)?; reader.read_exact(&mut buf)?;
if let Some(end) = buf.iter().position(|&b| b == b'\0') {
let handler_string = match String::from_utf8(buf) { buf.truncate(end);
Ok(t) => { }
if t.len() != buf_size as usize { let handler_string = String::from_utf8(buf).unwrap_or_default();
return Err(Error::InvalidData("string too small"));
}
t
}
_ => String::from("null"),
};
skip_bytes_to(reader, start + size)?; skip_bytes_to(reader, start + size)?;
@ -127,4 +122,52 @@ mod tests {
let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box); assert_eq!(src_box, dst_box);
} }
#[test]
fn test_hdlr_empty() {
let src_box = HdlrBox {
version: 0,
flags: 0,
handler_type: str::parse::<FourCC>("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::<FourCC>("vide").unwrap(),
name: String::from("Good"),
};
let src_box = HdlrBox {
version: 0,
flags: 0,
handler_type: str::parse::<FourCC>("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);
}
} }

View file

@ -237,7 +237,8 @@ boxtype! {
DayBox => 0xa9646179, DayBox => 0xa9646179,
CovrBox => 0x636f7672, CovrBox => 0x636f7672,
DescBox => 0x64657363, DescBox => 0x64657363,
WideBox => 0x77696465 WideBox => 0x77696465,
WaveBox => 0x77617665
} }
pub trait Mp4Box: Sized { pub trait Mp4Box: Sized {

View file

@ -82,16 +82,28 @@ impl<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
reader.read_u32::<BigEndian>()?; // reserved reader.read_u32::<BigEndian>()?; // reserved
reader.read_u16::<BigEndian>()?; // reserved reader.read_u16::<BigEndian>()?; // reserved
let data_reference_index = reader.read_u16::<BigEndian>()?; let data_reference_index = reader.read_u16::<BigEndian>()?;
let version = reader.read_u16::<BigEndian>()?;
reader.read_u64::<BigEndian>()?; // reserved reader.read_u16::<BigEndian>()?; // reserved
reader.read_u32::<BigEndian>()?; // reserved
let channelcount = reader.read_u16::<BigEndian>()?; let channelcount = reader.read_u16::<BigEndian>()?;
let samplesize = reader.read_u16::<BigEndian>()?; let samplesize = reader.read_u16::<BigEndian>()?;
reader.read_u32::<BigEndian>()?; // pre-defined, reserved reader.read_u32::<BigEndian>()?; // pre-defined, reserved
let samplerate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?); let samplerate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
if version == 1 {
// Skip QTFF
reader.read_u64::<BigEndian>()?;
reader.read_u64::<BigEndian>()?;
}
// Find esds in mp4a or wave
let mut esds = None; let mut esds = None;
let current = reader.stream_position()?; let end = start + size;
if current < start + size { loop {
let current = reader.stream_position()?;
if current >= end {
break;
}
let header = BoxHeader::read(reader)?; let header = BoxHeader::read(reader)?;
let BoxHeader { name, size: s } = header; let BoxHeader { name, size: s } = header;
if s > size { if s > size {
@ -99,13 +111,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
"mp4a box contains a box with a larger size than it", "mp4a box contains a box with a larger size than it",
)); ));
} }
if name == BoxType::EsdsBox { if name == BoxType::EsdsBox {
esds = Some(EsdsBox::read_box(reader, s)?); 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 { Ok(Mp4aBox {
data_reference_index, data_reference_index,
channelcount, channelcount,

View file

@ -262,6 +262,14 @@ impl<R: Read + Seek> Mp4Reader<R> {
Err(Error::TrakNotFound(track_id)) Err(Error::TrakNotFound(track_id))
} }
} }
pub fn sample_offset(&mut self, track_id: u32, sample_id: u32) -> Result<u64> {
if let Some(track) = self.tracks.get(&track_id) {
track.sample_offset(sample_id)
} else {
Err(Error::TrakNotFound(track_id))
}
}
} }
impl<R> Mp4Reader<R> { impl<R> Mp4Reader<R> {

View file

@ -167,11 +167,11 @@ impl Mp4Track {
} }
pub fn frame_rate(&self) -> f64 { pub fn frame_rate(&self) -> f64 {
let dur_msec = self.duration().as_millis() as u64; let dur = self.duration();
if dur_msec > 0 { if dur.is_zero() {
((self.sample_count() as u64 * 1000) / dur_msec) as f64
} else {
0.0 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 // mp4a.esds.es_desc.dec_config.avg_bitrate
} else { } else {
let dur_sec = self.duration().as_secs(); let dur = self.duration();
if dur_sec > 0 { if dur.is_zero() {
let bitrate = self.total_sample_size() * 8 / dur_sec;
bitrate as u32
} else {
0 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<u64> { pub fn sample_offset(&self, sample_id: u32) -> Result<u64> {
if !self.trafs.is_empty() { 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, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
let mut sample_offset = self.trafs[traf_idx] let mut sample_offset = self.trafs[traf_idx]

View file

@ -99,8 +99,8 @@ fn test_read_mp4() {
assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh); assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh);
assert_eq!(track1.width(), 320); assert_eq!(track1.width(), 320);
assert_eq!(track1.height(), 240); assert_eq!(track1.height(), 240);
assert_eq!(track1.bitrate(), 0); // XXX assert_eq!(track1.bitrate(), 150200);
assert_eq!(track1.frame_rate(), 25.00); // XXX assert_eq!(track1.frame_rate(), 25.00);
// track #2 // track #2
let track2 = mp4.tracks().get(&2).unwrap(); let track2 = mp4.tracks().get(&2).unwrap();