Dactyloidae/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp

400 lines
13 KiB
C++

/* -*- 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