Bug 1313556 - update rust parser for pssh parsing. r?rillian draft
authorAlfredo.Yang <ayang@mozilla.com>
Thu, 10 Nov 2016 14:50:30 +0800
changeset 437006 f815951c43d656de690c3e233df05950754a422c
parent 435827 037bd1d2360a446c5c25a328dcaf70e62867509f
child 536515 762d7dc9067fa29b0af882371519060654e662d0
push id35280
push userbmo:ayang@mozilla.com
push dateThu, 10 Nov 2016 06:55:42 +0000
reviewersrillian
bugs1313556
milestone52.0a1
Bug 1313556 - update rust parser for pssh parsing. r?rillian MozReview-Commit-ID: KH8K7mizUHl
media/libstagefright/binding/include/mp4parse.h
media/libstagefright/binding/mp4parse-cargo.patch
media/libstagefright/binding/mp4parse/src/boxes.rs
media/libstagefright/binding/mp4parse/src/lib.rs
media/libstagefright/binding/mp4parse/tests/public.rs
media/libstagefright/binding/mp4parse_capi/Cargo.toml
media/libstagefright/binding/mp4parse_capi/src/lib.rs
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- a/media/libstagefright/binding/include/mp4parse.h
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -43,26 +43,30 @@ typedef enum mp4parse_codec {
 typedef struct mp4parse_track_info {
 	mp4parse_track_type track_type;
 	mp4parse_codec codec;
 	uint32_t track_id;
 	uint64_t duration;
 	int64_t media_time;
 } mp4parse_track_info;
 
-typedef struct mp4parse_codec_specific_config {
+typedef struct mp4parse_byte_data {
 	uint32_t length;
 	uint8_t const* data;
-} mp4parse_codec_specific_config;
+} mp4parse_byte_data;
+
+typedef struct mp4parse_pssh_info {
+	mp4parse_byte_data data;
+} mp4parse_pssh_info;
 
 typedef struct mp4parse_track_audio_info {
 	uint16_t channels;
 	uint16_t bit_depth;
 	uint32_t sample_rate;
-	mp4parse_codec_specific_config codec_specific_config;
+	mp4parse_byte_data codec_specific_config;
 } mp4parse_track_audio_info;
 
 typedef struct mp4parse_track_video_info {
 	uint32_t display_width;
 	uint32_t display_height;
 	uint16_t image_width;
 	uint16_t image_height;
 } mp4parse_track_video_info;
@@ -94,20 +98,30 @@ mp4parse_error mp4parse_get_track_count(
 mp4parse_error mp4parse_get_track_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_info* info);
 
 /// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
 mp4parse_error mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_audio_info* info);
 
 /// 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);
 
+/// Fill the supplied `mp4parse_fragment_info` with metadata from fragmented file.
 mp4parse_error mp4parse_get_fragment_info(mp4parse_parser* parser, mp4parse_fragment_info* info);
 
+/// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
 mp4parse_error mp4parse_is_fragmented(mp4parse_parser* parser, uint32_t track_id, uint8_t* fragmented);
 
+/// 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)
+mp4parse_error mp4parse_get_pssh_info(mp4parse_parser* parser, mp4parse_pssh_info* info);
+
 
 
 #ifdef __cplusplus
 }
 #endif
 
 
 #endif
--- a/media/libstagefright/binding/mp4parse-cargo.patch
+++ b/media/libstagefright/binding/mp4parse-cargo.patch
@@ -20,23 +20,24 @@ index ff9422c..814c4c6 100644
 -
  # 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_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 index aeeebc65..5c0836a 100644
 --- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 +++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
-@@ -18,17 +18,9 @@ exclude = [
+@@ -18,18 +18,10 @@ exclude = [
    "*.mp4",
  ]
  
 -build = "build.rs"
 -
  [dependencies]
+ byteorder = "0.5.0"
  "mp4parse" = {version = "0.6.0", path = "../mp4parse"}
  
 -[build-dependencies]
 -rusty-cheddar = "0.3.2"
 -
 -[features]
 -fuzz = ["mp4parse/fuzz"]
 -
--- a/media/libstagefright/binding/mp4parse/src/boxes.rs
+++ b/media/libstagefright/binding/mp4parse/src/boxes.rs
@@ -18,45 +18,46 @@ macro_rules! box_database {
                     _ => UnknownBox(t),
                 }
             }
         }
     }
 }
 
 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"
 );
--- a/media/libstagefright/binding/mp4parse/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse/src/lib.rs
@@ -4,17 +4,17 @@
 // 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/.
 #![cfg_attr(feature = "fuzz", feature(plugin))]
 #![cfg_attr(feature = "fuzz", plugin(afl_plugin))]
 #[cfg(feature = "fuzz")]
 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;
 
 mod boxes;
 use boxes::BoxType;
 
 // Unit tests.
@@ -289,23 +289,36 @@ pub struct OpusSpecificBox {
     channel_mapping_table: Option<ChannelMappingTable>,
 }
 
 #[derive(Debug)]
 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 {
     pub fn new() -> MediaContext {
         Default::default()
     }
 }
 
@@ -596,23 +609,67 @@ fn read_moov<T: Read>(f: &mut BMFFBox<T>
                 try!(read_trak(&mut b, &mut track));
                 context.tracks.push(track);
             }
             BoxType::MovieExtendsBox => {
                 let mvex = try!(read_mvex(&mut b));
                 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);
+            }
+        }
+
+        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;
     while let Some(mut b) = try!(iter.next_box()) {
         match b.head.name {
             BoxType::MovieExtendsHeaderBox => {
                 let duration = try!(read_mehd(&mut b));
                 fragment_duration = Some(duration);
@@ -1697,8 +1754,12 @@ fn be_u24<T: ReadBytesExt>(src: &mut T) 
 
 fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
     src.read_u32::<byteorder::BigEndian>().map_err(From::from)
 }
 
 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)
+}
--- a/media/libstagefright/binding/mp4parse/tests/public.rs
+++ b/media/libstagefright/binding/mp4parse/tests/public.rs
@@ -90,8 +90,40 @@ fn public_api() {
                 }, "ES");
                 assert!(a.samplesize > 0);
                 assert!(a.samplerate > 0);
             }
             Some(mp4::SampleEntry::Unknown) | None => {}
         }
     }
 }
+
+#[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);
+    }
+}
--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
@@ -14,13 +14,14 @@ license = "MPL-2.0"
 repository = "https://github.com/mozilla/mp4parse-rust"
 
 # Avoid complaints about trying to package test files.
 exclude = [
   "*.mp4",
 ]
 
 [dependencies]
+byteorder = "0.5.0"
 "mp4parse" = {version = "0.6.0", path = "../mp4parse"}
 
 # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
 [profile.release]
 debug-assertions = true
--- a/media/libstagefright/binding/mp4parse_capi/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
@@ -30,19 +30,21 @@
 //! }
 //! ```
 
 // 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/.
 
 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;
 use mp4parse::TrackType;
 use mp4parse::read_mp4;
 use mp4parse::Error;
 use mp4parse::SampleEntry;
 use mp4parse::AudioCodecSpecific;
@@ -98,40 +100,53 @@ pub struct mp4parse_track_info {
     pub codec: mp4parse_codec,
     pub track_id: u32,
     pub duration: u64,
     pub media_time: i64, // wants to be u64? understand how elst adjustment works
     // TODO(kinetik): include crypto guff
 }
 
 #[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 {
     pub channels: u16,
     pub bit_depth: u16,
     pub sample_rate: u32,
     // 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)]
 pub struct mp4parse_track_video_info {
     pub display_width: u32,
     pub display_height: u32,
     pub image_width: u16,
     pub image_height: u16,
@@ -149,16 +164,17 @@ pub struct mp4parse_fragment_info {
 
 // Even though mp4parse_parser is opaque to C, rusty-cheddar won't let us
 // use more than one member, so we introduce *another* wrapper.
 struct Wrap {
     context: MediaContext,
     io: mp4parse_io,
     poisoned: bool,
     opus_header: HashMap<u32, Vec<u8>>,
+    pssh_data: Vec<u8>,
 }
 
 #[repr(C)]
 #[allow(non_camel_case_types)]
 pub struct mp4parse_parser(Wrap);
 
 impl mp4parse_parser {
     fn context(&self) -> &MediaContext {
@@ -179,16 +195,20 @@ impl mp4parse_parser {
 
     fn set_poisoned(&mut self, poisoned: bool) {
         self.0.poisoned = poisoned;
     }
 
     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)]
 #[derive(Clone)]
 pub struct mp4parse_io {
     pub read: extern fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize,
     pub userdata: *mut std::os::raw::c_void,
 }
@@ -223,16 +243,17 @@ pub unsafe extern fn mp4parse_new(io: *c
     if ((*io).read as *mut std::os::raw::c_void).is_null() {
         return std::ptr::null_mut();
     }
     let parser = Box::new(mp4parse_parser(Wrap {
         context: MediaContext::new(),
         io: (*io).clone(),
         poisoned: false,
         opus_header: HashMap::new(),
+        pssh_data: Vec::new(),
     }));
     Box::into_raw(parser)
 }
 
 /// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
 #[no_mangle]
 pub unsafe extern fn mp4parse_free(parser: *mut mp4parse_parser) {
     assert!(!parser.is_null());
@@ -524,16 +545,17 @@ pub unsafe extern fn mp4parse_get_track_
         return MP4PARSE_ERROR_INVALID;
     }
     (*info).image_width = video.width;
     (*info).image_height = video.height;
 
     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() {
         return MP4PARSE_ERROR_BADARG;
     }
 
     let context = (*parser).context();
     let info: &mut mp4parse_fragment_info = &mut *info;
@@ -550,17 +572,17 @@ pub unsafe extern fn mp4parse_get_fragme
             Some(time_us) => time_us as u64,
             None => return MP4PARSE_ERROR_INVALID,
         }
     }
 
     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() {
         return MP4PARSE_ERROR_BADARG;
     }
 
     let context = (*parser).context_mut();
     let tracks = &context.tracks;
@@ -576,16 +598,51 @@ pub unsafe extern fn mp4parse_is_fragmen
         Some(track) if track.empty_sample_boxes.all_empty() => (*fragmented) = true as u8,
         Some(_) => {},
         None => return MP4PARSE_ERROR_BADARG,
     }
 
     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");
 }
 
 #[cfg(test)]
 extern fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
     -1
--- a/toolkit/library/gtest/rust/Cargo.lock
+++ b/toolkit/library/gtest/rust/Cargo.lock
@@ -51,16 +51,17 @@ dependencies = [
 [[package]]
 name = "mp4parse-gtest"
 version = "0.1.0"
 
 [[package]]
 name = "mp4parse_capi"
 version = "0.6.0"
 dependencies = [
+ "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "mp4parse 0.6.0",
 ]
 
 [[package]]
 name = "nsstring"
 version = "0.1.0"
 
 [[package]]
--- a/toolkit/library/rust/Cargo.lock
+++ b/toolkit/library/rust/Cargo.lock
@@ -45,16 +45,17 @@ version = "0.6.0"
 dependencies = [
  "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mp4parse_capi"
 version = "0.6.0"
 dependencies = [
+ "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "mp4parse 0.6.0",
 ]
 
 [[package]]
 name = "nsstring"
 version = "0.1.0"
 
 [[package]]