summaryrefslogtreecommitdiffstats
path: root/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp')
-rw-r--r--dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp400
1 files changed, 400 insertions, 0 deletions
diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
new file mode 100644
index 000000000..70d2fd8e0
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
@@ -0,0 +1,400 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "WidevineVideoDecoder.h"
+
+#include "mp4_demuxer/AnnexB.h"
+#include "WidevineUtils.h"
+#include "WidevineVideoFrame.h"
+#include "mozilla/Move.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
+ RefPtr<CDMWrapper> aCDMWrapper)
+ : mVideoHost(aVideoHost)
+ , mCDMWrapper(Move(aCDMWrapper))
+ , mExtraData(new MediaByteBuffer())
+ , mSentInput(false)
+ , mCodecType(kGMPVideoCodecInvalid)
+ , mReturnOutputCallDepth(0)
+ , mDrainPending(false)
+ , mResetInProgress(false)
+{
+ // Expect to start with a CDM wrapper, will release it in DecodingComplete().
+ MOZ_ASSERT(mCDMWrapper);
+ Log("WidevineVideoDecoder created this=%p", this);
+
+ // Corresponding Release is in DecodingComplete().
+ AddRef();
+}
+
+WidevineVideoDecoder::~WidevineVideoDecoder()
+{
+ Log("WidevineVideoDecoder destroyed this=%p", this);
+}
+
+static
+VideoDecoderConfig::VideoCodecProfile
+ToCDMH264Profile(uint8_t aProfile)
+{
+ switch (aProfile) {
+ case 66: return VideoDecoderConfig::kH264ProfileBaseline;
+ case 77: return VideoDecoderConfig::kH264ProfileMain;
+ case 88: return VideoDecoderConfig::kH264ProfileExtended;
+ case 100: return VideoDecoderConfig::kH264ProfileHigh;
+ case 110: return VideoDecoderConfig::kH264ProfileHigh10;
+ case 122: return VideoDecoderConfig::kH264ProfileHigh422;
+ case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive;
+ }
+ return VideoDecoderConfig::kUnknownVideoCodecProfile;
+}
+
+void
+WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount)
+{
+ mCallback = aCallback;
+ VideoDecoderConfig config;
+ mCodecType = aCodecSettings.mCodecType;
+ if (mCodecType == kGMPVideoCodecH264) {
+ config.codec = VideoDecoderConfig::kCodecH264;
+ const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific);
+ config.profile = ToCDMH264Profile(h264->mAVCC.mProfile);
+ } else if (mCodecType == kGMPVideoCodecVP8) {
+ config.codec = VideoDecoderConfig::kCodecVp8;
+ config.profile = VideoDecoderConfig::kProfileNotNeeded;
+ } 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();
+ 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);
+}
+
+void
+WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs)
+{
+ // We should not be given new input if a drain has been initiated
+ MOZ_ASSERT(!mDrainPending);
+ // We may not get the same out of the CDM decoder as we put in, and there
+ // may be some latency, i.e. we may need to input (say) 30 frames before
+ // we receive output. So we need to store the durations of the frames input,
+ // and retrieve them on output.
+ mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();
+
+ mSentInput = true;
+ InputBuffer sample;
+
+ RefPtr<MediaRawData> raw(
+ new MediaRawData(aInputFrame->Buffer(), aInputFrame->Size()));
+ if (aInputFrame->Size() && !raw->Data()) {
+ // OOM.
+ mCallback->Error(GMPAllocErr);
+ return;
+ }
+ raw->mExtraData = mExtraData;
+ raw->mKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
+ if (mCodecType == kGMPVideoCodecH264) {
+ // Convert input from AVCC, which GMPAPI passes in, to AnnexB, which
+ // Chromium uses internally.
+ mp4_demuxer::AnnexB::ConvertSampleToAnnexB(raw);
+ }
+
+ const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
+ nsTArray<SubsampleEntry> subsamples;
+ InitInputBuffer(crypto, aInputFrame->TimeStamp(), raw->Data(), raw->Size(), sample, subsamples);
+
+ // For keyframes, ConvertSampleToAnnexB will stick the AnnexB extra data
+ // at the start of the input. So we need to account for that as clear data
+ // in the subsamples.
+ if (raw->mKeyframe && !subsamples.IsEmpty() && mCodecType == kGMPVideoCodecH264) {
+ subsamples[0].clear_bytes += mAnnexB->Length();
+ }
+
+ WidevineVideoFrame frame;
+ 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 (!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.
+ MOZ_ASSERT(!mResetInProgress);
+ // Only request more data if we don't have pending samples.
+ if (mFrameAllocationQueue.empty()) {
+ MOZ_ASSERT(mCDMWrapper);
+ mCallback->InputDataExhausted();
+ }
+ } else if (rv == kNeedMoreData) {
+ MOZ_ASSERT(mCDMWrapper);
+ mCallback->InputDataExhausted();
+ } else {
+ mCallback->Error(ToGMPErr(rv));
+ }
+ // Finish a drain if pending and we have no pending ReturnOutput calls on the stack.
+ if (mDrainPending && mReturnOutputCallDepth == 0) {
+ Drain();
+ }
+}
+
+// Util class to assist with counting mReturnOutputCallDepth.
+class CounterHelper {
+public:
+ // RAII, increment counter
+ explicit CounterHelper(int32_t& counter)
+ : mCounter(counter)
+ {
+ mCounter++;
+ }
+
+ // RAII, decrement counter
+ ~CounterHelper()
+ {
+ mCounter--;
+ }
+
+private:
+ int32_t& mCounter;
+};
+
+// Util class to make sure GMP frames are freed. Holds a GMPVideoi420Frame*
+// and will destroy it when the helper is destroyed unless the held frame
+// if forgotten with ForgetFrame.
+class FrameDestroyerHelper {
+public:
+ explicit FrameDestroyerHelper(GMPVideoi420Frame*& frame)
+ : frame(frame)
+ {
+ }
+
+ // RAII, destroy frame if held.
+ ~FrameDestroyerHelper()
+ {
+ if (frame) {
+ frame->Destroy();
+ }
+ frame = nullptr;
+ }
+
+ // Forget the frame without destroying it.
+ void ForgetFrame()
+ {
+ frame = nullptr;
+ }
+
+private:
+ GMPVideoi420Frame* frame;
+};
+
+
+// Special handing is needed around ReturnOutput as it spins the IPC message
+// queue when creating an empty frame and can end up with reentrant calls into
+// the class methods.
+bool
+WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame)
+{
+ MOZ_ASSERT(mReturnOutputCallDepth >= 0);
+ CounterHelper counterHelper(mReturnOutputCallDepth);
+ mFrameAllocationQueue.push_back(Move(aCDMFrame));
+ if (mReturnOutputCallDepth > 1) {
+ // In a reentrant call.
+ return true;
+ }
+ while (!mFrameAllocationQueue.empty()) {
+ MOZ_ASSERT(mReturnOutputCallDepth == 1);
+ // If we're at call level 1 a reset should not have been started. A
+ // reset may be received during CreateEmptyFrame below, but we should not
+ // be in a reset at this stage -- this would indicate receiving decode
+ // messages before completing our reset, which we should not.
+ MOZ_ASSERT(!mResetInProgress);
+ WidevineVideoFrame currentCDMFrame = Move(mFrameAllocationQueue.front());
+ mFrameAllocationQueue.pop_front();
+ GMPVideoFrame* f = nullptr;
+ auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f);
+ if (GMP_FAILED(err) || !f) {
+ Log("Failed to create i420 frame!\n");
+ return false;
+ }
+ auto gmpFrame = static_cast<GMPVideoi420Frame*>(f);
+ FrameDestroyerHelper frameDestroyerHelper(gmpFrame);
+ Size size = currentCDMFrame.Size();
+ const int32_t yStride = currentCDMFrame.Stride(VideoFrame::kYPlane);
+ const int32_t uStride = currentCDMFrame.Stride(VideoFrame::kUPlane);
+ const int32_t vStride = currentCDMFrame.Stride(VideoFrame::kVPlane);
+ const int32_t halfHeight = size.height / 2;
+ // This call can cause a shmem alloc, during this alloc other calls
+ // may be made to this class and placed on the stack. ***WARNING***:
+ // other IPC calls can happen during this call, resulting in calls
+ // being made to the CDM. After this call state can have changed,
+ // and should be reevaluated.
+ err = gmpFrame->CreateEmptyFrame(size.width,
+ size.height,
+ yStride,
+ uStride,
+ vStride);
+ // Assert possible reentrant calls or resets haven't altered level unexpectedly.
+ MOZ_ASSERT(mReturnOutputCallDepth == 1);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ // If a reset started we need to dump the current frame and complete the reset.
+ if (mResetInProgress) {
+ MOZ_ASSERT(mCDMWrapper);
+ MOZ_ASSERT(mFrameAllocationQueue.empty());
+ CompleteReset();
+ return true;
+ }
+
+ err = gmpFrame->SetWidth(size.width);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ err = gmpFrame->SetHeight(size.height);
+ ENSURE_GMP_SUCCESS(err, false);
+
+ Buffer* buffer = currentCDMFrame.FrameBuffer();
+ uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kYPlane),
+ yStride * size.height);
+
+ outBuffer = gmpFrame->Buffer(kGMPUPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kUPlane),
+ uStride * halfHeight);
+
+ outBuffer = gmpFrame->Buffer(kGMPVPlane);
+ ENSURE_TRUE(outBuffer != nullptr, false);
+ MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight);
+ memcpy(outBuffer,
+ buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kVPlane),
+ vStride * halfHeight);
+
+ gmpFrame->SetTimestamp(currentCDMFrame.Timestamp());
+
+ auto d = mFrameDurations.find(currentCDMFrame.Timestamp());
+ if (d != mFrameDurations.end()) {
+ gmpFrame->SetDuration(d->second);
+ mFrameDurations.erase(d);
+ }
+
+ // Forget frame so it's not deleted, call back taking ownership.
+ frameDestroyerHelper.ForgetFrame();
+ mCallback->Decoded(gmpFrame);
+ }
+
+ return true;
+}
+
+void
+WidevineVideoDecoder::Reset()
+{
+ Log("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput);
+ // We shouldn't reset if a drain is pending.
+ MOZ_ASSERT(!mDrainPending);
+ mResetInProgress = true;
+ if (mSentInput) {
+ CDM()->ResetDecoder(kStreamTypeVideo);
+ }
+ // Remove queued frames, but do not reset mReturnOutputCallDepth, let the
+ // ReturnOutput calls unwind and decrement the counter as needed.
+ mFrameAllocationQueue.clear();
+ mFrameDurations.clear();
+ // Only if no ReturnOutput calls are in progress can we complete, otherwise
+ // ReturnOutput needs to finalize the reset.
+ if (mReturnOutputCallDepth == 0) {
+ CompleteReset();
+ }
+}
+
+void
+WidevineVideoDecoder::CompleteReset()
+{
+ mCallback->ResetComplete();
+ mSentInput = false;
+ mResetInProgress = false;
+}
+
+void
+WidevineVideoDecoder::Drain()
+{
+ Log("WidevineVideoDecoder::Drain()");
+ if (mReturnOutputCallDepth > 0) {
+ Log("Drain call is reentrant, postponing drain");
+ mDrainPending = true;
+ return;
+ }
+
+ Status rv = kSuccess;
+ while (rv == kSuccess) {
+ WidevineVideoFrame frame;
+ InputBuffer sample;
+ Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
+ Log("WidevineVideoDecoder::Drain(); DecryptAndDecodeFrame() rv=%d", rv);
+ if (frame.Format() == kUnknownVideoFormat) {
+ break;
+ }
+ if (rv == kSuccess) {
+ if (!ReturnOutput(frame)) {
+ Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
+ }
+ }
+ }
+ // Shouldn't be reset while draining.
+ MOZ_ASSERT(!mResetInProgress);
+
+ CDM()->ResetDecoder(kStreamTypeVideo);
+ mDrainPending = false;
+ mCallback->DrainComplete();
+}
+
+void
+WidevineVideoDecoder::DecodingComplete()
+{
+ Log("WidevineVideoDecoder::DecodingComplete()");
+ if (mCDMWrapper) {
+ CDM()->DeinitializeDecoder(kStreamTypeVideo);
+ mCDMWrapper = nullptr;
+ }
+ // Release that corresponds to AddRef() in constructor.
+ Release();
+}
+
+} // namespace mozilla