Bug 1320026 - support crypto info in rust mp4 parser. r=kinetik
authorAlfredo.Yang <ayang@mozilla.com>
Mon, 12 Dec 2016 16:00:24 +0800
changeset 354487 3664eb1f7737b4bb72da94e22ff84e65cd474411
parent 354486 64551ddca98393b52002ac9a34b9c16c2e33a888
child 354488 8f01cf080e6925bf69a22ffb17e3b6b44938a7de
push id10621
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 16:02:43 +0000
treeherdermozilla-aurora@dca7b42e6c67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs1320026
milestone53.0a1
Bug 1320026 - support crypto info in rust mp4 parser. r=kinetik MozReview-Commit-ID: 1d8QKXJ1rnh
media/libstagefright/binding/DecoderData.cpp
media/libstagefright/binding/MP4Metadata.cpp
media/libstagefright/binding/include/mp4_demuxer/DecoderData.h
media/libstagefright/binding/include/mp4parse.h
media/libstagefright/binding/mp4parse/src/boxes.rs
media/libstagefright/binding/mp4parse/src/lib.rs
media/libstagefright/binding/mp4parse/tests/bipbop-cenc-audioinit.mp4
media/libstagefright/binding/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4
media/libstagefright/binding/mp4parse/tests/public.rs
media/libstagefright/binding/mp4parse_capi/src/lib.rs
--- a/media/libstagefright/binding/DecoderData.cpp
+++ b/media/libstagefright/binding/DecoderData.cpp
@@ -9,16 +9,18 @@
 #include <media/stagefright/foundation/ABitReader.h>
 #include "media/stagefright/MetaData.h"
 #include "media/stagefright/MediaDefs.h"
 #include "media/stagefright/Utils.h"
 #include "mozilla/ArrayUtils.h"
 #include "include/ESDS.h"
 
 #ifdef MOZ_RUST_MP4PARSE
+// OpusDecoder header is really needed only by MP4 in rust
+#include "OpusDecoder.h"
 #include "mp4parse.h"
 #endif
 
 using namespace stagefright;
 
 namespace mp4_demuxer
 {
 
@@ -183,20 +185,83 @@ MP4VideoInfo::Update(const MetaData* aMe
         mCodecSpecificConfig->AppendElements(cdata, size);
       }
     }
   }
 
 }
 
 #ifdef MOZ_RUST_MP4PARSE
+static void
+UpdateTrackProtectedInfo(mozilla::TrackInfo& aConfig,
+                         const mp4parser_sinf_info& aSinf)
+{
+  if (aSinf.is_encrypted != 0) {
+    aConfig.mCrypto.mValid = true;
+    aConfig.mCrypto.mMode = aSinf.is_encrypted;
+    aConfig.mCrypto.mIVSize = aSinf.iv_size;
+    aConfig.mCrypto.mKeyId.AppendElements(aSinf.kid.data, aSinf.kid.length);
+  }
+}
+
+void
+MP4AudioInfo::Update(const mp4parse_track_info* track,
+                     const mp4parse_track_audio_info* audio)
+{
+  UpdateTrackProtectedInfo(*this, audio->protected_data);
+
+  if (track->codec == MP4PARSE_CODEC_OPUS) {
+    mMimeType = NS_LITERAL_CSTRING("audio/opus");
+    // The Opus decoder expects the container's codec delay or
+    // pre-skip value, in microseconds, as a 64-bit int at the
+    // start of the codec-specific config blob.
+    MOZ_ASSERT(audio->codec_specific_config.data);
+    MOZ_ASSERT(audio->codec_specific_config.length >= 12);
+    uint16_t preskip =
+      LittleEndian::readUint16(audio->codec_specific_config.data + 10);
+    OpusDataDecoder::AppendCodecDelay(mCodecSpecificConfig,
+        mozilla::FramesToUsecs(preskip, 48000).value());
+  } else if (track->codec == MP4PARSE_CODEC_AAC) {
+    mMimeType = MEDIA_MIMETYPE_AUDIO_AAC;
+  } else if (track->codec == MP4PARSE_CODEC_FLAC) {
+    mMimeType = MEDIA_MIMETYPE_AUDIO_FLAC;
+  } else if (track->codec == MP4PARSE_CODEC_MP3) {
+    mMimeType = MEDIA_MIMETYPE_AUDIO_MPEG;
+  }
+
+  mRate = audio->sample_rate;
+  mChannels = audio->channels;
+  mBitDepth = audio->bit_depth;
+  mDuration = track->duration;
+  mMediaTime = track->media_time;
+  mTrackId = track->track_id;
+
+  // TODO: mProfile (kKeyAACProfile in stagefright)
+
+  const uint8_t* cdata = audio->codec_specific_config.data;
+  size_t size = audio->codec_specific_config.length;
+  if (size > 0) {
+    mCodecSpecificConfig->AppendElements(cdata, size);
+
+    if (size > 1) {
+      ABitReader br(cdata, size);
+      mExtendedProfile = br.getBits(5);
+
+      if (mExtendedProfile == 31) {  // AAC-ELD => additional 6 bits
+        mExtendedProfile = 32 + br.getBits(6);
+      }
+    }
+  }
+}
+
 void
 MP4VideoInfo::Update(const mp4parse_track_info* track,
                      const mp4parse_track_video_info* video)
 {
+  UpdateTrackProtectedInfo(*this, video->protected_data);
   if (track->codec == MP4PARSE_CODEC_AVC) {
     mMimeType = MEDIA_MIMETYPE_VIDEO_AVC;
   } else if (track->codec == MP4PARSE_CODEC_VP9) {
     mMimeType = NS_LITERAL_CSTRING("video/vp9");
   }
   mTrackId = track->track_id;
   mDuration = track->duration;
   mMediaTime = track->media_time;
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -20,18 +20,16 @@
 #include "mp4_demuxer/Stream.h"
 #include "MediaPrefs.h"
 
 #include <limits>
 #include <stdint.h>
 #include <vector>
 
 #ifdef MOZ_RUST_MP4PARSE
-// OpusDecoder header is really needed only by MP4 in rust
-#include "OpusDecoder.h"
 #include "mp4parse.h"
 
 struct FreeMP4Parser { void operator()(mp4parse_parser* aPtr) { mp4parse_free(aPtr); } };
 #endif
 
 using namespace stagefright;
 
 namespace mp4_demuxer
@@ -289,25 +287,29 @@ MP4Metadata::GetTrackInfo(mozilla::Track
     MOZ_DIAGNOSTIC_ASSERT(infoRust->mKind == info->mKind);
     MOZ_DIAGNOSTIC_ASSERT(infoRust->mLabel == info->mLabel);
     MOZ_DIAGNOSTIC_ASSERT(infoRust->mLanguage == info->mLanguage);
     MOZ_DIAGNOSTIC_ASSERT(infoRust->mEnabled == info->mEnabled);
     MOZ_DIAGNOSTIC_ASSERT(infoRust->mTrackId == info->mTrackId);
     MOZ_DIAGNOSTIC_ASSERT(infoRust->mMimeType == info->mMimeType);
     MOZ_DIAGNOSTIC_ASSERT(infoRust->mDuration == info->mDuration);
     MOZ_DIAGNOSTIC_ASSERT(infoRust->mMediaTime == info->mMediaTime);
+    MOZ_DIAGNOSTIC_ASSERT(infoRust->mCrypto.mValid == info->mCrypto.mValid);
+    MOZ_DIAGNOSTIC_ASSERT(infoRust->mCrypto.mMode == info->mCrypto.mMode);
+    MOZ_DIAGNOSTIC_ASSERT(infoRust->mCrypto.mIVSize == info->mCrypto.mIVSize);
+    MOZ_DIAGNOSTIC_ASSERT(infoRust->mCrypto.mKeyId == info->mCrypto.mKeyId);
     switch (aType) {
     case mozilla::TrackInfo::kAudioTrack: {
       AudioInfo *audioRust = infoRust->GetAsAudioInfo(), *audio = info->GetAsAudioInfo();
       MOZ_DIAGNOSTIC_ASSERT(audioRust->mRate == audio->mRate);
       MOZ_DIAGNOSTIC_ASSERT(audioRust->mChannels == audio->mChannels);
       MOZ_DIAGNOSTIC_ASSERT(audioRust->mBitDepth == audio->mBitDepth);
       // TODO: These fields aren't implemented in the Rust demuxer yet.
       //MOZ_DIAGNOSTIC_ASSERT(audioRust->mProfile != audio->mProfile);
-      //MOZ_DIAGNOSTIC_ASSERT(audioRust->mExtendedProfile != audio->mExtendedProfile);
+      MOZ_DIAGNOSTIC_ASSERT(audioRust->mExtendedProfile == audio->mExtendedProfile);
       break;
     }
     case mozilla::TrackInfo::kVideoTrack: {
       VideoInfo *videoRust = infoRust->GetAsVideoInfo(), *video = info->GetAsVideoInfo();
       MOZ_DIAGNOSTIC_ASSERT(videoRust->mDisplay == video->mDisplay);
       MOZ_DIAGNOSTIC_ASSERT(videoRust->mImage == video->mImage);
       MOZ_DIAGNOSTIC_ASSERT(*videoRust->mExtraData == *video->mExtraData);
       // mCodecSpecificConfig is for video/mp4-es, not video/avc. Since video/mp4-es
@@ -794,46 +796,18 @@ MP4MetadataRust::GetTrackInfo(mozilla::T
   switch (aType) {
     case TrackInfo::TrackType::kAudioTrack: {
       mp4parse_track_audio_info audio;
       auto rv = mp4parse_get_track_audio_info(mRustParser.get(), trackIndex.value(), &audio);
       if (rv != MP4PARSE_OK) {
         MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_audio_info returned error %d", rv));
         return nullptr;
       }
-      auto track = mozilla::MakeUnique<mozilla::AudioInfo>();
-      if (info.codec == MP4PARSE_CODEC_OPUS) {
-        track->mMimeType = NS_LITERAL_CSTRING("audio/opus");
-        // The Opus decoder expects the container's codec delay or
-        // pre-skip value, in microseconds, as a 64-bit int at the
-        // start of the codec-specific config blob.
-        MOZ_ASSERT(audio.codec_specific_config.data);
-        MOZ_ASSERT(audio.codec_specific_config.length >= 12);
-        uint16_t preskip =
-          LittleEndian::readUint16(audio.codec_specific_config.data + 10);
-        MOZ_LOG(sLog, LogLevel::Debug,
-            ("Copying opus pre-skip value of %d as CodecDelay.",(int)preskip));
-        OpusDataDecoder::AppendCodecDelay(track->mCodecSpecificConfig,
-            mozilla::FramesToUsecs(preskip, 48000).value());
-      } else if (info.codec == MP4PARSE_CODEC_AAC) {
-        track->mMimeType = MEDIA_MIMETYPE_AUDIO_AAC;
-      } else if (info.codec == MP4PARSE_CODEC_FLAC) {
-        track->mMimeType = MEDIA_MIMETYPE_AUDIO_FLAC;
-      } else if (info.codec == MP4PARSE_CODEC_MP3) {
-        track->mMimeType = MEDIA_MIMETYPE_AUDIO_MPEG;
-      }
-      track->mCodecSpecificConfig->AppendElements(
-          audio.codec_specific_config.data,
-          audio.codec_specific_config.length);
-      track->mRate = audio.sample_rate;
-      track->mChannels = audio.channels;
-      track->mBitDepth = audio.bit_depth;
-      track->mDuration = info.duration;
-      track->mMediaTime = info.media_time;
-      track->mTrackId = info.track_id;
+      auto track = mozilla::MakeUnique<MP4AudioInfo>();
+      track->Update(&info, &audio);
       e = Move(track);
     }
     break;
     case TrackInfo::TrackType::kVideoTrack: {
       mp4parse_track_video_info video;
       auto rv = mp4parse_get_track_video_info(mRustParser.get(), trackIndex.value(), &video);
       if (rv != MP4PARSE_OK) {
         MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_video_info returned error %d", rv));
--- a/media/libstagefright/binding/include/mp4_demuxer/DecoderData.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/DecoderData.h
@@ -68,16 +68,21 @@ private:
 class MP4AudioInfo : public mozilla::AudioInfo
 {
 public:
   MP4AudioInfo() = default;
 
   void Update(const stagefright::MetaData* aMetaData,
               const char* aMimeType);
 
+#ifdef MOZ_RUST_MP4PARSE
+  void Update(const mp4parse_track_info* track,
+              const mp4parse_track_audio_info* audio);
+#endif
+
   virtual bool IsValid() const override;
 };
 
 class MP4VideoInfo : public mozilla::VideoInfo
 {
 public:
   MP4VideoInfo() = default;
 
--- a/media/libstagefright/binding/include/mp4parse.h
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -52,29 +52,38 @@ typedef struct mp4parse_byte_data {
 	uint32_t length;
 	uint8_t const* data;
 } mp4parse_byte_data;
 
 typedef struct mp4parse_pssh_info {
 	mp4parse_byte_data data;
 } mp4parse_pssh_info;
 
+typedef struct mp4parser_sinf_info {
+	uint32_t is_encrypted;
+	uint8_t iv_size;
+	mp4parse_byte_data kid;
+} mp4parser_sinf_info;
+
 typedef struct mp4parse_track_audio_info {
 	uint16_t channels;
 	uint16_t bit_depth;
 	uint32_t sample_rate;
+	uint16_t profile;
 	mp4parse_byte_data codec_specific_config;
+	mp4parser_sinf_info protected_data;
 } 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_byte_data extra_data;
+	mp4parser_sinf_info protected_data;
 } mp4parse_track_video_info;
 
 typedef struct mp4parse_fragment_info {
 	uint64_t fragment_duration;
 } mp4parse_fragment_info;
 
 typedef struct mp4parse_parser mp4parse_parser;
 
--- a/media/libstagefright/binding/mp4parse/src/boxes.rs
+++ b/media/libstagefright/binding/mp4parse/src/boxes.rs
@@ -55,9 +55,13 @@ box_database!(
     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"
+    SchemeInformationBox              0x73636869, // "schi"
+    TrackEncryptionBox                0x74656e63, // "tenc"
+    ProtectionSchemeInformationBox    0x73696e66, // "sinf"
+    OriginalFormatBox                 0x66726d61, // "frma"
 );
--- a/media/libstagefright/binding/mp4parse/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse/src/lib.rs
@@ -203,16 +203,17 @@ pub enum SampleEntry {
     Video(VideoSampleEntry),
     Unknown,
 }
 
 #[allow(non_camel_case_types)]
 #[derive(Debug, Clone, Default)]
 pub struct ES_Descriptor {
     pub audio_codec: CodecType,
+    pub audio_object_type: Option<u16>,
     pub audio_sample_rate: Option<u32>,
     pub audio_channel_count: Option<u16>,
     pub codec_esds: Vec<u8>,
 }
 
 #[allow(non_camel_case_types)]
 #[derive(Debug, Clone)]
 pub enum AudioCodecSpecific {
@@ -223,30 +224,32 @@ pub enum AudioCodecSpecific {
 
 #[derive(Debug, Clone)]
 pub struct AudioSampleEntry {
     data_reference_index: u16,
     pub channelcount: u16,
     pub samplesize: u16,
     pub samplerate: u32,
     pub codec_specific: AudioCodecSpecific,
+    pub protection_info: Vec<ProtectionSchemeInfoBox>,
 }
 
 #[derive(Debug, Clone)]
 pub enum VideoCodecSpecific {
     AVCConfig(Vec<u8>),
     VPxConfig(VPxConfigBox),
 }
 
 #[derive(Debug, Clone)]
 pub struct VideoSampleEntry {
     data_reference_index: u16,
     pub width: u16,
     pub height: u16,
     pub codec_specific: VideoCodecSpecific,
+    pub protection_info: Vec<ProtectionSchemeInfoBox>,
 }
 
 /// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9).
 #[derive(Debug, Clone)]
 pub struct VPxConfigBox {
     profile: u8,
     level: u8,
     pub bit_depth: u8,
@@ -301,16 +304,29 @@ pub struct ProtectionSystemSpecificHeade
     pub system_id: ByteData,
     pub kid: Vec<ByteData>,
     pub data: ByteData,
 
     // The entire pssh box (include header) required by Gecko.
     pub box_content: ByteData,
 }
 
+#[derive(Debug, Default, Clone)]
+pub struct TrackEncryptionBox {
+    pub is_encrypted: u32,
+    pub iv_size: u8,
+    pub kid: Vec<u8>,
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct ProtectionSchemeInfoBox {
+    pub code_name: String,
+    pub tenc: Option<TrackEncryptionBox>,
+}
+
 /// 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>
@@ -1228,23 +1244,26 @@ fn read_ds_descriptor(data: &[u8], esds:
              (0x4, 44100), (0x5, 32000), (0x6, 24000), (0x7, 22050),
              (0x8, 16000), (0x9, 12000), (0xa, 11025), (0xb, 8000),
              (0xc, 7350)];
 
     let des = &mut Cursor::new(data);
 
     let audio_specific_config = be_u16(des)?;
 
+    let audio_object_type = audio_specific_config >> 11;
+
     let sample_index = (audio_specific_config & 0x07FF) >> 7;
 
     let channel_counts = (audio_specific_config & 0x007F) >> 3;
 
     let sample_frequency =
         frequency_table.iter().find(|item| item.0 == sample_index).map(|x| x.1);
 
+    esds.audio_object_type = Some(audio_object_type);
     esds.audio_sample_rate = sample_frequency;
     esds.audio_channel_count = Some(channel_counts);
 
     Ok(())
 }
 
 fn read_dc_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> {
     let des = &mut Cursor::new(data);
@@ -1471,54 +1490,65 @@ fn read_video_sample_entry<T: Read>(src:
 
     let _compressorname = read_fixed_length_pascal_string(src, 32)?;
 
     // Skip uninteresting fields.
     skip(src, 4)?;
 
     // Skip clap/pasp/etc. for now.
     let mut codec_specific = None;
+    let mut protection_info = Vec::new();
     let mut iter = src.box_iter();
     while let Some(mut b) = iter.next_box()? {
         match b.head.name {
             BoxType::AVCConfigurationBox => {
                 if (name != BoxType::AVCSampleEntry &&
                     name != BoxType::AVC3SampleEntry &&
                     name != BoxType::ProtectedVisualSampleEntry) ||
                     codec_specific.is_some() {
                         return Err(Error::InvalidData("malformed video sample entry"));
                     }
                 let avcc_size = b.head.size - b.head.offset;
                 if avcc_size > BUF_SIZE_LIMIT {
                     return Err(Error::InvalidData("avcC box exceeds BUF_SIZE_LIMIT"));
                 }
                 let avcc = read_buf(&mut b.content, avcc_size as usize)?;
+                log!("{:?} (avcc)", avcc);
                 // TODO(kinetik): Parse avcC box?  For now we just stash the data.
                 codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc));
             }
             BoxType::VPCodecConfigurationBox => { // vpcC
                 if (name != BoxType::VP8SampleEntry &&
                     name != BoxType::VP9SampleEntry) ||
                     codec_specific.is_some() {
                         return Err(Error::InvalidData("malformed video sample entry"));
                     }
                 let vpcc = read_vpcc(&mut b)?;
                 codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc));
             }
+            BoxType::ProtectionSchemeInformationBox => {
+                if name != BoxType::ProtectedVisualSampleEntry {
+                    return Err(Error::InvalidData("malformed video sample entry"));
+                }
+                let sinf = read_sinf(&mut b)?;
+                log!("{:?} (sinf)", sinf);
+                protection_info.push(sinf);
+            }
             _ => skip_box_content(&mut b)?,
         }
         check_parser_state!(b.content);
     }
 
     codec_specific
         .map(|codec_specific| SampleEntry::Video(VideoSampleEntry {
             data_reference_index: data_reference_index,
             width: width,
             height: height,
             codec_specific: codec_specific,
+            protection_info: protection_info,
         }))
         .ok_or_else(|| Error::InvalidData("malformed video sample entry"))
 }
 
 fn read_qt_wave_atom<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
     let mut codec_specific = None;
     let mut iter = src.box_iter();
     while let Some(mut b) = iter.next_box()? {
@@ -1532,24 +1562,16 @@ fn read_qt_wave_atom<T: Read>(src: &mut 
     }
 
     codec_specific.ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
 }
 
 /// Parse an audio description inside an stsd box.
 fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
     let name = src.get_header().name;
-    track.codec_type = match name {
-        // TODO(kinetik): stagefright inspects ESDS to detect MP3 (audio/mpeg).
-        BoxType::MP4AudioSampleEntry => CodecType::AAC,
-        BoxType::FLACSampleEntry => CodecType::FLAC,
-        BoxType::OpusSampleEntry => CodecType::Opus,
-        BoxType::ProtectedAudioSampleEntry => CodecType::EncryptedAudio,
-        _ => CodecType::Unknown,
-    };
 
     // Skip uninteresting fields.
     skip(src, 6)?;
 
     let data_reference_index = be_u16(src)?;
 
     // XXX(kinetik): This is "reserved" in BMFF, but some old QT MOV variant
     // uses it, need to work out if we have to support it.  Without checking
@@ -1575,16 +1597,17 @@ fn read_audio_sample_entry<T: Read>(src:
             // Skip uninteresting fields.
             skip(src, 16)?;
         },
         _ => return Err(Error::Unsupported("unsupported non-isom audio sample entry")),
     }
 
     // Skip chan/etc. for now.
     let mut codec_specific = None;
+    let mut protection_info = Vec::new();
     let mut iter = src.box_iter();
     while let Some(mut b) = iter.next_box()? {
         match b.head.name {
             BoxType::ESDBox => {
                 if (name != BoxType::MP4AudioSampleEntry &&
                     name != BoxType::ProtectedAudioSampleEntry) ||
                     codec_specific.is_some() {
                         return Err(Error::InvalidData("malformed audio sample entry"));
@@ -1612,28 +1635,38 @@ fn read_audio_sample_entry<T: Read>(src:
                 track.codec_type = CodecType::Opus;
                 codec_specific = Some(AudioCodecSpecific::OpusSpecificBox(dops));
             }
             BoxType::QTWaveAtom => {
                 let qt_esds = read_qt_wave_atom(&mut b)?;
                 track.codec_type = qt_esds.audio_codec;
                 codec_specific = Some(AudioCodecSpecific::ES_Descriptor(qt_esds));
             }
+            BoxType::ProtectionSchemeInformationBox => {
+                if name != BoxType::ProtectedAudioSampleEntry {
+                    return Err(Error::InvalidData("malformed audio sample entry"));
+                }
+                let sinf = read_sinf(&mut b)?;
+                log!("{:?} (sinf)", sinf);
+                track.codec_type = CodecType::EncryptedAudio;
+                protection_info.push(sinf);
+            }
             _ => skip_box_content(&mut b)?,
         }
         check_parser_state!(b.content);
     }
 
     codec_specific
         .map(|codec_specific| SampleEntry::Audio(AudioSampleEntry {
             data_reference_index: data_reference_index,
             channelcount: channelcount,
             samplesize: samplesize,
             samplerate: samplerate,
             codec_specific: codec_specific,
+            protection_info: protection_info,
         }))
         .ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
 }
 
 /// Parse a stsd box.
 fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleDescriptionBox> {
     let (_, _) = read_fullbox_extra(src)?;
 
@@ -1677,16 +1710,75 @@ fn read_stsd<T: Read>(src: &mut BMFFBox<
     // Padding could be added in some contents.
     skip_box_remain(src)?;
 
     Ok(SampleDescriptionBox {
         descriptions: descriptions,
     })
 }
 
+fn read_sinf<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSchemeInfoBox> {
+    let mut sinf = ProtectionSchemeInfoBox::default();
+
+    let mut iter = src.box_iter();
+    while let Some(mut b) = iter.next_box()? {
+        match b.head.name {
+            BoxType::OriginalFormatBox => {
+                let frma = read_frma(&mut b)?;
+                sinf.code_name = frma;
+            },
+            BoxType::SchemeInformationBox => {
+                // We only need tenc box in schi box so far.
+                sinf.tenc = read_schi(&mut b)?;
+            },
+            _ => skip_box_content(&mut b)?,
+        }
+        check_parser_state!(b.content);
+    }
+
+    Ok(sinf)
+}
+
+fn read_schi<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<TrackEncryptionBox>> {
+    let mut tenc = None;
+    let mut iter = src.box_iter();
+    while let Some(mut b) = iter.next_box()? {
+        match b.head.name {
+            BoxType::TrackEncryptionBox => {
+                if tenc.is_some() {
+                    return Err(Error::InvalidData("tenc box should be only one at most in sinf box"));
+                }
+                tenc = Some(read_tenc(&mut b)?);
+            },
+            _ => skip_box_content(&mut b)?,
+        }
+    }
+
+    Ok(tenc)
+}
+
+fn read_tenc<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> {
+    let (_, _) = read_fullbox_extra(src)?;
+
+    let default_is_encrypted = be_u24(src)?;
+    let default_iv_size = src.read_u8()?;
+    let default_kid = read_buf(src, 16)?;
+
+    Ok(TrackEncryptionBox {
+        is_encrypted: default_is_encrypted,
+        iv_size: default_iv_size,
+        kid: default_kid,
+    })
+}
+
+fn read_frma<T: Read>(src: &mut BMFFBox<T>) -> Result<String> {
+    let code_name = read_buf(src, 4)?;
+    String::from_utf8(code_name).map_err(From::from)
+}
+
 /// Skip a number of bytes that we don't care to parse.
 fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> {
     const BUF_SIZE: usize = 64 * 1024;
     let mut buf = vec![0; BUF_SIZE];
     while bytes > 0 {
         let buf_size = cmp::min(bytes, BUF_SIZE);
         let len = src.take(buf_size as u64).read(&mut buf)?;
         if len == 0 {
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b827aa49aac417fce998e4d5541ddc8dd9438815
GIT binary patch
literal 1000
zc$}3{&2G~`5MIXtap=JnMFOcs=BFwL9FkJQg+mbq%1@;LqDTk{ZN2N*syDWFHZJtQ
zGjQh#5S$PXz=0Rw72v`Fi3i}q%sNpv0U<H+dVY7lnQs|m%c*%;<xs|qF^|_tk8Hsq
zL;qN+r9^#~^c^U7q{w-AD=%c&*QFRGQiKP&$->+H>o>w~C+@6ATQ_$jE$d!4js`+*
z#FypONO3SyV<Z1im1T`aq3TSq8S%ec<=Jm4-fY#pzsde7KAvq=%NM8vB`26`m+%)F
zZQ~@ZrgPi98(_Yg=36I?=KE+rGyAp=^=4mIyV>~uym;P!&+mOy$S}>O#FU=~WA`lC
zk3ZW`l!-!5@=%V3ZkI>si)}s>g(cm4uJU0@3xS#;{k&-6%R<Z48E;{vi?Ff1b71xg
zwBLaN;ncj5!-W5_Qi}zono-%B1Oa6Dr!doA+&p2!ix21=X&rxk)Fa;d(2TK=YQ<Hx
zL2Z{H$rKeQh%*MbfU%soF%)Z%gI7;h+U~Jy8UxOn=tJW8OXwf6S;3m5bo`4js06`+
z%m;L5Ovm5w_C~_c%ukgx6H3mw?}Kluc~uoIV3ZeNP_Mo<B>}GpZZF$a@AEp1uXK8G
or`KIaf2#t>C=(p~{PF3-PV4=*Yo{N6{dJEh$0_`|Ge05w4LiP>jsO4v
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5db21d091bc41d7b0bd9a5f0de118fef576c6eb5
GIT binary patch
literal 1094
zc$}3|&1(}u6rafkKYLgqhahR$LdAk^Xlhq49xC-CeguM$3L?sMXEwWZC%eo{w(3DV
zdG_B3c=8}#1P>m(3W9<lRxcg|^(+|Q%r<1(rUf56``&NfoA-Wi-XMf(fqIZeGVu^X
zm}GuK+my%<@S#932Kt)TS7p*-G$PLBC}vJuB((1{>TE<Rbgr~7tT+pGufFJBytL{H
zmNgbUw@cZQcZQvFIgyG9NMwEFNs@sP^DLxjn^@nC!sZ(b-X66RFQa{>1gpt2b|19O
z*{J4$S@=0hnEg>!SVvzLYS`dU$Nth2CCED1chtJcV`;}b*Sb4_<$v+FI-L&wV&EL3
zN`kJjSgKtojD`Pln1bfd>G%!}4}9;bwmWakmK)MojQm+dS+VXa;G{`<G&Y0510MAP
zFe({0NsBb+Xzr^~!GZ#nslJGv+{TeqYcbfrrING|v&njXw#QYV(qm}52}X0Th+R@0
z4&iU7MC#F$Q9g!K@NidfYk6k4i7F>S52}@eFVOpH`S_<JA71^kF)Gi0KKvF!+cIxv
zDiECL_pY0ke#mupAJ}e@n1{Vc-?GwsIXnB&t!b;Uj%g}VnTd@Au=ESK0{G#MCLqR7
z3i!0_rc{@eEb8hrTbDYi-ddk3J^kC98LefuIB9qtf~09&Ea*o0b2>3li7q@%i8*Xq
sy+N|TJ6msf^~S;?;H6Z`P<!l+pFdtdZ<U^XJ3n*t>!0iTzywC<A8D7YlK=n!
--- a/media/libstagefright/binding/mp4parse/tests/public.rs
+++ b/media/libstagefright/binding/mp4parse/tests/public.rs
@@ -4,20 +4,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/.
 
 extern crate mp4parse as mp4;
 
 use std::io::{Cursor, Read};
 use std::fs::File;
 
+static MINI_MP4: &'static str = "tests/minimal.mp4";
+static AUDIO_EME_MP4: &'static str = "tests/bipbop-cenc-audioinit.mp4";
+static VIDEO_EME_MP4: &'static str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4";
+
 // Taken from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
 #[test]
 fn public_api() {
-    let mut fd = File::open("tests/minimal.mp4").expect("Unknown file");
+    let mut fd = File::open(MINI_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");
     assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000)));
     for track in context.tracks {
@@ -68,16 +72,17 @@ fn public_api() {
                 assert_eq!(tkhd.width, 0);
                 assert_eq!(tkhd.height, 0);
 
                 // track.data part
                 assert_eq!(match a.codec_specific {
                     mp4::AudioCodecSpecific::ES_Descriptor(esds) => {
                         assert_eq!(esds.audio_codec, mp4::CodecType::AAC);
                         assert_eq!(esds.audio_sample_rate.unwrap(), 48000);
+                        assert_eq!(esds.audio_object_type.unwrap(), 2);
                         "ES"
                     }
                     mp4::AudioCodecSpecific::FLACSpecificBox(flac) => {
                         // STREAMINFO block must be present and first.
                         assert!(flac.blocks.len() > 0);
                         assert_eq!(flac.blocks[0].block_type, 0);
                         assert_eq!(flac.blocks[0].data.len(), 34);
                         "FLAC"
@@ -92,37 +97,107 @@ fn public_api() {
                 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");
+fn public_audio_tenc() {
+    let kid =
+        vec![0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04,
+             0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04];
+
+    let mut fd = File::open(AUDIO_EME_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::EncryptedAudio);
+        match track.data {
+            Some(mp4::SampleEntry::Audio(a)) => {
+                match a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+                    Some(ref p) => {
+                        assert_eq!(p.code_name, "mp4a");
+                        if let Some(ref tenc) = p.tenc {
+                            assert!(tenc.is_encrypted > 0);
+                            assert!(tenc.iv_size ==  16);
+                            assert!(tenc.kid == kid);
+                        } else {
+                            assert!(false, "Invalid test condition");
+                        }
+                    },
+                    _=> {
+                        assert!(false, "Invalid test condition");
+                    },
+                }
+            },
+            _ => {
+                assert!(false, "Invalid test condition");
+            }
+        }
+    }
+}
+
+#[test]
+fn public_video_cenc() {
+    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];
+
+    let mut fd = File::open(VIDEO_EME_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);
+        match track.data {
+            Some(mp4::SampleEntry::Video(v)) => {
+                match v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+                    Some(ref p) => {
+                        assert_eq!(p.code_name, "avc1");
+                        if let Some(ref tenc) = p.tenc {
+                            assert!(tenc.is_encrypted > 0);
+                            assert!(tenc.iv_size ==  16);
+                            assert!(tenc.kid == kid);
+                        } else {
+                            assert!(false, "Invalid test condition");
+                        }
+                    },
+                    _=> {
+                        assert!(false, "Invalid test condition");
+                    },
+                }
+            },
+            _ => {
+                assert!(false, "Invalid test condition");
+            }
+        }
     }
 
-    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/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
@@ -77,29 +77,38 @@ pub enum mp4parse_error {
 
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_track_type {
     MP4PARSE_TRACK_TYPE_VIDEO = 0,
     MP4PARSE_TRACK_TYPE_AUDIO = 1,
 }
 
+impl Default for mp4parse_track_type {
+    fn default() -> Self { mp4parse_track_type::MP4PARSE_TRACK_TYPE_VIDEO }
+}
+
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_codec {
     MP4PARSE_CODEC_UNKNOWN,
     MP4PARSE_CODEC_AAC,
     MP4PARSE_CODEC_FLAC,
     MP4PARSE_CODEC_OPUS,
     MP4PARSE_CODEC_AVC,
     MP4PARSE_CODEC_VP9,
     MP4PARSE_CODEC_MP3,
 }
 
+impl Default for mp4parse_codec {
+    fn default() -> Self { mp4parse_codec::MP4PARSE_CODEC_UNKNOWN }
+}
+
 #[repr(C)]
+#[derive(Default)]
 pub struct mp4parse_track_info {
     pub track_type: mp4parse_track_type,
     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
 }
@@ -127,38 +136,48 @@ impl mp4parse_byte_data {
 }
 
 #[repr(C)]
 #[derive(Default)]
 pub struct mp4parse_pssh_info {
     pub data: mp4parse_byte_data,
 }
 
+#[repr(C)]
 #[derive(Default)]
+pub struct mp4parser_sinf_info {
+    pub is_encrypted: u32,
+    pub iv_size: u8,
+    pub kid: mp4parse_byte_data,
+}
+
 #[repr(C)]
+#[derive(Default)]
 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
+    pub profile: u16,
     pub codec_specific_config: mp4parse_byte_data,
+    pub protected_data: mp4parser_sinf_info,
 }
 
 #[repr(C)]
+#[derive(Default)]
 pub struct mp4parse_track_video_info {
     pub display_width: u32,
     pub display_height: u32,
     pub image_width: u16,
     pub image_height: u16,
     pub extra_data: mp4parse_byte_data,
+    pub protected_data: mp4parser_sinf_info,
 }
 
 #[repr(C)]
+#[derive(Default)]
 pub struct mp4parse_fragment_info {
     pub fragment_duration: u64,
     // TODO:
     // info in trex box.
 }
 
 // Even though mp4parse_parser is opaque to C, rusty-cheddar won't let us
 // use more than one member, so we introduce *another* wrapper.
@@ -341,16 +360,19 @@ fn track_time_to_us(time: TrackScaledTim
 
 /// 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 {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return MP4PARSE_ERROR_BADARG;
     }
 
+    // Initialize fields to default values to ensure all fields are always valid.
+    *info = Default::default();
+
     let context = (*parser).context_mut();
     let track_index: usize = track_index as usize;
     let info: &mut mp4parse_track_info = &mut *info;
 
     if track_index >= context.tracks.len() {
         return MP4PARSE_ERROR_BADARG;
     }
 
@@ -424,16 +446,19 @@ pub unsafe extern fn mp4parse_get_track_
 
 /// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
 #[no_mangle]
 pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_audio_info) -> mp4parse_error {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return MP4PARSE_ERROR_BADARG;
     }
 
+    // Initialize fields to default values to ensure all fields are always valid.
+    *info = Default::default();
+
     let context = (*parser).context_mut();
 
     if track_index as usize >= context.tracks.len() {
         return MP4PARSE_ERROR_BADARG;
     }
 
     let track = &context.tracks[track_index as usize];
 
@@ -464,16 +489,19 @@ pub unsafe extern fn mp4parse_get_track_
             (*info).codec_specific_config.length = v.codec_esds.len() as u32;
             (*info).codec_specific_config.data = v.codec_esds.as_ptr();
             if let Some(rate) = v.audio_sample_rate {
                 (*info).sample_rate = rate;
             }
             if let Some(channels) = v.audio_channel_count {
                 (*info).channels = channels;
             }
+            if let Some(profile) = v.audio_object_type {
+                (*info).profile = profile;
+            }
         }
         AudioCodecSpecific::FLACSpecificBox(ref flac) => {
             // Return the STREAMINFO metadata block in the codec_specific.
             let streaminfo = &flac.blocks[0];
             if streaminfo.block_type != 0 || streaminfo.data.len() != 34 {
                 return MP4PARSE_ERROR_INVALID;
             }
             (*info).codec_specific_config.length = streaminfo.data.len() as u32;
@@ -498,26 +526,40 @@ pub unsafe extern fn mp4parse_get_track_
                             (*info).codec_specific_config.data = v.as_ptr();
                         }
                     }
                 }
             }
         }
     }
 
+    match audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+        Some(ref p) => {
+            if let Some(ref tenc) = p.tenc {
+                (*info).protected_data.is_encrypted = tenc.is_encrypted;
+                (*info).protected_data.iv_size = tenc.iv_size;
+                (*info).protected_data.kid.set_data(&(tenc.kid));
+            }
+        },
+        _ => {},
+    }
+
     MP4PARSE_OK
 }
 
 /// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
 #[no_mangle]
 pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_video_info) -> mp4parse_error {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return MP4PARSE_ERROR_BADARG;
     }
 
+    // Initialize fields to default values to ensure all fields are always valid.
+    *info = Default::default();
+
     let context = (*parser).context_mut();
 
     if track_index as usize >= context.tracks.len() {
         return MP4PARSE_ERROR_BADARG;
     }
 
     let track = &context.tracks[track_index as usize];
 
@@ -547,26 +589,40 @@ pub unsafe extern fn mp4parse_get_track_
 
     match video.codec_specific {
         VideoCodecSpecific::AVCConfig(ref avc) => {
             (*info).extra_data.set_data(avc);
         },
         _ => {},
     }
 
+    match video.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+        Some(ref p) => {
+            if let Some(ref tenc) = p.tenc {
+                (*info).protected_data.is_encrypted = tenc.is_encrypted;
+                (*info).protected_data.iv_size = tenc.iv_size;
+                (*info).protected_data.kid.set_data(&(tenc.kid));
+            }
+        },
+        _ => {},
+    }
+
     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;
     }
 
+    // Initialize fields to default values to ensure all fields are always valid.
+    *info = Default::default();
+
     let context = (*parser).context();
     let info: &mut mp4parse_fragment_info = &mut *info;
 
     info.fragment_duration = 0;
 
     let duration = match context.mvex {
         Some(ref mvex) => mvex.fragment_duration,
         None => return MP4PARSE_ERROR_INVALID,
@@ -615,16 +671,19 @@ pub unsafe extern fn mp4parse_is_fragmen
 ///   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;
     }
 
+    // Initialize fields to default values to ensure all fields are always valid.
+    *info = Default::default();
+
     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) {
@@ -738,16 +797,17 @@ fn arg_validation() {
         assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
 
         let mut dummy_video = mp4parse_track_video_info {
             display_width: 0,
             display_height: 0,
             image_width: 0,
             image_height: 0,
             extra_data: mp4parse_byte_data::default(),
+            protected_data: Default::default(),
         };
         assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
 
         let mut dummy_audio = Default::default();
         assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
     }
 }
 
@@ -783,16 +843,17 @@ fn arg_validation_with_parser() {
         assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, &mut dummy_info));
 
         let mut dummy_video = mp4parse_track_video_info {
             display_width: 0,
             display_height: 0,
             image_width: 0,
             image_height: 0,
             extra_data: mp4parse_byte_data::default(),
+            protected_data: Default::default(),
         };
         assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
 
         let mut dummy_audio = Default::default();
         assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
 
         mp4parse_free(parser);
     }
@@ -855,16 +916,17 @@ fn arg_validation_with_data() {
         assert_eq!(info.media_time, 21333);
 
         let mut video = mp4parse_track_video_info {
             display_width: 0,
             display_height: 0,
             image_width: 0,
             image_height: 0,
             extra_data: mp4parse_byte_data::default(),
+            protected_data: Default::default(),
         };
         assert_eq!(MP4PARSE_OK, mp4parse_get_track_video_info(parser, 0, &mut video));
         assert_eq!(video.display_width, 320);
         assert_eq!(video.display_height, 240);
         assert_eq!(video.image_width, 320);
         assert_eq!(video.image_height, 240);
 
         let mut audio = Default::default();
@@ -887,17 +949,19 @@ fn arg_validation_with_data() {
         assert_eq!(info.track_id, 0);
         assert_eq!(info.duration, 0);
         assert_eq!(info.media_time, 0);
 
         let mut video = mp4parse_track_video_info { display_width: 0,
                                                     display_height: 0,
                                                     image_width: 0,
                                                     image_height: 0,
-                                                    extra_data: mp4parse_byte_data::default(),};
+                                                    extra_data: mp4parse_byte_data::default(),
+                                                    protected_data: Default::default(),
+        };
         assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 3, &mut video));
         assert_eq!(video.display_width, 0);
         assert_eq!(video.display_height, 0);
         assert_eq!(video.image_width, 0);
         assert_eq!(video.image_height, 0);
 
         let mut audio = Default::default();
         assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 3, &mut audio));