summaryrefslogtreecommitdiffstats
path: root/media/gmp-clearkey/0.1/VideoDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/gmp-clearkey/0.1/VideoDecoder.cpp')
-rw-r--r--media/gmp-clearkey/0.1/VideoDecoder.cpp455
1 files changed, 455 insertions, 0 deletions
diff --git a/media/gmp-clearkey/0.1/VideoDecoder.cpp b/media/gmp-clearkey/0.1/VideoDecoder.cpp
new file mode 100644
index 000000000..a3f25402b
--- /dev/null
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+#include <limits>
+
+#include "AnnexB.h"
+#include "BigEndian.h"
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeyUtils.h"
+#include "gmp-task-utils.h"
+#include "VideoDecoder.h"
+
+using namespace wmf;
+
+VideoDecoder::VideoDecoder(GMPVideoHost *aHostAPI)
+ : mHostAPI(aHostAPI)
+ , mCallback(nullptr)
+ , mWorkerThread(nullptr)
+ , mMutex(nullptr)
+ , mNumInputTasks(0)
+ , mSentExtraData(false)
+ , mIsFlushing(false)
+ , mHasShutdown(false)
+{
+ // We drop the ref in DecodingComplete().
+ AddRef();
+}
+
+VideoDecoder::~VideoDecoder()
+{
+ if (mMutex) {
+ mMutex->Destroy();
+ }
+}
+
+void
+VideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
+ const uint8_t* aCodecSpecific,
+ uint32_t aCodecSpecificLength,
+ GMPVideoDecoderCallback* aCallback,
+ int32_t aCoreCount)
+{
+ mCallback = aCallback;
+ assert(mCallback);
+ mDecoder = new WMFH264Decoder();
+ HRESULT hr = mDecoder->Init(aCoreCount);
+ if (FAILED(hr)) {
+ CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
+ mCallback->Error(GMPGenericErr);
+ return;
+ }
+
+ auto err = GetPlatform()->createmutex(&mMutex);
+ if (GMP_FAILED(err)) {
+ CK_LOGD("VideoDecoder::InitDecode failed to create GMPMutex");
+ mCallback->Error(GMPGenericErr);
+ return;
+ }
+
+ // The first byte is mPacketizationMode, which is only relevant for
+ // WebRTC/OpenH264 usecase.
+ const uint8_t* avcc = aCodecSpecific + 1;
+ const uint8_t* avccEnd = aCodecSpecific + aCodecSpecificLength;
+ mExtraData.insert(mExtraData.end(), avcc, avccEnd);
+
+ AnnexB::ConvertConfig(mExtraData, mAnnexB);
+}
+
+void
+VideoDecoder::EnsureWorker()
+{
+ if (!mWorkerThread) {
+ GetPlatform()->createthread(&mWorkerThread);
+ if (!mWorkerThread) {
+ mCallback->Error(GMPAllocErr);
+ return;
+ }
+ }
+}
+
+void
+VideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
+ bool aMissingFrames,
+ const uint8_t* aCodecSpecificInfo,
+ uint32_t aCodecSpecificInfoLength,
+ int64_t aRenderTimeMs)
+{
+ if (aInputFrame->BufferType() != GMP_BufferLength32) {
+ // Gecko should only send frames with 4 byte NAL sizes to GMPs.
+ mCallback->Error(GMPGenericErr);
+ return;
+ }
+
+ EnsureWorker();
+
+ {
+ AutoLock lock(mMutex);
+ mNumInputTasks++;
+ }
+
+ // Note: we don't need the codec specific info on a per-frame basis.
+ // It's mostly useful for WebRTC use cases.
+
+ // Make a copy of the data, so we can release aInputFrame ASAP,
+ // to avoid too many shmem handles being held by the GMP process.
+ // If the GMP process holds on to too many shmem handles, the Gecko
+ // side can fail to allocate a shmem to send more input. This is
+ // particularly a problem in Gecko mochitests, which can open multiple
+ // actors at once which share the same pool of shmems.
+ DecodeData* data = new DecodeData();
+ Assign(data->mBuffer, aInputFrame->Buffer(), aInputFrame->Size());
+ data->mTimestamp = aInputFrame->TimeStamp();
+ data->mDuration = aInputFrame->Duration();
+ data->mIsKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
+ const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
+ if (crypto) {
+ data->mCrypto.Init(crypto);
+ }
+ aInputFrame->Destroy();
+ mWorkerThread->Post(WrapTaskRefCounted(this,
+ &VideoDecoder::DecodeTask,
+ data));
+}
+
+void
+VideoDecoder::DecodeTask(DecodeData* aData)
+{
+ CK_LOGD("VideoDecoder::DecodeTask");
+ AutoPtr<DecodeData> d(aData);
+ HRESULT hr;
+
+ {
+ AutoLock lock(mMutex);
+ mNumInputTasks--;
+ assert(mNumInputTasks >= 0);
+ }
+
+ if (mIsFlushing) {
+ CK_LOGD("VideoDecoder::DecodeTask rejecting frame: flushing.");
+ return;
+ }
+
+ if (!aData || !mHostAPI || !mDecoder) {
+ CK_LOGE("Decode job not set up correctly!");
+ return;
+ }
+
+ std::vector<uint8_t>& buffer = aData->mBuffer;
+ if (aData->mCrypto.IsValid()) {
+ // Plugin host should have set up its decryptor/key sessions
+ // before trying to decode!
+ GMPErr rv =
+ ClearKeyDecryptionManager::Get()->Decrypt(buffer, aData->mCrypto);
+
+ if (GMP_FAILED(rv)) {
+ MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::Error, rv));
+ return;
+ }
+ }
+
+ AnnexB::ConvertFrameInPlace(buffer);
+
+ if (aData->mIsKeyframe) {
+ // We must send the SPS and PPS to Windows Media Foundation's decoder.
+ // Note: We do this *after* decryption, otherwise the subsample info
+ // would be incorrect.
+ buffer.insert(buffer.begin(), mAnnexB.begin(), mAnnexB.end());
+ }
+
+ hr = mDecoder->Input(buffer.data(),
+ buffer.size(),
+ aData->mTimestamp,
+ aData->mDuration);
+
+ CK_LOGD("VideoDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
+ if (FAILED(hr)) {
+ CK_LOGE("VideoDecoder::DecodeTask() decode failed ret=0x%x%s\n",
+ hr,
+ ((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
+ return;
+ }
+
+ while (hr == S_OK) {
+ CComPtr<IMFSample> output;
+ hr = mDecoder->Output(&output);
+ CK_LOGD("VideoDecoder::DecodeTask() output ret=0x%x\n", hr);
+ if (hr == S_OK) {
+ MaybeRunOnMainThread(
+ WrapTaskRefCounted(this,
+ &VideoDecoder::ReturnOutput,
+ CComPtr<IMFSample>(output),
+ mDecoder->GetFrameWidth(),
+ mDecoder->GetFrameHeight(),
+ mDecoder->GetStride()));
+ }
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ AutoLock lock(mMutex);
+ if (mNumInputTasks == 0) {
+ // We have run all input tasks. We *must* notify Gecko so that it will
+ // send us more data.
+ MaybeRunOnMainThread(
+ WrapTask(mCallback,
+ &GMPVideoDecoderCallback::InputDataExhausted));
+ }
+ }
+ if (FAILED(hr)) {
+ CK_LOGE("VideoDecoder::DecodeTask() output failed hr=0x%x\n", hr);
+ }
+ }
+}
+
+void
+VideoDecoder::ReturnOutput(IMFSample* aSample,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride)
+{
+ CK_LOGD("[%p] VideoDecoder::ReturnOutput()\n", this);
+ assert(aSample);
+
+ HRESULT hr;
+
+ GMPVideoFrame* f = nullptr;
+ auto err = mHostAPI->CreateFrame(kGMPI420VideoFrame, &f);
+ if (GMP_FAILED(err) || !f) {
+ CK_LOGE("Failed to create i420 frame!\n");
+ return;
+ }
+ if (HasShutdown()) {
+ // Note: GMPVideoHost::CreateFrame() can process messages before returning,
+ // including a message that calls VideoDecoder::DecodingComplete(), i.e.
+ // we can shutdown during the call!
+ CK_LOGD("Shutdown while waiting on GMPVideoHost::CreateFrame()!\n");
+ f->Destroy();
+ return;
+ }
+
+ auto vf = static_cast<GMPVideoi420Frame*>(f);
+
+ hr = SampleToVideoFrame(aSample, aWidth, aHeight, aStride, vf);
+ ENSURE(SUCCEEDED(hr), /*void*/);
+
+ mCallback->Decoded(vf);
+}
+
+HRESULT
+VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aStride,
+ GMPVideoi420Frame* aVideoFrame)
+{
+ ENSURE(aSample != nullptr, E_POINTER);
+ ENSURE(aVideoFrame != nullptr, E_POINTER);
+
+ HRESULT hr;
+ CComPtr<IMFMediaBuffer> mediaBuffer;
+
+ // Must convert to contiguous mediaBuffer to use IMD2DBuffer interface.
+ hr = aSample->ConvertToContiguousBuffer(&mediaBuffer);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ // Try and use the IMF2DBuffer interface if available, otherwise fallback
+ // to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient,
+ // but only some systems (Windows 8?) support it.
+ BYTE* data = nullptr;
+ LONG stride = 0;
+ CComPtr<IMF2DBuffer> twoDBuffer;
+ hr = mediaBuffer->QueryInterface(static_cast<IMF2DBuffer**>(&twoDBuffer));
+ if (SUCCEEDED(hr)) {
+ hr = twoDBuffer->Lock2D(&data, &stride);
+ ENSURE(SUCCEEDED(hr), hr);
+ } else {
+ hr = mediaBuffer->Lock(&data, NULL, NULL);
+ ENSURE(SUCCEEDED(hr), hr);
+ stride = aStride;
+ }
+
+ // The V and U planes are stored 16-row-aligned, so we need to add padding
+ // to the row heights to ensure the Y'CbCr planes are referenced properly.
+ // YV12, planar format: [YYYY....][VVVV....][UUUU....]
+ // i.e., Y, then V, then U.
+ uint32_t padding = 0;
+ if (aHeight % 16 != 0) {
+ padding = 16 - (aHeight % 16);
+ }
+ int32_t y_size = stride * (aHeight + padding);
+ int32_t v_size = stride * (aHeight + padding) / 4;
+ int32_t halfStride = (stride + 1) / 2;
+ int32_t halfHeight = (aHeight + 1) / 2;
+
+ auto err = aVideoFrame->CreateEmptyFrame(stride, aHeight, stride, halfStride, halfStride);
+ ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+
+ err = aVideoFrame->SetWidth(aWidth);
+ ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+ err = aVideoFrame->SetHeight(aHeight);
+ ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+
+ uint8_t* outBuffer = aVideoFrame->Buffer(kGMPYPlane);
+ ENSURE(outBuffer != nullptr, E_FAIL);
+ assert(aVideoFrame->AllocatedSize(kGMPYPlane) >= stride*aHeight);
+ memcpy(outBuffer, data, stride*aHeight);
+
+ outBuffer = aVideoFrame->Buffer(kGMPUPlane);
+ ENSURE(outBuffer != nullptr, E_FAIL);
+ assert(aVideoFrame->AllocatedSize(kGMPUPlane) >= halfStride*halfHeight);
+ memcpy(outBuffer, data+y_size, halfStride*halfHeight);
+
+ outBuffer = aVideoFrame->Buffer(kGMPVPlane);
+ ENSURE(outBuffer != nullptr, E_FAIL);
+ assert(aVideoFrame->AllocatedSize(kGMPVPlane) >= halfStride*halfHeight);
+ memcpy(outBuffer, data + y_size + v_size, halfStride*halfHeight);
+
+ if (twoDBuffer) {
+ twoDBuffer->Unlock2D();
+ } else {
+ mediaBuffer->Unlock();
+ }
+
+ LONGLONG hns = 0;
+ hr = aSample->GetSampleTime(&hns);
+ ENSURE(SUCCEEDED(hr), hr);
+ aVideoFrame->SetTimestamp(HNsToUsecs(hns));
+
+ hr = aSample->GetSampleDuration(&hns);
+ ENSURE(SUCCEEDED(hr), hr);
+ aVideoFrame->SetDuration(HNsToUsecs(hns));
+
+ return S_OK;
+}
+
+void
+VideoDecoder::ResetCompleteTask()
+{
+ mIsFlushing = false;
+ if (mCallback) {
+ MaybeRunOnMainThread(WrapTask(mCallback,
+ &GMPVideoDecoderCallback::ResetComplete));
+ }
+}
+
+void
+VideoDecoder::Reset()
+{
+ mIsFlushing = true;
+ if (mDecoder) {
+ mDecoder->Reset();
+ }
+
+ // Schedule ResetComplete callback to run after existing frames have been
+ // flushed out of the task queue.
+ EnsureWorker();
+ mWorkerThread->Post(WrapTaskRefCounted(this,
+ &VideoDecoder::ResetCompleteTask));
+}
+
+void
+VideoDecoder::DrainTask()
+{
+ mDecoder->Drain();
+
+ // Return any pending output.
+ HRESULT hr = S_OK;
+ while (hr == S_OK) {
+ CComPtr<IMFSample> output;
+ hr = mDecoder->Output(&output);
+ CK_LOGD("VideoDecoder::DrainTask() output ret=0x%x\n", hr);
+ if (hr == S_OK) {
+ MaybeRunOnMainThread(
+ WrapTaskRefCounted(this,
+ &VideoDecoder::ReturnOutput,
+ CComPtr<IMFSample>(output),
+ mDecoder->GetFrameWidth(),
+ mDecoder->GetFrameHeight(),
+ mDecoder->GetStride()));
+ }
+ }
+ MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
+}
+
+void
+VideoDecoder::Drain()
+{
+ if (!mDecoder) {
+ if (mCallback) {
+ mCallback->DrainComplete();
+ }
+ return;
+ }
+ EnsureWorker();
+ mWorkerThread->Post(WrapTaskRefCounted(this,
+ &VideoDecoder::DrainTask));
+}
+
+void
+VideoDecoder::DecodingComplete()
+{
+ if (mWorkerThread) {
+ mWorkerThread->Join();
+ }
+ mHasShutdown = true;
+
+ // Release the reference we added in the constructor. There may be
+ // WrapRefCounted tasks that also hold references to us, and keep
+ // us alive a little longer.
+ Release();
+}
+
+void
+VideoDecoder::MaybeRunOnMainThread(GMPTask* aTask)
+{
+ class MaybeRunTask : public GMPTask
+ {
+ public:
+ MaybeRunTask(VideoDecoder* aDecoder, GMPTask* aTask)
+ : mDecoder(aDecoder), mTask(aTask)
+ { }
+
+ virtual void Run(void) {
+ if (mDecoder->HasShutdown()) {
+ CK_LOGD("Trying to dispatch to main thread after VideoDecoder has shut down");
+ return;
+ }
+
+ mTask->Run();
+ }
+
+ virtual void Destroy()
+ {
+ mTask->Destroy();
+ delete this;
+ }
+
+ private:
+ RefPtr<VideoDecoder> mDecoder;
+ GMPTask* mTask;
+ };
+
+ GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
+}