1
0
Fork 0
mirror of https://github.com/alfg/mp4-rust.git synced 2024-04-27 06:09:00 +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>()?;
reader.read_i16::<BigEndian>()?; // 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)?;
}
}
}
}

View file

@ -264,22 +264,16 @@ impl<R: Read + Seek> 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)?;

View file

@ -53,20 +53,15 @@ impl<R: Read + Seek> 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::<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,
CovrBox => 0x636f7672,
DescBox => 0x64657363,
WideBox => 0x77696465
WideBox => 0x77696465,
WaveBox => 0x77617665
}
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_u16::<BigEndian>()?; // reserved
let data_reference_index = reader.read_u16::<BigEndian>()?;
reader.read_u64::<BigEndian>()?; // reserved
let version = reader.read_u16::<BigEndian>()?;
reader.read_u16::<BigEndian>()?; // reserved
reader.read_u32::<BigEndian>()?; // reserved
let channelcount = reader.read_u16::<BigEndian>()?;
let samplesize = reader.read_u16::<BigEndian>()?;
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
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 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<R: Read + Seek> 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,

View file

@ -262,6 +262,14 @@ impl<R: Read + Seek> Mp4Reader<R> {
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> {

View file

@ -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<u64> {
pub fn sample_offset(&self, sample_id: u32) -> Result<u64> {
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]

View file

@ -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();