зеркало из https://github.com/mozilla/gecko-dev.git
416 строки
13 KiB
C++
416 строки
13 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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 "EMEDecoderModule.h"
|
|
#include "EMEVideoDecoder.h"
|
|
#include "GMPDecoderModule.h"
|
|
#include "GMPService.h"
|
|
#include "MP4Decoder.h"
|
|
#include "MediaInfo.h"
|
|
#include "MediaPrefs.h"
|
|
#include "PDMFactory.h"
|
|
#include "gmp-decryption.h"
|
|
#include "mozIGeckoMediaPluginService.h"
|
|
#include "mozilla/CDMProxy.h"
|
|
#include "mozilla/EMEUtils.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "DecryptThroughputLimit.h"
|
|
|
|
namespace mozilla {
|
|
|
|
typedef MozPromiseRequestHolder<DecryptPromise> DecryptPromiseRequestHolder;
|
|
extern already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule();
|
|
|
|
class EMEDecryptor : public MediaDataDecoder
|
|
{
|
|
public:
|
|
EMEDecryptor(MediaDataDecoder* aDecoder, CDMProxy* aProxy,
|
|
TaskQueue* aDecodeTaskQueue, TrackInfo::TrackType aType,
|
|
MediaEventProducer<TrackInfo::TrackType>* aOnWaitingForKey)
|
|
: mDecoder(aDecoder)
|
|
, mTaskQueue(aDecodeTaskQueue)
|
|
, mProxy(aProxy)
|
|
, mSamplesWaitingForKey(
|
|
new SamplesWaitingForKey(mProxy, aType, aOnWaitingForKey))
|
|
, mThroughputLimiter(aDecodeTaskQueue)
|
|
, mIsShutdown(false)
|
|
{
|
|
}
|
|
|
|
RefPtr<InitPromise> Init() override
|
|
{
|
|
MOZ_ASSERT(!mIsShutdown);
|
|
return mDecoder->Init();
|
|
}
|
|
|
|
RefPtr<DecodePromise> Decode(MediaRawData* aSample) override
|
|
{
|
|
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
|
MOZ_RELEASE_ASSERT(mDecrypts.Count() == 0,
|
|
"Can only process one sample at a time");
|
|
RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
|
|
|
|
RefPtr<EMEDecryptor> self = this;
|
|
mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)
|
|
->Then(mTaskQueue, __func__,
|
|
[self, this](RefPtr<MediaRawData> aSample) {
|
|
mKeyRequest.Complete();
|
|
ThrottleDecode(aSample);
|
|
},
|
|
[self, this]() {
|
|
mKeyRequest.Complete();
|
|
})
|
|
->Track(mKeyRequest);
|
|
|
|
return p;
|
|
}
|
|
|
|
void ThrottleDecode(MediaRawData* aSample)
|
|
{
|
|
RefPtr<EMEDecryptor> self = this;
|
|
mThroughputLimiter.Throttle(aSample)
|
|
->Then(mTaskQueue, __func__,
|
|
[self, this] (RefPtr<MediaRawData> aSample) {
|
|
mThrottleRequest.Complete();
|
|
AttemptDecode(aSample);
|
|
},
|
|
[self, this]() {
|
|
mThrottleRequest.Complete();
|
|
})
|
|
->Track(mThrottleRequest);
|
|
}
|
|
|
|
void AttemptDecode(MediaRawData* aSample)
|
|
{
|
|
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
|
if (mIsShutdown) {
|
|
NS_WARNING("EME encrypted sample arrived after shutdown");
|
|
mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
|
return;
|
|
}
|
|
|
|
nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
|
|
mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
|
|
writer->mCrypto.mSessionIds);
|
|
|
|
mDecrypts.Put(aSample, new DecryptPromiseRequestHolder());
|
|
mProxy->Decrypt(aSample)
|
|
->Then(mTaskQueue, __func__, this,
|
|
&EMEDecryptor::Decrypted,
|
|
&EMEDecryptor::Decrypted)
|
|
->Track(*mDecrypts.Get(aSample));
|
|
}
|
|
|
|
void Decrypted(const DecryptResult& aDecrypted)
|
|
{
|
|
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
|
MOZ_ASSERT(aDecrypted.mSample);
|
|
|
|
nsAutoPtr<DecryptPromiseRequestHolder> holder;
|
|
mDecrypts.RemoveAndForget(aDecrypted.mSample, holder);
|
|
if (holder) {
|
|
holder->Complete();
|
|
} else {
|
|
// Decryption is not in the list of decrypt operations waiting
|
|
// for a result. It must have been flushed or drained. Ignore result.
|
|
return;
|
|
}
|
|
|
|
if (mIsShutdown) {
|
|
NS_WARNING("EME decrypted sample arrived after shutdown");
|
|
return;
|
|
}
|
|
|
|
if (aDecrypted.mStatus == eme::NoKeyErr) {
|
|
// Key became unusable after we sent the sample to CDM to decrypt.
|
|
// Call Decode() again, so that the sample is enqueued for decryption
|
|
// if the key becomes usable again.
|
|
AttemptDecode(aDecrypted.mSample);
|
|
} else if (aDecrypted.mStatus != eme::Ok) {
|
|
mDecodePromise.RejectIfExists(
|
|
MediaResult(
|
|
NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
|
RESULT_DETAIL("decrypted.mStatus=%u", uint32_t(aDecrypted.mStatus))),
|
|
__func__);
|
|
} else {
|
|
MOZ_ASSERT(!mIsShutdown);
|
|
// The sample is no longer encrypted, so clear its crypto metadata.
|
|
UniquePtr<MediaRawDataWriter> writer(aDecrypted.mSample->CreateWriter());
|
|
writer->mCrypto = CryptoSample();
|
|
RefPtr<EMEDecryptor> self = this;
|
|
mDecoder->Decode(aDecrypted.mSample)
|
|
->Then(mTaskQueue, __func__,
|
|
[self, this](const DecodedData& aResults) {
|
|
mDecodeRequest.Complete();
|
|
mDecodePromise.ResolveIfExists(aResults, __func__);
|
|
},
|
|
[self, this](const MediaResult& aError) {
|
|
mDecodeRequest.Complete();
|
|
mDecodePromise.RejectIfExists(aError, __func__);
|
|
})
|
|
->Track(mDecodeRequest);
|
|
}
|
|
}
|
|
|
|
RefPtr<FlushPromise> Flush() override
|
|
{
|
|
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
|
MOZ_ASSERT(!mIsShutdown);
|
|
mKeyRequest.DisconnectIfExists();
|
|
mThrottleRequest.DisconnectIfExists();
|
|
mDecodeRequest.DisconnectIfExists();
|
|
mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
|
mThroughputLimiter.Flush();
|
|
for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
|
|
nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
|
|
holder->DisconnectIfExists();
|
|
iter.Remove();
|
|
}
|
|
RefPtr<SamplesWaitingForKey> k = mSamplesWaitingForKey;
|
|
return mDecoder->Flush()->Then(
|
|
mTaskQueue, __func__,
|
|
[k]() {
|
|
k->Flush();
|
|
return FlushPromise::CreateAndResolve(true, __func__);
|
|
});
|
|
}
|
|
|
|
RefPtr<DecodePromise> Drain() override
|
|
{
|
|
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
|
MOZ_ASSERT(!mIsShutdown);
|
|
MOZ_ASSERT(mDecodePromise.IsEmpty() && !mDecodeRequest.Exists(),
|
|
"Must wait for decoding to complete");
|
|
for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
|
|
nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
|
|
holder->DisconnectIfExists();
|
|
iter.Remove();
|
|
}
|
|
return mDecoder->Drain();
|
|
}
|
|
|
|
RefPtr<ShutdownPromise> Shutdown() override
|
|
{
|
|
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
|
MOZ_ASSERT(!mIsShutdown);
|
|
mIsShutdown = true;
|
|
mSamplesWaitingForKey = nullptr;
|
|
RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
|
|
mProxy = nullptr;
|
|
return decoder->Shutdown();
|
|
}
|
|
|
|
const char* GetDescriptionName() const override
|
|
{
|
|
return mDecoder->GetDescriptionName();
|
|
}
|
|
|
|
ConversionRequired NeedsConversion() const override
|
|
{
|
|
return mDecoder->NeedsConversion();
|
|
}
|
|
|
|
private:
|
|
RefPtr<MediaDataDecoder> mDecoder;
|
|
RefPtr<TaskQueue> mTaskQueue;
|
|
RefPtr<CDMProxy> mProxy;
|
|
nsClassHashtable<nsRefPtrHashKey<MediaRawData>, DecryptPromiseRequestHolder>
|
|
mDecrypts;
|
|
RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
|
|
MozPromiseRequestHolder<SamplesWaitingForKey::WaitForKeyPromise> mKeyRequest;
|
|
DecryptThroughputLimit mThroughputLimiter;
|
|
MozPromiseRequestHolder<DecryptThroughputLimit::ThrottlePromise> mThrottleRequest;
|
|
MozPromiseHolder<DecodePromise> mDecodePromise;
|
|
MozPromiseHolder<DecodePromise> mDrainPromise;
|
|
MozPromiseHolder<FlushPromise> mFlushPromise;
|
|
MozPromiseRequestHolder<DecodePromise> mDecodeRequest;
|
|
|
|
bool mIsShutdown;
|
|
};
|
|
|
|
EMEMediaDataDecoderProxy::EMEMediaDataDecoderProxy(
|
|
already_AddRefed<AbstractThread> aProxyThread,
|
|
CDMProxy* aProxy,
|
|
const CreateDecoderParams& aParams)
|
|
: MediaDataDecoderProxy(Move(aProxyThread))
|
|
, mTaskQueue(AbstractThread::GetCurrent()->AsTaskQueue())
|
|
, mSamplesWaitingForKey(
|
|
new SamplesWaitingForKey(aProxy,
|
|
aParams.mType,
|
|
aParams.mOnWaitingForKeyEvent))
|
|
, mProxy(aProxy)
|
|
{
|
|
}
|
|
|
|
EMEMediaDataDecoderProxy::EMEMediaDataDecoderProxy(
|
|
const CreateDecoderParams& aParams,
|
|
already_AddRefed<MediaDataDecoder> aProxyDecoder,
|
|
CDMProxy* aProxy)
|
|
: MediaDataDecoderProxy(Move(aProxyDecoder))
|
|
, mTaskQueue(AbstractThread::GetCurrent()->AsTaskQueue())
|
|
, mSamplesWaitingForKey(
|
|
new SamplesWaitingForKey(aProxy,
|
|
aParams.mType,
|
|
aParams.mOnWaitingForKeyEvent))
|
|
, mProxy(aProxy)
|
|
{
|
|
}
|
|
|
|
RefPtr<MediaDataDecoder::DecodePromise>
|
|
EMEMediaDataDecoderProxy::Decode(MediaRawData* aSample)
|
|
{
|
|
RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
|
|
|
|
RefPtr<EMEMediaDataDecoderProxy> self = this;
|
|
mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)
|
|
->Then(mTaskQueue, __func__,
|
|
[self, this](RefPtr<MediaRawData> aSample) {
|
|
mKeyRequest.Complete();
|
|
|
|
nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
|
|
mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
|
|
writer->mCrypto.mSessionIds);
|
|
MediaDataDecoderProxy::Decode(aSample)
|
|
->Then(mTaskQueue, __func__,
|
|
[self, this](const DecodedData& aResults) {
|
|
mDecodeRequest.Complete();
|
|
mDecodePromise.Resolve(aResults, __func__);
|
|
},
|
|
[self, this](const MediaResult& aError) {
|
|
mDecodeRequest.Complete();
|
|
mDecodePromise.Reject(aError, __func__);
|
|
})
|
|
->Track(mDecodeRequest);
|
|
},
|
|
[self, this]() {
|
|
mKeyRequest.Complete();
|
|
MOZ_CRASH("Should never get here");
|
|
})
|
|
->Track(mKeyRequest);
|
|
|
|
return p;
|
|
}
|
|
|
|
RefPtr<MediaDataDecoder::FlushPromise>
|
|
EMEMediaDataDecoderProxy::Flush()
|
|
{
|
|
mKeyRequest.DisconnectIfExists();
|
|
mDecodeRequest.DisconnectIfExists();
|
|
mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
|
return MediaDataDecoderProxy::Flush();
|
|
}
|
|
|
|
RefPtr<ShutdownPromise>
|
|
EMEMediaDataDecoderProxy::Shutdown()
|
|
{
|
|
mSamplesWaitingForKey = nullptr;
|
|
mProxy = nullptr;
|
|
return MediaDataDecoderProxy::Shutdown();
|
|
}
|
|
|
|
EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM)
|
|
: mProxy(aProxy)
|
|
, mPDM(aPDM)
|
|
{
|
|
}
|
|
|
|
EMEDecoderModule::~EMEDecoderModule()
|
|
{
|
|
}
|
|
|
|
static already_AddRefed<MediaDataDecoderProxy>
|
|
CreateDecoderWrapper(CDMProxy* aProxy, const CreateDecoderParams& aParams)
|
|
{
|
|
RefPtr<gmp::GeckoMediaPluginService> s(
|
|
gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
|
|
if (!s) {
|
|
return nullptr;
|
|
}
|
|
RefPtr<AbstractThread> thread(s->GetAbstractGMPThread());
|
|
if (!thread) {
|
|
return nullptr;
|
|
}
|
|
RefPtr<MediaDataDecoderProxy> decoder(new EMEMediaDataDecoderProxy(
|
|
thread.forget(), aProxy, aParams));
|
|
return decoder.forget();
|
|
}
|
|
|
|
already_AddRefed<MediaDataDecoder>
|
|
EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
|
|
{
|
|
MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
|
|
|
|
if (MediaPrefs::EMEBlankVideo()) {
|
|
EME_LOG("EMEDecoderModule::CreateVideoDecoder() creating a blank decoder.");
|
|
RefPtr<PlatformDecoderModule> m(CreateBlankDecoderModule());
|
|
return m->CreateVideoDecoder(aParams);
|
|
}
|
|
|
|
if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
|
|
// GMP decodes. Assume that means it can decrypt too.
|
|
RefPtr<MediaDataDecoderProxy> wrapper =
|
|
CreateDecoderWrapper(mProxy, aParams);
|
|
auto params = GMPVideoDecoderParams(aParams);
|
|
if (MediaPrefs::EMEChromiumAPIEnabled()) {
|
|
wrapper->SetProxyTarget(new ChromiumCDMVideoDecoder(params, mProxy));
|
|
} else {
|
|
wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
|
|
}
|
|
return wrapper.forget();
|
|
}
|
|
|
|
MOZ_ASSERT(mPDM);
|
|
RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
|
|
if (!decoder) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(
|
|
decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue(),
|
|
aParams.mType, aParams.mOnWaitingForKeyEvent));
|
|
return emeDecoder.forget();
|
|
}
|
|
|
|
already_AddRefed<MediaDataDecoder>
|
|
EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
|
|
{
|
|
MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
|
|
|
|
// We don't support using the GMP to decode audio.
|
|
MOZ_ASSERT(!SupportsMimeType(aParams.mConfig.mMimeType, nullptr));
|
|
MOZ_ASSERT(mPDM);
|
|
|
|
if (MediaPrefs::EMEBlankAudio()) {
|
|
EME_LOG("EMEDecoderModule::CreateAudioDecoder() creating a blank decoder.");
|
|
RefPtr<PlatformDecoderModule> m(CreateBlankDecoderModule());
|
|
return m->CreateAudioDecoder(aParams);
|
|
}
|
|
|
|
RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
|
|
if (!decoder) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(
|
|
decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue(),
|
|
aParams.mType, aParams.mOnWaitingForKeyEvent));
|
|
return emeDecoder.forget();
|
|
}
|
|
|
|
bool
|
|
EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType,
|
|
DecoderDoctorDiagnostics* aDiagnostics) const
|
|
{
|
|
Maybe<nsCString> gmp;
|
|
gmp.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
|
|
return GMPDecoderModule::SupportsMimeType(aMimeType, gmp);
|
|
}
|
|
|
|
} // namespace mozilla
|