/* -*- 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 "EMEAudioDecoder.h" #include "EMEVideoDecoder.h" #include "MediaDataDecoderProxy.h" #include "mozIGeckoMediaPluginService.h" #include "mozilla/CDMProxy.h" #include "mozilla/Unused.h" #include "nsAutoPtr.h" #include "nsServiceManagerUtils.h" #include "MediaInfo.h" #include "nsClassHashtable.h" #include "GMPDecoderModule.h" #include "MP4Decoder.h" namespace mozilla { typedef MozPromiseRequestHolder DecryptPromiseRequestHolder; class EMEDecryptor : public MediaDataDecoder { public: EMEDecryptor(MediaDataDecoder* aDecoder, MediaDataDecoderCallback* aCallback, CDMProxy* aProxy, TaskQueue* aDecodeTaskQueue) : mDecoder(aDecoder) , mCallback(aCallback) , mTaskQueue(aDecodeTaskQueue) , mProxy(aProxy) , mSamplesWaitingForKey(new SamplesWaitingForKey(this, this->mCallback, mTaskQueue, mProxy)) , mIsShutdown(false) { } RefPtr Init() override { MOZ_ASSERT(!mIsShutdown); return mDecoder->Init(); } void Input(MediaRawData* aSample) override { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); if (mIsShutdown) { NS_WARNING("EME encrypted sample arrived after shutdown"); return; } if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) { return; } nsAutoPtr writer(aSample->CreateWriter()); mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId, writer->mCrypto.mSessionIds); mDecrypts.Put(aSample, new DecryptPromiseRequestHolder()); mDecrypts.Get(aSample)->Begin(mProxy->Decrypt(aSample)->Then( mTaskQueue, __func__, this, &EMEDecryptor::Decrypted, &EMEDecryptor::Decrypted)); return; } void Decrypted(const DecryptResult& aDecrypted) { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); MOZ_ASSERT(aDecrypted.mSample); nsAutoPtr 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 == NoKeyErr) { // Key became unusable after we sent the sample to CDM to decrypt. // Call Input() again, so that the sample is enqueued for decryption // if the key becomes usable again. Input(aDecrypted.mSample); } else if (aDecrypted.mStatus != Ok) { if (mCallback) { mCallback->Error(MediaResult( NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("decrypted.mStatus=%u", uint32_t(aDecrypted.mStatus)))); } } else { MOZ_ASSERT(!mIsShutdown); // The Adobe GMP AAC decoder gets confused if we pass it non-encrypted // samples with valid crypto data. So clear the crypto data, since the // sample should be decrypted now anyway. If we don't do this and we're // using the Adobe GMP for unencrypted decoding of data that is decrypted // by gmp-clearkey, decoding will fail. UniquePtr writer(aDecrypted.mSample->CreateWriter()); writer->mCrypto = CryptoSample(); mDecoder->Input(aDecrypted.mSample); } } void Flush() override { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); MOZ_ASSERT(!mIsShutdown); for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) { nsAutoPtr& holder = iter.Data(); holder->DisconnectIfExists(); iter.Remove(); } mDecoder->Flush(); mSamplesWaitingForKey->Flush(); } void Drain() override { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); MOZ_ASSERT(!mIsShutdown); for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) { nsAutoPtr& holder = iter.Data(); holder->DisconnectIfExists(); iter.Remove(); } mDecoder->Drain(); } void Shutdown() override { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); MOZ_ASSERT(!mIsShutdown); mIsShutdown = true; mDecoder->Shutdown(); mSamplesWaitingForKey->BreakCycles(); mSamplesWaitingForKey = nullptr; mDecoder = nullptr; mProxy = nullptr; mCallback = nullptr; } const char* GetDescriptionName() const override { return mDecoder->GetDescriptionName(); } private: RefPtr mDecoder; MediaDataDecoderCallback* mCallback; RefPtr mTaskQueue; RefPtr mProxy; nsClassHashtable, DecryptPromiseRequestHolder> mDecrypts; RefPtr mSamplesWaitingForKey; bool mIsShutdown; }; class EMEMediaDataDecoderProxy : public MediaDataDecoderProxy { public: EMEMediaDataDecoderProxy(already_AddRefed aProxyThread, MediaDataDecoderCallback* aCallback, CDMProxy* aProxy, TaskQueue* aTaskQueue) : MediaDataDecoderProxy(Move(aProxyThread), aCallback) , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback, aTaskQueue, aProxy)) , mProxy(aProxy) { } void Input(MediaRawData* aSample) override; void Shutdown() override; private: RefPtr mSamplesWaitingForKey; RefPtr mProxy; }; void EMEMediaDataDecoderProxy::Input(MediaRawData* aSample) { if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) { return; } nsAutoPtr writer(aSample->CreateWriter()); mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId, writer->mCrypto.mSessionIds); MediaDataDecoderProxy::Input(aSample); } void EMEMediaDataDecoderProxy::Shutdown() { MediaDataDecoderProxy::Shutdown(); mSamplesWaitingForKey->BreakCycles(); mSamplesWaitingForKey = nullptr; mProxy = nullptr; } EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM) : mProxy(aProxy) , mPDM(aPDM) { } EMEDecoderModule::~EMEDecoderModule() { } static already_AddRefed CreateDecoderWrapper(MediaDataDecoderCallback* aCallback, CDMProxy* aProxy, TaskQueue* aTaskQueue) { RefPtr s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService()); if (!s) { return nullptr; } RefPtr thread(s->GetAbstractGMPThread()); if (!thread) { return nullptr; } RefPtr decoder( new EMEMediaDataDecoderProxy(thread.forget(), aCallback, aProxy, aTaskQueue)); return decoder.forget(); } already_AddRefed EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) { MOZ_ASSERT(aParams.mConfig.mCrypto.mValid); if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) { // GMP decodes. Assume that means it can decrypt too. RefPtr wrapper = CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue); auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper); wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params)); return wrapper.forget(); } MOZ_ASSERT(mPDM); RefPtr decoder(mPDM->CreateDecoder(aParams)); if (!decoder) { return nullptr; } RefPtr emeDecoder(new EMEDecryptor(decoder, aParams.mCallback, mProxy, AbstractThread::GetCurrent()->AsTaskQueue())); return emeDecoder.forget(); } already_AddRefed EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) { MOZ_ASSERT(aParams.mConfig.mCrypto.mValid); if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) { // GMP decodes. Assume that means it can decrypt too. RefPtr wrapper = CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue); auto gmpParams = GMPAudioDecoderParams(aParams).WithCallback(wrapper); wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy, gmpParams)); return wrapper.forget(); } MOZ_ASSERT(mPDM); RefPtr decoder(mPDM->CreateDecoder(aParams)); if (!decoder) { return nullptr; } RefPtr emeDecoder(new EMEDecryptor(decoder, aParams.mCallback, mProxy, AbstractThread::GetCurrent()->AsTaskQueue())); return emeDecoder.forget(); } PlatformDecoderModule::ConversionRequired EMEDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const { if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) { return ConversionRequired::kNeedAVCC; } else { return ConversionRequired::kNeedNone; } } bool EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const { Maybe gmp; gmp.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem())); return GMPDecoderModule::SupportsMimeType(aMimeType, gmp); } } // namespace mozilla