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,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)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
20
src/track.rs
20
src/track.rs
|
@ -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]
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue