tenfourfox/media/libstagefright/binding/MP4Metadata.rs
Cameron Kaiser c9b2922b70 hello FPR
2017-04-19 00:56:45 -07:00

1246 lines
38 KiB
Rust

//! Module for parsing ISO Base Media Format aka video/mp4 streams.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
mod byteorder; // 'extern crate' upstream.
use byteorder::ReadBytesExt;
use std::error::Error as ErrorTrait; // For Err(e) => e.description().
use std::io::{Read, BufRead, Take};
use std::cmp;
use std::fmt;
// Expose C api wrapper.
pub mod capi;
// FIXME: We can 'pub use capi::*' in rustc 1.5 and later.
pub use capi::{mp4parse_new, mp4parse_free, mp4parse_read};
/// Describes parser failures.
///
/// This enum wraps athe standard `io::Error` type, unified with
/// our own parser error states and those of crates we use.
#[derive(Debug)]
pub enum Error {
/// Parse error caused by corrupt or malformed data.
InvalidData,
/// Parse error caused by limited parser support rather than invalid data.
Unsupported,
/// Reflect `byteorder::Error::UnexpectedEOF` for short data.
UnexpectedEOF,
/// Propagate underlying errors from `std::io`.
Io(std::io::Error),
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error { Error::Io(err) }
}
impl From<byteorder::Error> for Error {
fn from(err: byteorder::Error) -> Error {
match err {
byteorder::Error::UnexpectedEOF => Error::UnexpectedEOF,
byteorder::Error::Io(e) => Error::Io(e),
}
}
}
/// Result shorthand using our Error enum.
pub type Result<T> = std::result::Result<T, Error>;
/// Four-byte 'character code' describing the type of a piece of data.
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct FourCC([u8; 4]);
impl fmt::Debug for FourCC {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "'{}'", String::from_utf8_lossy(&self.0))
}
}
/// Basic ISO box structure.
///
/// mp4 files are a sequence of possibly-nested 'box' structures.
/// Each box begins with a header describing the length of the
/// box's data and a four-byte 'character code' or `FourCC` which
/// identifies the type of the box. Together these are enough to
/// interpret the contents of that section of the file.
#[derive(Debug)]
pub struct BoxHeader {
/// Four character box type.
pub name: FourCC,
/// Size of the box in bytes.
pub size: u64,
/// Offset to the start of the contained data (or header size).
pub offset: u64,
}
/// File type box 'ftyp'.
#[derive(Debug)]
struct FileTypeBox {
name: FourCC,
size: u64,
major_brand: FourCC,
minor_version: u32,
compatible_brands: Vec<FourCC>,
}
/// Movie header box 'mvhd'.
#[derive(Debug)]
struct MovieHeaderBox {
name: FourCC,
size: u64,
timescale: u32,
duration: u64,
// Ignore other fields.
}
/// Track header box 'tkhd'
#[derive(Debug, Clone)]
struct TrackHeaderBox {
name: FourCC,
size: u64,
track_id: u32,
enabled: bool,
duration: u64,
width: u32,
height: u32,
}
/// Edit list box 'elst'
#[derive(Debug)]
struct EditListBox {
name: FourCC,
size: u64,
edits: Vec<Edit>,
}
#[derive(Debug)]
struct Edit {
segment_duration: u64,
media_time: i64,
media_rate_integer: i16,
media_rate_fraction: i16,
}
/// Media header box 'mdhd'
#[derive(Debug)]
struct MediaHeaderBox {
name: FourCC,
size: u64,
timescale: u32,
duration: u64,
}
// Chunk offset box 'stco' or 'co64'
#[derive(Debug)]
struct ChunkOffsetBox {
name: FourCC,
size: u64,
offsets: Vec<u64>,
}
// Sync sample box 'stss'
#[derive(Debug)]
struct SyncSampleBox {
name: FourCC,
size: u64,
samples: Vec<u32>,
}
// Sample to chunk box 'stsc'
#[derive(Debug)]
struct SampleToChunkBox {
name: FourCC,
size: u64,
samples: Vec<SampleToChunk>,
}
#[derive(Debug)]
struct SampleToChunk {
first_chunk: u32,
samples_per_chunk: u32,
sample_description_index: u32,
}
// Sample size box 'stsz'
#[derive(Debug)]
struct SampleSizeBox {
name: FourCC,
size: u64,
sample_size: u32,
sample_sizes: Vec<u32>,
}
// Time to sample box 'stts'
#[derive(Debug)]
struct TimeToSampleBox {
name: FourCC,
size: u64,
samples: Vec<Sample>,
}
#[derive(Debug)]
struct Sample {
sample_count: u32,
sample_delta: u32,
}
// Handler reference box 'hdlr'
#[derive(Debug)]
struct HandlerBox {
name: FourCC,
size: u64,
handler_type: FourCC,
}
// Sample description box 'stsd'
#[derive(Debug)]
struct SampleDescriptionBox {
name: FourCC,
size: u64,
descriptions: Vec<SampleEntry>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
enum SampleEntry {
Audio(AudioSampleEntry),
Video(VideoSampleEntry),
Unknown,
}
#[derive(Debug, Clone)]
struct AudioSampleEntry {
data_reference_index: u16,
channelcount: u16,
samplesize: u16,
samplerate: u32,
esds: ES_Descriptor,
}
#[derive(Debug, Clone)]
struct VideoSampleEntry {
data_reference_index: u16,
width: u16,
height: u16,
avcc: AVCDecoderConfigurationRecord,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct AVCDecoderConfigurationRecord {
data: Vec<u8>,
}
#[allow(non_camel_case_types)]
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct ES_Descriptor {
data: Vec<u8>,
}
/// Internal data structures.
#[derive(Debug)]
pub struct MediaContext {
timescale: Option<MediaTimeScale>,
/// Tracks found in the file.
tracks: Vec<Track>,
/// Print boxes and other info as parsing proceeds. For debugging.
trace: bool,
}
impl MediaContext {
pub fn new() -> MediaContext {
MediaContext {
timescale: None,
tracks: Vec::new(),
trace: false,
}
}
pub fn trace(&mut self, on: bool) {
self.trace = on;
}
}
macro_rules! log {
( $ctx:expr, $( $args:expr),* ) => {
if $ctx.trace {
println!( $( $args, )* );
}
}
}
#[derive(Debug)]
enum TrackType {
Audio,
Video,
Unknown,
}
#[derive(Debug, Copy, Clone)]
struct MediaTimeScale(u64); /// The media's global (mvhd) timescale.
#[derive(Debug, Copy, Clone)]
struct MediaScaledTime(u64); /// A time scaled by the media's global (mvhd) timescale.
#[derive(Debug, Copy, Clone)]
struct TrackTimeScale(u64, usize); /// The track's local (mdhd) timescale.
#[derive(Debug, Copy, Clone)]
struct TrackScaledTime(u64, usize); /// A time scaled by the track's local (mdhd) timescale.
#[derive(Debug)]
struct Track {
track_type: TrackType,
empty_duration: Option<MediaScaledTime>,
media_time: Option<TrackScaledTime>,
timescale: Option<TrackTimeScale>,
duration: Option<TrackScaledTime>,
track_id: Option<u32>,
mime_type: String,
data: Option<SampleEntry>,
tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
}
impl Track {
fn new() -> Track {
Track {
track_type: TrackType::Unknown,
empty_duration: None,
media_time: None,
timescale: None,
duration: None,
track_id: None,
mime_type: String::new(),
data: None,
tkhd: None,
}
}
}
/// Read and parse a box header.
///
/// Call this first to determine the type of a particular mp4 box
/// and its length. Used internally for dispatching to specific
/// parsers for the internal content, or to get the length to
/// skip unknown or uninteresting boxes.
pub fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
let size32 = try!(be_u32(src));
let name = try!(be_fourcc(src));
let size = match size32 {
0 => return Err(Error::Unsupported),
1 => {
let size64 = try!(be_u64(src));
if size64 < 16 {
return Err(Error::InvalidData);
}
size64
},
2 ... 7 => return Err(Error::InvalidData),
_ => size32 as u64,
};
let offset = match size32 {
1 => 4 + 4 + 8,
_ => 4 + 4,
};
assert!(offset <= size);
Ok(BoxHeader{
name: name,
size: size,
offset: offset,
})
}
/// Parse the extra header fields for a full box.
fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> {
let version = try!(src.read_u8());
let flags_a = try!(src.read_u8());
let flags_b = try!(src.read_u8());
let flags_c = try!(src.read_u8());
Ok((version, (flags_a as u32) << 16 |
(flags_b as u32) << 8 |
(flags_c as u32)))
}
/// Skip over the entire contents of a box.
fn skip_box_content<T: BufRead> (src: &mut T, header: &BoxHeader) -> Result<usize> {
skip(src, (header.size - header.offset) as usize)
}
/// Skip over the remaining contents of a box.
fn skip_remaining_box_content<T: BufRead> (src: &mut T, header: &BoxHeader) -> Result<()> {
match skip(src, (header.size - header.offset) as usize) {
Ok(_) | Err(Error::UnexpectedEOF) => Ok(()),
e @ _ => Err(e.err().unwrap())
}
}
/// Helper to construct a Take over the contents of a box.
fn limit<'a, T: BufRead>(f: &'a mut T, h: &BoxHeader) -> Take<&'a mut T> {
f.take(h.size - h.offset)
}
fn driver<F, T: BufRead>(f: &mut T, context: &mut MediaContext, action: F) -> Result<()>
where F: Fn(&mut MediaContext, BoxHeader, &mut Take<&mut T>) -> Result<()> {
loop {
let r = read_box_header(f).and_then(|h| {
let mut content = limit(f, &h);
let r = action(context, h, &mut content);
if let Ok(_) = r {
// TODO(kinetik): can check this for "non-fatal" errors (e.g. EOF) too.
log!(context, "{} content bytes left", content.limit());
assert!(content.limit() == 0);
log!(context, "read_box context: {:?}", context);
}
r
});
match r {
Ok(_) => { },
Err(Error::UnexpectedEOF) => {
// byteorder returns EOF at the end of the buffer.
// This isn't an error for us, just an signal to
// stop recursion.
log!(context, "Caught Error::UnexpectedEOF");
break;
},
Err(Error::InvalidData) => {
log!(context, "Invalid data");
return Err(Error::InvalidData);
},
Err(Error::Unsupported) => {
log!(context, "Unsupported BMFF construct");
return Err(Error::Unsupported);
},
Err(Error::Io(e)) => {
log!(context, "I/O Error '{:?}' reading box: {:?}",
e.kind(), e.description());
return Err(Error::Io(e));
},
}
}
Ok(())
}
/// Read the contents of a box, including sub boxes.
///
/// Metadata is accumulated in the passed-through MediaContext struct,
/// which can be examined later.
pub fn read_mp4<T: BufRead>(f: &mut T, context: &mut MediaContext) -> Result<()> {
driver(f, context, |context, h, mut content| {
match &h.name.0 {
b"ftyp" => {
let ftyp = try!(read_ftyp(&mut content, &h));
log!(context, "{:?}", ftyp);
},
b"moov" => try!(read_moov(&mut content, &h, context)),
_ => {
// Skip the contents of unknown chunks.
try!(skip_box_content(&mut content, &h));
},
};
Ok(())
})
}
fn read_moov<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext) -> Result<()> {
driver(f, context, |context, h, mut content| {
match &h.name.0 {
b"mvhd" => {
let mvhd = try!(read_mvhd(&mut content, &h));
context.timescale = Some(MediaTimeScale(mvhd.timescale as u64));
log!(context, " {:?}", mvhd);
},
b"trak" => {
context.tracks.push(Track::new());
try!(read_trak(&mut content, &h, context));
},
_ => {
// Skip the contents of unknown chunks.
log!(context, "{:?} (skipped)", h);
try!(skip_box_content(&mut content, &h));
},
};
Ok(())
})
}
fn read_trak<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext) -> Result<()> {
driver(f, context, |context, h, mut content| {
match &h.name.0 {
b"tkhd" => {
let tkhd = try!(read_tkhd(&mut content, &h));
if let Some(track) = context.tracks.last_mut() {
track.track_id = Some(tkhd.track_id);
track.tkhd = Some(tkhd.clone())
} else {
return Err(Error::InvalidData);
}
log!(context, " {:?}", tkhd);
},
b"edts" => try!(read_edts(&mut content, &h, context)),
b"mdia" => try!(read_mdia(&mut content, &h, context)),
_ => {
// Skip the contents of unknown chunks.
log!(context, "{:?} (skipped)", h);
try!(skip_box_content(&mut content, &h));
},
};
Ok(()) // and_then needs a Result to return.
})
}
fn read_edts<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext) -> Result<()> {
driver(f, context, |context, h, mut content| {
match &h.name.0 {
b"elst" => {
let elst = try!(read_elst(&mut content, &h));
let mut empty_duration = 0;
let track_idx = context.tracks.len() - 1;
if let Some(track) = context.tracks.last_mut() {
let mut idx = 0;
if elst.edits.len() > 2 {
return Err(Error::Unsupported);
}
if elst.edits[idx].media_time == -1 {
empty_duration = elst.edits[0].segment_duration;
idx += 1;
}
track.empty_duration = Some(MediaScaledTime(empty_duration));
if elst.edits[idx].media_time < 0 {
return Err(Error::InvalidData);
}
track.media_time = Some(TrackScaledTime(elst.edits[idx].media_time as u64,
track_idx));
} else {
return Err(Error::InvalidData);
}
log!(context, " {:?}", elst);
},
_ => {
// Skip the contents of unknown chunks.
log!(context, "{:?} (skipped)", h);
try!(skip_box_content(&mut content, &h));
},
};
Ok(())
})
}
fn read_mdia<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext) -> Result<()> {
driver(f, context, |context, h, mut content| {
match &h.name.0 {
b"mdhd" => {
let mdhd = try!(read_mdhd(&mut content, &h));
let track_idx = context.tracks.len() - 1;
if let Some(track) = context.tracks.last_mut() {
track.duration = Some(TrackScaledTime(mdhd.duration, track_idx));
track.timescale = Some(TrackTimeScale(mdhd.timescale as u64,
track_idx));
} else {
return Err(Error::InvalidData);
}
log!(context, " {:?}", mdhd);
},
b"hdlr" => {
let hdlr = try!(read_hdlr(&mut content, &h));
if let Some(track) = context.tracks.last_mut() {
match &hdlr.handler_type.0 {
b"vide" => track.track_type = TrackType::Video,
b"soun" => track.track_type = TrackType::Audio,
_ => ()
}
} else {
return Err(Error::InvalidData);
}
log!(context, " {:?}", hdlr);
},
b"minf" => try!(read_minf(&mut content, &h, context)),
_ => {
// Skip the contents of unknown chunks.
log!(context, "{:?} (skipped)", h);
try!(skip_box_content(&mut content, &h));
},
};
Ok(())
})
}
fn read_minf<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext) -> Result<()> {
driver(f, context, |context, h, mut content| {
match &h.name.0 {
b"stbl" => try!(read_stbl(&mut content, &h, context)),
_ => {
// Skip the contents of unknown chunks.
log!(context, "{:?} (skipped)", h);
try!(skip_box_content(&mut content, &h));
},
};
Ok(())
})
}
fn read_stbl<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext) -> Result<()> {
driver(f, context, |context, h, mut content| {
match &h.name.0 {
b"stsd" => {
if let Some(track) = context.tracks.last_mut() {
let stsd = try!(read_stsd(&mut content, &h, track));
log!(context, " {:?}", stsd);
} else {
return Err(Error::InvalidData);
}
},
b"stts" => {
let stts = try!(read_stts(&mut content, &h));
log!(context, " {:?}", stts);
},
b"stsc" => {
let stsc = try!(read_stsc(&mut content, &h));
log!(context, " {:?}", stsc);
},
b"stsz" => {
let stsz = try!(read_stsz(&mut content, &h));
log!(context, " {:?}", stsz);
},
b"stco" => {
let stco = try!(read_stco(&mut content, &h));
log!(context, " {:?}", stco);
},
b"co64" => {
let co64 = try!(read_co64(&mut content, &h));
log!(context, " {:?}", co64);
},
b"stss" => {
let stss = try!(read_stss(&mut content, &h));
log!(context, " {:?}", stss);
},
_ => {
// Skip the contents of unknown chunks.
log!(context, "{:?} (skipped)", h);
try!(skip_box_content(&mut content, &h));
},
};
Ok(())
})
}
/// Parse an ftyp box.
fn read_ftyp<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<FileTypeBox> {
let major = try!(be_fourcc(src));
let minor = try!(be_u32(src));
let bytes_left = head.size - head.offset - 8;
if bytes_left % 4 != 0 {
return Err(Error::InvalidData);
}
// Is a brand_count of zero valid?
let brand_count = bytes_left / 4;
let mut brands = Vec::new();
for _ in 0..brand_count {
brands.push(try!(be_fourcc(src)));
}
Ok(FileTypeBox{
name: head.name,
size: head.size,
major_brand: major,
minor_version: minor,
compatible_brands: brands,
})
}
/// Parse an mvhd box.
fn read_mvhd<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> Result<MovieHeaderBox> {
let (version, _) = try!(read_fullbox_extra(src));
match version {
// 64 bit creation and modification times.
1 => { try!(skip(src, 16)); },
// 32 bit creation and modification times.
0 => { try!(skip(src, 8)); },
_ => return Err(Error::InvalidData),
}
let timescale = try!(be_u32(src));
let duration = match version {
1 => try!(be_u64(src)),
0 => try!(be_u32(src)) as u64,
_ => return Err(Error::InvalidData),
};
// Skip remaining fields.
try!(skip(src, 80));
Ok(MovieHeaderBox {
name: head.name,
size: head.size,
timescale: timescale,
duration: duration,
})
}
/// Parse a tkhd box.
fn read_tkhd<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> Result<TrackHeaderBox> {
let (version, flags) = try!(read_fullbox_extra(src));
let disabled = flags & 0x1u32 == 0 || flags & 0x2u32 == 0;
match version {
// 64 bit creation and modification times.
1 => { try!(skip(src, 16)); },
// 32 bit creation and modification times.
0 => { try!(skip(src, 8)); },
_ => return Err(Error::InvalidData),
}
let track_id = try!(be_u32(src));
try!(skip(src, 4));
let duration = match version {
1 => try!(be_u64(src)),
0 => try!(be_u32(src)) as u64,
_ => return Err(Error::InvalidData),
};
// Skip uninteresting fields.
try!(skip(src, 52));
let width = try!(be_u32(src));
let height = try!(be_u32(src));
Ok(TrackHeaderBox {
name: head.name,
size: head.size,
track_id: track_id,
enabled: !disabled,
duration: duration,
width: width,
height: height,
})
}
/// Parse a elst box.
fn read_elst<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<EditListBox> {
let (version, _) = try!(read_fullbox_extra(src));
let edit_count = try!(be_u32(src));
let mut edits = Vec::new();
for _ in 0..edit_count {
let (segment_duration, media_time) = match version {
1 => {
// 64 bit segment duration and media times.
(try!(be_u64(src)),
try!(be_i64(src)))
},
0 => {
// 32 bit segment duration and media times.
(try!(be_u32(src)) as u64,
try!(be_i32(src)) as i64)
},
_ => return Err(Error::InvalidData),
};
let media_rate_integer = try!(be_i16(src));
let media_rate_fraction = try!(be_i16(src));
edits.push(Edit{
segment_duration: segment_duration,
media_time: media_time,
media_rate_integer: media_rate_integer,
media_rate_fraction: media_rate_fraction,
})
}
Ok(EditListBox{
name: head.name,
size: head.size,
edits: edits
})
}
/// Parse a mdhd box.
fn read_mdhd<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> Result<MediaHeaderBox> {
let (version, _) = try!(read_fullbox_extra(src));
let (timescale, duration) = match version {
1 => {
// Skip 64-bit creation and modification times.
try!(skip(src, 16));
// 64 bit duration.
(try!(be_u32(src)),
try!(be_u64(src)))
},
0 => {
// Skip 32-bit creation and modification times.
try!(skip(src, 8));
// 32 bit duration.
(try!(be_u32(src)),
try!(be_u32(src)) as u64)
},
_ => return Err(Error::InvalidData),
};
// Skip uninteresting fields.
try!(skip(src, 4));
Ok(MediaHeaderBox{
name: head.name,
size: head.size,
timescale: timescale,
duration: duration,
})
}
/// Parse a stco box.
fn read_stco<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<ChunkOffsetBox> {
let (_, _) = try!(read_fullbox_extra(src));
let offset_count = try!(be_u32(src));
let mut offsets = Vec::new();
for _ in 0..offset_count {
offsets.push(try!(be_u32(src)) as u64);
}
Ok(ChunkOffsetBox{
name: head.name,
size: head.size,
offsets: offsets,
})
}
/// Parse a stco box.
fn read_co64<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<ChunkOffsetBox> {
let (_, _) = try!(read_fullbox_extra(src));
let offset_count = try!(be_u32(src));
let mut offsets = Vec::new();
for _ in 0..offset_count {
offsets.push(try!(be_u64(src)));
}
Ok(ChunkOffsetBox{
name: head.name,
size: head.size,
offsets: offsets,
})
}
/// Parse a stss box.
fn read_stss<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<SyncSampleBox> {
let (_, _) = try!(read_fullbox_extra(src));
let sample_count = try!(be_u32(src));
let mut samples = Vec::new();
for _ in 0..sample_count {
samples.push(try!(be_u32(src)));
}
Ok(SyncSampleBox{
name: head.name,
size: head.size,
samples: samples,
})
}
/// Parse a stsc box.
fn read_stsc<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<SampleToChunkBox> {
let (_, _) = try!(read_fullbox_extra(src));
let sample_count = try!(be_u32(src));
let mut samples = Vec::new();
for _ in 0..sample_count {
let first_chunk = try!(be_u32(src));
let samples_per_chunk = try!(be_u32(src));
let sample_description_index = try!(be_u32(src));
samples.push(SampleToChunk{
first_chunk: first_chunk,
samples_per_chunk: samples_per_chunk,
sample_description_index: sample_description_index
});
}
Ok(SampleToChunkBox{
name: head.name,
size: head.size,
samples: samples,
})
}
/// Parse a stsz box.
fn read_stsz<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<SampleSizeBox> {
let (_, _) = try!(read_fullbox_extra(src));
let sample_size = try!(be_u32(src));
let sample_count = try!(be_u32(src));
let mut sample_sizes = Vec::new();
if sample_size == 0 {
for _ in 0..sample_count {
sample_sizes.push(try!(be_u32(src)));
}
}
Ok(SampleSizeBox{
name: head.name,
size: head.size,
sample_size: sample_size,
sample_sizes: sample_sizes,
})
}
/// Parse a stts box.
fn read_stts<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<TimeToSampleBox> {
let (_, _) = try!(read_fullbox_extra(src));
let sample_count = try!(be_u32(src));
let mut samples = Vec::new();
for _ in 0..sample_count {
let sample_count = try!(be_u32(src));
let sample_delta = try!(be_u32(src));
samples.push(Sample{
sample_count: sample_count,
sample_delta: sample_delta,
});
}
Ok(TimeToSampleBox{
name: head.name,
size: head.size,
samples: samples,
})
}
/// Parse a hdlr box.
fn read_hdlr<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> Result<HandlerBox> {
let (_, _) = try!(read_fullbox_extra(src));
// Skip uninteresting fields.
try!(skip(src, 4));
let handler_type = try!(be_fourcc(src));
// Skip uninteresting fields.
try!(skip(src, 12));
// TODO(kinetik): Find a copy of ISO/IEC 14496-1 to work out how strings are encoded.
// As a hack, just consume the rest of the box.
try!(skip_remaining_box_content(src, head));
Ok(HandlerBox{
name: head.name,
size: head.size,
handler_type: handler_type,
})
}
/// Parse a stsd box.
fn read_stsd<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader, track: &mut Track) -> Result<SampleDescriptionBox> {
let (_, _) = try!(read_fullbox_extra(src));
let description_count = try!(be_u32(src));
let mut descriptions = Vec::new();
// TODO(kinetik): check if/when more than one desc per track? do we need to support?
for _ in 0..description_count {
let description = match track.track_type {
TrackType::Video => {
let h = try!(read_box_header(src));
// TODO(kinetik): encv here also?
if &h.name.0 != b"avc1" && &h.name.0 != b"avc3" {
return Err(Error::Unsupported);
}
// Skip uninteresting fields.
try!(skip(src, 6));
let data_reference_index = try!(be_u16(src));
// Skip uninteresting fields.
try!(skip(src, 16));
let width = try!(be_u16(src));
let height = try!(be_u16(src));
// Skip uninteresting fields.
try!(skip(src, 50));
// TODO(kinetik): Parse avcC atom? For now we just stash the data.
let h = try!(read_box_header(src));
if &h.name.0 != b"avcC" {
return Err(Error::InvalidData);
}
let mut data: Vec<u8> = vec![0; (h.size - h.offset) as usize];
let r = try!(src.read(&mut data));
assert!(r == data.len());
let avcc = AVCDecoderConfigurationRecord { data: data };
try!(skip_remaining_box_content(src, head));
track.mime_type = String::from("video/avc");
SampleEntry::Video(VideoSampleEntry {
data_reference_index: data_reference_index,
width: width,
height: height,
avcc: avcc,
})
},
TrackType::Audio => {
let h = try!(read_box_header(src));
// TODO(kinetik): enca here also?
if &h.name.0 != b"mp4a" {
return Err(Error::Unsupported);
}
// Skip uninteresting fields.
try!(skip(src, 6));
let data_reference_index = try!(be_u16(src));
// Skip uninteresting fields.
try!(skip(src, 8));
let channelcount = try!(be_u16(src));
let samplesize = try!(be_u16(src));
// Skip uninteresting fields.
try!(skip(src, 4));
let samplerate = try!(be_u32(src));
// TODO(kinetik): Parse esds atom? For now we just stash the data.
let h = try!(read_box_header(src));
if &h.name.0 != b"esds" {
return Err(Error::InvalidData);
}
let (_, _) = try!(read_fullbox_extra(src));
let mut data: Vec<u8> = vec![0; (h.size - h.offset - 4) as usize];
let r = try!(src.read(&mut data));
assert!(r == data.len());
let esds = ES_Descriptor { data: data };
// TODO(kinetik): stagefright inspects ESDS to detect MP3 (audio/mpeg).
track.mime_type = String::from("audio/mp4a-latm");
SampleEntry::Audio(AudioSampleEntry {
data_reference_index: data_reference_index,
channelcount: channelcount,
samplesize: samplesize,
samplerate: samplerate,
esds: esds,
})
},
TrackType::Unknown => {
SampleEntry::Unknown
}
};
if track.data.is_none() {
track.data = Some(description.clone());
} else {
return Err(Error::InvalidData);
}
descriptions.push(description);
}
Ok(SampleDescriptionBox{
name: head.name,
size: head.size,
descriptions: descriptions,
})
}
/// Skip a number of bytes that we don't care to parse.
fn skip<T: BufRead>(src: &mut T, bytes: usize) -> Result<usize> {
let mut bytes_to_skip = bytes;
while bytes_to_skip > 0 {
let len = try!(src.fill_buf()).len();
if len == 0 {
return Err(Error::UnexpectedEOF);
}
let discard = cmp::min(len, bytes_to_skip);
src.consume(discard);
bytes_to_skip -= discard;
}
assert!(bytes_to_skip == 0);
Ok(bytes)
}
fn media_time_to_ms(time: MediaScaledTime, scale: MediaTimeScale) -> u64 {
assert!(scale.0 != 0);
time.0 * 1000000 / scale.0
}
fn track_time_to_ms(time: TrackScaledTime, scale: TrackTimeScale) -> u64 {
assert!(time.1 == scale.1);
assert!(scale.0 != 0);
time.0 * 1000000 / scale.0
}
fn be_i16<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<i16> {
src.read_i16::<byteorder::BigEndian>()
}
fn be_i32<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<i32> {
src.read_i32::<byteorder::BigEndian>()
}
fn be_i64<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<i64> {
src.read_i64::<byteorder::BigEndian>()
}
fn be_u16<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u16> {
src.read_u16::<byteorder::BigEndian>()
}
fn be_u32<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u32> {
src.read_u32::<byteorder::BigEndian>()
}
fn be_u64<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u64> {
src.read_u64::<byteorder::BigEndian>()
}
fn be_fourcc<T: Read>(src: &mut T) -> Result<FourCC> {
let mut fourcc = [0; 4];
match src.read(&mut fourcc) {
// Expect all 4 bytes read.
Ok(4) => Ok(FourCC(fourcc)),
// Short read means EOF.
Ok(_) => Err(Error::UnexpectedEOF),
// Propagate std::io errors.
Err(e) => Err(Error::Io(e)),
}
}
#[test]
fn test_read_box_header() {
use std::io::{Cursor, Write};
let mut test: Vec<u8> = vec![0, 0, 0, 8]; // minimal box length
write!(&mut test, "test").unwrap(); // box type
let mut stream = Cursor::new(test);
let parsed = read_box_header(&mut stream).unwrap();
assert_eq!(parsed.name, FourCC(*b"test"));
assert_eq!(parsed.size, 8);
println!("box {:?}", parsed);
}
#[test]
fn test_read_box_header_long() {
use std::io::Cursor;
let mut test: Vec<u8> = vec![0, 0, 0, 1]; // long box extension code
test.extend("long".to_string().into_bytes()); // box type
test.extend(vec![0, 0, 0, 0, 0, 0, 16, 0]); // 64 bit size
// Skip generating box content.
let mut stream = Cursor::new(test);
let parsed = read_box_header(&mut stream).unwrap();
assert_eq!(parsed.name, FourCC(*b"long"));
assert_eq!(parsed.size, 4096);
println!("box {:?}", parsed);
}
#[test]
fn test_read_ftyp() {
use std::io::{Cursor, Write};
let mut test: Vec<u8> = vec![0, 0, 0, 24]; // size
write!(&mut test, "ftyp").unwrap(); // type
write!(&mut test, "mp42").unwrap(); // major brand
test.extend(vec![0, 0, 0, 0]); // minor version
write!(&mut test, "isom").unwrap(); // compatible brands...
write!(&mut test, "mp42").unwrap();
assert_eq!(test.len(), 24);
let mut stream = Cursor::new(test);
let header = read_box_header(&mut stream).unwrap();
let parsed = read_ftyp(&mut stream, &header).unwrap();
assert_eq!(parsed.name, FourCC(*b"ftyp"));
assert_eq!(parsed.size, 24);
assert_eq!(parsed.major_brand, FourCC(*b"mp42"));
assert_eq!(parsed.minor_version, 0);
assert_eq!(parsed.compatible_brands.len(), 2);
assert_eq!(parsed.compatible_brands[0], FourCC(*b"isom"));
assert_eq!(parsed.compatible_brands[1], FourCC(*b"mp42"));
println!("box {:?}", parsed);
}
#[test]
fn test_read_elst_v0() {
use std::io::{Cursor, Write};
let mut test: Vec<u8> = vec![0, 0, 0, 28]; // size
write!(&mut test, "elst").unwrap(); // type
test.extend(vec![0, 0, 0, 0]); // fullbox
test.extend(vec![0, 0, 0, 1]); // count
test.extend(vec![1, 2, 3, 4,
5, 6, 7, 8,
9, 10,
11, 12]);
assert_eq!(test.len(), 28);
let mut stream = Cursor::new(test);
let header = read_box_header(&mut stream).unwrap();
let parsed = read_elst(&mut stream, &header).unwrap();
assert_eq!(parsed.name, FourCC(*b"elst"));
assert_eq!(parsed.size, 28);
assert_eq!(parsed.edits.len(), 1);
assert_eq!(parsed.edits[0].segment_duration, 16909060);
assert_eq!(parsed.edits[0].media_time, 84281096);
assert_eq!(parsed.edits[0].media_rate_integer, 2314);
assert_eq!(parsed.edits[0].media_rate_fraction, 2828);
println!("box {:?}", parsed);
}
#[test]
fn test_read_elst_v1() {
use std::io::{Cursor, Write};
let mut test: Vec<u8> = vec![0, 0, 0, 56]; // size
write!(&mut test, "elst").unwrap(); // type
test.extend(vec![1, 0, 0, 0]); // fullbox
test.extend(vec![0, 0, 0, 2]); // count
test.extend(vec![1, 2, 3, 4, 1, 2, 3, 4,
5, 6, 7, 8, 5, 6, 7, 8,
9, 10,
11, 12]);
test.extend(vec![1, 2, 3, 4, 1, 2, 3, 4,
5, 6, 7, 8, 5, 6, 7, 8,
9, 10,
11, 12]);
assert_eq!(test.len(), 56);
let mut stream = Cursor::new(test);
let header = read_box_header(&mut stream).unwrap();
let parsed = read_elst(&mut stream, &header).unwrap();
assert_eq!(parsed.name, FourCC(*b"elst"));
assert_eq!(parsed.size, 56);
assert_eq!(parsed.edits.len(), 2);
assert_eq!(parsed.edits[1].segment_duration, 72623859723010820);
assert_eq!(parsed.edits[1].media_time, 361984551075317512);
assert_eq!(parsed.edits[1].media_rate_integer, 2314);
assert_eq!(parsed.edits[1].media_rate_fraction, 2828);
println!("box {:?}", parsed);
}
#[test]
fn test_read_mdhd_v0() {
use std::io::{Cursor, Write};
let mut test: Vec<u8> = vec![0, 0, 0, 32]; // size
write!(&mut test, "mdhd").unwrap(); // type
test.extend(vec![0, 0, 0, 0]); // fullbox
test.extend(vec![0, 0, 0, 0,
0, 0, 0, 0,
1, 2, 3, 4,
5, 6, 7, 8,
0, 0, 0, 0]);
assert_eq!(test.len(), 32);
let mut stream = Cursor::new(test);
let header = read_box_header(&mut stream).unwrap();
let parsed = read_mdhd(&mut stream, &header).unwrap();
assert_eq!(parsed.name, FourCC(*b"mdhd"));
assert_eq!(parsed.size, 32);
assert_eq!(parsed.timescale, 16909060);
assert_eq!(parsed.duration, 84281096);
println!("box {:?}", parsed);
}
#[test]
fn test_read_mdhd_v1() {
use std::io::{Cursor, Write};
let mut test: Vec<u8> = vec![0, 0, 0, 44]; // size
write!(&mut test, "mdhd").unwrap(); // type
test.extend(vec![1, 0, 0, 0]); // fullbox
test.extend(vec![0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 2, 3, 4,
5, 6, 7, 8, 5, 6, 7, 8,
0, 0, 0, 0]);
assert_eq!(test.len(), 44);
let mut stream = Cursor::new(test);
let header = read_box_header(&mut stream).unwrap();
let parsed = read_mdhd(&mut stream, &header).unwrap();
assert_eq!(parsed.name, FourCC(*b"mdhd"));
assert_eq!(parsed.size, 44);
assert_eq!(parsed.timescale, 16909060);
assert_eq!(parsed.duration, 361984551075317512);
println!("box {:?}", parsed);
}