summaryrefslogtreecommitdiffstats
path: root/media/gmp-clearkey/0.1/VideoDecoder.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /media/gmp-clearkey/0.1/VideoDecoder.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'media/gmp-clearkey/0.1/VideoDecoder.cpp')
-rw-r--r--media/gmp-clearkey/0.1/VideoDecoder.cpp325
1 files changed, 325 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 0000000000..6417defdaf
--- /dev/null
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -0,0 +1,325 @@
+/*
+ * 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 <algorithm>
+#include <cstdint>
+#include <limits>
+
+#include "BigEndian.h"
+#include "ClearKeyUtils.h"
+#include "ClearKeyDecryptionManager.h"
+#include "VideoDecoder.h"
+#include "content_decryption_module.h"
+#include "mozilla/CheckedInt.h"
+
+using namespace wmf;
+
+VideoDecoder::VideoDecoder(cdm::Host_10* aHost)
+ : mHost(aHost), mHasShutdown(false) {
+ CK_LOGD("VideoDecoder created");
+
+ // We drop the ref in DecodingComplete().
+ AddRef();
+
+ mDecoder = new WMFH264Decoder();
+
+ uint32_t cores = std::max(1u, std::thread::hardware_concurrency());
+
+ HRESULT hr = mDecoder->Init(cores);
+ if (FAILED(hr)) {
+ CK_LOGE("Failed to initialize mDecoder!");
+ }
+}
+
+VideoDecoder::~VideoDecoder() { CK_LOGD("VideoDecoder destroyed"); }
+
+cdm::Status VideoDecoder::InitDecode(const cdm::VideoDecoderConfig_2& aConfig) {
+ CK_LOGD("VideoDecoder::InitDecode");
+
+ if (!mDecoder) {
+ CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
+
+ return cdm::Status::kDecodeError;
+ }
+
+ return cdm::Status::kSuccess;
+}
+
+cdm::Status VideoDecoder::Decode(const cdm::InputBuffer_2& aInputBuffer,
+ cdm::VideoFrame* aVideoFrame) {
+ CK_LOGD("VideoDecoder::Decode");
+ // If the input buffer we have been passed has a null buffer, it means we
+ // should drain.
+ if (!aInputBuffer.data) {
+ // This will drain the decoder until there are no frames left to drain,
+ // whereupon it will return 'NeedsMoreData'.
+ CK_LOGD("VideoDecoder::Decode Input buffer null: Draining");
+ return Drain(aVideoFrame);
+ }
+
+ DecodeData* data = new DecodeData();
+ Assign(data->mBuffer, aInputBuffer.data, aInputBuffer.data_size);
+ data->mTimestamp = aInputBuffer.timestamp;
+ data->mCrypto = CryptoMetaData(&aInputBuffer);
+
+ AutoPtr<DecodeData> d(data);
+ HRESULT hr;
+
+ if (!data || !mDecoder) {
+ CK_LOGE("Decode job not set up correctly!");
+ return cdm::Status::kDecodeError;
+ }
+
+ std::vector<uint8_t>& buffer = data->mBuffer;
+
+ if (data->mCrypto.IsValid()) {
+ cdm::Status rv =
+ ClearKeyDecryptionManager::Get()->Decrypt(buffer, data->mCrypto);
+
+ if (STATUS_FAILED(rv)) {
+ CK_LOGARRAY("Failed to decrypt video using key ", aInputBuffer.key_id,
+ aInputBuffer.key_id_size);
+ return rv;
+ }
+ }
+
+ hr = mDecoder->Input(buffer.data(), buffer.size(), data->mTimestamp);
+
+ CK_LOGD("VideoDecoder::Decode() Input ret hr=0x%x", hr);
+
+ if (FAILED(hr)) {
+ assert(hr != MF_E_TRANSFORM_NEED_MORE_INPUT);
+
+ CK_LOGE("VideoDecoder::Decode() decode failed ret=0x%x%s", hr,
+ ((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
+ CK_LOGD("Decode failed. The decoder is not accepting input");
+ return cdm::Status::kDecodeError;
+ }
+
+ return OutputFrame(aVideoFrame);
+}
+
+cdm::Status VideoDecoder::OutputFrame(cdm::VideoFrame* aVideoFrame) {
+ CK_LOGD("VideoDecoder::OutputFrame");
+
+ HRESULT hr = S_OK;
+
+ // Read all the output from the decoder. Ideally, this would be a while loop
+ // where we read the output and check the result as the condition. However,
+ // this produces a memory leak connected to assigning a new CComPtr to the
+ // address of the old one, which avoids the CComPtr cleaning up.
+ while (true) {
+ CComPtr<IMFSample> output;
+ hr = mDecoder->Output(&output);
+
+ if (hr != S_OK) {
+ break;
+ }
+
+ CK_LOGD("VideoDecoder::OutputFrame Decoder output ret=0x%x", hr);
+
+ mOutputQueue.push(output);
+ CK_LOGD("VideoDecoder::OutputFrame: Queue size: %u", mOutputQueue.size());
+ }
+
+ // If we don't have any inputs, we need more data.
+ if (mOutputQueue.empty()) {
+ CK_LOGD("Decode failed. Not enought data; Requesting more input");
+ return cdm::Status::kNeedMoreData;
+ }
+
+ // We will get a MF_E_TRANSFORM_NEED_MORE_INPUT every time, as we always
+ // consume everything in the buffer.
+ if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT && FAILED(hr)) {
+ CK_LOGD("Decode failed output ret=0x%x", hr);
+ return cdm::Status::kDecodeError;
+ }
+
+ CComPtr<IMFSample> result = mOutputQueue.front();
+ mOutputQueue.pop();
+
+ // The Chromium CDM API doesn't have support for negative strides, though
+ // they are theoretically possible in real world data.
+ if (mDecoder->GetStride() <= 0) {
+ CK_LOGD("VideoDecoder::OutputFrame Failed! (negative stride)");
+ return cdm::Status::kDecodeError;
+ }
+
+ const IntRect& picture = mDecoder->GetPictureRegion();
+ hr = SampleToVideoFrame(result, picture.width, picture.height,
+ mDecoder->GetStride(), mDecoder->GetFrameHeight(),
+ aVideoFrame);
+ if (FAILED(hr)) {
+ CK_LOGD("VideoDecoder::OutputFrame Failed!");
+ return cdm::Status::kDecodeError;
+ }
+
+ CK_LOGD("VideoDecoder::OutputFrame Succeeded.");
+ return cdm::Status::kSuccess;
+}
+
+HRESULT
+VideoDecoder::SampleToVideoFrame(IMFSample* aSample, int32_t aPictureWidth,
+ int32_t aPictureHeight, int32_t aStride,
+ int32_t aFrameHeight,
+ cdm::VideoFrame* aVideoFrame) {
+ CK_LOGD("[%p] VideoDecoder::SampleToVideoFrame()", this);
+
+ ENSURE(aSample != nullptr, E_POINTER);
+ ENSURE(aVideoFrame != nullptr, E_POINTER);
+
+ HRESULT hr;
+ CComPtr<IMFMediaBuffer> mediaBuffer;
+
+ aVideoFrame->SetFormat(cdm::kI420);
+
+ // 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, nullptr, nullptr);
+ ENSURE(SUCCEEDED(hr), hr);
+ stride = aStride;
+ }
+
+ // WMF stores the U and V planes 16-row-aligned, so we need to add padding
+ // to the row heights to ensure the source offsets of the Y'CbCr planes are
+ // referenced properly.
+ // YV12, planar format: [YYYY....][UUUU....][VVVV....]
+ // i.e., Y, then U, then V.
+ uint32_t padding = 0;
+ if (aFrameHeight % 16 != 0) {
+ padding = 16 - (aFrameHeight % 16);
+ }
+ uint32_t srcYSize = stride * (aFrameHeight + padding);
+ uint32_t srcUVSize = stride * (aFrameHeight + padding) / 4;
+ uint32_t halfStride = (stride + 1) / 2;
+
+ aVideoFrame->SetStride(cdm::VideoPlane::kYPlane, stride);
+ aVideoFrame->SetStride(cdm::VideoPlane::kUPlane, halfStride);
+ aVideoFrame->SetStride(cdm::VideoPlane::kVPlane, halfStride);
+
+ aVideoFrame->SetSize(cdm::Size{aPictureWidth, aPictureHeight});
+
+ // Note: We allocate the minimal sized buffer required to send the
+ // frame back over to the parent process. This is so that we request the
+ // same sized frame as the buffer allocator expects.
+ using mozilla::CheckedUint32;
+ CheckedUint32 bufferSize = CheckedUint32(stride) * aPictureHeight +
+ ((CheckedUint32(stride) * aPictureHeight) / 4) * 2;
+
+ // If the buffer is bigger than the max for a 32 bit, fail to avoid buffer
+ // overflows.
+ if (!bufferSize.isValid()) {
+ CK_LOGD("VideoDecoder::SampleToFrame Buffersize bigger than UINT32_MAX");
+ return E_FAIL;
+ }
+
+ // Get the buffer from the host.
+ cdm::Buffer* buffer = mHost->Allocate(bufferSize.value());
+ aVideoFrame->SetFrameBuffer(buffer);
+
+ // Make sure the buffer is non-null (allocate guarantees it will be of
+ // sufficient size).
+ if (!buffer) {
+ CK_LOGD("VideoDecoder::SampleToFrame Out of memory");
+ return E_OUTOFMEMORY;
+ }
+
+ uint8_t* outBuffer = buffer->Data();
+
+ aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kYPlane, 0);
+
+ // Offset of U plane is the size of the Y plane, excluding the padding that
+ // WMF adds.
+ uint32_t dstUOffset = stride * aPictureHeight;
+ aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kUPlane, dstUOffset);
+
+ // Offset of the V plane is the size of the Y plane + the size of the U plane,
+ // excluding any padding WMF adds.
+ uint32_t dstVOffset = stride * aPictureHeight + (stride * aPictureHeight) / 4;
+ aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kVPlane, dstVOffset);
+
+ // Copy the pixel data, excluding WMF's padding.
+ memcpy(outBuffer, data, stride * aPictureHeight);
+ memcpy(outBuffer + dstUOffset, data + srcYSize,
+ (stride * aPictureHeight) / 4);
+ memcpy(outBuffer + dstVOffset, data + srcYSize + srcUVSize,
+ (stride * aPictureHeight) / 4);
+
+ if (twoDBuffer) {
+ twoDBuffer->Unlock2D();
+ } else {
+ mediaBuffer->Unlock();
+ }
+
+ LONGLONG hns = 0;
+ hr = aSample->GetSampleTime(&hns);
+ ENSURE(SUCCEEDED(hr), hr);
+
+ aVideoFrame->SetTimestamp(HNsToUsecs(hns));
+
+ return S_OK;
+}
+
+void VideoDecoder::Reset() {
+ CK_LOGD("VideoDecoder::Reset");
+
+ if (mDecoder) {
+ mDecoder->Reset();
+ }
+
+ // Remove all the frames from the output queue.
+ while (!mOutputQueue.empty()) {
+ mOutputQueue.pop();
+ }
+}
+
+cdm::Status VideoDecoder::Drain(cdm::VideoFrame* aVideoFrame) {
+ CK_LOGD("VideoDecoder::Drain()");
+
+ if (!mDecoder) {
+ CK_LOGD("Drain failed! Decoder was not initialized");
+ return cdm::Status::kDecodeError;
+ }
+
+ mDecoder->Drain();
+
+ // Return any pending output.
+ return OutputFrame(aVideoFrame);
+}
+
+void VideoDecoder::DecodingComplete() {
+ CK_LOGD("VideoDecoder::DecodingComplete()");
+
+ 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();
+}