diff --git a/mp4parse/src/boxes.rs b/mp4parse/src/boxes.rs index 689439ad..2af02657 100644 --- a/mp4parse/src/boxes.rs +++ b/mp4parse/src/boxes.rs @@ -23,40 +23,41 @@ macro_rules! box_database { } box_database!( - FileTypeBox 0x66747970, // "ftyp" - MovieBox 0x6d6f6f76, // "moov" - MovieHeaderBox 0x6d766864, // "mvhd" - TrackBox 0x7472616b, // "trak" - TrackHeaderBox 0x746b6864, // "tkhd" - EditBox 0x65647473, // "edts" - MediaBox 0x6d646961, // "mdia" - EditListBox 0x656c7374, // "elst" - MediaHeaderBox 0x6d646864, // "mdhd" - HandlerBox 0x68646c72, // "hdlr" - MediaInformationBox 0x6d696e66, // "minf" - SampleTableBox 0x7374626c, // "stbl" - SampleDescriptionBox 0x73747364, // "stsd" - TimeToSampleBox 0x73747473, // "stts" - SampleToChunkBox 0x73747363, // "stsc" - SampleSizeBox 0x7374737a, // "stsz" - ChunkOffsetBox 0x7374636f, // "stco" - ChunkLargeOffsetBox 0x636f3634, // "co64" - SyncSampleBox 0x73747373, // "stss" - AVCSampleEntry 0x61766331, // "avc1" - AVC3SampleEntry 0x61766333, // "avc3" - Need to check official name in spec. - AVCConfigurationBox 0x61766343, // "avcC" - MP4AudioSampleEntry 0x6d703461, // "mp4a" - ESDBox 0x65736473, // "esds" - VP8SampleEntry 0x76703038, // "vp08" - VP9SampleEntry 0x76703039, // "vp09" - VPCodecConfigurationBox 0x76706343, // "vpcC" - FLACSampleEntry 0x664c6143, // "fLaC" - FLACSpecificBox 0x64664c61, // "dfLa" - OpusSampleEntry 0x4f707573, // "Opus" - 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" - MovieExtendsHeaderBox 0x6d656864, // "mehd" - QTWaveAtom 0x77617665, // "wave" - quicktime atom + FileTypeBox 0x66747970, // "ftyp" + MovieBox 0x6d6f6f76, // "moov" + MovieHeaderBox 0x6d766864, // "mvhd" + TrackBox 0x7472616b, // "trak" + TrackHeaderBox 0x746b6864, // "tkhd" + EditBox 0x65647473, // "edts" + MediaBox 0x6d646961, // "mdia" + EditListBox 0x656c7374, // "elst" + MediaHeaderBox 0x6d646864, // "mdhd" + HandlerBox 0x68646c72, // "hdlr" + MediaInformationBox 0x6d696e66, // "minf" + SampleTableBox 0x7374626c, // "stbl" + SampleDescriptionBox 0x73747364, // "stsd" + TimeToSampleBox 0x73747473, // "stts" + SampleToChunkBox 0x73747363, // "stsc" + SampleSizeBox 0x7374737a, // "stsz" + ChunkOffsetBox 0x7374636f, // "stco" + ChunkLargeOffsetBox 0x636f3634, // "co64" + SyncSampleBox 0x73747373, // "stss" + AVCSampleEntry 0x61766331, // "avc1" + AVC3SampleEntry 0x61766333, // "avc3" - Need to check official name in spec. + AVCConfigurationBox 0x61766343, // "avcC" + MP4AudioSampleEntry 0x6d703461, // "mp4a" + ESDBox 0x65736473, // "esds" + VP8SampleEntry 0x76703038, // "vp08" + VP9SampleEntry 0x76703039, // "vp09" + VPCodecConfigurationBox 0x76706343, // "vpcC" + FLACSampleEntry 0x664c6143, // "fLaC" + FLACSpecificBox 0x64664c61, // "dfLa" + OpusSampleEntry 0x4f707573, // "Opus" + 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" + MovieExtendsHeaderBox 0x6d656864, // "mehd" + QTWaveAtom 0x77617665, // "wave" - quicktime atom + ProtectionSystemSpecificHeaderBox 0x70737368, // "pssh" ); diff --git a/mp4parse/src/lib.rs b/mp4parse/src/lib.rs index 37606a5e..28f30d4e 100644 --- a/mp4parse/src/lib.rs +++ b/mp4parse/src/lib.rs @@ -9,7 +9,7 @@ extern crate afl; extern crate byteorder; -use byteorder::ReadBytesExt; +use byteorder::{ReadBytesExt, WriteBytesExt}; use std::io::{Read, Take}; use std::io::Cursor; use std::cmp; @@ -294,6 +294,18 @@ pub struct MovieExtendsBox { pub fragment_duration: Option, } +pub type ByteData = Vec; + +#[derive(Debug, Default)] +pub struct ProtectionSystemSpecificHeaderBox { + pub system_id: ByteData, + pub kid: Vec, + pub data: ByteData, + + // The entire pssh box (include header) required by Gecko. + pub box_content: ByteData, +} + /// Internal data structures. #[derive(Debug, Default)] pub struct MediaContext { @@ -301,6 +313,7 @@ pub struct MediaContext { /// Tracks found in the file. pub tracks: Vec, pub mvex: Option, + pub psshs: Vec } impl MediaContext { @@ -601,6 +614,11 @@ fn read_moov(f: &mut BMFFBox, context: &mut MediaContext) -> Result< log!("{:?}", mvex); context.mvex = Some(mvex); } + BoxType::ProtectionSystemSpecificHeaderBox => { + let pssh = try!(read_pssh(&mut b)); + log!("{:?}", pssh); + context.psshs.push(pssh); + } _ => try!(skip_box_content(&mut b)), }; check_parser_state!(b.content); @@ -608,6 +626,45 @@ fn read_moov(f: &mut BMFFBox, context: &mut MediaContext) -> Result< Ok(()) } +fn read_pssh(src: &mut BMFFBox) -> Result { + let mut box_content = Vec::with_capacity(src.head.size as usize); + try!(src.read_to_end(&mut box_content)); + + let (system_id, kid, data) = { + let pssh = &mut Cursor::new(box_content.as_slice()); + + let (version, _) = try!(read_fullbox_extra(pssh)); + + let system_id = try!(read_buf(pssh, 16)); + + let mut kid: Vec = Vec::new(); + if version > 0 { + let count = try!(be_i32(pssh)); + for _ in 0..count { + let item = try!(read_buf(pssh, 16)); + kid.push(item); + } + } + + let data_size = try!(be_i32(pssh)) as usize; + let data = try!(read_buf(pssh, data_size)); + + (system_id, kid, data) + }; + + let mut pssh_box = Vec::new(); + try!(write_be_u32(&mut pssh_box, src.head.size as u32)); + pssh_box.append(&mut b"pssh".to_vec()); + pssh_box.append(&mut box_content); + + Ok(ProtectionSystemSpecificHeaderBox { + system_id: system_id, + kid: kid, + data: data, + box_content: pssh_box, + }) +} + fn read_mvex(src: &mut BMFFBox) -> Result { let mut iter = src.box_iter(); let mut fragment_duration = None; @@ -1702,3 +1759,7 @@ fn be_u32(src: &mut T) -> Result { fn be_u64(src: &mut T) -> Result { src.read_u64::().map_err(From::from) } + +fn write_be_u32(des: &mut T, num: u32) -> Result<()> { + des.write_u32::(num).map_err(From::from) +} diff --git a/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 b/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 new file mode 100644 index 00000000..5db21d09 Binary files /dev/null and b/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 differ diff --git a/mp4parse/tests/public.rs b/mp4parse/tests/public.rs index 1d36f5f5..a17dd64d 100644 --- a/mp4parse/tests/public.rs +++ b/mp4parse/tests/public.rs @@ -95,3 +95,35 @@ fn public_api() { } } } + +#[test] +fn public_cenc() { + let mut fd = File::open("tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4").expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let mut context = mp4::MediaContext::new(); + mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed"); + for track in context.tracks { + assert_eq!(track.codec_type, mp4::CodecType::EncryptedVideo); + } + + let system_id = vec![0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b]; + + let kid = vec![0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11]; + + let pssh_box = vec![0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, + 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, + 0x00, 0x01, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, + 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00]; + + for pssh in context.psshs { + assert_eq!(pssh.system_id, system_id); + for kid_id in pssh.kid { + assert_eq!(kid_id, kid); + } + assert_eq!(pssh.data.len(), 0); + assert_eq!(pssh.box_content, pssh_box); + } +} diff --git a/mp4parse_capi/Cargo.toml b/mp4parse_capi/Cargo.toml index aeeebc65..3d91de42 100644 --- a/mp4parse_capi/Cargo.toml +++ b/mp4parse_capi/Cargo.toml @@ -21,6 +21,7 @@ exclude = [ build = "build.rs" [dependencies] +byteorder = "0.5.0" "mp4parse" = {version = "0.6.0", path = "../mp4parse"} [build-dependencies] diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index f52d8b16..e37afe26 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -35,9 +35,11 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. extern crate mp4parse; +extern crate byteorder; use std::io::Read; use std::collections::HashMap; +use byteorder::WriteBytesExt; // Symbols we need from our rust api. use mp4parse::MediaContext; @@ -103,20 +105,33 @@ pub struct mp4parse_track_info { } #[repr(C)] -pub struct mp4parse_codec_specific_config { +pub struct mp4parse_byte_data { pub length: u32, pub data: *const u8, } -impl Default for mp4parse_codec_specific_config { +impl Default for mp4parse_byte_data { fn default() -> Self { - mp4parse_codec_specific_config { + mp4parse_byte_data { length: 0, data: std::ptr::null_mut(), } } } +impl mp4parse_byte_data { + fn set_data(&mut self, data: &Vec) { + self.length = data.len() as u32; + self.data = data.as_ptr(); + } +} + +#[repr(C)] +#[derive(Default)] +pub struct mp4parse_pssh_info { + pub data: mp4parse_byte_data, +} + #[derive(Default)] #[repr(C)] pub struct mp4parse_track_audio_info { @@ -126,7 +141,7 @@ pub struct mp4parse_track_audio_info { // TODO(kinetik): // int32_t profile; // int32_t extended_profile; // check types - codec_specific_config: mp4parse_codec_specific_config, + codec_specific_config: mp4parse_byte_data, } #[repr(C)] @@ -154,6 +169,7 @@ struct Wrap { io: mp4parse_io, poisoned: bool, opus_header: HashMap>, + pssh_data: Vec, } #[repr(C)] @@ -184,6 +200,10 @@ impl mp4parse_parser { fn opus_header_mut(&mut self) -> &mut HashMap> { &mut self.0.opus_header } + + fn pssh_data_mut(&mut self) -> &mut Vec { + &mut self.0.pssh_data + } } #[repr(C)] @@ -228,6 +248,7 @@ pub unsafe extern fn mp4parse_new(io: *const mp4parse_io) -> *mut mp4parse_parse io: (*io).clone(), poisoned: false, opus_header: HashMap::new(), + pssh_data: Vec::new(), })); Box::into_raw(parser) } @@ -529,6 +550,7 @@ pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, MP4PARSE_OK } +/// Fill the supplied `mp4parse_fragment_info` with metadata from fragmented file. #[no_mangle] pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut mp4parse_parser, info: *mut mp4parse_fragment_info) -> mp4parse_error { if parser.is_null() || info.is_null() || (*parser).poisoned() { @@ -555,7 +577,7 @@ pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut mp4parse_parser, in MP4PARSE_OK } -// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes. +/// 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() { @@ -581,6 +603,41 @@ pub unsafe extern fn mp4parse_is_fragmented(parser: *mut mp4parse_parser, track_ MP4PARSE_OK } +/// Get 'pssh' system id and 'pssh' box content for eme playback. +/// +/// The data format in 'info' passing to gecko is: +/// system_id +/// pssh box size (in native endian) +/// pssh box content (including header) +#[no_mangle] +pub unsafe extern fn mp4parse_get_pssh_info(parser: *mut mp4parse_parser, info: *mut mp4parse_pssh_info) -> mp4parse_error { + if parser.is_null() || info.is_null() || (*parser).poisoned() { + return MP4PARSE_ERROR_BADARG; + } + + let context = (*parser).context_mut(); + let pssh_data = (*parser).pssh_data_mut(); + let info: &mut mp4parse_pssh_info = &mut *info; + + pssh_data.clear(); + for pssh in &context.psshs { + let mut data_len = Vec::new(); + match data_len.write_u32::(pssh.box_content.len() as u32) { + Err(_) => { + return MP4PARSE_ERROR_IO; + }, + _ => (), + } + pssh_data.extend_from_slice(pssh.system_id.as_slice()); + pssh_data.extend_from_slice(data_len.as_slice()); + pssh_data.extend_from_slice(pssh.box_content.as_slice()); + } + + info.data.set_data(&pssh_data); + + 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");