Bug 1306477 - Adds support for subsample encrypted WebMs r?cpearce draft
authorJay Harris <jharris@mozilla.com>
Tue, 07 Feb 2017 13:30:08 +1300
changeset 483299 fa5c430fa0b2ca7249591e612be8b3ed125c5ae3
parent 483298 7c5e6588dfd857433bff801ad8aece0971641c7a
child 483300 8d6189577ef2aa834cdd354a56ab9e5d0792ef27
push id45277
push userbmo:jharris@mozilla.com
push dateTue, 14 Feb 2017 02:59:42 +0000
reviewerscpearce
bugs1306477
milestone54.0a1
Bug 1306477 - Adds support for subsample encrypted WebMs r?cpearce MozReview-Commit-ID: HzQKShlmcON
dom/media/webm/WebMDemuxer.cpp
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -24,16 +24,17 @@
 #include "mozilla/Sprintf.h"
 
 #include <algorithm>
 #include <stdint.h>
 
 #define VPX_DONT_DEFINE_STDINT_TYPES
 #include "vpx/vp8dx.h"
 #include "vpx/vpx_decoder.h"
+#include <numeric>
 
 #define WEBM_DEBUG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("WebMDemuxer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 extern mozilla::LazyLogModule gMediaDemuxerLog;
 
 namespace mozilla {
 
 using namespace gfx;
 
@@ -730,17 +731,18 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
           media::TimeUnit::FromNanoseconds(discardPadding), mInfo.mAudio.mRate);
       }
       if (discardFrames.isValid()) {
         sample->mDiscardPadding = discardFrames.value();
       }
     }
 
     if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED
-        || packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED) {
+        || packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED
+        || packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED) {
       nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
       unsigned char const* iv;
       size_t ivLength;
       nestegg_packet_iv(holder->Packet(), &iv, &ivLength);
       writer->mCrypto.mValid = true;
       writer->mCrypto.mIVSize = ivLength;
       if (ivLength == 0) {
         // Frame is not encrypted
@@ -749,18 +751,82 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
       } else {
         // Frame is encrypted
         writer->mCrypto.mIV.AppendElements(iv, 8);
         // Iv from a sample is 64 bits, must be padded with 64 bits more 0s
         // in compliance with spec
         for (uint32_t i = 0; i < 8; i++) {
           writer->mCrypto.mIV.AppendElement(0);
         }
-        writer->mCrypto.mPlainSizes.AppendElement(0);
-        writer->mCrypto.mEncryptedSizes.AppendElement(length);
+
+        if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED) {
+          writer->mCrypto.mPlainSizes.AppendElement(0);
+          writer->mCrypto.mEncryptedSizes.AppendElement(length);
+        } else if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED) {
+          uint8_t numPartitions = 0;
+          const uint32_t* partitions = NULL;
+          nestegg_packet_offsets(holder->Packet(), &partitions, &numPartitions);
+
+          // WebM stores a list of 'partitions' in the data, which alternate
+          // clear, encrypted. The data in the first partition is always clear.
+          // So, and sample might look as follows:
+          // 00|XXXX|000|XX, where | represents a partition, 0 a clear byte and
+          // X an encrypted byte. If the first bytes in sample are unencrypted,
+          // the first partition will be at zero |XXXX|000|XX.
+          //
+          // As GMP expects the lengths of the clear and encrypted chunks of
+          // data, we calculate these from the difference between the last two
+          // partitions.
+          uint32_t lastOffset = 0;
+          bool encrypted = false;
+
+          for (uint8_t i = 0; i < numPartitions; i++) {
+            uint32_t partition = partitions[i];
+            uint32_t currentLength = partition - lastOffset;
+
+            if (encrypted) {
+              writer->mCrypto.mEncryptedSizes.AppendElement(currentLength);
+            } else {
+              writer->mCrypto.mPlainSizes.AppendElement(currentLength);
+            }
+
+            encrypted = !encrypted;
+            lastOffset = partition;
+
+            assert(lastOffset <= length);
+          }
+
+          // Add the data between the last offset and the end of the data.
+          // 000|XXX|000
+          //        ^---^
+          if (encrypted) {
+            writer->mCrypto.mEncryptedSizes.AppendElement(length - lastOffset);
+          } else {
+            writer->mCrypto.mPlainSizes.AppendElement(length - lastOffset);
+          }
+
+          // Make sure we have an equal number of encrypted and plain sizes (GMP
+          // expects this). This simple check is sufficient as there are two
+          // possible cases at this point:
+          // 1. The number of samples are even (so we don't need to do anything)
+          // 2. There is one more clear sample than encrypted samples, so add a
+          // zero length encrypted chunk.
+          // There can never be more encrypted partitions than clear partitions
+          // due to the alternating structure of the WebM samples and the
+          // restriction that the first chunk is always clear.
+          if (numPartitions % 2 == 0) {
+            writer->mCrypto.mEncryptedSizes.AppendElement(0);
+          }
+
+          // Assert that the lengths of the encrypted and plain samples add to
+          // the length of the data.
+          assert(((size_t)(std::accumulate(writer->mCrypto.mPlainSizes.begin(), writer->mCrypto.mPlainSizes.end(), 0) \
+                 + std::accumulate(writer->mCrypto.mEncryptedSizes.begin(), writer->mCrypto.mEncryptedSizes.end(), 0)) \
+                 == length));
+        }
       }
     }
     if (aType == TrackInfo::kVideoTrack) {
       sample->mTrackInfo = mSharedVideoTrackInfo;
     }
     aSamples->Push(sample);
   }
   return true;