Bug 1343140 - Output a black frame if Widevine CDM refuses to decode due to losing a key. r=gerald, a=gchang
authorChris Pearce <cpearce@mozilla.com>
Fri, 03 Mar 2017 14:10:28 +1300
changeset 379091 5674d54cd39e3b37ae8ab8bcff53981bed292995
parent 379090 6bd7ff837fdb04cbd40cbcb4eb5439abae86cfc5
child 379092 6c881e550cde17c118bad70dc390f7c66617eb06
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerald, gchang
bugs1343140
milestone53.0
Bug 1343140 - Output a black frame if Widevine CDM refuses to decode due to losing a key. r=gerald, a=gchang MozReview-Commit-ID: JQon5f87FSF
dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
--- a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -3,16 +3,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 "WidevineDecryptor.h"
 
 #include "WidevineAdapter.h"
 #include "WidevineUtils.h"
 #include "WidevineFileIO.h"
+#include "WidevineVideoFrame.h"
 #include <mozilla/SizePrintfMacros.h>
 #include <stdarg.h>
 
 using namespace cdm;
 using namespace std;
 
 namespace mozilla {
 
@@ -252,16 +253,43 @@ public:
 
 private:
   WidevineBuffer(const WidevineBuffer&);
   void operator=(const WidevineBuffer&);
 
   nsTArray<uint8_t> mBuffer;
 };
 
+void
+WidevineVideoFrame::InitToBlack(uint32_t aWidth, uint32_t aHeight, int64_t aTimeStamp)
+{
+  SetFormat(VideoFormat::kI420);
+  SetSize(cdm::Size(aWidth, aHeight));
+  size_t ySize = aWidth * aHeight;
+  size_t uSize = ((aWidth + 1) / 2) * ((aHeight + 1) / 2);
+  WidevineBuffer* buffer = new WidevineBuffer(ySize + uSize);
+  // Black in YCbCr is (0,128,128).
+  memset(buffer->Data(), 0, ySize);
+  memset(buffer->Data() + ySize, 128, uSize);
+  if (mBuffer) {
+    mBuffer->Destroy();
+    mBuffer = nullptr;
+  }
+  SetFrameBuffer(buffer);
+  SetPlaneOffset(VideoFrame::kYPlane, 0);
+  SetStride(VideoFrame::kYPlane, aWidth);
+  // Note: U and V planes are stored at the same place in order to
+  // save memory since their contents are the same.
+  SetPlaneOffset(VideoFrame::kUPlane, ySize);
+  SetStride(VideoFrame::kUPlane, (aWidth + 1) / 2);
+  SetPlaneOffset(VideoFrame::kVPlane, ySize);
+  SetStride(VideoFrame::kVPlane, (aWidth + 1) / 2);
+  SetTimestamp(aTimeStamp);
+}
+
 Buffer*
 WidevineDecryptor::Allocate(uint32_t aCapacity)
 {
   Log("Decryptor::Allocate(capacity=%u)", aCapacity);
   return new WidevineBuffer(aCapacity);
 }
 
 class TimerTask : public GMPTask {
--- a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
@@ -74,20 +74,23 @@ WidevineVideoDecoder::InitDecode(const G
   } else if (mCodecType == kGMPVideoCodecVP9) {
     config.codec = VideoDecoderConfig::kCodecVp9;
     config.profile = VideoDecoderConfig::kProfileNotNeeded;
   } else {
     mCallback->Error(GMPInvalidArgErr);
     return;
   }
   config.format = kYv12;
-  config.coded_size = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
-  mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength);
-  config.extra_data = mExtraData->Elements();
-  config.extra_data_size = mExtraData->Length();
+  config.coded_size = mCodedSize = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
+  if (aCodecSpecificLength > 0) {
+    // The first byte is the WebRTC packetization mode, which can be ignored.
+    mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength);
+    config.extra_data = mExtraData->Elements();
+    config.extra_data_size = mExtraData->Length();
+  }
   Status rv = CDM()->InitializeVideoDecoder(config);
   if (rv != kSuccess) {
     mCallback->Error(ToGMPErr(rv));
     return;
   }
   Log("WidevineVideoDecoder::InitDecode() rv=%d", rv);
   mAnnexB = mp4_demuxer::AnnexB::ConvertExtraDataToAnnexB(mExtraData);
 }
@@ -140,17 +143,27 @@ WidevineVideoDecoder::Decode(GMPVideoEnc
   Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
   Log("WidevineVideoDecoder::Decode(timestamp=%lld) rv=%d", sample.timestamp, rv);
 
   // Destroy frame, so that the shmem is now free to be used to return
   // output to the Gecko process.
   aInputFrame->Destroy();
   aInputFrame = nullptr;
 
-  if (rv == kSuccess) {
+  if (rv == kSuccess || rv == kNoKey) {
+    if (rv == kNoKey) {
+      // Somehow our key became unusable. Typically this would happen when
+      // a stream requires output protection, and the configuration changed
+      // such that output protection is no longer available. For example, a
+      // non-compliant monitor was attached. The JS player should notice the
+      // key status changing to "output-restricted", and is supposed to switch
+      // to a stream that doesn't require OP. In order to keep the playback
+      // pipeline rolling, just output a black frame. See bug 1343140.
+      frame.InitToBlack(mCodedSize.width, mCodedSize.height, sample.timestamp);
+    }
     if (!ReturnOutput(frame)) {
       Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
       mCallback->Error(GMPDecodeErr);
       return;
     }
     // A reset should only be started at most at level mReturnOutputCallDepth 1,
     // and if it's started it should be finished by that call by the time
     // the it returns, so it should always be false by this point.
--- a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
@@ -68,13 +68,14 @@ private:
   // Number of calls of ReturnOutput currently in progress.
   int32_t mReturnOutputCallDepth;
   // If we're waiting to drain. Used to prevent drain completing while
   // ReturnOutput calls are still on the stack.
   bool mDrainPending;
   // If a reset is being performed. Used to track if ReturnOutput should
   // dump current frame.
   bool mResetInProgress;
+  cdm::Size mCodedSize;
 };
 
 } // namespace mozilla
 
 #endif // WidevineVideoDecoder_h_
--- a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h
@@ -31,16 +31,18 @@ public:
   uint32_t PlaneOffset(cdm::VideoFrame::VideoPlane aPlane) override;
 
   void SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride) override;
   uint32_t Stride(cdm::VideoFrame::VideoPlane aPlane) override;
 
   void SetTimestamp(int64_t aTimestamp) override;
   int64_t Timestamp() const override;
 
+  void InitToBlack(uint32_t aWidth, uint32_t aHeight, int64_t aTimeStamp);
+
 protected:
   cdm::VideoFormat mFormat;
   cdm::Size mSize;
   cdm::Buffer* mBuffer;
   uint32_t mPlaneOffsets[kMaxPlanes];
   uint32_t mPlaneStrides[kMaxPlanes];
   int64_t mTimestamp;
 };