//! 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 for Error { fn from(err: std::io::Error) -> Error { Error::Io(err) } } impl From 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 = std::result::Result; /// 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, } /// 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, } #[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, } // Sync sample box 'stss' #[derive(Debug)] struct SyncSampleBox { name: FourCC, size: u64, samples: Vec, } // Sample to chunk box 'stsc' #[derive(Debug)] struct SampleToChunkBox { name: FourCC, size: u64, samples: Vec, } #[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, } // Time to sample box 'stts' #[derive(Debug)] struct TimeToSampleBox { name: FourCC, size: u64, samples: Vec, } #[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, } #[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, } #[allow(non_camel_case_types)] #[allow(dead_code)] #[derive(Debug, Clone)] struct ES_Descriptor { data: Vec, } /// Internal data structures. #[derive(Debug)] pub struct MediaContext { timescale: Option, /// Tracks found in the file. tracks: Vec, /// 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, media_time: Option, timescale: Option, duration: Option, track_id: Option, mime_type: String, data: Option, tkhd: Option, // 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(src: &mut T) -> Result { 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(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 (src: &mut T, header: &BoxHeader) -> Result { skip(src, (header.size - header.offset) as usize) } /// Skip over the remaining contents of a box. fn skip_remaining_box_content (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: &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(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(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(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(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(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(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(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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader) -> Result { 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(src: &mut T, head: &BoxHeader, track: &mut Track) -> Result { 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 = 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 = 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(src: &mut T, bytes: usize) -> Result { 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(src: &mut T) -> byteorder::Result { src.read_i16::() } fn be_i32(src: &mut T) -> byteorder::Result { src.read_i32::() } fn be_i64(src: &mut T) -> byteorder::Result { src.read_i64::() } fn be_u16(src: &mut T) -> byteorder::Result { src.read_u16::() } fn be_u32(src: &mut T) -> byteorder::Result { src.read_u32::() } fn be_u64(src: &mut T) -> byteorder::Result { src.read_u64::() } fn be_fourcc(src: &mut T) -> Result { 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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); }