diff --git a/media/libstagefright/binding/include/mp4parse.h b/media/libstagefright/binding/include/mp4parse.h index 6c444e1592ba..7b25afddf3ba 100644 --- a/media/libstagefright/binding/include/mp4parse.h +++ b/media/libstagefright/binding/include/mp4parse.h @@ -93,6 +93,8 @@ mp4parse_error mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t t /// Fill the supplied `mp4parse_track_video_info` with metadata for `track`. mp4parse_error mp4parse_get_track_video_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_video_info* info); +mp4parse_error mp4parse_is_fragmented(mp4parse_parser* parser, uint32_t track_id, uint8_t* fragmented); + #ifdef __cplusplus diff --git a/media/libstagefright/binding/mp4parse/Cargo.toml b/media/libstagefright/binding/mp4parse/Cargo.toml index 46bf8db0d439..814c4c668812 100644 --- a/media/libstagefright/binding/mp4parse/Cargo.toml +++ b/media/libstagefright/binding/mp4parse/Cargo.toml @@ -1,25 +1,28 @@ [package] name = "mp4parse" -version = "0.4.0" +version = "0.5.0" authors = [ "Ralph Giles ", "Matthew Gregan ", ] description = "Parser for ISO base media file format (mp4)" +documentation = "https://mp4parse-docs.surge.sh/mp4parse/" license = "MPL-2.0" repository = "https://github.com/mozilla/mp4parse-rust" -# Cargo includes random files from the working directory -# by default! Avoid bloating the package with test files. +# Avoid complaints about trying to package test files. exclude = [ "*.mp4", ] +[dependencies] +byteorder = { version = "0.5.0", path = "../byteorder" } + +[dev-dependencies] +test-assembler = "0.1.2" + # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on. [profile.release] debug-assertions = true - -[dependencies] -byteorder = { path = "../byteorder" } diff --git a/media/libstagefright/binding/mp4parse/src/boxes.rs b/media/libstagefright/binding/mp4parse/src/boxes.rs index e1a5b6252995..ece3b6394429 100644 --- a/media/libstagefright/binding/mp4parse/src/boxes.rs +++ b/media/libstagefright/binding/mp4parse/src/boxes.rs @@ -54,4 +54,5 @@ box_database!( OpusSpecificBox 0x644f7073, // "dOps" ProtectedVisualSampleEntry 0x656e6376, // "encv" - Need to check official name in spec. ProtectedAudioSampleEntry 0x656e6361, // "enca" - Need to check official name in spec. + MovieExtendsBox 0x6d766578, // "mvex" ); diff --git a/media/libstagefright/binding/mp4parse/src/lib.rs b/media/libstagefright/binding/mp4parse/src/lib.rs index 9b94d5e4b9a8..4e8db561d074 100644 --- a/media/libstagefright/binding/mp4parse/src/lib.rs +++ b/media/libstagefright/binding/mp4parse/src/lib.rs @@ -13,10 +13,6 @@ use byteorder::ReadBytesExt; use std::io::{Read, Take}; use std::cmp; -// Expose C api wrapper. -pub mod capi; -pub use capi::*; - mod boxes; use boxes::BoxType; @@ -109,18 +105,18 @@ struct FileTypeBox { /// Movie header box 'mvhd'. #[derive(Debug)] struct MovieHeaderBox { - timescale: u32, + pub timescale: u32, duration: u64, } /// Track header box 'tkhd' #[derive(Debug, Clone)] -struct TrackHeaderBox { +pub struct TrackHeaderBox { track_id: u32, - disabled: bool, - duration: u64, - width: u32, - height: u32, + pub disabled: bool, + pub duration: u64, + pub width: u32, + pub height: u32, } /// Edit list box 'elst' @@ -201,7 +197,7 @@ struct SampleDescriptionBox { } #[derive(Debug, Clone)] -enum SampleEntry { +pub enum SampleEntry { Audio(AudioSampleEntry), Video(VideoSampleEntry), Unknown, @@ -209,45 +205,45 @@ enum SampleEntry { #[allow(non_camel_case_types)] #[derive(Debug, Clone)] -enum AudioCodecSpecific { +pub enum AudioCodecSpecific { ES_Descriptor(Vec), OpusSpecificBox(OpusSpecificBox), } #[derive(Debug, Clone)] -struct AudioSampleEntry { +pub struct AudioSampleEntry { data_reference_index: u16, - channelcount: u16, - samplesize: u16, - samplerate: u32, - codec_specific: AudioCodecSpecific, + pub channelcount: u16, + pub samplesize: u16, + pub samplerate: u32, + pub codec_specific: AudioCodecSpecific, } #[derive(Debug, Clone)] -enum VideoCodecSpecific { +pub enum VideoCodecSpecific { AVCConfig(Vec), VPxConfig(VPxConfigBox), } #[derive(Debug, Clone)] -struct VideoSampleEntry { +pub struct VideoSampleEntry { data_reference_index: u16, - width: u16, - height: u16, - codec_specific: VideoCodecSpecific, + pub width: u16, + pub height: u16, + pub codec_specific: VideoCodecSpecific, } /// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9). #[derive(Debug, Clone)] -struct VPxConfigBox { +pub struct VPxConfigBox { profile: u8, level: u8, - bit_depth: u8, - color_space: u8, // Really an enum - chroma_subsampling: u8, + pub bit_depth: u8, + pub color_space: u8, // Really an enum + pub chroma_subsampling: u8, transfer_function: u8, video_full_range: bool, - codec_init: Vec, // Empty for vp8/vp9. + pub codec_init: Vec, // Empty for vp8/vp9. } #[derive(Debug, Clone)] @@ -259,8 +255,8 @@ struct ChannelMappingTable { /// Represent an OpusSpecificBox 'dOps' #[derive(Debug, Clone)] -struct OpusSpecificBox { - version: u8, +pub struct OpusSpecificBox { + pub version: u8, output_channel_count: u8, pre_skip: u16, input_sample_rate: u32, @@ -270,73 +266,80 @@ struct OpusSpecificBox { } /// Internal data structures. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct MediaContext { - timescale: Option, + pub timescale: Option, + pub has_mvex: bool, /// Tracks found in the file. - tracks: Vec, + pub tracks: Vec, } impl MediaContext { pub fn new() -> MediaContext { - MediaContext { - timescale: None, - tracks: Vec::new(), - } + Default::default() } } #[derive(Debug)] -enum TrackType { +pub enum TrackType { Audio, Video, Unknown, } +impl Default for TrackType { + fn default() -> Self { TrackType::Unknown } +} + /// The media's global (mvhd) timescale. -#[derive(Debug, Copy, Clone)] -struct MediaTimeScale(u64); +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct MediaTimeScale(pub u64); /// A time scaled by the media's global (mvhd) timescale. -#[derive(Debug, Copy, Clone)] -struct MediaScaledTime(u64); +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct MediaScaledTime(pub u64); /// The track's local (mdhd) timescale. -#[derive(Debug, Copy, Clone)] -struct TrackTimeScale(u64, usize); +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct TrackTimeScale(pub u64, pub usize); /// A time scaled by the track's local (mdhd) timescale. -#[derive(Debug, Copy, Clone)] -struct TrackScaledTime(u64, usize); +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct TrackScaledTime(pub u64, pub usize); -#[derive(Debug)] -struct Track { +/// A fragmented file contains no sample data in stts, stsc, and stco. +#[derive(Debug, Default)] +pub struct EmptySampleTableBoxes { + pub empty_stts : bool, + pub empty_stsc : bool, + pub empty_stco : bool, +} + +/// Check boxes contain data. +impl EmptySampleTableBoxes { + pub fn all_empty(&self) -> bool { + self.empty_stts & self.empty_stsc & self.empty_stco + } +} + +#[derive(Debug, Default)] +pub struct Track { id: usize, - 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. + pub track_type: TrackType, + pub empty_duration: Option, + pub media_time: Option, + pub timescale: Option, + pub duration: Option, + pub track_id: Option, + pub mime_type: String, + pub empty_sample_boxes: EmptySampleTableBoxes, + pub data: Option, + pub tkhd: Option, // TODO(kinetik): find a nicer way to export this. } impl Track { fn new(id: usize) -> Track { - Track { - id: id, - 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, - } + Track { id: id, ..Default::default() } } } @@ -454,7 +457,7 @@ macro_rules! check_parser_state { /// Read the contents of a box, including sub boxes. /// -/// Metadata is accumulated in the passed-through MediaContext struct, +/// 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<()> { let mut found_ftyp = false; @@ -533,6 +536,10 @@ fn read_moov(f: &mut BMFFBox, context: &mut MediaContext) -> Result< try!(read_trak(&mut b, &mut track)); context.tracks.push(track); } + BoxType::MovieExtendsBox => { + context.has_mvex = true; + try!(skip_box_content(&mut b)); + } _ => try!(skip_box_content(&mut b)), }; check_parser_state!(b.content); @@ -654,10 +661,12 @@ fn read_stbl(f: &mut BMFFBox, track: &mut Track) -> Result<()> { } BoxType::TimeToSampleBox => { let stts = try!(read_stts(&mut b)); + track.empty_sample_boxes.empty_stts = stts.samples.is_empty(); log!("{:?}", stts); } BoxType::SampleToChunkBox => { let stsc = try!(read_stsc(&mut b)); + track.empty_sample_boxes.empty_stsc = stsc.samples.is_empty(); log!("{:?}", stsc); } BoxType::SampleSizeBox => { @@ -666,6 +675,7 @@ fn read_stbl(f: &mut BMFFBox, track: &mut Track) -> Result<()> { } BoxType::ChunkOffsetBox => { let stco = try!(read_stco(&mut b)); + track.empty_sample_boxes.empty_stco = stco.offsets.is_empty(); log!("{:?}", stco); } BoxType::ChunkLargeOffsetBox => { @@ -985,7 +995,7 @@ fn read_vpcc(src: &mut BMFFBox) -> Result { }) } -/// Parse OpusSpecificBox. +/// Parse `OpusSpecificBox`. fn read_dops(src: &mut BMFFBox) -> Result { let version = try!(src.read_u8()); if version != 0 { @@ -1024,13 +1034,13 @@ fn read_dops(src: &mut BMFFBox) -> Result { }) } -/// Re-serialize the Opus codec-specific config data as an OpusHead packet. +/// Re-serialize the Opus codec-specific config data as an `OpusHead` packet. /// /// Some decoders expect the initialization data in the format used by the -/// Ogg and WebM encapsulations. To support this we prepend the 'OpusHead' +/// Ogg and WebM encapsulations. To support this we prepend the `OpusHead` /// tag and byte-swap the data from big- to little-endian relative to the /// dOps box. -fn serialize_opus_header(opus: &OpusSpecificBox, dst: &mut W) -> Result<()> { +pub fn serialize_opus_header(opus: &OpusSpecificBox, dst: &mut W) -> Result<()> { match dst.write(b"OpusHead") { Err(e) => return Err(Error::from(e)), Ok(bytes) => { @@ -1151,16 +1161,14 @@ fn read_video_desc(src: &mut BMFFBox, track: &mut Track) -> Result(src: &mut BMFFBox, track: &mut Track) -> Result(src: &mut T, size: usize) -> Result< String::from_utf8(buf).map_err(From::from) } -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) -> Result { src.read_i16::().map_err(From::from) } diff --git a/media/libstagefright/binding/mp4parse/src/tests.rs b/media/libstagefright/binding/mp4parse/src/tests.rs index f217343018dc..56e8b0a2d181 100644 --- a/media/libstagefright/binding/mp4parse/src/tests.rs +++ b/media/libstagefright/binding/mp4parse/src/tests.rs @@ -6,7 +6,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::io::Cursor; -use super::*; +use super::read_mp4; +use super::MediaContext; +use super::Error; extern crate test_assembler; use self::test_assembler::*; diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml new file mode 100644 index 000000000000..ecbc8c065a3f --- /dev/null +++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "mp4parse_capi" +version = "0.5.0" +authors = [ + "Ralph Giles ", + "Matthew Gregan ", +] + +description = "Parser for ISO base media file format (mp4)" +documentation = "https://mp4parse-docs.surge.sh/mp4parse/" +license = "MPL-2.0" + +repository = "https://github.com/mozilla/mp4parse-rust" + +# Avoid complaints about trying to package test files. +exclude = [ + "*.mp4", +] + +[dependencies] +"mp4parse" = {version = "0.5.0", path = "../mp4parse"} + +[features] +fuzz = ["mp4parse/fuzz"] + +# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on. +[profile.release] +debug-assertions = true diff --git a/media/libstagefright/binding/mp4parse/build.rs b/media/libstagefright/binding/mp4parse_capi/build.rs similarity index 91% rename from media/libstagefright/binding/mp4parse/build.rs rename to media/libstagefright/binding/mp4parse_capi/build.rs index 7e3fec4cc066..66019bed52e5 100644 --- a/media/libstagefright/binding/mp4parse/build.rs +++ b/media/libstagefright/binding/mp4parse_capi/build.rs @@ -3,7 +3,6 @@ extern crate cheddar; fn main() { // Generate mp4parse.h. cheddar::Cheddar::new().expect("could not read manifest") - .module("capi").expect("invalid module path") .insert_code("// THIS FILE IS AUTOGENERATED BY mp4parse-rust/build.rs - DO NOT EDIT\n\n") .insert_code("// This Source Code Form is subject to the terms of the Mozilla Public\n") .insert_code("// License, v. 2.0. If a copy of the MPL was not distributed with this\n") diff --git a/media/libstagefright/binding/mp4parse/src/capi.rs b/media/libstagefright/binding/mp4parse_capi/src/lib.rs similarity index 88% rename from media/libstagefright/binding/mp4parse/src/capi.rs rename to media/libstagefright/binding/mp4parse_capi/src/lib.rs index 343d91e5431a..93205675a095 100644 --- a/media/libstagefright/binding/mp4parse/src/capi.rs +++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs @@ -5,7 +5,7 @@ //! # Examples //! //! ```rust -//! extern crate mp4parse; +//! extern crate mp4parse_capi; //! use std::io::Read; //! //! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { @@ -17,14 +17,16 @@ //! } //! } //! -//! let mut file = std::fs::File::open("examples/minimal.mp4").unwrap(); -//! let io = mp4parse::mp4parse_io { read: buf_read, -//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void }; +//! let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap(); +//! let io = mp4parse_capi::mp4parse_io { +//! read: buf_read, +//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void +//! }; //! unsafe { -//! let parser = mp4parse::mp4parse_new(&io); -//! let rv = mp4parse::mp4parse_read(parser); -//! assert_eq!(rv, mp4parse::mp4parse_error::MP4PARSE_OK); -//! mp4parse::mp4parse_free(parser); +//! let parser = mp4parse_capi::mp4parse_new(&io); +//! let rv = mp4parse_capi::mp4parse_read(parser); +//! assert_eq!(rv, mp4parse_capi::mp4parse_error::MP4PARSE_OK); +//! mp4parse_capi::mp4parse_free(parser); //! } //! ``` @@ -32,21 +34,24 @@ // 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/. -use std; +extern crate mp4parse; + use std::io::Read; use std::collections::HashMap; // Symbols we need from our rust api. -use MediaContext; -use TrackType; -use read_mp4; -use Error; -use media_time_to_ms; -use track_time_to_ms; -use SampleEntry; -use AudioCodecSpecific; -use VideoCodecSpecific; -use serialize_opus_header; +use mp4parse::MediaContext; +use mp4parse::TrackType; +use mp4parse::read_mp4; +use mp4parse::Error; +use mp4parse::SampleEntry; +use mp4parse::AudioCodecSpecific; +use mp4parse::VideoCodecSpecific; +use mp4parse::MediaTimeScale; +use mp4parse::MediaScaledTime; +use mp4parse::TrackTimeScale; +use mp4parse::TrackScaledTime; +use mp4parse::serialize_opus_header; // rusty-cheddar's C enum generation doesn't namespace enum members by // prefixing them, so we're forced to do it in our member names until @@ -266,13 +271,24 @@ pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, co let context = (*parser).context(); // Make sure the track count fits in a u32. - if context.tracks.len() >= u32::max_value() as usize { + if context.tracks.len() > u32::max_value() as usize { return MP4PARSE_ERROR_INVALID; } *count = context.tracks.len() as u32; MP4PARSE_OK } +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 +} + /// Fill the supplied `mp4parse_track_info` with metadata for `track`. #[no_mangle] pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_error { @@ -310,22 +326,28 @@ pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track _ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN, }; - // Maybe context & track should just have a single simple is_valid() instead? - if context.timescale.is_none() || - context.tracks[track_index].timescale.is_none() || - context.tracks[track_index].duration.is_none() || - context.tracks[track_index].track_id.is_none() { - return MP4PARSE_ERROR_INVALID; + let track = &context.tracks[track_index]; + + if let (Some(track_timescale), + Some(context_timescale), + Some(track_duration)) = (track.timescale, + context.timescale, + track.duration) { + info.media_time = track.media_time.map_or(0, |media_time| { + track_time_to_ms(media_time, track_timescale) as i64 + }) - track.empty_duration.map_or(0, |empty_duration| { + media_time_to_ms(empty_duration, context_timescale) as i64 + }); + + info.duration = track_time_to_ms(track_duration, track_timescale); + } else { + return MP4PARSE_ERROR_INVALID } - let track = &context.tracks[track_index]; - info.media_time = track.media_time.map_or(0, |media_time| { - track_time_to_ms(media_time, track.timescale.unwrap()) as i64 - }) - track.empty_duration.map_or(0, |empty_duration| { - media_time_to_ms(empty_duration, context.timescale.unwrap()) as i64 - }); - info.duration = track_time_to_ms(track.duration.unwrap(), track.timescale.unwrap()); - info.track_id = track.track_id.unwrap(); + info.track_id = match track.track_id { + Some(track_id) => track_id, + None => return MP4PARSE_ERROR_INVALID, + }; MP4PARSE_OK } @@ -438,6 +460,32 @@ pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, MP4PARSE_OK } +// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes. +#[no_mangle] +pub unsafe extern fn mp4parse_is_fragmented(parser: *mut mp4parse_parser, track_id: u32, fragmented: *mut u8) -> mp4parse_error { + if parser.is_null() || (*parser).poisoned() { + return MP4PARSE_ERROR_BADARG; + } + + let context = (*parser).context_mut(); + let tracks = &context.tracks; + (*fragmented) = false as u8; + + if !context.has_mvex { + return MP4PARSE_OK; + } + + // check sample tables. + let mut iter = tracks.iter(); + match iter.find(|track| track.track_id == Some(track_id)) { + Some(track) if track.empty_sample_boxes.all_empty() => (*fragmented) = true as u8, + Some(_) => {}, + None => return MP4PARSE_ERROR_BADARG, + } + + MP4PARSE_OK +} + #[cfg(test)] extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize { panic!("panic_read shouldn't be called in these tests"); @@ -614,7 +662,7 @@ fn get_track_count_poisoned_parser() { #[test] fn arg_validation_with_data() { unsafe { - let mut file = std::fs::File::open("examples/minimal.mp4").unwrap(); + let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap(); let io = mp4parse_io { read: valid_read, userdata: &mut file as *mut _ as *mut std::os::raw::c_void }; let parser = mp4parse_new(&io); diff --git a/toolkit/library/rust/Cargo.lock b/toolkit/library/rust/Cargo.lock index 2aed7cfa336e..0052f7300a3c 100644 --- a/toolkit/library/rust/Cargo.lock +++ b/toolkit/library/rust/Cargo.lock @@ -2,7 +2,7 @@ name = "gkrust" version = "0.1.0" dependencies = [ - "mp4parse 0.4.0", + "mp4parse_capi 0.5.0", ] [[package]] @@ -11,8 +11,15 @@ version = "0.5.3" [[package]] name = "mp4parse" -version = "0.4.0" +version = "0.5.0" dependencies = [ "byteorder 0.5.3", ] +[[package]] +name = "mp4parse_capi" +version = "0.5.0" +dependencies = [ + "mp4parse 0.5.0", +] + diff --git a/toolkit/library/rust/Cargo.toml b/toolkit/library/rust/Cargo.toml index 48c77f853aeb..81583c4d2212 100644 --- a/toolkit/library/rust/Cargo.toml +++ b/toolkit/library/rust/Cargo.toml @@ -6,7 +6,7 @@ license = "MPL-2.0" description = "Rust code for libxul" [dependencies] -mp4parse = { path = "../../../media/libstagefright/binding/mp4parse" } +mp4parse_capi = { path = "../../../media/libstagefright/binding/mp4parse_capi" } [lib] path = "lib.rs" diff --git a/toolkit/library/rust/lib.rs b/toolkit/library/rust/lib.rs index 22eba26d9e70..5f5141050e95 100644 --- a/toolkit/library/rust/lib.rs +++ b/toolkit/library/rust/lib.rs @@ -2,4 +2,4 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -extern crate mp4parse; +extern crate mp4parse_capi;