Bug 1487416 - Index.cpp's SampleIterator populates cbcs data. r=jya
authorBryce Van Dyk <bvandyk@mozilla.com>
Fri, 11 Jan 2019 15:13:37 +0000
changeset 510600 9851d4b03b567f9aba8645dc3560242266d0ffc6
parent 510599 306cd39838d6a53d88020d50ac06c28feafaff53
child 510601 5686d29392e8aecb57c0f75414bedebf54631447
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1487416
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 1487416 - Index.cpp's SampleIterator populates cbcs data. r=jya Rework our mp4 sample iterator to handle cbcs crypto data. To support this we populate the following new data for samples: - Crypto pattern information, this is split into a count of encrypted blocks and a count of clear blocks. - A constant IV. This information is available at a track level and a sample group level. The sample group level supersedes track level information if both a present. Prior to this patch, some crypto information was written to samples in the SampleIterator in Index.cpp, and some in the MP4Demuxer (based on if the SampleIterator had not populated the data). This patch moves all these operations into the SampleIterator -- the idea being that the sample iterator should be the component responsible for setting up sample meta data. Differential Revision: https://phabricator.services.mozilla.com/D15877
dom/media/MediaData.h
dom/media/mp4/Index.cpp
dom/media/mp4/Index.h
dom/media/mp4/MP4Demuxer.cpp
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -493,20 +493,27 @@ class VideoData : public MediaData {
 enum class CryptoScheme : uint8_t {
   None,
   Cenc,
   Cbcs,
 };
 
 class CryptoTrack {
  public:
-  CryptoTrack() : mCryptoScheme(CryptoScheme::None), mIVSize(0) {}
+  CryptoTrack()
+      : mCryptoScheme(CryptoScheme::None),
+        mIVSize(0),
+        mCryptByteBlock(0),
+        mSkipByteBlock(0) {}
   CryptoScheme mCryptoScheme;
   int32_t mIVSize;
   nsTArray<uint8_t> mKeyId;
+  uint8_t mCryptByteBlock;
+  uint8_t mSkipByteBlock;
+  nsTArray<uint8_t> mConstantIV;
 
   bool IsEncrypted() const { return mCryptoScheme != CryptoScheme::None; }
 };
 
 class CryptoSample : public CryptoTrack {
  public:
   nsTArray<uint16_t> mPlainSizes;
   nsTArray<uint32_t> mEncryptedSizes;
--- a/dom/media/mp4/Index.cpp
+++ b/dom/media/mp4/Index.cpp
@@ -106,94 +106,168 @@ already_AddRefed<MediaRawData> SampleIte
 
   size_t bytesRead;
   if (!mIndex->mSource->ReadAt(sample->mOffset, writer->Data(), sample->Size(),
                                &bytesRead) ||
       bytesRead != sample->Size()) {
     return nullptr;
   }
 
-  if (mCurrentSample == 0 && mIndex->mMoofParser) {
-    const nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
-    MOZ_ASSERT(mCurrentMoof < moofs.Length());
-    const Moof* currentMoof = &moofs[mCurrentMoof];
-    if (!currentMoof->mPsshes.IsEmpty()) {
-      // This Moof contained crypto init data. Report that. We only report
-      // the init data on the Moof's first sample, to avoid reporting it more
-      // than once per Moof.
-      // We only handle cenc for now, but update this once we're handling cbcs
+  MoofParser* moofParser = mIndex->mMoofParser.get();
+  if (!moofParser) {
+    // File is not fragmented, we can't have crypto, just early return.
+    Next();
+    return sample.forget();
+  }
+
+  SampleDescriptionEntry* sampleDescriptionEntry = GetSampleDescriptionEntry();
+  if (!sampleDescriptionEntry) {
+    // For the file to be valid the tfhd must reference a sample description
+    // entry.
+    return nullptr;
+  }
+
+  // Match scheme type box to enum representation.
+  CryptoScheme cryptoScheme = CryptoScheme::None;
+  // If a fragment references a sample description without crypto info them we
+  // treat it as unencrypted, even if other fragments may be encrypted.
+  if (sampleDescriptionEntry->mIsEncryptedEntry) {
+    if (!moofParser->mSinf.IsValid()) {
+      MOZ_ASSERT_UNREACHABLE(
+          "Sample description entry reports sample is encrypted, but no "
+          "sinf was parsed!");
+      return nullptr;
+    }
+    if (moofParser->mSinf.mDefaultEncryptionType == AtomType("cenc")) {
+      cryptoScheme = CryptoScheme::Cenc;
       writer->mCrypto.mCryptoScheme = CryptoScheme::Cenc;
-      writer->mCrypto.mInitDatas.AppendElements(currentMoof->mPsshes);
       writer->mCrypto.mInitDataType = NS_LITERAL_STRING("cenc");
+    } else if (moofParser->mSinf.mDefaultEncryptionType == AtomType("cbcs")) {
+      cryptoScheme = CryptoScheme::Cbcs;
+      writer->mCrypto.mCryptoScheme = CryptoScheme::Cbcs;
+      writer->mCrypto.mInitDataType = NS_LITERAL_STRING("cbcs");
+    } else {
+      MOZ_ASSERT_UNREACHABLE(
+          "Sample description entry reports sample is encrypted, but no "
+          "scheme, or an unsupported shceme is in use!");
+      return nullptr;
     }
   }
 
-  if (!s->mCencRange.IsEmpty()) {
-    MoofParser* parser = mIndex->mMoofParser.get();
-
-    if (!parser || !parser->mSinf.IsValid()) {
-      return nullptr;
+  if (mCurrentSample == 0) {
+    const nsTArray<Moof>& moofs = moofParser->Moofs();
+    const Moof* currentMoof = &moofs[mCurrentMoof];
+    if (!currentMoof->mPsshes.IsEmpty()) {
+      MOZ_ASSERT(sampleDescriptionEntry->mIsEncryptedEntry,
+                 "Unencrypted fragments should not contain pssh boxes");
+      MOZ_ASSERT(cryptoScheme != CryptoScheme::None);
+      // This Moof contained crypto init data. Report that. We only report
+      // the init data on the Moof's first sample, to avoid reporting it more
+      // than once per Moof.
+      writer->mCrypto.mInitDatas.AppendElements(currentMoof->mPsshes);
     }
-
-    uint8_t ivSize = parser->mSinf.mDefaultIVSize;
+  }
 
-    // The size comes from an 8 bit field
-    AutoTArray<uint8_t, 256> cenc;
-    cenc.SetLength(s->mCencRange.Length());
-    if (!mIndex->mSource->ReadAt(s->mCencRange.mStart, cenc.Elements(),
-                                 cenc.Length(), &bytesRead) ||
-        bytesRead != cenc.Length()) {
-      return nullptr;
-    }
-    BufferReader reader(cenc);
-    writer->mCrypto.mCryptoScheme = CryptoScheme::Cenc;
+  if (sampleDescriptionEntry->mIsEncryptedEntry) {
+    writer->mCrypto.mCryptoScheme = cryptoScheme;
 
+    MOZ_ASSERT(writer->mCrypto.mKeyId.IsEmpty(),
+               "Sample should not already have a key ID");
+    MOZ_ASSERT(writer->mCrypto.mConstantIV.IsEmpty(),
+               "Sample should not already have a constant IV");
     CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry();
     if (sampleInfo) {
       // Use sample group information if present, this supersedes track level
       // information.
       writer->mCrypto.mKeyId.AppendElements(sampleInfo->mKeyId);
-      ivSize = sampleInfo->mIVSize;
+      writer->mCrypto.mIVSize = sampleInfo->mIVSize;
+      writer->mCrypto.mCryptByteBlock = sampleInfo->mCryptByteBlock;
+      writer->mCrypto.mSkipByteBlock = sampleInfo->mSkipByteBlock;
+      writer->mCrypto.mConstantIV.AppendElements(sampleInfo->mConsantIV);
+    } else {
+      // Use the crypto info from track metadata
+      writer->mCrypto.mKeyId.AppendElements(moofParser->mSinf.mDefaultKeyID,
+                                            16);
+      writer->mCrypto.mIVSize = moofParser->mSinf.mDefaultIVSize;
+      writer->mCrypto.mCryptByteBlock =
+          moofParser->mSinf.mDefaultCryptByteBlock;
+      writer->mCrypto.mSkipByteBlock = moofParser->mSinf.mDefaultSkipByteBlock;
+      writer->mCrypto.mConstantIV.AppendElements(
+          moofParser->mSinf.mDefaultConstantIV);
     }
 
-    writer->mCrypto.mIVSize = ivSize;
-
-    if (!reader.ReadArray(writer->mCrypto.mIV, ivSize)) {
-      return nullptr;
-    }
-
-    auto res = reader.ReadU16();
-    if (res.isOk() && res.unwrap() > 0) {
-      uint16_t count = res.unwrap();
-
-      if (reader.Remaining() < count * 6) {
+    MOZ_ASSERT((writer->mCrypto.mIVSize == 0 &&
+                !writer->mCrypto.mConstantIV.IsEmpty()) ||
+                   !s->mCencRange.IsEmpty(),
+               "Crypto information should contain either a constant IV, or "
+               "have auxiliary information that will contain an IV");
+    // Parse auxiliary information if present
+    if (!s->mCencRange.IsEmpty()) {
+      // The size comes from an 8 bit field
+      AutoTArray<uint8_t, 256> cencAuxInfo;
+      cencAuxInfo.SetLength(s->mCencRange.Length());
+      if (!mIndex->mSource->ReadAt(s->mCencRange.mStart, cencAuxInfo.Elements(),
+                                   cencAuxInfo.Length(), &bytesRead) ||
+          bytesRead != cencAuxInfo.Length()) {
+        return nullptr;
+      }
+      BufferReader reader(cencAuxInfo);
+      if (!reader.ReadArray(writer->mCrypto.mIV, writer->mCrypto.mIVSize)) {
         return nullptr;
       }
 
-      for (size_t i = 0; i < count; i++) {
-        auto res_16 = reader.ReadU16();
-        auto res_32 = reader.ReadU32();
-        if (res_16.isErr() || res_32.isErr()) {
+      // Parse the auxiliary information for subsample information
+      auto res = reader.ReadU16();
+      if (res.isOk() && res.unwrap() > 0) {
+        uint16_t count = res.unwrap();
+
+        if (reader.Remaining() < count * 6) {
           return nullptr;
         }
-        writer->mCrypto.mPlainSizes.AppendElement(res_16.unwrap());
-        writer->mCrypto.mEncryptedSizes.AppendElement(res_32.unwrap());
+
+        for (size_t i = 0; i < count; i++) {
+          auto res_16 = reader.ReadU16();
+          auto res_32 = reader.ReadU32();
+          if (res_16.isErr() || res_32.isErr()) {
+            return nullptr;
+          }
+          writer->mCrypto.mPlainSizes.AppendElement(res_16.unwrap());
+          writer->mCrypto.mEncryptedSizes.AppendElement(res_32.unwrap());
+        }
+      } else {
+        // No subsample information means the entire sample is encrypted.
+        writer->mCrypto.mPlainSizes.AppendElement(0);
+        writer->mCrypto.mEncryptedSizes.AppendElement(sample->Size());
       }
-    } else {
-      // No subsample information means the entire sample is encrypted.
-      writer->mCrypto.mPlainSizes.AppendElement(0);
-      writer->mCrypto.mEncryptedSizes.AppendElement(sample->Size());
     }
   }
 
   Next();
 
   return sample.forget();
 }
 
+SampleDescriptionEntry* SampleIterator::GetSampleDescriptionEntry() {
+  nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
+  Moof& currentMoof = moofs[mCurrentMoof];
+  uint32_t sampleDescriptionIndex =
+      currentMoof.mTfhd.mDefaultSampleDescriptionIndex;
+  // Mp4 indices start at 1, shift down 1 so we index our array correctly.
+  sampleDescriptionIndex--;
+  FallibleTArray<SampleDescriptionEntry>& sampleDescriptions =
+      mIndex->mMoofParser->mSampleDescriptions;
+  if (sampleDescriptionIndex >= sampleDescriptions.Length()) {
+    MOZ_ASSERT_UNREACHABLE(
+        "Should always be able to find the appropriate sample description! "
+        "Malformed mp4?");
+    return nullptr;
+  }
+  return &sampleDescriptions[sampleDescriptionIndex];
+}
+
 CencSampleEncryptionInfoEntry* SampleIterator::GetSampleEncryptionEntry() {
   nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
   Moof* currentMoof = &moofs[mCurrentMoof];
   SampleToGroupEntry* sampleToGroupEntry = nullptr;
 
   // Default to using the sample to group entries for the fragment, otherwise
   // fall back to the sample to group entries for the track.
   FallibleTArray<SampleToGroupEntry>* sampleToGroupEntries =
--- a/dom/media/mp4/Index.h
+++ b/dom/media/mp4/Index.h
@@ -31,16 +31,19 @@ class SampleIterator {
   ~SampleIterator();
   already_AddRefed<mozilla::MediaRawData> GetNext();
   void Seek(Microseconds aTime);
   Microseconds GetNextKeyframeTime();
 
  private:
   Sample* Get();
 
+  // Gets the sample description entry for the current moof, or nullptr if
+  // called without a valid current moof.
+  SampleDescriptionEntry* GetSampleDescriptionEntry();
   CencSampleEncryptionInfoEntry* GetSampleEncryptionEntry();
 
   void Next();
   RefPtr<Index> mIndex;
   friend class Index;
   size_t mCurrentMoof;
   size_t mCurrentSample;
 };
--- a/dom/media/mp4/MP4Demuxer.cpp
+++ b/dom/media/mp4/MP4Demuxer.cpp
@@ -453,26 +453,16 @@ already_AddRefed<MediaRawData> MP4TrackD
           // So we keep the invalid frame, relying on the H264 decoder to
           // handle the error later.
           // TODO: make demuxer errors non-fatal.
           break;
       }
     }
   }
 
-  if (sample->mCrypto.IsEncrypted()) {
-    UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
-
-    // Only use the default key parsed from the moov if we haven't already got
-    // one from the sample group description.
-    if (writer->mCrypto.mKeyId.Length() == 0) {
-      writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
-      writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
-    }
-  }
   return sample.forget();
 }
 
 RefPtr<MP4TrackDemuxer::SamplesPromise> MP4TrackDemuxer::GetSamples(
     int32_t aNumSamples) {
   EnsureUpToDateIndex();
   RefPtr<SamplesHolder> samples = new SamplesHolder;
   if (!aNumSamples) {