mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-12-21 19:46:27 +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:
parent
b4fca8a199
commit
19e4eaa3c8
8 changed files with 140 additions and 68 deletions
|
@ -101,6 +101,12 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
let depth = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_i16::<BigEndian>()?; // pre-defined
|
||||
|
||||
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 {
|
||||
|
@ -113,7 +119,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(Avc1Box {
|
||||
return Ok(Avc1Box {
|
||||
data_reference_index,
|
||||
width,
|
||||
height,
|
||||
|
@ -122,9 +128,10 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
frame_count,
|
||||
depth,
|
||||
avcc,
|
||||
})
|
||||
});
|
||||
} else {
|
||||
Err(Error::InvalidData("avcc not found"))
|
||||
skip_bytes_to(reader, current + s)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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)?;
|
||||
match String::from_utf8(buf) {
|
||||
Ok(t) => {
|
||||
if t.len() != buf_size as usize {
|
||||
return Err(Error::InvalidData("string too small"));
|
||||
if let Some(end) = buf.iter().position(|&b| b == b'\0') {
|
||||
buf.truncate(end);
|
||||
}
|
||||
t
|
||||
}
|
||||
_ => String::default(),
|
||||
}
|
||||
} else {
|
||||
String::default()
|
||||
};
|
||||
let location = String::from_utf8(buf).unwrap_or_default();
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
|
|
|
@ -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"));
|
||||
if let Some(end) = buf.iter().position(|&b| b == b'\0') {
|
||||
buf.truncate(end);
|
||||
}
|
||||
t
|
||||
}
|
||||
_ => String::from("null"),
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,7 +237,8 @@ boxtype! {
|
|||
DayBox => 0xa9646179,
|
||||
CovrBox => 0x636f7672,
|
||||
DescBox => 0x64657363,
|
||||
WideBox => 0x77696465
|
||||
WideBox => 0x77696465,
|
||||
WaveBox => 0x77617665
|
||||
}
|
||||
|
||||
pub trait Mp4Box: Sized {
|
||||
|
|
|
@ -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 end = start + size;
|
||||
loop {
|
||||
let current = reader.stream_position()?;
|
||||
if current < start + size {
|
||||
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,
|
||||
|
|
|
@ -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> {
|
||||
|
|
20
src/track.rs
20
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<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]
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue