Bug 1257727, 1257729 - Update WebM handling to deal with encrypted WebMs r?jya draft
authorBryce Van Dyk <bvandyk@mozilla.com>
Fri, 27 May 2016 14:34:54 +1200
changeset 374270 87676e5b6db574ea9f5b68b43b9d5eaeabaf120a
parent 373935 111970c738234569c8c180319155327316335deb
child 522591 ee428e8dcdac91171de5fa70672de12b4e7382d0
push id19971
push userbvandyk@mozilla.com
push dateThu, 02 Jun 2016 04:15:11 +0000
reviewersjya
bugs1257727, 1257729
milestone49.0a1
Bug 1257727, 1257729 - Update WebM handling to deal with encrypted WebMs r?jya - WebMDemuxer will read crypto information from WebM metadata. - WebMDemumer adds crypto information to samples. - WebMDemuxer can now return encryption info from GetCrypto(). - WebMDexmuer will not attempt to peek encrypted frames as it will give back garbage data. This means resolution changes internal to encrypted WebM files will not work. - WebMDecoder now exposes a single string version of CanHandleMediaType. This is done in the same way as the Mp4Decoder, so that the future update to MediaKeySystemAccess for WebM handling can maintain the same conventions. MozReview-Commit-ID: CU3JVi3t7Vn
dom/media/webm/WebMDecoder.cpp
dom/media/webm/WebMDecoder.h
dom/media/webm/WebMDemuxer.cpp
dom/media/webm/WebMDemuxer.h
--- a/dom/media/webm/WebMDecoder.cpp
+++ b/dom/media/webm/WebMDecoder.cpp
@@ -4,16 +4,17 @@
  * 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/. */
 
 #include "mozilla/Preferences.h"
 #include "MediaDecoderStateMachine.h"
 #include "WebMDemuxer.h"
 #include "WebMDecoder.h"
 #include "VideoUtils.h"
+#include "nsContentTypeParser.h"
 
 namespace mozilla {
 
 MediaDecoderStateMachine* WebMDecoder::CreateStateMachine()
 {
   mReader =
     new MediaFormatReader(this, new WebMDemuxer(GetResource()), GetVideoFrameContainer());
   return new MediaDecoderStateMachine(this, mReader);
@@ -65,16 +66,32 @@ WebMDecoder::CanHandleMediaType(const ns
       continue;
     }
     // Some unsupported codec.
     return false;
   }
   return true;
 }
 
+/* static */ bool
+WebMDecoder::CanHandleMediaType(const nsAString& aContentType)
+{
+  nsContentTypeParser parser(aContentType);
+  nsAutoString mimeType;
+  nsresult rv = parser.GetType(mimeType);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  nsString codecs;
+  parser.GetParameter("codecs", codecs);
+
+  return CanHandleMediaType(NS_ConvertUTF16toUTF8(mimeType),
+                            codecs);
+}
+
 void
 WebMDecoder::GetMozDebugReaderData(nsAString& aString)
 {
   if (mReader) {
     mReader->GetMozDebugReaderData(aString);
   }
 }
 
--- a/dom/media/webm/WebMDecoder.h
+++ b/dom/media/webm/WebMDecoder.h
@@ -28,16 +28,18 @@ public:
 
   // Returns true if aMIMEType is a type that we think we can render with the
   // a WebM platform decoder backend. If aCodecs is non emtpy, it is filled
   // with a comma-delimited list of codecs to check support for. Notes in
   // out params whether the codecs string contains Opus/Vorbis or VP8/VP9.
   static bool CanHandleMediaType(const nsACString& aMIMETypeExcludingCodecs,
                                  const nsAString& aCodecs);
 
+  static bool CanHandleMediaType(const nsAString& aContentType);
+
   void GetMozDebugReaderData(nsAString& aString) override;
 
 private:
   RefPtr<MediaFormatReader> mReader;
 };
 
 } // namespace mozilla
 
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -377,16 +377,20 @@ WebMDemuxer::ReadMetadata()
           mInfo.mVideo.mStereoMode = StereoMode::RIGHT_LEFT;
           break;
       }
       uint64_t duration = 0;
       r = nestegg_duration(context, &duration);
       if (!r) {
         mInfo.mVideo.mDuration = media::TimeUnit::FromNanoseconds(duration).ToMicroseconds();
       }
+      mInfo.mVideo.mCrypto = GetTrackCrypto(TrackInfo::kVideoTrack, track);
+      if (mInfo.mVideo.mCrypto.mValid) {
+        mCrypto.AddInitData(NS_LITERAL_STRING("webm"), mInfo.mVideo.mCrypto.mKeyId);
+      }
     } else if (type == NESTEGG_TRACK_AUDIO && !mHasAudio) {
       nestegg_audio_params params;
       r = nestegg_track_audio_params(context, track, &params);
       if (r == -1) {
         return NS_ERROR_FAILURE;
       }
 
       mAudioTrack = track;
@@ -439,16 +443,20 @@ WebMDemuxer::ReadMetadata()
         mInfo.mAudio.mCodecSpecificConfig->AppendElements(headers[0],
                                                           headerLens[0]);
       }
       uint64_t duration = 0;
       r = nestegg_duration(context, &duration);
       if (!r) {
         mInfo.mAudio.mDuration = media::TimeUnit::FromNanoseconds(duration).ToMicroseconds();
       }
+      mInfo.mAudio.mCrypto = GetTrackCrypto(TrackInfo::kAudioTrack, track);
+      if (mInfo.mAudio.mCrypto.mValid) {
+        mCrypto.AddInitData(NS_LITERAL_STRING("webm"), mInfo.mAudio.mCrypto.mKeyId);
+      }
     }
   }
   return NS_OK;
 }
 
 bool
 WebMDemuxer::IsSeekable() const
 {
@@ -502,17 +510,48 @@ WebMDemuxer::NotifyDataRemoved()
     mBufferedState->NotifyDataArrived(mInitData->Elements(), mInitData->Length(), 0);
   }
   mNeedReIndex = true;
 }
 
 UniquePtr<EncryptionInfo>
 WebMDemuxer::GetCrypto()
 {
-  return nullptr;
+  return mCrypto.IsEncrypted() ? MakeUnique<EncryptionInfo>(mCrypto) : nullptr;
+}
+
+CryptoTrack
+WebMDemuxer::GetTrackCrypto(TrackInfo::TrackType aType, size_t aTrackNumber) {
+  const int WEBM_IV_SIZE = 16;
+  const unsigned char * contentEncKeyId;
+  size_t contentEncKeyIdLength;
+  CryptoTrack crypto;
+  nestegg* context = Context(aType);
+
+  int r = nestegg_track_content_enc_key_id(context, aTrackNumber, &contentEncKeyId, &contentEncKeyIdLength);
+
+  if (r == -1) {
+    WEBM_DEBUG("nestegg_track_content_enc_key_id failed r=%d", r);
+    return crypto;
+  }
+
+  uint32_t i;
+  nsTArray<uint8_t> initData;
+  for (i = 0; i < contentEncKeyIdLength; i++) {
+    initData.AppendElement(contentEncKeyId[i]);
+  }
+
+  if (!initData.IsEmpty()) {
+    crypto.mValid = true;
+    // crypto.mMode is not used for WebMs
+    crypto.mIVSize = WEBM_IV_SIZE;
+    crypto.mKeyId = Move(initData);
+  }
+
+  return crypto;
 }
 
 bool
 WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSamples)
 {
   if (mIsMediaSource) {
     // To ensure mLastWebMBlockOffset is properly up to date.
     EnsureUpToDateIndex();
@@ -572,51 +611,58 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
 
   if (mIsMediaSource && next_tstamp == INT64_MIN) {
     return false;
   }
 
   int64_t discardPadding = 0;
   (void) nestegg_packet_discard_padding(holder->Packet(), &discardPadding);
 
+  int packetEncryption = nestegg_packet_encryption(holder->Packet());
+
   for (uint32_t i = 0; i < count; ++i) {
     unsigned char* data;
     size_t length;
     r = nestegg_packet_data(holder->Packet(), i, &data, &length);
     if (r == -1) {
       WEBM_DEBUG("nestegg_packet_data failed r=%d", r);
       return false;
     }
     bool isKeyframe = false;
     if (aType == TrackInfo::kAudioTrack) {
       isKeyframe = true;
     } else if (aType == TrackInfo::kVideoTrack) {
-      vpx_codec_stream_info_t si;
-      PodZero(&si);
-      si.sz = sizeof(si);
-      switch (mVideoCodec) {
+      if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED) {
+        // Packet is encrypted, can't peek, use packet info
+        isKeyframe = nestegg_packet_has_keyframe(holder->Packet()) == NESTEGG_PACKET_HAS_KEYFRAME_TRUE;
+      } else {
+        vpx_codec_stream_info_t si;
+        PodZero(&si);
+        si.sz = sizeof(si);
+        switch (mVideoCodec) {
         case NESTEGG_CODEC_VP8:
           vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), data, length, &si);
           break;
         case NESTEGG_CODEC_VP9:
           vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), data, length, &si);
           break;
-      }
-      isKeyframe = si.is_kf;
-      if (isKeyframe) {
-        // We only look for resolution changes on keyframes for both VP8 and
-        // VP9. Other resolution changes are invalid.
-        if (mLastSeenFrameWidth.isSome() && mLastSeenFrameHeight.isSome() &&
+        }
+        isKeyframe = si.is_kf;
+        if (isKeyframe) {
+          // We only look for resolution changes on keyframes for both VP8 and
+          // VP9. Other resolution changes are invalid.
+          if (mLastSeenFrameWidth.isSome() && mLastSeenFrameHeight.isSome() &&
             (si.w != mLastSeenFrameWidth.value() ||
-             si.h != mLastSeenFrameHeight.value())) {
-          mInfo.mVideo.mDisplay = nsIntSize(si.w, si.h);
-          mSharedVideoTrackInfo = new SharedTrackInfo(mInfo.mVideo, ++sStreamSourceID);
+              si.h != mLastSeenFrameHeight.value())) {
+            mInfo.mVideo.mDisplay = nsIntSize(si.w, si.h);
+            mSharedVideoTrackInfo = new SharedTrackInfo(mInfo.mVideo, ++sStreamSourceID);
+          }
+          mLastSeenFrameWidth = Some(si.w);
+          mLastSeenFrameHeight = Some(si.h);
         }
-        mLastSeenFrameWidth = Some(si.w);
-        mLastSeenFrameHeight = Some(si.h);
       }
     }
 
     WEBM_DEBUG("push sample tstamp: %ld next_tstamp: %ld length: %ld kf: %d",
                tstamp, next_tstamp, length, isKeyframe);
     RefPtr<MediaRawData> sample = new MediaRawData(data, length);
     sample->mTimecode = tstamp;
     sample->mTime = tstamp;
@@ -624,16 +670,41 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
     sample->mOffset = holder->Offset();
     sample->mKeyframe = isKeyframe;
     if (discardPadding && i == count - 1) {
       uint8_t c[8];
       BigEndian::writeInt64(&c[0], discardPadding);
       sample->mExtraData = new MediaByteBuffer;
       sample->mExtraData->AppendElements(&c[0], 8);
     }
+
+    if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED ||
+        packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED) {
+      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
+        writer->mCrypto.mPlainSizes.AppendElement(length);
+        writer->mCrypto.mEncryptedSizes.AppendElement(0);
+      } 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 (aType == TrackInfo::kVideoTrack) {
       sample->mTrackInfo = mSharedVideoTrackInfo;
     }
     aSamples->Push(sample);
   }
   return true;
 }
 
@@ -981,16 +1052,24 @@ WebMTrackDemuxer::Reset()
   } else {
     mNextKeyframeTime.reset();
   }
 }
 
 void
 WebMTrackDemuxer::UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples)
 {
+  for (const auto& sample : aSamples) {
+    if (sample->mCrypto.mValid) {
+      nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
+      writer->mCrypto.mMode = mInfo->mCrypto.mMode;
+      writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
+      writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
+    }
+  }
   if (mNextKeyframeTime.isNothing() ||
       aSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) {
     SetNextKeyFrameTime();
   }
 }
 
 nsresult
 WebMTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -165,16 +165,17 @@ private:
   void InitBufferedState();
   nsresult ReadMetadata();
   void NotifyDataArrived() override;
   void NotifyDataRemoved() override;
   void EnsureUpToDateIndex();
   media::TimeIntervals GetBuffered();
   nsresult SeekInternal(TrackInfo::TrackType aType,
                         const media::TimeUnit& aTarget);
+  CryptoTrack GetTrackCrypto(TrackInfo::TrackType aType, size_t aTrackNumber);
 
   // Read a packet from the nestegg file. Returns nullptr if all packets for
   // the particular track have been read. Pass TrackInfo::kVideoTrack or
   // TrackInfo::kVideoTrack to indicate the type of the packet we want to read.
   RefPtr<NesteggPacketHolder> NextPacket(TrackInfo::TrackType aType);
 
   // Internal method that demuxes the next packet from the stream. The caller
   // is responsible for making sure it doesn't get lost.
@@ -239,16 +240,18 @@ private:
   int64_t mLastWebMBlockOffset;
   const bool mIsMediaSource;
 
   Maybe<uint32_t> mLastSeenFrameWidth;
   Maybe<uint32_t> mLastSeenFrameHeight;
   // This will be populated only if a resolution change occurs, otherwise it
   // will be left as null so the original metadata is used
   RefPtr<SharedTrackInfo> mSharedVideoTrackInfo;
+
+  EncryptionInfo mCrypto;
 };
 
 class WebMTrackDemuxer : public MediaTrackDemuxer
 {
 public:
   WebMTrackDemuxer(WebMDemuxer* aParent,
                   TrackInfo::TrackType aType,
                   uint32_t aTrackNumber);