/* -*- 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 #include "Adts.h" #include "BlankDecoderModule.h" #include "ChromiumCDMVideoDecoder.h" #include "DecryptThroughputLimit.h" #include "GMPDecoderModule.h" #include "GMPService.h" #include "GMPVideoDecoder.h" #include "MP4Decoder.h" #include "MediaInfo.h" #include "PDMFactory.h" #include "mozilla/CDMProxy.h" #include "mozilla/EMEUtils.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/UniquePtr.h" #include "mozilla/Unused.h" #include "nsClassHashtable.h" #include "nsServiceManagerUtils.h" namespace mozilla { typedef MozPromiseRequestHolder DecryptPromiseRequestHolder; DDLoggedTypeDeclNameAndBase(EMEDecryptor, MediaDataDecoder); class ADTSSampleConverter { public: explicit ADTSSampleConverter(const AudioInfo& aInfo) : mNumChannels(aInfo.mChannels) // Note: we set profile to 2 if we encounter an extended profile (which // set mProfile to 0 and then set mExtendedProfile) such as HE-AACv2 // (profile 5). These can then pass through conversion to ADTS and back. // This is done as ADTS only has 2 bits for profile, and the transform // subtracts one from the value. We check if the profile supplied is > 4 // for safety. 2 is used as a fallback value, though it seems the CDM // doesn't care what is set. , mProfile(aInfo.mProfile < 1 || aInfo.mProfile > 4 ? 2 : aInfo.mProfile), mFrequencyIndex(Adts::GetFrequencyIndex(aInfo.mRate)) { EME_LOG("ADTSSampleConvertor(): aInfo.mProfile=%" PRIi8 " aInfo.mExtendedProfile=%" PRIi8, aInfo.mProfile, aInfo.mExtendedProfile); if (aInfo.mProfile < 1 || aInfo.mProfile > 4) { EME_LOG( "ADTSSampleConvertor(): Profile not in [1, 4]! Samples will " "their profile set to 2!"); } } bool Convert(MediaRawData* aSample) const { return Adts::ConvertSample(mNumChannels, mFrequencyIndex, mProfile, aSample); } bool Revert(MediaRawData* aSample) const { return Adts::RevertSample(aSample); } private: const uint32_t mNumChannels; const uint8_t mProfile; const uint8_t mFrequencyIndex; }; class EMEDecryptor final : public MediaDataDecoder, public DecoderDoctorLifeLogger { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(EMEDecryptor, final); EMEDecryptor(MediaDataDecoder* aDecoder, CDMProxy* aProxy, TrackInfo::TrackType aType, const std::function*()>& aOnWaitingForKey, UniquePtr aConverter = nullptr) : mDecoder(aDecoder), mProxy(aProxy), mSamplesWaitingForKey( new SamplesWaitingForKey(mProxy, aType, aOnWaitingForKey)), mADTSSampleConverter(std::move(aConverter)), mIsShutdown(false) { DDLINKCHILD("decoder", mDecoder.get()); } RefPtr Init() override { MOZ_ASSERT(!mIsShutdown); mThread = GetCurrentSerialEventTarget(); uint32_t maxThroughputMs = StaticPrefs::media_eme_max_throughput_ms(); EME_LOG("EME max-throughput-ms=%" PRIu32, maxThroughputMs); mThroughputLimiter.emplace(mThread, maxThroughputMs); return mDecoder->Init(); } RefPtr Decode(MediaRawData* aSample) override { MOZ_ASSERT(mThread->IsOnCurrentThread()); MOZ_RELEASE_ASSERT(mDecrypts.Count() == 0, "Can only process one sample at a time"); RefPtr p = mDecodePromise.Ensure(__func__); RefPtr self = this; mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample) ->Then( mThread, __func__, [self](const RefPtr& aSample) { self->mKeyRequest.Complete(); self->ThrottleDecode(aSample); }, [self]() { self->mKeyRequest.Complete(); }) ->Track(mKeyRequest); return p; } void ThrottleDecode(MediaRawData* aSample) { MOZ_ASSERT(mThread->IsOnCurrentThread()); RefPtr self = this; mThroughputLimiter->Throttle(aSample) ->Then( mThread, __func__, [self](RefPtr aSample) { self->mThrottleRequest.Complete(); self->AttemptDecode(aSample); }, [self]() { self->mThrottleRequest.Complete(); }) ->Track(mThrottleRequest); } void AttemptDecode(MediaRawData* aSample) { MOZ_ASSERT(mThread->IsOnCurrentThread()); if (mIsShutdown) { NS_WARNING("EME encrypted sample arrived after shutdown"); mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); return; } if (mADTSSampleConverter && !mADTSSampleConverter->Convert(aSample)) { mDecodePromise.RejectIfExists( MediaResult( NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("Failed to convert encrypted AAC sample to ADTS")), __func__); return; } const auto& decrypt = mDecrypts.InsertOrUpdate( aSample, MakeUnique()); mProxy->Decrypt(aSample) ->Then(mThread, __func__, this, &EMEDecryptor::Decrypted, &EMEDecryptor::Decrypted) ->Track(*decrypt); } void Decrypted(const DecryptResult& aDecrypted) { MOZ_ASSERT(mThread->IsOnCurrentThread()); MOZ_ASSERT(aDecrypted.mSample); UniquePtr holder; mDecrypts.Remove(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 (mADTSSampleConverter && !mADTSSampleConverter->Revert(aDecrypted.mSample)) { mDecodePromise.RejectIfExists( MediaResult( NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("Failed to revert decrypted ADTS sample to AAC")), __func__); 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 writer(aDecrypted.mSample->CreateWriter()); writer->mCrypto = CryptoSample(); RefPtr self = this; mDecoder->Decode(aDecrypted.mSample) ->Then(mThread, __func__, [self](DecodePromise::ResolveOrRejectValue&& aValue) { self->mDecodeRequest.Complete(); self->mDecodePromise.ResolveOrReject(std::move(aValue), __func__); }) ->Track(mDecodeRequest); } } RefPtr Flush() override { MOZ_ASSERT(mThread->IsOnCurrentThread()); 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()) { auto holder = iter.UserData(); holder->DisconnectIfExists(); iter.Remove(); } RefPtr k = mSamplesWaitingForKey; return mDecoder->Flush()->Then(mThread, __func__, [k]() { k->Flush(); return FlushPromise::CreateAndResolve(true, __func__); }); } RefPtr Drain() override { MOZ_ASSERT(mThread->IsOnCurrentThread()); MOZ_ASSERT(!mIsShutdown); MOZ_ASSERT(mDecodePromise.IsEmpty() && !mDecodeRequest.Exists(), "Must wait for decoding to complete"); for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) { auto holder = iter.UserData(); holder->DisconnectIfExists(); iter.Remove(); } return mDecoder->Drain(); } RefPtr Shutdown() override { // mThread may not be set if Init hasn't been called first. MOZ_ASSERT(!mThread || mThread->IsOnCurrentThread()); MOZ_ASSERT(!mIsShutdown); mIsShutdown = true; mSamplesWaitingForKey->BreakCycles(); mSamplesWaitingForKey = nullptr; RefPtr decoder = std::move(mDecoder); mProxy = nullptr; return decoder->Shutdown(); } nsCString GetDescriptionName() const override { return mDecoder->GetDescriptionName(); } nsCString GetCodecName() const override { return mDecoder->GetCodecName(); } ConversionRequired NeedsConversion() const override { return mDecoder->NeedsConversion(); } private: ~EMEDecryptor() = default; RefPtr mDecoder; nsCOMPtr mThread; RefPtr mProxy; nsClassHashtable, DecryptPromiseRequestHolder> mDecrypts; RefPtr mSamplesWaitingForKey; MozPromiseRequestHolder mKeyRequest; Maybe mThroughputLimiter; MozPromiseRequestHolder mThrottleRequest; MozPromiseHolder mDecodePromise; MozPromiseHolder mDrainPromise; MozPromiseHolder mFlushPromise; MozPromiseRequestHolder mDecodeRequest; UniquePtr mADTSSampleConverter; bool mIsShutdown; }; EMEMediaDataDecoderProxy::EMEMediaDataDecoderProxy( const CreateDecoderParams& aParams, already_AddRefed aProxyDecoder, already_AddRefed aProxyThread, CDMProxy* aProxy) : MediaDataDecoderProxy(std::move(aProxyDecoder), std::move(aProxyThread)), mThread(GetCurrentSerialEventTarget()), mSamplesWaitingForKey(new SamplesWaitingForKey( aProxy, aParams.mType, aParams.mOnWaitingForKeyEvent)), mProxy(aProxy) {} EMEMediaDataDecoderProxy::EMEMediaDataDecoderProxy( const CreateDecoderParams& aParams, already_AddRefed aProxyDecoder, CDMProxy* aProxy) : MediaDataDecoderProxy(std::move(aProxyDecoder), do_AddRef(GetCurrentSerialEventTarget())), mThread(GetCurrentSerialEventTarget()), mSamplesWaitingForKey(new SamplesWaitingForKey( aProxy, aParams.mType, aParams.mOnWaitingForKeyEvent)), mProxy(aProxy) {} RefPtr EMEMediaDataDecoderProxy::Decode( MediaRawData* aSample) { RefPtr self = this; RefPtr sample = aSample; return InvokeAsync(mThread, __func__, [self, this, sample]() { RefPtr p = mDecodePromise.Ensure(__func__); mSamplesWaitingForKey->WaitIfKeyNotUsable(sample) ->Then( mThread, __func__, [self, this](RefPtr aSample) { mKeyRequest.Complete(); MediaDataDecoderProxy::Decode(aSample) ->Then(mThread, __func__, [self, this](DecodePromise::ResolveOrRejectValue&& aValue) { mDecodeRequest.Complete(); mDecodePromise.ResolveOrReject(std::move(aValue), __func__); }) ->Track(mDecodeRequest); }, [self]() { self->mKeyRequest.Complete(); MOZ_CRASH("Should never get here"); }) ->Track(mKeyRequest); return p; }); } RefPtr EMEMediaDataDecoderProxy::Flush() { RefPtr self = this; return InvokeAsync(mThread, __func__, [self, this]() { mKeyRequest.DisconnectIfExists(); mDecodeRequest.DisconnectIfExists(); mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); return MediaDataDecoderProxy::Flush(); }); } RefPtr EMEMediaDataDecoderProxy::Shutdown() { RefPtr self = this; return InvokeAsync(mThread, __func__, [self, this]() { mSamplesWaitingForKey->BreakCycles(); mSamplesWaitingForKey = nullptr; mProxy = nullptr; return MediaDataDecoderProxy::Shutdown(); }); } EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM) : mProxy(aProxy), mPDM(aPDM) {} EMEDecoderModule::~EMEDecoderModule() = default; static already_AddRefed CreateDecoderWrapper( CDMProxy* aProxy, const CreateDecoderParams& aParams) { RefPtr s( gmp::GeckoMediaPluginService::GetGeckoMediaPluginService()); if (!s) { return nullptr; } nsCOMPtr thread(s->GetGMPThread()); if (!thread) { return nullptr; } RefPtr decoder( new EMEMediaDataDecoderProxy(aParams, do_AddRef(new ChromiumCDMVideoDecoder( GMPVideoDecoderParams(aParams), aProxy)), thread.forget(), aProxy)); return decoder.forget(); } RefPtr EMEDecoderModule::AsyncCreateDecoder(const CreateDecoderParams& aParams) { MOZ_ASSERT(aParams.mConfig.mCrypto.IsEncrypted()); MOZ_ASSERT(mPDM); if (aParams.mConfig.IsVideo()) { if (StaticPrefs::media_eme_video_blank()) { EME_LOG( "EMEDecoderModule::CreateVideoDecoder() creating a blank decoder."); RefPtr m(BlankDecoderModule::Create()); RefPtr decoder = m->CreateVideoDecoder(aParams); return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve(decoder, __func__); } if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr) != media::DecodeSupport::Unsupported) { // GMP decodes. Assume that means it can decrypt too. return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve( CreateDecoderWrapper(mProxy, aParams), __func__); } RefPtr p = mPDM->CreateDecoder(aParams)->Then( GetCurrentSerialEventTarget(), __func__, [self = RefPtr{this}, params = CreateDecoderParamsForAsync(aParams)]( RefPtr&& aDecoder) { RefPtr emeDecoder( new EMEDecryptor(aDecoder, self->mProxy, params.mType, params.mOnWaitingForKeyEvent)); return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve( emeDecoder, __func__); }, [](const MediaResult& aError) { return EMEDecoderModule::CreateDecoderPromise::CreateAndReject( aError, __func__); }); return p; } MOZ_ASSERT(aParams.mConfig.IsAudio()); // We don't support using the GMP to decode audio. MOZ_ASSERT(SupportsMimeType(aParams.mConfig.mMimeType, nullptr) == media::DecodeSupport::Unsupported); MOZ_ASSERT(mPDM); if (StaticPrefs::media_eme_audio_blank()) { EME_LOG("EMEDecoderModule::CreateAudioDecoder() creating a blank decoder."); RefPtr m(BlankDecoderModule::Create()); RefPtr decoder = m->CreateAudioDecoder(aParams); return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve(decoder, __func__); } UniquePtr converter = nullptr; if (MP4Decoder::IsAAC(aParams.mConfig.mMimeType)) { // The CDM expects encrypted AAC to be in ADTS format. // See bug 1433344. converter = MakeUnique(aParams.AudioConfig()); } RefPtr p = mPDM->CreateDecoder(aParams)->Then( GetCurrentSerialEventTarget(), __func__, [self = RefPtr{this}, params = CreateDecoderParamsForAsync(aParams), converter = std::move(converter)]( RefPtr&& aDecoder) mutable { RefPtr emeDecoder(new EMEDecryptor( aDecoder, self->mProxy, params.mType, params.mOnWaitingForKeyEvent, std::move(converter))); return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve( emeDecoder, __func__); }, [](const MediaResult& aError) { return EMEDecoderModule::CreateDecoderPromise::CreateAndReject( aError, __func__); }); return p; } media::DecodeSupportSet EMEDecoderModule::SupportsMimeType( const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const { Maybe keySystem; keySystem.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem())); return GMPDecoderModule::SupportsMimeType( aMimeType, nsLiteralCString(CHROMIUM_CDM_API), keySystem); } } // namespace mozilla