LCOV - code coverage report
Current view: top level - dom/media/gmp/widevine-adapter - WidevineVideoDecoder.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 211 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 16 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       5             : 
       6             : #include "WidevineVideoDecoder.h"
       7             : 
       8             : #include "WidevineUtils.h"
       9             : #include "WidevineVideoFrame.h"
      10             : #include "mozilla/Move.h"
      11             : #include <inttypes.h>
      12             : 
      13             : using namespace cdm;
      14             : 
      15             : namespace mozilla {
      16             : 
      17           0 : WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
      18           0 :                                            RefPtr<CDMWrapper> aCDMWrapper)
      19             :   : mVideoHost(aVideoHost)
      20           0 :   , mCDMWrapper(Move(aCDMWrapper))
      21             :   , mSentInput(false)
      22             :   , mCodecType(kGMPVideoCodecInvalid)
      23             :   , mReturnOutputCallDepth(0)
      24             :   , mDrainPending(false)
      25           0 :   , mResetInProgress(false)
      26             : {
      27             :   // Expect to start with a CDM wrapper, will release it in DecodingComplete().
      28           0 :   MOZ_ASSERT(mCDMWrapper);
      29           0 :   CDM_LOG("WidevineVideoDecoder created this=%p", this);
      30             : 
      31             :   // Corresponding Release is in DecodingComplete().
      32           0 :   AddRef();
      33           0 : }
      34             : 
      35           0 : WidevineVideoDecoder::~WidevineVideoDecoder()
      36             : {
      37           0 :   CDM_LOG("WidevineVideoDecoder destroyed this=%p", this);
      38           0 : }
      39             : 
      40             : static
      41             : VideoDecoderConfig::VideoCodecProfile
      42           0 : ToCDMH264Profile(uint8_t aProfile)
      43             : {
      44           0 :   switch (aProfile) {
      45           0 :     case 66: return VideoDecoderConfig::kH264ProfileBaseline;
      46           0 :     case 77: return VideoDecoderConfig::kH264ProfileMain;
      47           0 :     case 88: return VideoDecoderConfig::kH264ProfileExtended;
      48           0 :     case 100: return VideoDecoderConfig::kH264ProfileHigh;
      49           0 :     case 110: return VideoDecoderConfig::kH264ProfileHigh10;
      50           0 :     case 122: return VideoDecoderConfig::kH264ProfileHigh422;
      51           0 :     case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive;
      52             :   }
      53           0 :   return VideoDecoderConfig::kUnknownVideoCodecProfile;
      54             : }
      55             : 
      56             : void
      57           0 : WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
      58             :                                  const uint8_t* aCodecSpecific,
      59             :                                  uint32_t aCodecSpecificLength,
      60             :                                  GMPVideoDecoderCallback* aCallback,
      61             :                                  int32_t aCoreCount)
      62             : {
      63           0 :   mCallback = aCallback;
      64           0 :   VideoDecoderConfig config;
      65           0 :   mCodecType = aCodecSettings.mCodecType;
      66           0 :   if (mCodecType == kGMPVideoCodecH264) {
      67           0 :     config.codec = VideoDecoderConfig::kCodecH264;
      68           0 :     const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific);
      69           0 :     config.profile = ToCDMH264Profile(h264->mAVCC.mProfile);
      70           0 :   } else if (mCodecType == kGMPVideoCodecVP8) {
      71           0 :     config.codec = VideoDecoderConfig::kCodecVp8;
      72           0 :     config.profile = VideoDecoderConfig::kProfileNotNeeded;
      73           0 :   } else if (mCodecType == kGMPVideoCodecVP9) {
      74           0 :     config.codec = VideoDecoderConfig::kCodecVp9;
      75           0 :     config.profile = VideoDecoderConfig::kProfileNotNeeded;
      76             :   } else {
      77           0 :     mCallback->Error(GMPInvalidArgErr);
      78           0 :     return;
      79             :   }
      80           0 :   config.format = kYv12;
      81           0 :   config.coded_size = mCodedSize = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
      82           0 :   nsTArray<uint8_t> extraData;
      83           0 :   if (aCodecSpecificLength > 0) {
      84             :     // The first byte is the WebRTC packetization mode, which can be ignored.
      85           0 :     extraData.AppendElements(aCodecSpecific + 1, aCodecSpecificLength - 1);
      86           0 :     config.extra_data = extraData.Elements();
      87           0 :     config.extra_data_size = extraData.Length();
      88             :   }
      89           0 :   Status rv = CDM()->InitializeVideoDecoder(config);
      90           0 :   if (rv != kSuccess) {
      91           0 :     mCallback->Error(ToGMPErr(rv));
      92           0 :     return;
      93             :   }
      94           0 :   CDM_LOG("WidevineVideoDecoder::InitDecode() rv=%d", rv);
      95             : }
      96             : 
      97             : void
      98           0 : WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
      99             :                              bool aMissingFrames,
     100             :                              const uint8_t* aCodecSpecificInfo,
     101             :                              uint32_t aCodecSpecificInfoLength,
     102             :                              int64_t aRenderTimeMs)
     103             : {
     104             :   // We should not be given new input if a drain has been initiated
     105           0 :   MOZ_ASSERT(!mDrainPending);
     106             :   // We may not get the same out of the CDM decoder as we put in, and there
     107             :   // may be some latency, i.e. we may need to input (say) 30 frames before
     108             :   // we receive output. So we need to store the durations of the frames input,
     109             :   // and retrieve them on output.
     110           0 :   mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();
     111             : 
     112           0 :   mSentInput = true;
     113           0 :   InputBuffer sample;
     114             : 
     115           0 :   const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
     116           0 :   nsTArray<SubsampleEntry> subsamples;
     117           0 :   InitInputBuffer(crypto, aInputFrame->TimeStamp(),
     118           0 :                   aInputFrame->Buffer(), aInputFrame->Size(),
     119           0 :                   sample, subsamples);
     120             : 
     121           0 :   WidevineVideoFrame frame;
     122           0 :   Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
     123           0 :   CDM_LOG("WidevineVideoDecoder::Decode(timestamp=%" PRId64 ") rv=%d",
     124             :           sample.timestamp, rv);
     125             : 
     126             :   // Destroy frame, so that the shmem is now free to be used to return
     127             :   // output to the Gecko process.
     128           0 :   aInputFrame->Destroy();
     129           0 :   aInputFrame = nullptr;
     130             : 
     131           0 :   if (rv == kSuccess || rv == kNoKey) {
     132           0 :     if (rv == kNoKey) {
     133           0 :       CDM_LOG("NoKey for sample at time=%" PRId64 "!", sample.timestamp);
     134             :       // Somehow our key became unusable. Typically this would happen when
     135             :       // a stream requires output protection, and the configuration changed
     136             :       // such that output protection is no longer available. For example, a
     137             :       // non-compliant monitor was attached. The JS player should notice the
     138             :       // key status changing to "output-restricted", and is supposed to switch
     139             :       // to a stream that doesn't require OP. In order to keep the playback
     140             :       // pipeline rolling, just output a black frame. See bug 1343140.
     141           0 :       if (!frame.InitToBlack(mCodedSize.width, mCodedSize.height,
     142             :                              sample.timestamp)) {
     143           0 :         mCallback->Error(GMPDecodeErr);
     144           0 :         return;
     145             :       }
     146             :     }
     147           0 :     if (!ReturnOutput(frame)) {
     148           0 :       CDM_LOG("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
     149           0 :       mCallback->Error(GMPDecodeErr);
     150           0 :       return;
     151             :     }
     152             :     // A reset should only be started at most at level mReturnOutputCallDepth 1,
     153             :     // and if it's started it should be finished by that call by the time
     154             :     // the it returns, so it should always be false by this point.
     155           0 :     MOZ_ASSERT(!mResetInProgress);
     156             :     // Only request more data if we don't have pending samples.
     157           0 :     if (mFrameAllocationQueue.empty()) {
     158           0 :       MOZ_ASSERT(mCDMWrapper);
     159           0 :       mCallback->InputDataExhausted();
     160             :     }
     161           0 :   } else if (rv == kNeedMoreData) {
     162           0 :     MOZ_ASSERT(mCDMWrapper);
     163           0 :     mCallback->InputDataExhausted();
     164             :   } else {
     165           0 :     mCallback->Error(ToGMPErr(rv));
     166             :   }
     167             :   // Finish a drain if pending and we have no pending ReturnOutput calls on the
     168             :   // stack.
     169           0 :   if (mDrainPending && mReturnOutputCallDepth == 0) {
     170           0 :     Drain();
     171             :   }
     172             : }
     173             : 
     174             : // Util class to assist with counting mReturnOutputCallDepth.
     175             : class CounterHelper {
     176             : public:
     177             :   // RAII, increment counter
     178           0 :   explicit CounterHelper(int32_t& counter)
     179           0 :     : mCounter(counter)
     180             :   {
     181           0 :     mCounter++;
     182           0 :   }
     183             : 
     184             :   // RAII, decrement counter
     185           0 :   ~CounterHelper()
     186           0 :   {
     187           0 :     mCounter--;
     188           0 :   }
     189             : 
     190             : private:
     191             :   int32_t& mCounter;
     192             : };
     193             : 
     194             : // Util class to make sure GMP frames are freed. Holds a GMPVideoi420Frame*
     195             : // and will destroy it when the helper is destroyed unless the held frame
     196             : // if forgotten with ForgetFrame.
     197             : class FrameDestroyerHelper
     198             : {
     199             : public:
     200           0 :   explicit FrameDestroyerHelper(GMPVideoi420Frame*& frame) : frame(frame) { }
     201             : 
     202             :   // RAII, destroy frame if held.
     203           0 :   ~FrameDestroyerHelper()
     204           0 :   {
     205           0 :     if (frame) {
     206           0 :       frame->Destroy();
     207             :     }
     208           0 :     frame = nullptr;
     209           0 :   }
     210             : 
     211             :   // Forget the frame without destroying it.
     212           0 :   void ForgetFrame()
     213             :   {
     214           0 :     frame = nullptr;
     215           0 :   }
     216             : 
     217             : private:
     218             :   GMPVideoi420Frame* frame;
     219             : };
     220             : 
     221             : 
     222             : // Special handing is needed around ReturnOutput as it spins the IPC message
     223             : // queue when creating an empty frame and can end up with reentrant calls into
     224             : // the class methods.
     225             : bool
     226           0 : WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame)
     227             : {
     228           0 :   MOZ_ASSERT(mReturnOutputCallDepth >= 0);
     229           0 :   CounterHelper counterHelper(mReturnOutputCallDepth);
     230           0 :   mFrameAllocationQueue.push_back(Move(aCDMFrame));
     231           0 :   if (mReturnOutputCallDepth > 1) {
     232             :     // In a reentrant call.
     233           0 :     return true;
     234             :   }
     235           0 :   while (!mFrameAllocationQueue.empty()) {
     236           0 :     MOZ_ASSERT(mReturnOutputCallDepth == 1);
     237             :     // If we're at call level 1 a reset should not have been started. A
     238             :     // reset may be received during CreateEmptyFrame below, but we should not
     239             :     // be in a reset at this stage -- this would indicate receiving decode
     240             :     // messages before completing our reset, which we should not.
     241           0 :     MOZ_ASSERT(!mResetInProgress);
     242           0 :     WidevineVideoFrame currentCDMFrame = Move(mFrameAllocationQueue.front());
     243           0 :     mFrameAllocationQueue.pop_front();
     244           0 :     GMPVideoFrame* f = nullptr;
     245           0 :     auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f);
     246           0 :     if (GMP_FAILED(err) || !f) {
     247           0 :       CDM_LOG("Failed to create i420 frame!\n");
     248           0 :       return false;
     249             :     }
     250           0 :     auto gmpFrame = static_cast<GMPVideoi420Frame*>(f);
     251           0 :     FrameDestroyerHelper frameDestroyerHelper(gmpFrame);
     252           0 :     Size size = currentCDMFrame.Size();
     253           0 :     const int32_t yStride = currentCDMFrame.Stride(VideoFrame::kYPlane);
     254           0 :     const int32_t uStride = currentCDMFrame.Stride(VideoFrame::kUPlane);
     255           0 :     const int32_t vStride = currentCDMFrame.Stride(VideoFrame::kVPlane);
     256           0 :     const int32_t halfHeight = size.height / 2;
     257             :     // This call can cause a shmem alloc, during this alloc other calls
     258             :     // may be made to this class and placed on the stack. ***WARNING***:
     259             :     // other IPC calls can happen during this call, resulting in calls
     260             :     // being made to the CDM. After this call state can have changed,
     261             :     // and should be reevaluated.
     262           0 :     err = gmpFrame->CreateEmptyFrame(size.width,
     263             :                                      size.height,
     264             :                                      yStride,
     265             :                                      uStride,
     266           0 :                                      vStride);
     267             :     // Assert possible reentrant calls or resets haven't altered level
     268             :     // unexpectedly.
     269           0 :     MOZ_ASSERT(mReturnOutputCallDepth == 1);
     270           0 :     ENSURE_GMP_SUCCESS(err, false);
     271             : 
     272             :     // If a reset started we need to dump the current frame and complete the
     273             :     // reset.
     274           0 :     if (mResetInProgress) {
     275           0 :       MOZ_ASSERT(mCDMWrapper);
     276           0 :       MOZ_ASSERT(mFrameAllocationQueue.empty());
     277           0 :       CompleteReset();
     278           0 :       return true;
     279             :     }
     280             : 
     281           0 :     err = gmpFrame->SetWidth(size.width);
     282           0 :     ENSURE_GMP_SUCCESS(err, false);
     283             : 
     284           0 :     err = gmpFrame->SetHeight(size.height);
     285           0 :     ENSURE_GMP_SUCCESS(err, false);
     286             : 
     287           0 :     Buffer* buffer = currentCDMFrame.FrameBuffer();
     288           0 :     uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane);
     289           0 :     ENSURE_TRUE(outBuffer != nullptr, false);
     290           0 :     MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height);
     291           0 :     memcpy(outBuffer,
     292           0 :            buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kYPlane),
     293           0 :            yStride * size.height);
     294             : 
     295           0 :     outBuffer = gmpFrame->Buffer(kGMPUPlane);
     296           0 :     ENSURE_TRUE(outBuffer != nullptr, false);
     297           0 :     MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight);
     298           0 :     memcpy(outBuffer,
     299           0 :            buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kUPlane),
     300           0 :            uStride * halfHeight);
     301             : 
     302           0 :     outBuffer = gmpFrame->Buffer(kGMPVPlane);
     303           0 :     ENSURE_TRUE(outBuffer != nullptr, false);
     304           0 :     MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight);
     305           0 :     memcpy(outBuffer,
     306           0 :            buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kVPlane),
     307           0 :            vStride * halfHeight);
     308             : 
     309           0 :     gmpFrame->SetTimestamp(currentCDMFrame.Timestamp());
     310             : 
     311           0 :     auto d = mFrameDurations.find(currentCDMFrame.Timestamp());
     312           0 :     if (d != mFrameDurations.end()) {
     313           0 :       gmpFrame->SetDuration(d->second);
     314           0 :       mFrameDurations.erase(d);
     315             :     }
     316             : 
     317             :     // Forget frame so it's not deleted, call back taking ownership.
     318           0 :     frameDestroyerHelper.ForgetFrame();
     319           0 :     mCallback->Decoded(gmpFrame);
     320             :   }
     321             : 
     322           0 :   return true;
     323             : }
     324             : 
     325             : void
     326           0 : WidevineVideoDecoder::Reset()
     327             : {
     328           0 :   CDM_LOG("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput);
     329             :   // We shouldn't reset if a drain is pending.
     330           0 :   MOZ_ASSERT(!mDrainPending);
     331           0 :   mResetInProgress = true;
     332           0 :   if (mSentInput) {
     333           0 :     CDM()->ResetDecoder(kStreamTypeVideo);
     334             :   }
     335             :   // Remove queued frames, but do not reset mReturnOutputCallDepth, let the
     336             :   // ReturnOutput calls unwind and decrement the counter as needed.
     337           0 :   mFrameAllocationQueue.clear();
     338           0 :   mFrameDurations.clear();
     339             :   // Only if no ReturnOutput calls are in progress can we complete, otherwise
     340             :   // ReturnOutput needs to finalize the reset.
     341           0 :   if (mReturnOutputCallDepth == 0) {
     342           0 :     CompleteReset();
     343             :   }
     344           0 : }
     345             : 
     346             : void
     347           0 : WidevineVideoDecoder::CompleteReset()
     348             : {
     349           0 :   mCallback->ResetComplete();
     350           0 :   mSentInput = false;
     351           0 :   mResetInProgress = false;
     352           0 : }
     353             : 
     354             : void
     355           0 : WidevineVideoDecoder::Drain()
     356             : {
     357           0 :   CDM_LOG("WidevineVideoDecoder::Drain()");
     358           0 :   if (mReturnOutputCallDepth > 0) {
     359           0 :     CDM_LOG("Drain call is reentrant, postponing drain");
     360           0 :     mDrainPending = true;
     361           0 :     return;
     362             :   }
     363             : 
     364           0 :   Status rv = kSuccess;
     365           0 :   while (rv == kSuccess) {
     366           0 :     WidevineVideoFrame frame;
     367           0 :     InputBuffer sample;
     368           0 :     Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
     369           0 :     CDM_LOG("WidevineVideoDecoder::Drain();  DecryptAndDecodeFrame() rv=%d", rv);
     370           0 :     if (frame.Format() == kUnknownVideoFormat) {
     371           0 :       break;
     372             :     }
     373           0 :     if (rv == kSuccess) {
     374           0 :       if (!ReturnOutput(frame)) {
     375           0 :         CDM_LOG("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
     376             :       }
     377             :     }
     378             :   }
     379             :   // Shouldn't be reset while draining.
     380           0 :   MOZ_ASSERT(!mResetInProgress);
     381             : 
     382           0 :   CDM()->ResetDecoder(kStreamTypeVideo);
     383           0 :   mDrainPending = false;
     384           0 :   mCallback->DrainComplete();
     385             : }
     386             : 
     387             : void
     388           0 : WidevineVideoDecoder::DecodingComplete()
     389             : {
     390           0 :   CDM_LOG("WidevineVideoDecoder::DecodingComplete()");
     391             : 
     392           0 :   if (mCDMWrapper) {
     393             :     // mCallback will be null if the decoder has not been fully initialized.
     394           0 :     if (mCallback) {
     395           0 :       CDM()->DeinitializeDecoder(kStreamTypeVideo);
     396             :     } else {
     397           0 :       CDM_LOG("WideVineDecoder::DecodingComplete() Decoder was not fully initialized!");
     398             :     }
     399             : 
     400           0 :     mCDMWrapper = nullptr;
     401             :   }
     402             : 
     403             :   // Release that corresponds to AddRef() in constructor.
     404           0 :   Release();
     405           0 : }
     406             : 
     407             : } // namespace mozilla

Generated by: LCOV version 1.13