Source code
Revision control
Copy as Markdown
Other Tools
/*
* 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
*
*
* 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();
}