Skip to content

Parse 'pssh' box for eme playback. #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 37 additions & 36 deletions mp4parse/src/boxes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
63 changes: 62 additions & 1 deletion mp4parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -294,13 +294,26 @@ pub struct MovieExtendsBox {
pub fragment_duration: Option<MediaScaledTime>,
}

pub type ByteData = Vec<u8>;

#[derive(Debug, Default)]
pub struct ProtectionSystemSpecificHeaderBox {
pub system_id: ByteData,
pub kid: Vec<ByteData>,
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 {
pub timescale: Option<MediaTimeScale>,
/// Tracks found in the file.
pub tracks: Vec<Track>,
pub mvex: Option<MovieExtendsBox>,
pub psshs: Vec<ProtectionSystemSpecificHeaderBox>
}

impl MediaContext {
Expand Down Expand Up @@ -601,13 +614,57 @@ fn read_moov<T: Read>(f: &mut BMFFBox<T>, 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);
}
Ok(())
}

fn read_pssh<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSystemSpecificHeaderBox> {
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<ByteData> = 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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for _ in 0..count would be better than a while loop here. It's a little awkward, but there's one less line of code, and count doesn't need to be mut.

}

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<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieExtendsBox> {
let mut iter = src.box_iter();
let mut fragment_duration = None;
Expand Down Expand Up @@ -1702,3 +1759,7 @@ fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
src.read_u64::<byteorder::BigEndian>().map_err(From::from)
}

fn write_be_u32<T: WriteBytesExt>(des: &mut T, num: u32) -> Result<()> {
des.write_u32::<byteorder::BigEndian>(num).map_err(From::from)
}
Binary file not shown.
32 changes: 32 additions & 0 deletions mp4parse/tests/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
1 change: 1 addition & 0 deletions mp4parse_capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exclude = [
build = "build.rs"

[dependencies]
byteorder = "0.5.0"
"mp4parse" = {version = "0.6.0", path = "../mp4parse"}

[build-dependencies]
Expand Down
67 changes: 62 additions & 5 deletions mp4parse_capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<u8>) {
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 {
Expand All @@ -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)]
Expand Down Expand Up @@ -154,6 +169,7 @@ struct Wrap {
io: mp4parse_io,
poisoned: bool,
opus_header: HashMap<u32, Vec<u8>>,
pssh_data: Vec<u8>,
}

#[repr(C)]
Expand Down Expand Up @@ -184,6 +200,10 @@ impl mp4parse_parser {
fn opus_header_mut(&mut self) -> &mut HashMap<u32, Vec<u8>> {
&mut self.0.opus_header
}

fn pssh_data_mut(&mut self) -> &mut Vec<u8> {
&mut self.0.pssh_data
}
}

#[repr(C)]
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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() {
Expand All @@ -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() {
Expand All @@ -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::<byteorder::NativeEndian>(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");
Expand Down