gecko-dev/media/gmp-clearkey/0.1/AudioDecoder.cpp
Chris Pearce d6743b5aff Bug 1186406 - Copy input to ClearKey's decoder, so we can return its containing shmem earlier. r=gerald
We're failing in the "Very rough kill-switch" case in
GMPVideoDecoderParent::Decode() we find that too many shmems are in use when we
come to send a "Decode" message to the GMP, and that causes an error which
percolates up to cause the test failure.

This patch changes gmp-clearkey to copy the input encrypted and compressed
sample and immediately return the shmem to the parent process. We are
copying the data anyway when we decrypt, so we can rejigg things so that we
don't actually end up doing a second copy.
2015-12-01 18:13:58 +13:00

313 lines
7.5 KiB
C++

/*
* Copyright 2015, 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 "AudioDecoder.h"
#include "ClearKeyDecryptionManager.h"
#include "ClearKeyUtils.h"
#include "gmp-task-utils.h"
using namespace wmf;
AudioDecoder::AudioDecoder(GMPAudioHost *aHostAPI)
: mHostAPI(aHostAPI)
, mCallback(nullptr)
, mWorkerThread(nullptr)
, mMutex(nullptr)
, mNumInputTasks(0)
, mHasShutdown(false)
{
// We drop the ref in DecodingComplete().
AddRef();
}
AudioDecoder::~AudioDecoder()
{
if (mMutex) {
mMutex->Destroy();
}
}
void
AudioDecoder::InitDecode(const GMPAudioCodec& aConfig,
GMPAudioDecoderCallback* aCallback)
{
mCallback = aCallback;
assert(mCallback);
mDecoder = new WMFAACDecoder();
HRESULT hr = mDecoder->Init(aConfig.mChannelCount,
aConfig.mSamplesPerSecond,
(BYTE*)aConfig.mExtraData,
aConfig.mExtraDataLen);
LOG("[%p] AudioDecoder::InitializeAudioDecoder() hr=0x%x\n", this, hr);
if (FAILED(hr)) {
mCallback->Error(GMPGenericErr);
return;
}
auto err = GetPlatform()->createmutex(&mMutex);
if (GMP_FAILED(err)) {
mCallback->Error(GMPGenericErr);
return;
}
}
void
AudioDecoder::EnsureWorker()
{
if (!mWorkerThread) {
GetPlatform()->createthread(&mWorkerThread);
if (!mWorkerThread) {
mCallback->Error(GMPAllocErr);
return;
}
}
}
void
AudioDecoder::Decode(GMPAudioSamples* aInput)
{
EnsureWorker();
{
AutoLock lock(mMutex);
mNumInputTasks++;
}
mWorkerThread->Post(WrapTaskRefCounted(this,
&AudioDecoder::DecodeTask,
aInput));
}
void
AudioDecoder::DecodeTask(GMPAudioSamples* aInput)
{
HRESULT hr;
{
AutoLock lock(mMutex);
mNumInputTasks--;
assert(mNumInputTasks >= 0);
}
if (!aInput || !mHostAPI || !mDecoder) {
LOG("Decode job not set up correctly!");
return;
}
const uint8_t* inBuffer = aInput->Buffer();
if (!inBuffer) {
LOG("No buffer for encoded samples!\n");
return;
}
const GMPEncryptedBufferMetadata* crypto = aInput->GetDecryptionData();
std::vector<uint8_t> buffer(inBuffer, inBuffer + aInput->Size());
if (crypto) {
// Plugin host should have set up its decryptor/key sessions
// before trying to decode!
GMPErr rv =
ClearKeyDecryptionManager::Get()->Decrypt(buffer, CryptoMetaData(crypto));
if (GMP_FAILED(rv)) {
CK_LOGE("Failed to decrypt with key id %08x...", *(uint32_t*)crypto->KeyId());
MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Error, rv));
return;
}
}
hr = mDecoder->Input(&buffer[0],
buffer.size(),
aInput->TimeStamp());
// We must delete the input sample!
GetPlatform()->runonmainthread(WrapTask(aInput, &GMPAudioSamples::Destroy));
SAMPLE_LOG("AudioDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
if (FAILED(hr)) {
LOG("AudioDecoder::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);
SAMPLE_LOG("AudioDecoder::DecodeTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
ReturnOutput(output);
}
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, &GMPAudioDecoderCallback::InputDataExhausted));
}
} else if (FAILED(hr)) {
LOG("AudioDecoder::DecodeTask() output failed hr=0x%x\n", hr);
}
}
}
void
AudioDecoder::ReturnOutput(IMFSample* aSample)
{
SAMPLE_LOG("[%p] AudioDecoder::ReturnOutput()\n", this);
assert(aSample);
HRESULT hr;
GMPAudioSamples* samples = nullptr;
mHostAPI->CreateSamples(kGMPAudioIS16Samples, &samples);
if (!samples) {
LOG("Failed to create i420 frame!\n");
return;
}
hr = MFToGMPSample(aSample, samples);
if (FAILED(hr)) {
samples->Destroy();
LOG("Failed to prepare output sample!");
return;
}
ENSURE(SUCCEEDED(hr), /*void*/);
MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Decoded, samples));
}
HRESULT
AudioDecoder::MFToGMPSample(IMFSample* aInput,
GMPAudioSamples* aOutput)
{
ENSURE(aInput != nullptr, E_POINTER);
ENSURE(aOutput != nullptr, E_POINTER);
HRESULT hr;
CComPtr<IMFMediaBuffer> mediaBuffer;
hr = aInput->ConvertToContiguousBuffer(&mediaBuffer);
ENSURE(SUCCEEDED(hr), hr);
BYTE* data = nullptr; // Note: *data will be owned by the IMFMediaBuffer, we don't need to free it.
DWORD maxLength = 0, currentLength = 0;
hr = mediaBuffer->Lock(&data, &maxLength, &currentLength);
ENSURE(SUCCEEDED(hr), hr);
auto err = aOutput->SetBufferSize(currentLength);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
memcpy(aOutput->Buffer(), data, currentLength);
mediaBuffer->Unlock();
LONGLONG hns = 0;
hr = aInput->GetSampleTime(&hns);
ENSURE(SUCCEEDED(hr), hr);
aOutput->SetTimeStamp(HNsToUsecs(hns));
aOutput->SetChannels(mDecoder->Channels());
aOutput->SetRate(mDecoder->Rate());
return S_OK;
}
void
AudioDecoder::Reset()
{
if (mDecoder) {
mDecoder->Reset();
}
if (mCallback) {
mCallback->ResetComplete();
}
}
void
AudioDecoder::DrainTask()
{
mDecoder->Drain();
// Return any pending output.
HRESULT hr = S_OK;
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
SAMPLE_LOG("AudioDecoder::DrainTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
ReturnOutput(output);
}
}
MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::DrainComplete));
}
void
AudioDecoder::Drain()
{
if (!mDecoder) {
return;
}
EnsureWorker();
mWorkerThread->Post(WrapTaskRefCounted(this,
&AudioDecoder::DrainTask));
}
void
AudioDecoder::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
AudioDecoder::MaybeRunOnMainThread(GMPTask* aTask)
{
class MaybeRunTask : public GMPTask
{
public:
MaybeRunTask(AudioDecoder* aDecoder, GMPTask* aTask)
: mDecoder(aDecoder), mTask(aTask)
{ }
virtual void Run(void) {
if (mDecoder->HasShutdown()) {
CK_LOGD("Trying to dispatch to main thread after AudioDecoder has shut down");
return;
}
mTask->Run();
}
virtual void Destroy()
{
mTask->Destroy();
delete this;
}
private:
RefPtr<AudioDecoder> mDecoder;
GMPTask* mTask;
};
GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
}