Bug 1513042 - Update mp4parse-rust to v0.11.2. r=jya
authorBryce Van Dyk <bvandyk@mozilla.com>
Wed, 12 Dec 2018 15:04:18 +0000
changeset 450294 2a19d8451acb31c581136143bcd6f0ff124548b3
parent 450293 239271d26c0c10091850c565f45bd9ac0c4139b6
child 450295 ae6af51f926f0451ff904fcd871083ccff3463dd
push id35197
push usercsabou@mozilla.com
push dateThu, 13 Dec 2018 03:55:02 +0000
treeherdermozilla-central@1e7843022450 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1513042
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1513042 - Update mp4parse-rust to v0.11.2. r=jya Update mp4parse-rust update script and pull the new version. This update changes the mp4parse C-API. Specifically, each track can now have multiple sample descriptions. Previously we'd just exposed the first for the entire track, and if others were available they were not exposed via the API. Because of the API change, we update the C++ interface with mp4parse-rust. We now inspect the sample info to make sure they're consistent with the parsers expectations: - Only a single codec is present for a track, multiple codecs in a track will result in us returning an error. - Only 0 or 1 crypto info is present for a track, more than one set of info will result in us returning an error. We still generalize some of the first sample info to the samples of the track, as we did before this patch. However, we will now catch the above cases explicitly. We now handle crypto information if it is not present on the first sample info. The parser will iterate through sample infos and use the first set of crypto info it finds (and fail if it finds 2+). Differential Revision: https://phabricator.services.mozilla.com/D14107
Cargo.lock
dom/media/mp4/DecoderData.cpp
dom/media/mp4/DecoderData.h
dom/media/mp4/MP4Metadata.cpp
media/mp4parse-rust/mp4parse.h
media/mp4parse-rust/mp4parse/Cargo.toml
media/mp4parse-rust/mp4parse/src/boxes.rs
media/mp4parse-rust/mp4parse/src/lib.rs
media/mp4parse-rust/mp4parse/src/tests.rs
media/mp4parse-rust/mp4parse/tests/public.rs
media/mp4parse-rust/mp4parse_capi/Cargo.toml
media/mp4parse-rust/mp4parse_capi/src/lib.rs
media/mp4parse-rust/update-rust.sh
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1044,17 +1044,17 @@ dependencies = [
  "cubeb-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_glue 0.1.0",
  "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "geckoservo 0.0.1",
  "jsrust_shared 0.1.0",
  "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "mozurl 0.0.1",
- "mp4parse_capi 0.10.1",
+ "mp4parse_capi 0.11.2",
  "netwerk_helper 0.0.1",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "prefs_parser 0.0.1",
  "profiler_helper 0.1.0",
  "rkv 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rsdparsa_capi 0.1.0",
  "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1604,36 +1604,36 @@ version = "0.1.3"
 dependencies = [
  "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rust-ini 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mp4parse"
-version = "0.10.1"
+version = "0.11.2"
 dependencies = [
  "bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "mp4parse_fallible 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mp4parse-gtest"
 version = "0.1.0"
 
 [[package]]
 name = "mp4parse_capi"
-version = "0.10.1"
+version = "0.11.2"
 dependencies = [
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "mp4parse 0.10.1",
+ "mp4parse 0.11.2",
  "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mp4parse_fallible"
 version = "0.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
--- a/dom/media/mp4/DecoderData.cpp
+++ b/dom/media/mp4/DecoderData.cpp
@@ -52,91 +52,160 @@ static void UpdateTrackProtectedInfo(moz
   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 Mp4parseTrackInfo* track,
-                          const Mp4parseTrackAudioInfo* audio) {
-  UpdateTrackProtectedInfo(*this, audio->protected_data);
+MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* track,
+                                 const Mp4parseTrackAudioInfo* audio) {
+  MOZ_DIAGNOSTIC_ASSERT(audio->sample_info_count > 0,
+                        "Must have at least one audio sample info");
+  if (audio->sample_info_count == 0) {
+    return MediaResult(
+        NS_ERROR_DOM_MEDIA_METADATA_ERR,
+        RESULT_DETAIL("Got 0 audio sample info while updating audio track"));
+  }
+
+  bool hasCrypto = false;
+  Mp4parseCodec codecType = audio->sample_info[0].codec_type;
+  for (uint32_t i = 0; i < audio->sample_info_count; i++) {
+    if (audio->sample_info[0].codec_type != codecType) {
+      // Different codecs in a single track. We don't handle this.
+      return MediaResult(
+          NS_ERROR_DOM_MEDIA_METADATA_ERR,
+          RESULT_DETAIL(
+              "Multiple codecs encountered while updating audio track"));
+    }
 
-  if (track->codec == MP4PARSE_CODEC_OPUS) {
+    // Update our encryption info if any is present on the sample info.
+    if (audio->sample_info[i].protected_data.is_encrypted) {
+      if (hasCrypto) {
+        // Multiple crypto entries found. We don't handle this.
+        return MediaResult(
+            NS_ERROR_DOM_MEDIA_METADATA_ERR,
+            RESULT_DETAIL(
+                "Multiple crypto info encountered while updating audio track"));
+      }
+      UpdateTrackProtectedInfo(*this, audio->sample_info[i].protected_data);
+      hasCrypto = true;
+    }
+  }
+
+  // We assume that the members of the first sample info are representative of
+  // the entire track. This code will need to be updated should this assumption
+  // ever not hold. E.g. if we need to handle different codecs in a single
+  // track, or if we have different numbers or channels in a single track.
+  Mp4parseByteData codecSpecificConfig =
+      audio->sample_info[0].codec_specific_config;
+  if (codecType == 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.
-    if (audio->codec_specific_config.data &&
-        audio->codec_specific_config.length >= 12) {
-      uint16_t preskip = mozilla::LittleEndian::readUint16(
-          audio->codec_specific_config.data + 10);
+    if (codecSpecificConfig.data && codecSpecificConfig.length >= 12) {
+      uint16_t preskip =
+          mozilla::LittleEndian::readUint16(codecSpecificConfig.data + 10);
       mozilla::OpusDataDecoder::AppendCodecDelay(
           mCodecSpecificConfig, mozilla::FramesToUsecs(preskip, 48000).value());
     } else {
       // This file will error later as it will be rejected by the opus decoder.
       mozilla::OpusDataDecoder::AppendCodecDelay(mCodecSpecificConfig, 0);
     }
-  } else if (track->codec == MP4PARSE_CODEC_AAC) {
+  } else if (codecType == MP4PARSE_CODEC_AAC) {
     mMimeType = NS_LITERAL_CSTRING("audio/mp4a-latm");
-  } else if (track->codec == MP4PARSE_CODEC_FLAC) {
+  } else if (codecType == MP4PARSE_CODEC_FLAC) {
     mMimeType = NS_LITERAL_CSTRING("audio/flac");
-  } else if (track->codec == MP4PARSE_CODEC_MP3) {
+  } else if (codecType == MP4PARSE_CODEC_MP3) {
     mMimeType = NS_LITERAL_CSTRING("audio/mpeg");
   }
 
-  mRate = audio->sample_rate;
-  mChannels = audio->channels;
-  mBitDepth = audio->bit_depth;
-  mExtendedProfile = audio->extended_profile;
+  mRate = audio->sample_info[0].sample_rate;
+  mChannels = audio->sample_info[0].channels;
+  mBitDepth = audio->sample_info[0].bit_depth;
+  mExtendedProfile = audio->sample_info[0].extended_profile;
   mDuration = TimeUnit::FromMicroseconds(track->duration);
   mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
   mTrackId = track->track_id;
 
   // In stagefright, mProfile is kKeyAACProfile, mExtendedProfile is kKeyAACAOT.
-  if (audio->profile <= 4) {
-    mProfile = audio->profile;
+  if (audio->sample_info[0].profile <= 4) {
+    mProfile = audio->sample_info[0].profile;
   }
 
-  if (audio->extra_data.length > 0) {
-    mExtraData->AppendElements(audio->extra_data.data,
-                               audio->extra_data.length);
+  Mp4parseByteData extraData = audio->sample_info[0].extra_data;
+  // If length is 0 we append nothing
+  mExtraData->AppendElements(extraData.data, extraData.length);
+  mCodecSpecificConfig->AppendElements(codecSpecificConfig.data,
+                                       codecSpecificConfig.length);
+  return NS_OK;
+}
+
+MediaResult MP4VideoInfo::Update(const Mp4parseTrackInfo* track,
+                                 const Mp4parseTrackVideoInfo* video) {
+  MOZ_DIAGNOSTIC_ASSERT(video->sample_info_count > 0,
+                        "Must have at least one video sample info");
+  if (video->sample_info_count == 0) {
+    return MediaResult(
+        NS_ERROR_DOM_MEDIA_METADATA_ERR,
+        RESULT_DETAIL("Got 0 audio sample info while updating video track"));
   }
 
-  if (audio->codec_specific_config.length > 0) {
-    mCodecSpecificConfig->AppendElements(audio->codec_specific_config.data,
-                                         audio->codec_specific_config.length);
-  }
-}
+  bool hasCrypto = false;
+  Mp4parseCodec codecType = video->sample_info[0].codec_type;
+  for (uint32_t i = 0; i < video->sample_info_count; i++) {
+    if (video->sample_info[0].codec_type != codecType) {
+      // Different codecs in a single track. We don't handle this.
+      return MediaResult(
+          NS_ERROR_DOM_MEDIA_METADATA_ERR,
+          RESULT_DETAIL(
+              "Multiple codecs encountered while updating video track"));
+    }
 
-void MP4VideoInfo::Update(const Mp4parseTrackInfo* track,
-                          const Mp4parseTrackVideoInfo* video) {
-  UpdateTrackProtectedInfo(*this, video->protected_data);
-  if (track->codec == MP4PARSE_CODEC_AVC) {
+    // Update our encryption info if any is present on the sample info.
+    if (video->sample_info[i].protected_data.is_encrypted) {
+      if (hasCrypto) {
+        // Multiple crypto entries found. We don't handle this.
+        return MediaResult(
+            NS_ERROR_DOM_MEDIA_METADATA_ERR,
+            RESULT_DETAIL(
+                "Multiple crypto info encountered while updating video track"));
+      }
+      UpdateTrackProtectedInfo(*this, video->sample_info[i].protected_data);
+      hasCrypto = true;
+    }
+  }
+
+  // We assume that the members of the first sample info are representative of
+  // the entire track. This code will need to be updated should this assumption
+  // ever not hold. E.g. if we need to handle different codecs in a single
+  // track, or if we have different numbers or channels in a single track.
+  if (codecType == MP4PARSE_CODEC_AVC) {
     mMimeType = NS_LITERAL_CSTRING("video/avc");
-  } else if (track->codec == MP4PARSE_CODEC_VP9) {
+  } else if (codecType == MP4PARSE_CODEC_VP9) {
     mMimeType = NS_LITERAL_CSTRING("video/vp9");
-  } else if (track->codec == MP4PARSE_CODEC_AV1) {
+  } else if (codecType == MP4PARSE_CODEC_AV1) {
     mMimeType = NS_LITERAL_CSTRING("video/av1");
-  } else if (track->codec == MP4PARSE_CODEC_MP4V) {
+  } else if (codecType == MP4PARSE_CODEC_MP4V) {
     mMimeType = NS_LITERAL_CSTRING("video/mp4v-es");
   }
   mTrackId = track->track_id;
   mDuration = TimeUnit::FromMicroseconds(track->duration);
   mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
   mDisplay.width = video->display_width;
   mDisplay.height = video->display_height;
-  mImage.width = video->image_width;
-  mImage.height = video->image_height;
+  mImage.width = video->sample_info[0].image_width;
+  mImage.height = video->sample_info[0].image_height;
   mRotation = ToSupportedRotation(video->rotation);
-  if (video->extra_data.data) {
-    mExtraData->AppendElements(video->extra_data.data,
-                               video->extra_data.length);
-  }
+  Mp4parseByteData extraData = video->sample_info[0].extra_data;
+  // If length is 0 we append nothing
+  mExtraData->AppendElements(extraData.data, extraData.length);
+  return NS_OK;
 }
 
 bool MP4VideoInfo::IsValid() const {
   return (mDisplay.width > 0 && mDisplay.height > 0) ||
          (mImage.width > 0 && mImage.height > 0);
 }
 
 }  // namespace mozilla
--- a/dom/media/mp4/DecoderData.h
+++ b/dom/media/mp4/DecoderData.h
@@ -1,16 +1,17 @@
 /* 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 http://mozilla.org/MPL/2.0/. */
 
 #ifndef DECODER_DATA_H_
 #define DECODER_DATA_H_
 
 #include "MediaInfo.h"
+#include "MediaResult.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Result.h"
 #include "mozilla/Types.h"
 #include "mozilla/Vector.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "mp4parse.h"
@@ -48,27 +49,27 @@ class CryptoFile {
   mozilla::Result<mozilla::Ok, nsresult> DoUpdate(const uint8_t* aData,
                                                   size_t aLength);
 };
 
 class MP4AudioInfo : public mozilla::AudioInfo {
  public:
   MP4AudioInfo() = default;
 
-  void Update(const Mp4parseTrackInfo* track,
-              const Mp4parseTrackAudioInfo* audio);
+  MediaResult Update(const Mp4parseTrackInfo* track,
+                     const Mp4parseTrackAudioInfo* audio);
 
   virtual bool IsValid() const override;
 };
 
 class MP4VideoInfo : public mozilla::VideoInfo {
  public:
   MP4VideoInfo() = default;
 
-  void Update(const Mp4parseTrackInfo* track,
-              const Mp4parseTrackVideoInfo* video);
+  MediaResult Update(const Mp4parseTrackInfo* track,
+                     const Mp4parseTrackVideoInfo* video);
 
   virtual bool IsValid() const override;
 };
 
 }  // namespace mozilla
 
 #endif
--- a/dom/media/mp4/MP4Metadata.cpp
+++ b/dom/media/mp4/MP4Metadata.cpp
@@ -150,17 +150,65 @@ MP4Metadata::ResultAndTrackCount MP4Meta
 
   uint32_t total = 0;
   for (uint32_t i = 0; i < tracks; ++i) {
     Mp4parseTrackInfo track_info;
     rv = mp4parse_get_track_info(mParser.get(), i, &track_info);
     if (rv != MP4PARSE_STATUS_OK) {
       continue;
     }
-    if (track_info.codec == MP4PARSE_CODEC_UNKNOWN) {
+
+    if (track_info.track_type == MP4PARSE_TRACK_TYPE_AUDIO) {
+      Mp4parseTrackAudioInfo audio;
+      auto rv = mp4parse_get_track_audio_info(mParser.get(), i, &audio);
+      if (rv != MP4PARSE_STATUS_OK) {
+        MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
+                ("mp4parse_get_track_audio_info returned error %d", rv));
+        continue;
+      }
+      MOZ_DIAGNOSTIC_ASSERT(audio.sample_info_count > 0,
+                            "Must have at least one audio sample info");
+      if (audio.sample_info_count == 0) {
+        return {
+            MediaResult(
+                NS_ERROR_DOM_MEDIA_METADATA_ERR,
+                RESULT_DETAIL(
+                    "Got 0 audio sample info while checking number tracks")),
+            MP4Metadata::NumberTracksError()};
+      }
+      // We assume the codec of the first sample info is representative of the
+      // whole track and skip it if we don't recognize the codec.
+      if (audio.sample_info[0].codec_type == MP4PARSE_CODEC_UNKNOWN) {
+        continue;
+      }
+    } else if (track_info.track_type == MP4PARSE_TRACK_TYPE_VIDEO) {
+      Mp4parseTrackVideoInfo video;
+      auto rv = mp4parse_get_track_video_info(mParser.get(), i, &video);
+      if (rv != MP4PARSE_STATUS_OK) {
+        MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
+                ("mp4parse_get_track_video_info returned error %d", rv));
+        continue;
+      }
+      MOZ_DIAGNOSTIC_ASSERT(video.sample_info_count > 0,
+                            "Must have at least one video sample info");
+      if (video.sample_info_count == 0) {
+        return {
+            MediaResult(
+                NS_ERROR_DOM_MEDIA_METADATA_ERR,
+                RESULT_DETAIL(
+                    "Got 0 video sample info while checking number tracks")),
+            MP4Metadata::NumberTracksError()};
+      }
+      // We assume the codec of the first sample info is representative of the
+      // whole track and skip it if we don't recognize the codec.
+      if (video.sample_info[0].codec_type == MP4PARSE_CODEC_UNKNOWN) {
+        continue;
+      }
+    } else {
+      // Only audio and video are supported
       continue;
     }
     if (TrackTypeEqual(aType, track_info.track_type)) {
       total += 1;
     }
   }
 
   MOZ_LOG(gMP4MetadataLog, LogLevel::Info,
@@ -214,60 +262,81 @@ MP4Metadata::ResultAndTrackInfo MP4Metad
     MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
             ("mp4parse_get_track_info returned %d", rv));
     return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                         RESULT_DETAIL("Cannot find %s track #%zu",
                                       TrackTypeToStr(aType), aTrackNumber)),
             nullptr};
   }
 #ifdef DEBUG
-  const char* codec_string = "unrecognized";
-  switch (info.codec) {
-    case MP4PARSE_CODEC_UNKNOWN:
-      codec_string = "unknown";
-      break;
-    case MP4PARSE_CODEC_AAC:
-      codec_string = "aac";
-      break;
-    case MP4PARSE_CODEC_OPUS:
-      codec_string = "opus";
-      break;
-    case MP4PARSE_CODEC_FLAC:
-      codec_string = "flac";
-      break;
-    case MP4PARSE_CODEC_ALAC:
-      codec_string = "alac";
-      break;
-    case MP4PARSE_CODEC_AVC:
-      codec_string = "h.264";
-      break;
-    case MP4PARSE_CODEC_VP9:
-      codec_string = "vp9";
-      break;
-    case MP4PARSE_CODEC_AV1:
-      codec_string = "av1";
-      break;
-    case MP4PARSE_CODEC_MP3:
-      codec_string = "mp3";
-      break;
-    case MP4PARSE_CODEC_MP4V:
-      codec_string = "mp4v";
-      break;
-    case MP4PARSE_CODEC_JPEG:
-      codec_string = "jpeg";
-      break;
-    case MP4PARSE_CODEC_AC3:
-      codec_string = "ac-3";
-      break;
-    case MP4PARSE_CODEC_EC3:
-      codec_string = "ec-3";
-      break;
+  bool haveSampleInfo = false;
+  const char* codecString = "unrecognized";
+  Mp4parseCodec codecType = MP4PARSE_CODEC_UNKNOWN;
+  if (info.track_type == MP4PARSE_TRACK_TYPE_AUDIO) {
+    Mp4parseTrackAudioInfo audio;
+    auto rv = mp4parse_get_track_audio_info(mParser.get(), trackIndex.value(),
+                                            &audio);
+    if (rv == MP4PARSE_STATUS_OK && audio.sample_info_count > 0) {
+      codecType = audio.sample_info[0].codec_type;
+      haveSampleInfo = true;
+    }
+  } else if (info.track_type == MP4PARSE_TRACK_TYPE_VIDEO) {
+    Mp4parseTrackVideoInfo video;
+    auto rv = mp4parse_get_track_video_info(mParser.get(), trackIndex.value(),
+                                            &video);
+    if (rv == MP4PARSE_STATUS_OK && video.sample_info_count > 0) {
+      codecType = video.sample_info[0].codec_type;
+      haveSampleInfo = true;
+    }
+  }
+  if (haveSampleInfo) {
+    switch (codecType) {
+      case MP4PARSE_CODEC_UNKNOWN:
+        codecString = "unknown";
+        break;
+      case MP4PARSE_CODEC_AAC:
+        codecString = "aac";
+        break;
+      case MP4PARSE_CODEC_OPUS:
+        codecString = "opus";
+        break;
+      case MP4PARSE_CODEC_FLAC:
+        codecString = "flac";
+        break;
+      case MP4PARSE_CODEC_ALAC:
+        codecString = "alac";
+        break;
+      case MP4PARSE_CODEC_AVC:
+        codecString = "h.264";
+        break;
+      case MP4PARSE_CODEC_VP9:
+        codecString = "vp9";
+        break;
+      case MP4PARSE_CODEC_AV1:
+        codecString = "av1";
+        break;
+      case MP4PARSE_CODEC_MP3:
+        codecString = "mp3";
+        break;
+      case MP4PARSE_CODEC_MP4V:
+        codecString = "mp4v";
+        break;
+      case MP4PARSE_CODEC_JPEG:
+        codecString = "jpeg";
+        break;
+      case MP4PARSE_CODEC_AC3:
+        codecString = "ac-3";
+        break;
+      case MP4PARSE_CODEC_EC3:
+        codecString = "ec-3";
+        break;
+    }
   }
   MOZ_LOG(gMP4MetadataLog, LogLevel::Debug,
-          ("track codec %s (%u)\n", codec_string, info.codec));
+          ("track codec %s (%u)\n", codecString, codecType));
 #endif
 
   // This specialization interface is crazy.
   UniquePtr<mozilla::TrackInfo> e;
   switch (aType) {
     case TrackInfo::TrackType::kAudioTrack: {
       Mp4parseTrackAudioInfo audio;
       auto rv = mp4parse_get_track_audio_info(mParser.get(), trackIndex.value(),
@@ -276,33 +345,55 @@ MP4Metadata::ResultAndTrackInfo MP4Metad
         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
                 ("mp4parse_get_track_audio_info returned error %d", rv));
         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                             RESULT_DETAIL("Cannot parse %s track #%zu",
                                           TrackTypeToStr(aType), aTrackNumber)),
                 nullptr};
       }
       auto track = mozilla::MakeUnique<MP4AudioInfo>();
-      track->Update(&info, &audio);
+      MediaResult updateStatus = track->Update(&info, &audio);
+      if (updateStatus != NS_OK) {
+        MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
+                ("Updating audio track failed with %s",
+                 updateStatus.Message().get()));
+        return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
+                            RESULT_DETAIL(
+                                "Failed to update %s track #%zu with error: %s",
+                                TrackTypeToStr(aType), aTrackNumber,
+                                updateStatus.Message().get())),
+                nullptr};
+      }
       e = std::move(track);
     } break;
     case TrackInfo::TrackType::kVideoTrack: {
       Mp4parseTrackVideoInfo video;
       auto rv = mp4parse_get_track_video_info(mParser.get(), trackIndex.value(),
                                               &video);
       if (rv != MP4PARSE_STATUS_OK) {
         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
                 ("mp4parse_get_track_video_info returned error %d", rv));
         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                             RESULT_DETAIL("Cannot parse %s track #%zu",
                                           TrackTypeToStr(aType), aTrackNumber)),
                 nullptr};
       }
       auto track = mozilla::MakeUnique<MP4VideoInfo>();
-      track->Update(&info, &video);
+      MediaResult updateStatus = track->Update(&info, &video);
+      if (updateStatus != NS_OK) {
+        MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
+                ("Updating video track failed with %s",
+                 updateStatus.Message().get()));
+        return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
+                            RESULT_DETAIL(
+                                "Failed to update %s track #%zu with error: %s",
+                                TrackTypeToStr(aType), aTrackNumber,
+                                updateStatus.Message().get())),
+                nullptr};
+      }
       e = std::move(track);
     } break;
     default:
       MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
               ("unhandled track type %d", aType));
       return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                           RESULT_DETAIL("Cannot handle %s track #%zu",
                                         TrackTypeToStr(aType), aTrackNumber)),
--- a/media/mp4parse-rust/mp4parse.h
+++ b/media/mp4parse-rust/mp4parse.h
@@ -11,16 +11,24 @@ extern "C" {
 
 // THIS FILE IS AUTOGENERATED BY mp4parse_capi/build.rs - DO NOT EDIT
 
 #include <stdint.h>
 #include <stdlib.h>
 #include <stdbool.h>
 
 typedef enum {
+  MP4_PARSE_ENCRYPTION_SCHEME_TYPE_NONE,
+  MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CENC,
+  MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CBC1,
+  MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CENS,
+  MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CBCS,
+} Mp4ParseEncryptionSchemeType;
+
+typedef enum {
   MP4PARSE_CODEC_UNKNOWN,
   MP4PARSE_CODEC_AAC,
   MP4PARSE_CODEC_FLAC,
   MP4PARSE_CODEC_OPUS,
   MP4PARSE_CODEC_AVC,
   MP4PARSE_CODEC_VP9,
   MP4PARSE_CODEC_AV1,
   MP4PARSE_CODEC_MP3,
@@ -68,48 +76,63 @@ typedef struct {
   const Mp4parseIndice *indices;
 } Mp4parseByteData;
 
 typedef struct {
   Mp4parseByteData data;
 } Mp4parsePsshInfo;
 
 typedef struct {
-  uint32_t is_encrypted;
+  Mp4ParseEncryptionSchemeType scheme_type;
+  uint8_t is_encrypted;
   uint8_t iv_size;
   Mp4parseByteData kid;
+  uint8_t crypt_byte_block;
+  uint8_t skip_byte_block;
+  Mp4parseByteData constant_iv;
 } Mp4parseSinfInfo;
 
 typedef struct {
+  Mp4parseCodec codec_type;
   uint16_t channels;
   uint16_t bit_depth;
   uint32_t sample_rate;
   uint16_t profile;
   uint16_t extended_profile;
   Mp4parseByteData codec_specific_config;
   Mp4parseByteData extra_data;
   Mp4parseSinfInfo protected_data;
+} Mp4parseTrackAudioSampleInfo;
+
+typedef struct {
+  uint32_t sample_info_count;
+  const Mp4parseTrackAudioSampleInfo *sample_info;
 } Mp4parseTrackAudioInfo;
 
 typedef struct {
   Mp4parseTrackType track_type;
-  Mp4parseCodec codec;
   uint32_t track_id;
   uint64_t duration;
   int64_t media_time;
 } Mp4parseTrackInfo;
 
 typedef struct {
-  uint32_t display_width;
-  uint32_t display_height;
+  Mp4parseCodec codec_type;
   uint16_t image_width;
   uint16_t image_height;
-  uint16_t rotation;
   Mp4parseByteData extra_data;
   Mp4parseSinfInfo protected_data;
+} Mp4parseTrackVideoSampleInfo;
+
+typedef struct {
+  uint32_t display_width;
+  uint32_t display_height;
+  uint16_t rotation;
+  uint32_t sample_info_count;
+  const Mp4parseTrackVideoSampleInfo *sample_info;
 } Mp4parseTrackVideoInfo;
 
 typedef struct {
   intptr_t (*read)(uint8_t*, uintptr_t, void*);
   void *userdata;
 } Mp4parseIo;
 
 // THIS FILE IS AUTOGENERATED BY mp4parse_capi/build.rs - DO NOT EDIT
--- a/media/mp4parse-rust/mp4parse/Cargo.toml
+++ b/media/mp4parse-rust/mp4parse/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "mp4parse"
-version = "0.10.1"
+version = "0.11.2"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
 documentation = "https://docs.rs/mp4parse/"
--- a/media/mp4parse-rust/mp4parse/src/boxes.rs
+++ b/media/mp4parse-rust/mp4parse/src/boxes.rs
@@ -35,17 +35,17 @@ macro_rules! box_database {
             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                 let fourcc: FourCC = From::from(self.clone());
                 write!(f, "{}", fourcc)
             }
         }
     }
 }
 
-#[derive(Default, PartialEq)]
+#[derive(Default, PartialEq, Clone)]
 pub struct FourCC {
     pub value: String
 }
 
 impl From<u32> for FourCC {
     fn from(number: u32) -> FourCC {
         let mut box_chars = Vec::new();
         for x in 0..4 {
@@ -132,14 +132,15 @@ box_database!(
     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"
+    SchemeTypeBox                     0x7363686d, // "schm"
     MP3AudioSampleEntry               0x2e6d7033, // ".mp3" - from F4V.
     CompositionOffsetBox              0x63747473, // "ctts"
     LPCMAudioSampleEntry              0x6C70636D, // "lpcm" - quicktime atom
     ALACSpecificBox                   0x616C6163, // "alac" - Also used by ALACSampleEntry
     UuidBox                           0x75756964, // "uuid"
 );
--- a/media/mp4parse-rust/mp4parse/src/lib.rs
+++ b/media/mp4parse-rust/mp4parse/src/lib.rs
@@ -272,18 +272,18 @@ pub struct CompositionOffsetBox {
 // Handler reference box 'hdlr'
 #[derive(Debug)]
 struct HandlerBox {
     handler_type: FourCC,
 }
 
 // Sample description box 'stsd'
 #[derive(Debug)]
-struct SampleDescriptionBox {
-    descriptions: Vec<SampleEntry>,
+pub struct SampleDescriptionBox {
+    pub descriptions: Vec<SampleEntry>,
 }
 
 #[derive(Debug, Clone)]
 pub enum SampleEntry {
     Audio(AudioSampleEntry),
     Video(VideoSampleEntry),
     Unknown,
 }
@@ -308,16 +308,17 @@ pub enum AudioCodecSpecific {
     OpusSpecificBox(OpusSpecificBox),
     ALACSpecificBox(ALACSpecificBox),
     MP3,
     LPCM,
 }
 
 #[derive(Debug, Clone)]
 pub struct AudioSampleEntry {
+    pub codec_type: CodecType,
     data_reference_index: u16,
     pub channelcount: u32,
     pub samplesize: u16,
     pub samplerate: f64,
     pub codec_specific: AudioCodecSpecific,
     pub protection_info: Vec<ProtectionSchemeInfoBox>,
 }
 
@@ -326,16 +327,17 @@ pub enum VideoCodecSpecific {
     AVCConfig(Vec<u8>),
     VPxConfig(VPxConfigBox),
     AV1Config(AV1ConfigBox),
     ESDSConfig(Vec<u8>),
 }
 
 #[derive(Debug, Clone)]
 pub struct VideoSampleEntry {
+    pub codec_type: CodecType,
     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).
@@ -419,25 +421,37 @@ pub struct ProtectionSystemSpecificHeade
     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 SchemeTypeBox {
+    pub scheme_type: FourCC,
+    pub scheme_version: u32,
+}
+
+#[derive(Debug, Default, Clone)]
 pub struct TrackEncryptionBox {
-    pub is_encrypted: u32,
+    pub is_encrypted: u8,
     pub iv_size: u8,
     pub kid: Vec<u8>,
+    // Members for pattern encryption schemes
+    pub crypt_byte_block_count: Option<u8>,
+    pub skip_byte_block_count: Option<u8>,
+    pub constant_iv: Option<Vec<u8>>,
+    // End pattern encryption scheme members
 }
 
 #[derive(Debug, Default, Clone)]
 pub struct ProtectionSchemeInfoBox {
     pub code_name: String,
+    pub scheme_type: Option<SchemeTypeBox>,
     pub tenc: Option<TrackEncryptionBox>,
 }
 
 /// Internal data structures.
 #[derive(Debug, Default)]
 pub struct MediaContext {
     pub timescale: Option<MediaTimeScale>,
     /// Tracks found in the file.
@@ -516,19 +530,18 @@ impl <T> std::ops::Add for TrackScaledTi
 pub struct Track {
     pub id: usize,
     pub track_type: TrackType,
     pub empty_duration: Option<MediaScaledTime>,
     pub media_time: Option<TrackScaledTime<u64>>,
     pub timescale: Option<TrackTimeScale<u64>>,
     pub duration: Option<TrackScaledTime<u64>>,
     pub track_id: Option<u32>,
-    pub codec_type: CodecType,
-    pub data: Option<SampleEntry>,
     pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
+    pub stsd: Option<SampleDescriptionBox>,
     pub stts: Option<TimeToSampleBox>,
     pub stsc: Option<SampleToChunkBox>,
     pub stsz: Option<SampleSizeBox>,
     pub stco: Option<ChunkOffsetBox>,   // It is for stco or co64.
     pub stss: Option<SyncSampleBox>,
     pub ctts: Option<CompositionOffsetBox>,
 }
 
@@ -957,16 +970,17 @@ fn read_minf<T: Read>(f: &mut BMFFBox<T>
 
 fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
     let mut iter = f.box_iter();
     while let Some(mut b) = iter.next_box()? {
         match b.head.name {
             BoxType::SampleDescriptionBox => {
                 let stsd = read_stsd(&mut b, track)?;
                 debug!("{:?}", stsd);
+                track.stsd = Some(stsd);
             }
             BoxType::TimeToSampleBox => {
                 let stts = read_stts(&mut b)?;
                 debug!("{:?}", stts);
                 track.stts = Some(stts);
             }
             BoxType::SampleToChunkBox => {
                 let stsc = read_stsc(&mut b)?;
@@ -1717,16 +1731,18 @@ fn read_es_descriptor(data: &[u8], esds:
     }
 
     Ok(())
 }
 
 fn read_esds<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
     let (_, _) = read_fullbox_extra(src)?;
 
+    // Subtract 4 extra to offset the members of fullbox not accounted for in
+    // head.offset
     let esds_size = src.head.size - src.head.offset - 4;
     let esds_array = read_buf(src, esds_size as usize)?;
 
     let mut es_data = ES_Descriptor::default();
     find_descriptor(&esds_array, &mut es_data)?;
 
     es_data.codec_esds = esds_array;
 
@@ -1884,17 +1900,17 @@ fn read_hdlr<T: Read>(src: &mut BMFFBox<
     skip_box_remain(src)?;
 
     Ok(HandlerBox {
         handler_type: handler_type,
     })
 }
 
 /// Parse an video description inside an stsd box.
-fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType, SampleEntry)> {
+fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleEntry> {
     let name = src.get_header().name;
     let codec_type = match name {
         BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264,
         BoxType::MP4VideoSampleEntry => CodecType::MP4V,
         BoxType::VP8SampleEntry => CodecType::VP8,
         BoxType::VP9SampleEntry => CodecType::VP9,
         BoxType::AV1SampleEntry => CodecType::AV1,
         BoxType::ProtectedVisualSampleEntry => CodecType::EncryptedVideo,
@@ -1954,16 +1970,18 @@ fn read_video_sample_entry<T: Read>(src:
               let av1c = read_av1c(&mut b)?;
               codec_specific = Some(VideoCodecSpecific::AV1Config(av1c));
             }
             BoxType::ESDBox => {
                 if name != BoxType::MP4VideoSampleEntry || codec_specific.is_some() {
                     return Err(Error::InvalidData("malformed video sample entry"));
                 }
                 let (_, _) = read_fullbox_extra(&mut b.content)?;
+                // Subtract 4 extra to offset the members of fullbox not
+                // accounted for in head.offset
                 let esds_size = b.head.size - b.head.offset - 4;
                 let esds = read_buf(&mut b.content, esds_size as usize)?;
                 codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds));
             }
             BoxType::ProtectionSchemeInformationBox => {
                 if name != BoxType::ProtectedVisualSampleEntry {
                     return Err(Error::InvalidData("malformed video sample entry"));
                 }
@@ -1974,24 +1992,25 @@ fn read_video_sample_entry<T: Read>(src:
             _ => {
                 debug!("Unsupported video codec, box {:?} found", b.head.name);
                 skip_box_content(&mut b)?;
             }
         }
         check_parser_state!(b.content);
     }
 
-    Ok(codec_specific.map_or((CodecType::Unknown, SampleEntry::Unknown),
-        |codec_specific| (codec_type, SampleEntry::Video(VideoSampleEntry {
+    Ok(codec_specific.map_or(SampleEntry::Unknown,
+        |codec_specific| SampleEntry::Video(VideoSampleEntry {
+            codec_type: codec_type,
             data_reference_index: data_reference_index,
             width: width,
             height: height,
             codec_specific: codec_specific,
             protection_info: protection_info,
-        })))
+        }))
     )
 }
 
 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()? {
         match b.head.name {
@@ -2002,17 +2021,17 @@ fn read_qt_wave_atom<T: Read>(src: &mut 
             _ => skip_box_content(&mut b)?,
         }
     }
 
     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>) -> Result<(CodecType, SampleEntry)> {
+fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleEntry> {
     let name = src.get_header().name;
 
     // 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
@@ -2114,65 +2133,57 @@ fn read_audio_sample_entry<T: Read>(src:
             _ => {
                 debug!("Unsupported audio codec, box {:?} found", b.head.name);
                 skip_box_content(&mut b)?;
             }
         }
         check_parser_state!(b.content);
     }
 
-    Ok(codec_specific.map_or((CodecType::Unknown, SampleEntry::Unknown),
-        |codec_specific| (codec_type, SampleEntry::Audio(AudioSampleEntry {
+    Ok(codec_specific.map_or(SampleEntry::Unknown,
+        |codec_specific| SampleEntry::Audio(AudioSampleEntry {
+            codec_type: codec_type,
             data_reference_index: data_reference_index,
             channelcount: channelcount,
             samplesize: samplesize,
             samplerate: samplerate,
             codec_specific: codec_specific,
             protection_info: protection_info,
-        })))
+        }))
     )
 }
 
 /// Parse a stsd box.
 fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleDescriptionBox> {
     let (_, _) = read_fullbox_extra(src)?;
 
     let description_count = be_u32(src)?;
     let mut descriptions = Vec::new();
 
     {
-        // TODO(kinetik): check if/when more than one desc per track? do we need to support?
         let mut iter = src.box_iter();
         while let Some(mut b) = iter.next_box()? {
             let description = match track.track_type {
                 TrackType::Video => read_video_sample_entry(&mut b),
                 TrackType::Audio => read_audio_sample_entry(&mut b),
                 TrackType::Metadata => Err(Error::Unsupported("metadata track")),
                 TrackType::Unknown => Err(Error::Unsupported("unknown track type")),
             };
             let description = match description {
-                Ok((codec_type, desc)) => {
-                    track.codec_type = codec_type;
-                    desc
-                }
+                Ok(desc) => desc,
                 Err(Error::Unsupported(_)) => {
                     // read_{audio,video}_desc may have returned Unsupported
                     // after partially reading the box content, so we can't
                     // simply use skip_box_content here.
                     let to_skip = b.bytes_left();
                     skip(&mut b, to_skip)?;
                     SampleEntry::Unknown
-                }
+                },
                 Err(e) => return Err(e),
             };
-            if track.data.is_none() {
-                track.data = Some(description.clone());
-            } else {
-                debug!("** don't know how to handle multiple descriptions **");
-            }
             vec_push(&mut descriptions, description)?;
             check_parser_state!(b.content);
             if descriptions.len() == description_count as usize {
                 break;
             }
         }
     }
 
@@ -2189,16 +2200,19 @@ fn read_sinf<T: Read>(src: &mut BMFFBox<
 
     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::SchemeTypeBox => {
+                sinf.scheme_type = Some(read_schm(&mut b)?);
+            }
             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);
     }
@@ -2220,34 +2234,74 @@ fn read_schi<T: Read>(src: &mut BMFFBox<
             _ => skip_box_content(&mut b)?,
         }
     }
 
     Ok(tenc)
 }
 
 fn read_tenc<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> {
-    let (_, _) = read_fullbox_extra(src)?;
+    let (version, _) = read_fullbox_extra(src)?;
 
-    let default_is_encrypted = be_u24(src)?;
+    // reserved byte
+    skip(src, 1)?;
+    // the next byte is used to signal the default pattern in version >= 1
+    let (default_crypt_byte_block, default_skip_byte_block) = match version {
+        0 => {
+            skip(src, 1)?;
+            (None, None)
+        },
+        _ => {
+            let pattern_byte = src.read_u8()?;
+            let crypt_bytes = pattern_byte >> 4;
+            let skip_bytes = pattern_byte & 0x0f;
+            (Some(crypt_bytes), Some(skip_bytes))
+        }
+    };
+    let default_is_encrypted = src.read_u8()?;
     let default_iv_size = src.read_u8()?;
     let default_kid = read_buf(src, 16)?;
+    // If default_is_encrypted == 1 && default_iv_size == 0 we expect a default_constant_iv
+    let default_constant_iv = match (default_is_encrypted, default_iv_size) {
+        (1, 0) => {
+            let default_constant_iv_size = src.read_u8()?;
+            Some(read_buf(src, default_constant_iv_size as usize)?)
+        },
+        _ => None,
+    };
 
     Ok(TrackEncryptionBox {
         is_encrypted: default_is_encrypted,
         iv_size: default_iv_size,
         kid: default_kid,
+        crypt_byte_block_count: default_crypt_byte_block,
+        skip_byte_block_count: default_skip_byte_block,
+        constant_iv: default_constant_iv
     })
 }
 
 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)
 }
 
+fn read_schm<T: Read>(src: &mut BMFFBox<T>) -> Result<SchemeTypeBox> {
+    // Flags can be used to signal presence of URI in the box, but we don't
+    // use the URI so don't bother storing the flags.
+    let (_, _) = read_fullbox_extra(src)?;
+    let scheme_type =  FourCC::from(be_u32(src)?);
+    let scheme_version = be_u32(src)?;
+    // Null terminated scheme URI may follow, but we don't use it right now.
+    skip_box_remain(src)?;
+    Ok(SchemeTypeBox {
+        scheme_type: scheme_type,
+        scheme_version: scheme_version,
+    })
+}
+
 /// 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 {
--- a/media/mp4parse-rust/mp4parse/src/tests.rs
+++ b/media/mp4parse-rust/mp4parse/src/tests.rs
@@ -937,19 +937,23 @@ fn read_qt_wave_atom() {
          .B32(48000 << 16)
          .append_repeated(0, 16)
          .append_bytes(wave.as_slice())
          .append_bytes(chan.as_slice())
     });
 
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
-    let (codec_type, _) = super::read_audio_sample_entry(&mut stream)
+    let sample_entry = super::read_audio_sample_entry(&mut stream)
           .expect("fail to read qt wave atom");
-    assert_eq!(codec_type, super::CodecType::MP3);
+    match sample_entry {
+        super::SampleEntry::Audio(sample_entry) =>
+          assert_eq!(sample_entry.codec_type, super::CodecType::MP3),
+        _ => assert!(false, "fail to read audio sample enctry"),
+    }
 }
 
 #[test]
 fn read_descriptor_80() {
     let aac_esds =
         vec![
             0x03, 0x80, 0x80, 0x80, 0x22, 0x00, 0x02, 0x00,
             0x04, 0x80, 0x80, 0x80, 0x17, 0x40, 0x15, 0x00,
@@ -964,16 +968,17 @@ fn read_descriptor_80() {
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
 
     let es = super::read_esds(&mut stream).unwrap();
 
     assert_eq!(es.audio_codec, super::CodecType::AAC);
     assert_eq!(es.audio_object_type, Some(2));
+    assert_eq!(es.extended_audio_object_type, None);
     assert_eq!(es.audio_sample_rate, Some(48000));
     assert_eq!(es.audio_channel_count, Some(2));
     assert_eq!(es.codec_esds, aac_esds);
     assert_eq!(es.decoder_specific_data, aac_dc_descriptor);
 }
 
 #[test]
 fn read_esds() {
@@ -993,23 +998,57 @@ fn read_esds() {
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
 
     let es = super::read_esds(&mut stream).unwrap();
 
     assert_eq!(es.audio_codec, super::CodecType::AAC);
     assert_eq!(es.audio_object_type, Some(2));
+    assert_eq!(es.extended_audio_object_type, None);
     assert_eq!(es.audio_sample_rate, Some(24000));
     assert_eq!(es.audio_channel_count, Some(6));
     assert_eq!(es.codec_esds, aac_esds);
     assert_eq!(es.decoder_specific_data, aac_dc_descriptor);
 }
 
 #[test]
+fn read_esds_aac_type5() {
+    let aac_esds =
+        vec![
+            0x03, 0x80, 0x80, 0x80,
+            0x2F, 0x00, 0x00, 0x00, 0x04, 0x80, 0x80, 0x80,
+            0x21, 0x40, 0x15, 0x00, 0x15, 0x00, 0x00, 0x03,
+            0xED, 0xAA, 0x00, 0x03, 0x6B, 0x00, 0x05, 0x80,
+            0x80, 0x80, 0x0F, 0x2B, 0x01, 0x88, 0x02, 0xC4,
+            0x04, 0x90, 0x2C, 0x10, 0x8C, 0x80, 0x00, 0x00,
+            0xED, 0x40, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02,
+        ];
+
+    let aac_dc_descriptor = &aac_esds[31 .. 46];
+
+    let mut stream = make_box(BoxSize::Auto, b"esds", |s| {
+        s.B32(0) // reserved
+         .append_bytes(aac_esds.as_slice())
+    });
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+
+    let es = super::read_esds(&mut stream).unwrap();
+
+    assert_eq!(es.audio_codec, super::CodecType::AAC);
+    assert_eq!(es.audio_object_type, Some(2));
+    assert_eq!(es.extended_audio_object_type, Some(5));
+    assert_eq!(es.audio_sample_rate, Some(24000));
+    assert_eq!(es.audio_channel_count, Some(8));
+    assert_eq!(es.codec_esds, aac_esds);
+    assert_eq!(es.decoder_specific_data, aac_dc_descriptor);
+}
+
+#[test]
 fn read_stsd_mp4v() {
     let mp4v =
         vec![
                                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x01, 0xe0, 0x00, 0x48,
             0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
             0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
@@ -1028,22 +1067,21 @@ fn read_stsd_mp4v() {
     println!("esds_specific_data {:?}", esds_specific_data);
 
     let mut stream = make_box(BoxSize::Auto, b"mp4v", |s| {
         s.append_bytes(mp4v.as_slice())
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
 
-    let (codec_type, sample_entry) = super::read_video_sample_entry(&mut stream).unwrap();
-
-    assert_eq!(codec_type, super::CodecType::MP4V);
+    let sample_entry = super::read_video_sample_entry(&mut stream).unwrap();
 
     match sample_entry {
         super::SampleEntry::Video(v) => {
+            assert_eq!(v.codec_type, super::CodecType::MP4V);
             assert_eq!(v.width, 720);
             assert_eq!(v.height, 480);
             match v.codec_specific {
                 super::VideoCodecSpecific::ESDSConfig(esds_data) => {
                     assert_eq!(esds_data, esds_specific_data.to_vec());
                 },
                 _ => panic!("it should be ESDSConfig!"),
             }
@@ -1069,16 +1107,17 @@ fn read_esds_one_byte_extension_descript
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
 
     let es = super::read_esds(&mut stream).unwrap();
 
     assert_eq!(es.audio_codec, super::CodecType::AAC);
     assert_eq!(es.audio_object_type, Some(2));
+    assert_eq!(es.extended_audio_object_type, None);
     assert_eq!(es.audio_sample_rate, Some(48000));
     assert_eq!(es.audio_channel_count, Some(2));
 }
 
 #[test]
 fn read_esds_byte_extension_descriptor() {
     let mut stream = make_box(BoxSize::Auto, b"esds", |s| {
         s.B32(0) // reserved
@@ -1105,19 +1144,23 @@ fn read_f4v_stsd() {
          .B16(2)
          .B16(16)
          .append_repeated(0, 4)
          .B32(48000 << 16)
     });
 
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
-    let (codec_type, _) = super::read_audio_sample_entry(&mut stream)
+    let sample_entry = super::read_audio_sample_entry(&mut stream)
           .expect("failed to read f4v stsd atom");
-    assert_eq!(codec_type, super::CodecType::MP3);
+    match sample_entry {
+        super::SampleEntry::Audio(sample_entry) =>
+          assert_eq!(sample_entry.codec_type, super::CodecType::MP3),
+        _ => assert!(false, "fail to read audio sample enctry"),
+    }
 }
 
 #[test]
 fn max_table_limit() {
     let elst = make_fullbox(BoxSize::Auto, b"elst", 1, |s| {
         s.B32(super::TABLE_SIZE_LIMIT + 1)
     }).into_inner();
     let mut stream = make_box(BoxSize::Auto, b"edts", |s| {
@@ -1147,17 +1190,17 @@ fn unknown_video_sample_entry() {
          .append_repeated(0, 14)
          .append_repeated(0, 32)
          .append_repeated(0, 4)
          .append_bytes(unknown_codec.as_slice())
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
     match super::read_video_sample_entry(&mut stream) {
-        Ok((super::CodecType::Unknown, super::SampleEntry::Unknown)) => (),
+        Ok(super::SampleEntry::Unknown) => (),
         _ => panic!("expected a different error result"),
     }
 }
 
 #[test]
 fn unknown_audio_sample_entry() {
     let unknown_codec = make_box(BoxSize::Auto, b"yyyy", |s| {
         s.append_repeated(0, 16)
@@ -1172,31 +1215,31 @@ fn unknown_audio_sample_entry() {
          .B16(0)
          .B16(0)
          .B32(48000 << 16)
          .append_bytes(unknown_codec.as_slice())
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
     match super::read_audio_sample_entry(&mut stream) {
-        Ok((super::CodecType::Unknown, super::SampleEntry::Unknown)) => (),
+        Ok(super::SampleEntry::Unknown) => (),
         _ => panic!("expected a different error result"),
     }
 }
 
 #[test]
 fn read_esds_invalid_descriptor() {
     // tag 0x06, 0xff, 0x7f is incorrect.
     let esds =
         vec![
                   0x03, 0x80, 0x80, 0x80, 0x22, 0x00, 0x00,
             0x00, 0x04, 0x80, 0x80, 0x80, 0x14, 0x40, 0x01,
             0x00, 0x04, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00,
             0x00, 0xfa, 0x00, 0x05, 0x80, 0x80, 0x80, 0x02,
-            0xe8, 0x35, 0x06, 0xff, 0x7f, 0x00, 0x00, 0x02,
+            0xe8, 0x35, 0x06, 0xff, 0x7f, 0x00, 0x00,
         ];
 
     let mut stream = make_box(BoxSize::Auto, b"esds", |s| {
         s.B32(0) // reserved
          .append_bytes(esds.as_slice())
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
@@ -1276,22 +1319,21 @@ fn read_stsd_lpcm() {
         ];
 
     let mut stream = make_box(BoxSize::Auto, b"lpcm", |s| {
         s.append_bytes(lpcm.as_slice())
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
 
-    let (codec_type, sample_entry) = super::read_audio_sample_entry(&mut stream).unwrap();
-
-    assert_eq!(codec_type, super::CodecType::LPCM);
+    let sample_entry = super::read_audio_sample_entry(&mut stream).unwrap();
 
     match sample_entry {
         super::SampleEntry::Audio(a) => {
+            assert_eq!(a.codec_type, super::CodecType::LPCM);
             assert_eq!(a.samplerate, 96000.0);
             assert_eq!(a.channelcount, 1);
             match a.codec_specific {
                 super::AudioCodecSpecific::LPCM => (),
                 _ => panic!("it should be LPCM!"),
             }
         },
         _ => panic!("it should be a audio sample entry!"),
--- a/media/mp4parse-rust/mp4parse/tests/public.rs
+++ b/media/mp4parse-rust/mp4parse/tests/public.rs
@@ -5,161 +5,188 @@
 // 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";
+static AUDIO_EME_CENC_MP4: &'static str = "tests/bipbop-cenc-audioinit.mp4";
+static VIDEO_EME_CENC_MP4: &'static str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4";
+// The cbcs files were created via shaka-packager from Firefox's test suite's bipbop.mp4 using:
+// packager-win.exe
+// in=bipbop.mp4,stream=audio,init_segment=bipbop_cbcs_audio_init.mp4,segment_template=bipbop_cbcs_audio_$Number$.m4s
+// in=bipbop.mp4,stream=video,init_segment=bipbop_cbcs_video_init.mp4,segment_template=bipbop_cbcs_video_$Number$.m4s
+// --protection_scheme cbcs --enable_raw_key_encryption
+// --keys label=:key_id=7e571d047e571d047e571d047e571d21:key=7e5744447e5744447e5744447e574421
+// --iv 11223344556677889900112233445566
+// --generate_static_mpd --mpd_output bipbop_cbcs.mpd
+// note: only the init files are needed for these tests
+static AUDIO_EME_CBCS_MP4: &'static str = "tests/bipbop_cbcs_audio_init.mp4";
+static VIDEO_EME_CBCS_MP4: &'static str = "tests/bipbop_cbcs_video_init.mp4";
 static VIDEO_AV1_MP4: &'static str = "tests/tiny_av1.mp4";
 
-// Taken from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
+// Adapted from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
 #[test]
 fn public_api() {
     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 {
-        match track.data {
-            Some(mp4::SampleEntry::Video(v)) => {
+        match track.track_type {
+            mp4::TrackType::Video => {
                 // track part
                 assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0)));
                 assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
                 assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0)));
                 assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12800, 0)));
-                assert_eq!(v.width, 320);
-                assert_eq!(v.height, 240);
 
                 // track.tkhd part
                 let tkhd = track.tkhd.unwrap();
                 assert_eq!(tkhd.disabled, false);
                 assert_eq!(tkhd.duration, 40);
                 assert_eq!(tkhd.width, 20971520);
                 assert_eq!(tkhd.height, 15728640);
 
-                // track.data part
+                // track.stsd part
+                let stsd = track.stsd.expect("expected an stsd");
+                let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
+                    mp4::SampleEntry::Video(v) => v,
+                    _ => panic!("expected a VideoSampleEntry"),
+                };
+                assert_eq!(v.width, 320);
+                assert_eq!(v.height, 240);
                 assert_eq!(match v.codec_specific {
-                    mp4::VideoCodecSpecific::AVCConfig(v) => {
-                        assert!(!v.is_empty());
+                    mp4::VideoCodecSpecific::AVCConfig(ref avc) => {
+                        assert!(!avc.is_empty());
                         "AVC"
                     }
-                    mp4::VideoCodecSpecific::VPxConfig(vpx) => {
+                    mp4::VideoCodecSpecific::VPxConfig(ref vpx) => {
                         // We don't enter in here, we just check if fields are public.
                         assert!(vpx.bit_depth > 0);
                         assert!(vpx.color_space > 0);
                         assert!(vpx.chroma_subsampling > 0);
                         assert!(!vpx.codec_init.is_empty());
                         "VPx"
                     }
-                    mp4::VideoCodecSpecific::ESDSConfig(mp4v) => {
+                    mp4::VideoCodecSpecific::ESDSConfig(ref mp4v) => {
                         assert!(!mp4v.is_empty());
                         "MP4V"
                     }
-                    mp4::VideoCodecSpecific::AV1Config(_av1c) => {
+                    mp4::VideoCodecSpecific::AV1Config(ref _av1c) => {
                         "AV1"
                     }
                 }, "AVC");
             }
-            Some(mp4::SampleEntry::Audio(a)) => {
+            mp4::TrackType::Audio => {
                 // track part
                 assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1)));
                 assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
                 assert_eq!(track.media_time, Some(mp4::TrackScaledTime(1024, 1)));
                 assert_eq!(track.timescale, Some(mp4::TrackTimeScale(48000, 1)));
 
                 // track.tkhd part
                 let tkhd = track.tkhd.unwrap();
                 assert_eq!(tkhd.disabled, false);
                 assert_eq!(tkhd.duration, 62);
                 assert_eq!(tkhd.width, 0);
                 assert_eq!(tkhd.height, 0);
 
-                // track.data part
+                // track.stsd part
+                let stsd = track.stsd.expect("expected an stsd");
+                let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
+                    mp4::SampleEntry::Audio(a) => a,
+                    _ => panic!("expected a AudioSampleEntry"),
+                };
                 assert_eq!(match a.codec_specific {
-                    mp4::AudioCodecSpecific::ES_Descriptor(esds) => {
+                    mp4::AudioCodecSpecific::ES_Descriptor(ref 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) => {
+                    mp4::AudioCodecSpecific::FLACSpecificBox(ref flac) => {
                         // STREAMINFO block must be present and first.
                         assert!(!flac.blocks.is_empty());
                         assert_eq!(flac.blocks[0].block_type, 0);
                         assert_eq!(flac.blocks[0].data.len(), 34);
                         "FLAC"
                     }
-                    mp4::AudioCodecSpecific::OpusSpecificBox(opus) => {
+                    mp4::AudioCodecSpecific::OpusSpecificBox(ref opus) => {
                         // We don't enter in here, we just check if fields are public.
                         assert!(opus.version > 0);
                         "Opus"
                     }
-                    mp4::AudioCodecSpecific::ALACSpecificBox(alac) => {
+                    mp4::AudioCodecSpecific::ALACSpecificBox(ref alac) => {
                         assert!(alac.data.len() == 24 || alac.data.len() == 48);
                         "ALAC"
                     }
                     mp4::AudioCodecSpecific::MP3 => {
                         "MP3"
                     }
                     mp4::AudioCodecSpecific::LPCM => {
                         "LPCM"
                     }
                 }, "ES");
                 assert!(a.samplesize > 0);
                 assert!(a.samplerate > 0.0);
             }
-            Some(mp4::SampleEntry::Unknown) | None => {}
+            mp4::TrackType::Metadata | mp4::TrackType::Unknown => {}
         }
     }
 }
 
 #[test]
 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 fd = File::open(AUDIO_EME_CENC_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(p) => {
-                        assert_eq!(p.code_name, "mp4a");
-                        if let Some(ref tenc) = p.tenc {
-                            assert!(tenc.is_encrypted > 0);
-                            assert_eq!(tenc.iv_size, 16);
-                            assert_eq!(tenc.kid, kid);
-                        } else {
-                            assert!(false, "Invalid test condition");
-                        }
-                    },
-                    _=> {
-                        assert!(false, "Invalid test condition");
-                    },
+        let stsd = track.stsd.expect("expected an stsd");
+        let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
+            mp4::SampleEntry::Audio(a) => a,
+            _ => panic!("expected a AudioSampleEntry"),
+        };
+        assert_eq!(a.codec_type, mp4::CodecType::EncryptedAudio);
+        match a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+            Some(ref p) => {
+                assert_eq!(p.code_name, "mp4a");
+                if let Some(ref schm) = p.scheme_type {
+                    assert_eq!(schm.scheme_type.value, "cenc");
+                } else {
+                    assert!(false, "Expected scheme type info");
+                }
+                if let Some(ref tenc) = p.tenc {
+                    assert!(tenc.is_encrypted > 0);
+                    assert_eq!(tenc.iv_size, 16);
+                    assert_eq!(tenc.kid, kid);
+                    assert_eq!(tenc.crypt_byte_block_count, None);
+                    assert_eq!(tenc.skip_byte_block_count, None);
+                    assert_eq!(tenc.constant_iv, None);
+                } else {
+                    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,
@@ -173,103 +200,265 @@ fn public_video_cenc() {
         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 fd = File::open(VIDEO_EME_CENC_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(p) => {
-                        assert_eq!(p.code_name, "avc1");
-                        if let Some(ref tenc) = p.tenc {
-                            assert!(tenc.is_encrypted > 0);
-                            assert_eq!(tenc.iv_size, 16);
-                            assert_eq!(tenc.kid, kid);
-                        } else {
-                            assert!(false, "Invalid test condition");
-                        }
-                    },
-                    _=> {
-                        assert!(false, "Invalid test condition");
-                    },
+        let stsd = track.stsd.expect("expected an stsd");
+        let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
+            mp4::SampleEntry::Video(ref v) => v,
+            _ => panic!("expected a VideoSampleEntry"),
+        };
+        assert_eq!(v.codec_type, mp4::CodecType::EncryptedVideo);
+        match v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+            Some(ref p) => {
+                assert_eq!(p.code_name, "avc1");
+                if let Some(ref schm) = p.scheme_type {
+                    assert_eq!(schm.scheme_type.value, "cenc");
+                } else {
+                    assert!(false, "Expected scheme type info");
+                }
+                if let Some(ref tenc) = p.tenc {
+                    assert!(tenc.is_encrypted > 0);
+                    assert_eq!(tenc.iv_size, 16);
+                    assert_eq!(tenc.kid, kid);
+                    assert_eq!(tenc.crypt_byte_block_count, None);
+                    assert_eq!(tenc.skip_byte_block_count, None);
+                    assert_eq!(tenc.constant_iv, None);
+                } else {
+                    assert!(false, "Invalid test condition");
                 }
             },
-            _ => {
+            _=> {
                 assert!(false, "Invalid test condition");
             }
         }
     }
 
     for pssh in context.psshs {
         assert_eq!(pssh.system_id, system_id);
         for kid_id in pssh.kid {
             assert_eq!(kid_id, kid);
         }
         assert!(pssh.data.is_empty());
         assert_eq!(pssh.box_content, pssh_box);
     }
 }
 
 #[test]
-fn public_video_av1() {
-  let mut fd = File::open(VIDEO_AV1_MP4).expect("Unknown file");
-  let mut buf = Vec::new();
-  fd.read_to_end(&mut buf).expect("File error");
+fn publicaudio_cbcs() {
+    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, 0x04, 0x7e, 0x57, 0x1d, 0x04,
+             0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21];
+
+    let default_iv =
+        vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
+             0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+
+    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, 0x04, 0x7e, 0x57, 0x1d, 0x04,
+             0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21,
+             0x00, 0x00, 0x00, 0x00];
+
+    let mut fd = File::open(AUDIO_EME_CBCS_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::AV1);
-      match track.data {
-          Some(mp4::SampleEntry::Video(v)) => {
-              // track part
-              assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0)));
-              assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
-              assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0,0)));
-              assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12288, 0)));
-              assert_eq!(v.width, 64);
-              assert_eq!(v.height, 64);
+    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 {
+        let stsd = track.stsd.expect("expected an stsd");
+        assert_eq!(stsd.descriptions.len(), 2);
+        let mut found_encrypted_sample_description = false;
+        for description in stsd.descriptions {
+            match description {
+                mp4::SampleEntry::Audio(ref a) => {
+                    if let Some(p) = a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+                        found_encrypted_sample_description = true;
+                        assert_eq!(p.code_name, "mp4a");
+                        if let Some(ref schm) = p.scheme_type {
+                            assert_eq!(schm.scheme_type.value, "cbcs");
+                        } else {
+                            assert!(false, "Expected scheme type info");
+                        }
+                        if let Some(ref tenc) = p.tenc {
+                            assert!(tenc.is_encrypted > 0);
+                            assert_eq!(tenc.iv_size, 0);
+                            assert_eq!(tenc.kid, kid);
+                            // Note: 0 for both crypt and skip seems odd but
+                            // that's what shaka-packager produced. It appears
+                            // to indicate full encryption.
+                            assert_eq!(tenc.crypt_byte_block_count, Some(0));
+                            assert_eq!(tenc.skip_byte_block_count, Some(0));
+                            assert_eq!(tenc.constant_iv, Some(default_iv.clone()));
+                        } else {
+                            assert!(false, "Invalid test condition");
+                        }
+                    }
+                },
+                _ => {
+                    panic!("expected a VideoSampleEntry");
+                },
+            }
+        }
+        assert!(found_encrypted_sample_description,
+                "Should have found an encrypted sample description");
+    }
+
+    for pssh in context.psshs {
+        assert_eq!(pssh.system_id, system_id);
+        for kid_id in pssh.kid {
+            assert_eq!(kid_id, kid);
+        }
+        assert!(pssh.data.is_empty());
+        assert_eq!(pssh.box_content, pssh_box);
+    }
+}
+
+#[test]
+fn public_video_cbcs() {
+    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, 0x04, 0x7e, 0x57, 0x1d, 0x04,
+             0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21];
+
+    let default_iv =
+        vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
+             0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
+
+    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, 0x04, 0x7e, 0x57, 0x1d, 0x04,
+             0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21,
+             0x00, 0x00, 0x00, 0x00];
 
-              // track.tkhd part
-              let tkhd = track.tkhd.unwrap();
-              assert_eq!(tkhd.disabled, false);
-              assert_eq!(tkhd.duration, 42);
-              assert_eq!(tkhd.width, 4194304);
-              assert_eq!(tkhd.height, 4194304);
+    let mut fd = File::open(VIDEO_EME_CBCS_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 {
+        let stsd = track.stsd.expect("expected an stsd");
+        assert_eq!(stsd.descriptions.len(), 2);
+        let mut found_encrypted_sample_description = false;
+        for description in stsd.descriptions {
+            match description {
+                mp4::SampleEntry::Video(ref v) => {
+                    assert_eq!(v.width, 400);
+                    assert_eq!(v.height, 300);
+                    if let Some(p) = v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+                        found_encrypted_sample_description = true;
+                        assert_eq!(p.code_name, "avc1");
+                        if let Some(ref schm) = p.scheme_type {
+                            assert_eq!(schm.scheme_type.value, "cbcs");
+                        } else {
+                            assert!(false, "Expected scheme type info");
+                        }
+                        if let Some(ref tenc) = p.tenc {
+                            assert!(tenc.is_encrypted > 0);
+                            assert_eq!(tenc.iv_size, 0);
+                            assert_eq!(tenc.kid, kid);
+                            assert_eq!(tenc.crypt_byte_block_count, Some(1));
+                            assert_eq!(tenc.skip_byte_block_count, Some(9));
+                            assert_eq!(tenc.constant_iv, Some(default_iv.clone()));
+                        } else {
+                            assert!(false, "Invalid test condition");
+                        }
+                    }
+                },
+                _ => {
+                    panic!("expected a VideoSampleEntry");
+                },
+            }
+        }
+        assert!(found_encrypted_sample_description,
+                "Should have found an encrypted sample description");
+    }
 
-              match v.codec_specific {
-                  mp4::VideoCodecSpecific::AV1Config(av1c) => {
-                      // TODO: test av1c fields once ffmpeg is updated
-                      assert_eq!(av1c.profile, 0);
-                      assert_eq!(av1c.level, 0);
-                      assert_eq!(av1c.tier, 0);
-                      assert_eq!(av1c.bit_depth, 8);
-                      assert_eq!(av1c.monochrome, false);
-                      assert_eq!(av1c.chroma_subsampling_x, 1);
-                      assert_eq!(av1c.chroma_subsampling_y, 1);
-                      assert_eq!(av1c.chroma_sample_position, 0);
-                      assert_eq!(av1c.initial_presentation_delay_present, false);
-                      assert_eq!(av1c.initial_presentation_delay_minus_one, 0);
-                  },
-                  _ => assert!(false, "Invalid test condition"),
-              }
+    for pssh in context.psshs {
+        assert_eq!(pssh.system_id, system_id);
+        for kid_id in pssh.kid {
+            assert_eq!(kid_id, kid);
+        }
+        assert!(pssh.data.is_empty());
+        assert_eq!(pssh.box_content, pssh_box);
+    }
+}
+
+#[test]
+fn public_video_av1() {
+    let mut fd = File::open(VIDEO_AV1_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 {
+        // track part
+        assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0)));
+        assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
+        assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0,0)));
+        assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12288, 0)));
 
-          },
-          _ => {
-              assert!(false, "Invalid test condition");
-          }
-      }
-  }
+        // track.tkhd part
+        let tkhd = track.tkhd.unwrap();
+        assert_eq!(tkhd.disabled, false);
+        assert_eq!(tkhd.duration, 42);
+        assert_eq!(tkhd.width, 4194304);
+        assert_eq!(tkhd.height, 4194304);
+
+        // track.stsd part
+        let stsd = track.stsd.expect("expected an stsd");
+        let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
+            mp4::SampleEntry::Video(ref v) => v,
+            _ => panic!("expected a VideoSampleEntry"),
+        };
+        assert_eq!(v.codec_type, mp4::CodecType::AV1);
+        assert_eq!(v.width, 64);
+        assert_eq!(v.height, 64);
+
+        match v.codec_specific {
+            mp4::VideoCodecSpecific::AV1Config(ref av1c) => {
+                // TODO: test av1c fields once ffmpeg is updated
+                assert_eq!(av1c.profile, 0);
+                assert_eq!(av1c.level, 0);
+                assert_eq!(av1c.tier, 0);
+                assert_eq!(av1c.bit_depth, 8);
+                assert_eq!(av1c.monochrome, false);
+                assert_eq!(av1c.chroma_subsampling_x, 1);
+                assert_eq!(av1c.chroma_subsampling_y, 1);
+                assert_eq!(av1c.chroma_sample_position, 0);
+                assert_eq!(av1c.initial_presentation_delay_present, false);
+                assert_eq!(av1c.initial_presentation_delay_minus_one, 0);
+            },
+            _ => assert!(false, "Invalid test condition"),
+        }
+    }
 }
--- a/media/mp4parse-rust/mp4parse_capi/Cargo.toml
+++ b/media/mp4parse-rust/mp4parse_capi/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "mp4parse_capi"
-version = "0.10.1"
+version = "0.11.2"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
 documentation = "https://docs.rs/mp4parse_capi/"
@@ -21,17 +21,17 @@ exclude = [
 build = false
 
 [badges]
 travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
 
 [dependencies]
 byteorder = "1.2.1"
 log = "0.4"
-mp4parse = {version = "0.10.1", path = "../mp4parse"}
+mp4parse = {version = "0.11.2", path = "../mp4parse"}
 num-traits = "0.2.0"
 
 [dev-dependencies]
 env_logger = "0.5.3"
 
 [features]
 fuzz = ["mp4parse/fuzz"]
 # Enable mp4parse_fallible to use fallible memory allocation rather than
--- a/media/mp4parse-rust/mp4parse_capi/src/lib.rs
+++ b/media/mp4parse-rust/mp4parse_capi/src/lib.rs
@@ -103,20 +103,36 @@ pub enum Mp4parseCodec {
     Alac,
 }
 
 impl Default for Mp4parseCodec {
     fn default() -> Self { Mp4parseCodec::Unknown }
 }
 
 #[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum Mp4ParseEncryptionSchemeType {
+    None,
+    Cenc,
+    Cbc1,
+    Cens,
+    Cbcs,
+    // Schemes also have a version component. At the time of writing, this does
+    // not impact handling, so we do not expose it. Note that this may need to
+    // be exposed in future, should the spec change.
+}
+
+impl Default for Mp4ParseEncryptionSchemeType {
+    fn default() -> Self { Mp4ParseEncryptionSchemeType::None }
+}
+
+#[repr(C)]
 #[derive(Default, Debug)]
 pub struct Mp4parseTrackInfo {
     pub track_type: Mp4parseTrackType,
-    pub codec: Mp4parseCodec,
     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)]
 #[derive(Default, Debug, PartialEq)]
@@ -164,44 +180,88 @@ impl Mp4parseByteData {
 #[derive(Default)]
 pub struct Mp4parsePsshInfo {
     pub data: Mp4parseByteData,
 }
 
 #[repr(C)]
 #[derive(Default, Debug)]
 pub struct Mp4parseSinfInfo {
-    pub is_encrypted: u32,
+    pub scheme_type: Mp4ParseEncryptionSchemeType,
+    pub is_encrypted: u8,
     pub iv_size: u8,
     pub kid: Mp4parseByteData,
+    // Members for pattern encryption schemes, may be 0 (u8) or empty
+    // (Mp4parseByteData) if pattern encryption is not in use
+    pub crypt_byte_block: u8,
+    pub skip_byte_block: u8,
+    pub constant_iv: Mp4parseByteData,
+    // End pattern encryption scheme members
 }
 
 #[repr(C)]
 #[derive(Default, Debug)]
-pub struct Mp4parseTrackAudioInfo {
+pub struct Mp4parseTrackAudioSampleInfo {
+    pub codec_type: Mp4parseCodec,
     pub channels: u16,
     pub bit_depth: u16,
     pub sample_rate: u32,
     pub profile: u16,
     pub extended_profile: u16,
     pub codec_specific_config: Mp4parseByteData,
     pub extra_data: Mp4parseByteData,
     pub protected_data: Mp4parseSinfInfo,
 }
 
 #[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseTrackAudioInfo {
+    pub sample_info_count: u32,
+    pub sample_info: *const Mp4parseTrackAudioSampleInfo,
+}
+
+impl Default for Mp4parseTrackAudioInfo {
+    fn default() -> Self {
+        Self {
+            sample_info_count: 0,
+            sample_info: std::ptr::null(),
+        }
+    }
+}
+
+#[repr(C)]
 #[derive(Default, Debug)]
+pub struct Mp4parseTrackVideoSampleInfo {
+    pub codec_type: Mp4parseCodec,
+    pub image_width: u16,
+    pub image_height: u16,
+    pub extra_data: Mp4parseByteData,
+    pub protected_data: Mp4parseSinfInfo,
+}
+
+#[repr(C)]
+#[derive(Debug)]
 pub struct Mp4parseTrackVideoInfo {
     pub display_width: u32,
     pub display_height: u32,
-    pub image_width: u16,
-    pub image_height: u16,
     pub rotation: u16,
-    pub extra_data: Mp4parseByteData,
-    pub protected_data: Mp4parseSinfInfo,
+    pub sample_info_count: u32,
+    pub sample_info: *const Mp4parseTrackVideoSampleInfo,
+}
+
+impl Default for Mp4parseTrackVideoInfo {
+    fn default() -> Self {
+        Self {
+            display_width: 0,
+            display_height: 0,
+            rotation: 0,
+            sample_info_count: 0,
+            sample_info: std::ptr::null(),
+        }
+    }
 }
 
 #[repr(C)]
 #[derive(Default, Debug)]
 pub struct Mp4parseFragmentInfo {
     pub fragment_duration: u64,
     // TODO:
     // info in trex box.
@@ -209,16 +269,22 @@ pub struct Mp4parseFragmentInfo {
 
 pub struct Mp4parseParser {
     context: MediaContext,
     io: Mp4parseIo,
     poisoned: bool,
     opus_header: HashMap<u32, Vec<u8>>,
     pssh_data: Vec<u8>,
     sample_table: HashMap<u32, Vec<Mp4parseIndice>>,
+    // Store a mapping from track index (not id) to associated sample
+    // descriptions. Because each track has a variable number of sample
+    // descriptions, and because we need the data to live long enough to be
+    // copied out by callers, we store these on the parser struct.
+    audio_track_sample_descriptions: HashMap<u32, Vec<Mp4parseTrackAudioSampleInfo>>,
+    video_track_sample_descriptions: HashMap<u32, Vec<Mp4parseTrackVideoSampleInfo>>,
 }
 
 impl Mp4parseParser {
     fn context(&self) -> &MediaContext {
         &self.context
     }
 
     fn context_mut(&mut self) -> &mut MediaContext {
@@ -284,16 +350,18 @@ pub unsafe extern fn mp4parse_new(io: *c
     }
     let parser = Box::new(Mp4parseParser {
         context: MediaContext::new(),
         io: (*io).clone(),
         poisoned: false,
         opus_header: HashMap::new(),
         pssh_data: Vec::new(),
         sample_table: HashMap::new(),
+        audio_track_sample_descriptions: HashMap::new(),
+        video_track_sample_descriptions: HashMap::new(),
     });
 
     Box::into_raw(parser)
 }
 
 /// Free an `Mp4parseParser*` allocated by `mp4parse_new()`.
 #[no_mangle]
 pub unsafe extern fn mp4parse_free(parser: *mut Mp4parseParser) {
@@ -408,47 +476,16 @@ pub unsafe extern fn mp4parse_get_track_
 
     info.track_type = match context.tracks[track_index].track_type {
         TrackType::Video => Mp4parseTrackType::Video,
         TrackType::Audio => Mp4parseTrackType::Audio,
         TrackType::Metadata => Mp4parseTrackType::Metadata,
         TrackType::Unknown => return Mp4parseStatus::Unsupported,
     };
 
-    // Return UNKNOWN for unsupported format.
-    info.codec = match context.tracks[track_index].data {
-        Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific {
-            AudioCodecSpecific::OpusSpecificBox(_) =>
-                Mp4parseCodec::Opus,
-            AudioCodecSpecific::FLACSpecificBox(_) =>
-                Mp4parseCodec::Flac,
-            AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC =>
-                Mp4parseCodec::Aac,
-            AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 =>
-                Mp4parseCodec::Mp3,
-            AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM =>
-                Mp4parseCodec::Unknown,
-            AudioCodecSpecific::MP3 =>
-                Mp4parseCodec::Mp3,
-            AudioCodecSpecific::ALACSpecificBox(_) =>
-                Mp4parseCodec::Alac,
-        },
-        Some(SampleEntry::Video(ref video)) => match video.codec_specific {
-            VideoCodecSpecific::VPxConfig(_) =>
-                Mp4parseCodec::Vp9,
-            VideoCodecSpecific::AV1Config(_) =>
-                Mp4parseCodec::Av1,
-            VideoCodecSpecific::AVCConfig(_) =>
-                Mp4parseCodec::Avc,
-            VideoCodecSpecific::ESDSConfig(_) => // MP4V (14496-2) video is unsupported.
-                Mp4parseCodec::Unknown,
-        },
-        _ => Mp4parseCodec::Unknown,
-    };
-
     let track = &context.tracks[track_index];
 
     if let (Some(track_timescale),
             Some(context_timescale)) = (track.timescale,
                                         context.timescale) {
         let media_time =
             match track.media_time.map_or(Some(0), |media_time| {
                     track_time_to_us(media_time, track_timescale) }) {
@@ -489,177 +526,313 @@ pub unsafe extern fn mp4parse_get_track_
 pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut Mp4parseParser, track_index: u32, info: *mut Mp4parseTrackAudioInfo) -> Mp4parseStatus {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return Mp4parseStatus::BadArg;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
     *info = Default::default();
 
-    let context = (*parser).context_mut();
+    let context = (*parser).context();
 
     if track_index as usize >= context.tracks.len() {
         return Mp4parseStatus::BadArg;
     }
 
     let track = &context.tracks[track_index as usize];
 
-    match track.track_type {
-        TrackType::Audio => {}
-        _ => return Mp4parseStatus::Invalid,
-    };
+    if track.track_type != TrackType::Audio {
+        return Mp4parseStatus::Invalid;
+    }
 
-    let audio = match track.data {
-        Some(ref data) => data,
-        None => return Mp4parseStatus::Invalid,
-    };
-
-    let audio = match *audio {
-        SampleEntry::Audio(ref x) => x,
-        _ => return Mp4parseStatus::Invalid,
+    // Handle track.stsd
+    let stsd = match track.stsd {
+        Some(ref stsd) => stsd,
+        None => return Mp4parseStatus::Invalid, // Stsd should be present
     };
 
-    (*info).channels = audio.channelcount as u16;
-    (*info).bit_depth = audio.samplesize;
-    (*info).sample_rate = audio.samplerate as u32;
+    if stsd.descriptions.len() == 0 {
+        return Mp4parseStatus::Invalid; // Should have at least 1 description
+    }
+
+    let mut audio_sample_infos = Vec:: with_capacity(stsd.descriptions.len());
+    for description in stsd.descriptions.iter() {
+        let mut sample_info = Mp4parseTrackAudioSampleInfo::default();
+        let audio = match description {
+            SampleEntry::Audio(a) => a,
+            _ => return Mp4parseStatus::Invalid,
+        };
 
-    match audio.codec_specific {
-        AudioCodecSpecific::ES_Descriptor(ref v) => {
-            if v.codec_esds.len() > std::u32::MAX as usize {
-                return Mp4parseStatus::Invalid;
-            }
-            (*info).extra_data.length = v.codec_esds.len() as u32;
-            (*info).extra_data.data = v.codec_esds.as_ptr();
-            (*info).codec_specific_config.length = v.decoder_specific_data.len() as u32;
-            (*info).codec_specific_config.data = v.decoder_specific_data.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;
-            }
-            (*info).extended_profile = match v.extended_audio_object_type {
-                Some(extended_profile) => extended_profile,
-                _ =>  (*info).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 Mp4parseStatus::Invalid;
-            }
-            (*info).codec_specific_config.length = streaminfo.data.len() as u32;
-            (*info).codec_specific_config.data = streaminfo.data.as_ptr();
-        }
-        AudioCodecSpecific::OpusSpecificBox(ref opus) => {
-            let mut v = Vec::new();
-            match serialize_opus_header(opus, &mut v) {
-                Err(_) => {
+        // UNKNOWN for unsupported format.
+        sample_info.codec_type = match audio.codec_specific {
+            AudioCodecSpecific::OpusSpecificBox(_) =>
+                Mp4parseCodec::Opus,
+            AudioCodecSpecific::FLACSpecificBox(_) =>
+                Mp4parseCodec::Flac,
+            AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC =>
+                Mp4parseCodec::Aac,
+            AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 =>
+                Mp4parseCodec::Mp3,
+            AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM =>
+                Mp4parseCodec::Unknown,
+            AudioCodecSpecific::MP3 =>
+                Mp4parseCodec::Mp3,
+            AudioCodecSpecific::ALACSpecificBox(_) =>
+                Mp4parseCodec::Alac,
+        };
+        sample_info.channels = audio.channelcount as u16;
+        sample_info.bit_depth = audio.samplesize;
+        sample_info.sample_rate = audio.samplerate as u32;
+        // sample_info.profile is handled below on a per case basis
+
+        match audio.codec_specific {
+            AudioCodecSpecific::ES_Descriptor(ref esds) => {
+                if esds.codec_esds.len() > std::u32::MAX as usize {
                     return Mp4parseStatus::Invalid;
                 }
-                Ok(_) => {
-                    let header = (*parser).opus_header_mut();
-                    header.insert(track_index, v);
-                    if let Some(v) = header.get(&track_index) {
-                        if v.len() > std::u32::MAX as usize {
-                            return Mp4parseStatus::Invalid;
+                sample_info.extra_data.length = esds.codec_esds.len() as u32;
+                sample_info.extra_data.data = esds.codec_esds.as_ptr();
+                sample_info.codec_specific_config.length = esds.decoder_specific_data.len() as u32;
+                sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr();
+                if let Some(rate) = esds.audio_sample_rate {
+                    sample_info.sample_rate = rate;
+                }
+                if let Some(channels) = esds.audio_channel_count {
+                    sample_info.channels = channels;
+                }
+                if let Some(profile) = esds.audio_object_type {
+                    sample_info.profile = profile;
+                }
+                sample_info.extended_profile = match esds.extended_audio_object_type {
+                    Some(extended_profile) => extended_profile,
+                    _ => sample_info.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 Mp4parseStatus::Invalid;
+                }
+                sample_info.codec_specific_config.length = streaminfo.data.len() as u32;
+                sample_info.codec_specific_config.data = streaminfo.data.as_ptr();
+            }
+            AudioCodecSpecific::OpusSpecificBox(ref opus) => {
+                let mut v = Vec::new();
+                match serialize_opus_header(opus, &mut v) {
+                    Err(_) => {
+                        return Mp4parseStatus::Invalid;
+                    }
+                    Ok(_) => {
+                        let header = (*parser).opus_header_mut();
+                        header.insert(track_index, v);
+                        if let Some(v) = header.get(&track_index) {
+                            if v.len() > std::u32::MAX as usize {
+                                return Mp4parseStatus::Invalid;
+                            }
+                            sample_info.codec_specific_config.length = v.len() as u32;
+                            sample_info.codec_specific_config.data = v.as_ptr();
                         }
-                        (*info).codec_specific_config.length = v.len() as u32;
-                        (*info).codec_specific_config.data = v.as_ptr();
                     }
                 }
             }
+            AudioCodecSpecific::ALACSpecificBox(ref alac) => {
+                sample_info.codec_specific_config.length = alac.data.len() as u32;
+                sample_info.codec_specific_config.data = alac.data.as_ptr();
+            }
+            AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
         }
-        AudioCodecSpecific::ALACSpecificBox(ref alac) => {
-            (*info).codec_specific_config.length = alac.data.len() as u32;
-            (*info).codec_specific_config.data = alac.data.as_ptr();
+
+        if let Some(p) = audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+            sample_info.protected_data.scheme_type = match p.scheme_type {
+                Some(ref scheme_type_box) => {
+                    match scheme_type_box.scheme_type.value.as_ref() {
+                        "cenc" => Mp4ParseEncryptionSchemeType::Cenc,
+                        "cbcs" => Mp4ParseEncryptionSchemeType::Cbcs,
+                        // We don't support other schemes, and shouldn't reach
+                        // this case. Try to gracefully handle by treating as
+                        // no encryption case.
+                        _ => Mp4ParseEncryptionSchemeType::None,
+                    }
+                },
+                None => Mp4ParseEncryptionSchemeType::None,
+            };
+            if let Some(ref tenc) = p.tenc {
+                sample_info.protected_data.is_encrypted = tenc.is_encrypted;
+                sample_info.protected_data.iv_size = tenc.iv_size;
+                sample_info.protected_data.kid.set_data(&(tenc.kid));
+                sample_info.protected_data.crypt_byte_block = match tenc.crypt_byte_block_count {
+                    Some(n) => n,
+                    None => 0,
+                };
+                sample_info.protected_data.skip_byte_block = match tenc.skip_byte_block_count {
+                    Some(n) => n,
+                    None => 0,
+                };
+                match tenc.constant_iv {
+                    Some(ref iv_vec) => {
+                        if iv_vec.len() > std::u32::MAX as usize {
+                            return Mp4parseStatus::Invalid;
+                        }
+                        sample_info.protected_data.constant_iv.set_data(iv_vec);
+                    },
+                    None => {}, // Don't need to do anything, defaults are correct
+                };
+            }
         }
-        AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
+        audio_sample_infos.push(sample_info);
     }
 
-    if let Some(p) = audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
-        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));
-        }
+    (*parser).audio_track_sample_descriptions.insert(track_index, audio_sample_infos);
+    match (*parser).audio_track_sample_descriptions.get(&track_index) {
+        Some(sample_info) => {
+            if sample_info.len() > std::u32::MAX as usize {
+                // Should never happen due to upper limits on number of sample
+                // descriptions a track can have, but lets be safe.
+                return Mp4parseStatus::Invalid;
+            }
+            (*info).sample_info_count = sample_info.len() as u32;
+            (*info).sample_info = sample_info.as_ptr();
+        },
+        None => return Mp4parseStatus::Invalid, // Shouldn't happen, we just inserted the info!
     }
 
     Mp4parseStatus::Ok
 }
 
 /// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`.
 #[no_mangle]
 pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut Mp4parseParser, track_index: u32, info: *mut Mp4parseTrackVideoInfo) -> Mp4parseStatus {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return Mp4parseStatus::BadArg;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
     *info = Default::default();
 
-    let context = (*parser).context_mut();
+    let context = (*parser).context();
 
     if track_index as usize >= context.tracks.len() {
         return Mp4parseStatus::BadArg;
     }
 
     let track = &context.tracks[track_index as usize];
 
-    match track.track_type {
-        TrackType::Video => {}
-        _ => return Mp4parseStatus::Invalid,
-    };
+    if track.track_type != TrackType::Video {
+        return Mp4parseStatus::Invalid;
+    }
 
-    let video = match track.data {
-        Some(ref data) => data,
-        None => return Mp4parseStatus::Invalid,
-    };
-
-    let video = match *video {
-        SampleEntry::Video(ref x) => x,
-        _ => return Mp4parseStatus::Invalid,
-    };
-
+    // Handle track.tkhd
     if let Some(ref tkhd) = track.tkhd {
         (*info).display_width = tkhd.width >> 16; // 16.16 fixed point
         (*info).display_height = tkhd.height >> 16; // 16.16 fixed point
         let matrix = (tkhd.matrix.a >> 16, tkhd.matrix.b >> 16,
                       tkhd.matrix.c >> 16, tkhd.matrix.d >> 16);
         (*info).rotation = match matrix {
             ( 0,  1, -1,  0) => 90, // rotate 90 degrees
             (-1,  0,  0, -1) => 180, // rotate 180 degrees
             ( 0, -1,  1,  0) => 270, // rotate 270 degrees
             _ => 0,
         };
     } else {
         return Mp4parseStatus::Invalid;
     }
-    (*info).image_width = video.width;
-    (*info).image_height = video.height;
 
-    match video.codec_specific {
-        VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => {
-          (*info).extra_data.set_data(data);
-        },
-        _ => {}
+    // Handle track.stsd
+    let stsd = match track.stsd {
+        Some(ref stsd) => stsd,
+        None => return Mp4parseStatus::Invalid, // Stsd should be present
+    };
+
+    if stsd.descriptions.len() == 0 {
+        return Mp4parseStatus::Invalid; // Should have at least 1 description
     }
 
-    if let Some(p) = video.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
-        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));
+    let mut video_sample_infos = Vec:: with_capacity(stsd.descriptions.len());
+    for description in stsd.descriptions.iter() {
+        let mut sample_info = Mp4parseTrackVideoSampleInfo::default();
+        let video = match description {
+            SampleEntry::Video(v) => v,
+            _ => return Mp4parseStatus::Invalid,
+        };
+
+        // UNKNOWN for unsupported format.
+        sample_info.codec_type = match video.codec_specific {
+            VideoCodecSpecific::VPxConfig(_) =>
+                Mp4parseCodec::Vp9,
+            VideoCodecSpecific::AV1Config(_) =>
+                Mp4parseCodec::Av1,
+            VideoCodecSpecific::AVCConfig(_) =>
+                Mp4parseCodec::Avc,
+            VideoCodecSpecific::ESDSConfig(_) => // MP4V (14496-2) video is unsupported.
+                Mp4parseCodec::Unknown,
+        };
+        sample_info.image_width = video.width;
+        sample_info.image_height = video.height;
+
+        match video.codec_specific {
+            VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => {
+                sample_info.extra_data.set_data(data);
+            },
+            _ => {}
         }
+
+        if let Some(p) = video.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
+            sample_info.protected_data.scheme_type = match p.scheme_type {
+                Some(ref scheme_type_box) => {
+                    match scheme_type_box.scheme_type.value.as_ref() {
+                        "cenc" => Mp4ParseEncryptionSchemeType::Cenc,
+                        "cbcs" => Mp4ParseEncryptionSchemeType::Cbcs,
+                        // We don't support other schemes, and shouldn't reach
+                        // this case. Try to gracefully handle by treating as
+                        // no encryption case.
+                        _ => Mp4ParseEncryptionSchemeType::None,
+                    }
+                },
+                None => Mp4ParseEncryptionSchemeType::None,
+            };
+            if let Some(ref tenc) = p.tenc {
+                sample_info.protected_data.is_encrypted = tenc.is_encrypted;
+                sample_info.protected_data.iv_size = tenc.iv_size;
+                sample_info.protected_data.kid.set_data(&(tenc.kid));
+                sample_info.protected_data.crypt_byte_block = match tenc.crypt_byte_block_count {
+                    Some(n) => n,
+                    None => 0,
+                };
+                sample_info.protected_data.skip_byte_block = match tenc.skip_byte_block_count {
+                    Some(n) => n,
+                    None => 0,
+                };
+                match tenc.constant_iv {
+                    Some(ref iv_vec) => {
+                        if iv_vec.len() > std::u32::MAX as usize {
+                            return Mp4parseStatus::Invalid;
+                        }
+                        sample_info.protected_data.constant_iv.set_data(iv_vec);
+                    },
+                    None => {}, // Don't need to do anything, defaults are correct
+                };
+            }
+        }
+        video_sample_infos.push(sample_info);
     }
 
+    (*parser).video_track_sample_descriptions.insert(track_index, video_sample_infos);
+    match (*parser).video_track_sample_descriptions.get(&track_index) {
+        Some(sample_info) => {
+            if sample_info.len() > std::u32::MAX as usize {
+                // Should never happen due to upper limits on number of sample
+                // descriptions a track can have, but lets be safe.
+                return Mp4parseStatus::Invalid;
+            }
+            (*info).sample_info_count = sample_info.len() as u32;
+            (*info).sample_info = sample_info.as_ptr();
+        },
+        None => return Mp4parseStatus::Invalid, // Shouldn't happen, we just inserted the info!
+    }
     Mp4parseStatus::Ok
 }
 
 #[no_mangle]
 pub unsafe extern fn mp4parse_get_indice_table(parser: *mut Mp4parseParser, track_id: u32, indices: *mut Mp4parseByteData) -> Mp4parseStatus {
     if parser.is_null() || (*parser).poisoned() {
         return Mp4parseStatus::BadArg;
     }
@@ -1203,31 +1376,28 @@ fn arg_validation() {
         let parser = mp4parse_new(&io);
         assert!(parser.is_null());
 
         // Passing a null Mp4parseParser is an error.
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_read(std::ptr::null_mut()));
 
         let mut dummy_info = Mp4parseTrackInfo {
             track_type: Mp4parseTrackType::Video,
-            codec: Mp4parseCodec::Unknown,
             track_id: 0,
             duration: 0,
             media_time: 0,
         };
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
 
         let mut dummy_video = Mp4parseTrackVideoInfo {
             display_width: 0,
             display_height: 0,
-            image_width: 0,
-            image_height: 0,
             rotation: 0,
-            extra_data: Mp4parseByteData::default(),
-            protected_data: Default::default(),
+            sample_info_count: 0,
+            sample_info: std::ptr::null(),
         };
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
 
         let mut dummy_audio = Default::default();
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
     }
 }
 
@@ -1250,31 +1420,28 @@ fn arg_validation_with_parser() {
 
         // Null info pointers are an error.
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 0, std::ptr::null_mut()));
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()));
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()));
 
         let mut dummy_info = Mp4parseTrackInfo {
             track_type: Mp4parseTrackType::Video,
-            codec: Mp4parseCodec::Unknown,
             track_id: 0,
             duration: 0,
             media_time: 0,
         };
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 0, &mut dummy_info));
 
         let mut dummy_video = Mp4parseTrackVideoInfo {
             display_width: 0,
             display_height: 0,
-            image_width: 0,
-            image_height: 0,
             rotation: 0,
-            extra_data: Mp4parseByteData::default(),
-            protected_data: Default::default(),
+            sample_info_count: 0,
+            sample_info: std::ptr::null(),
         };
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
 
         let mut dummy_audio = Default::default();
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
 
         mp4parse_free(parser);
     }
@@ -1314,91 +1481,71 @@ fn arg_validation_with_data() {
         assert_eq!(Mp4parseStatus::Ok, mp4parse_read(parser));
 
         let mut count: u32 = 0;
         assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_count(parser, &mut count));
         assert_eq!(2, count);
 
         let mut info = Mp4parseTrackInfo {
             track_type: Mp4parseTrackType::Video,
-            codec: Mp4parseCodec::Unknown,
             track_id: 0,
             duration: 0,
             media_time: 0,
         };
         assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 0, &mut info));
         assert_eq!(info.track_type, Mp4parseTrackType::Video);
-        assert_eq!(info.codec, Mp4parseCodec::Avc);
         assert_eq!(info.track_id, 1);
         assert_eq!(info.duration, 40000);
         assert_eq!(info.media_time, 0);
 
         assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 1, &mut info));
         assert_eq!(info.track_type, Mp4parseTrackType::Audio);
-        assert_eq!(info.codec, Mp4parseCodec::Aac);
         assert_eq!(info.track_id, 2);
         assert_eq!(info.duration, 61333);
         assert_eq!(info.media_time, 21333);
 
-        let mut video = Mp4parseTrackVideoInfo {
-            display_width: 0,
-            display_height: 0,
-            image_width: 0,
-            image_height: 0,
-            rotation: 0,
-            extra_data: Mp4parseByteData::default(),
-            protected_data: Default::default(),
-        };
+        let mut video = Mp4parseTrackVideoInfo::default();
         assert_eq!(Mp4parseStatus::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);
+        assert_eq!(video.sample_info_count, 1);
+
+        assert_eq!((*video.sample_info).image_width, 320);
+        assert_eq!((*video.sample_info).image_height, 240);
 
-        let mut audio = Default::default();
+        let mut audio = Mp4parseTrackAudioInfo::default();
         assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_audio_info(parser, 1, &mut audio));
-        assert_eq!(audio.channels, 1);
-        assert_eq!(audio.bit_depth, 16);
-        assert_eq!(audio.sample_rate, 48000);
+        assert_eq!(audio.sample_info_count, 1);
+
+        assert_eq!((*audio.sample_info).channels, 1);
+        assert_eq!((*audio.sample_info).bit_depth, 16);
+        assert_eq!((*audio.sample_info).sample_rate, 48000);
 
         // Test with an invalid track number.
         let mut info = Mp4parseTrackInfo {
             track_type: Mp4parseTrackType::Video,
-            codec: Mp4parseCodec::Unknown,
             track_id: 0,
             duration: 0,
             media_time: 0,
         };
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 3, &mut info));
         assert_eq!(info.track_type, Mp4parseTrackType::Video);
-        assert_eq!(info.codec, Mp4parseCodec::Unknown);
         assert_eq!(info.track_id, 0);
         assert_eq!(info.duration, 0);
         assert_eq!(info.media_time, 0);
 
-        let mut video = Mp4parseTrackVideoInfo {
-            display_width: 0,
-            display_height: 0,
-            image_width: 0,
-            image_height: 0,
-            rotation: 0,
-            extra_data: Mp4parseByteData::default(),
-            protected_data: Default::default(),
-        };
+        let mut video = Mp4parseTrackVideoInfo::default();
         assert_eq!(Mp4parseStatus::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);
+        assert_eq!(video.sample_info_count, 0);
 
         let mut audio = Default::default();
         assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 3, &mut audio));
-        assert_eq!(audio.channels, 0);
-        assert_eq!(audio.bit_depth, 0);
-        assert_eq!(audio.sample_rate, 0);
+        assert_eq!(audio.sample_info_count, 0);
 
         mp4parse_free(parser);
     }
 }
 
 #[test]
 fn rational_scale_overflow() {
     assert_eq!(rational_scale::<u64, u64>(17, 3, 1000), Some(5666));
--- a/media/mp4parse-rust/update-rust.sh
+++ b/media/mp4parse-rust/update-rust.sh
@@ -1,15 +1,15 @@
 #!/bin/sh
 # Script to update mp4parse-rust sources to latest upstream
 
 set -e
 
 # Default version.
-VER="f6032a118aa498525145adf611cd7b3bec0e0216"
+VER="26e614bbcb4d7322dc2e4b7e15391bdb30b9f7be"
 
 # Accept version or commit from the command line.
 if test -n "$1"; then
   VER=$1
 fi
 
 echo "Fetching sources..."
 rm -rf _upstream