From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../agnostic/eme/ChromiumCDMVideoDecoder.cpp | 156 +++++++ .../agnostic/eme/ChromiumCDMVideoDecoder.h | 54 +++ .../agnostic/eme/DecryptThroughputLimit.h | 103 +++++ .../platforms/agnostic/eme/EMEDecoderModule.cpp | 479 +++++++++++++++++++++ .../platforms/agnostic/eme/EMEDecoderModule.h | 79 ++++ .../agnostic/eme/SamplesWaitingForKey.cpp | 79 ++++ .../platforms/agnostic/eme/SamplesWaitingForKey.h | 68 +++ dom/media/platforms/agnostic/eme/moz.build | 22 + 8 files changed, 1040 insertions(+) create mode 100644 dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp create mode 100644 dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h create mode 100644 dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h create mode 100644 dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp create mode 100644 dom/media/platforms/agnostic/eme/EMEDecoderModule.h create mode 100644 dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp create mode 100644 dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h create mode 100644 dom/media/platforms/agnostic/eme/moz.build (limited to 'dom/media/platforms/agnostic/eme') diff --git a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp new file mode 100644 index 0000000000..e71632e6d3 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp @@ -0,0 +1,156 @@ +/* -*- 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 "ChromiumCDMVideoDecoder.h" +#include "ChromiumCDMProxy.h" +#include "content_decryption_module.h" +#include "GMPService.h" +#include "GMPVideoDecoder.h" +#include "MP4Decoder.h" +#include "VPXDecoder.h" + +namespace mozilla { + +ChromiumCDMVideoDecoder::ChromiumCDMVideoDecoder( + const GMPVideoDecoderParams& aParams, CDMProxy* aCDMProxy) + : mCDMParent(aCDMProxy->AsChromiumCDMProxy()->GetCDMParent()), + mConfig(aParams.mConfig), + mCrashHelper(aParams.mCrashHelper), + mGMPThread(GetGMPThread()), + mImageContainer(aParams.mImageContainer), + mKnowsCompositor(aParams.mKnowsCompositor) {} + +ChromiumCDMVideoDecoder::~ChromiumCDMVideoDecoder() = default; + +static uint32_t ToCDMH264Profile(uint8_t aProfile) { + switch (aProfile) { + case 66: + return cdm::VideoCodecProfile::kH264ProfileBaseline; + case 77: + return cdm::VideoCodecProfile::kH264ProfileMain; + case 88: + return cdm::VideoCodecProfile::kH264ProfileExtended; + case 100: + return cdm::VideoCodecProfile::kH264ProfileHigh; + case 110: + return cdm::VideoCodecProfile::kH264ProfileHigh10; + case 122: + return cdm::VideoCodecProfile::kH264ProfileHigh422; + case 144: + return cdm::VideoCodecProfile::kH264ProfileHigh444Predictive; + } + return cdm::VideoCodecProfile::kUnknownVideoCodecProfile; +} + +RefPtr ChromiumCDMVideoDecoder::Init() { + if (!mCDMParent) { + // Must have failed to get the CDMParent from the ChromiumCDMProxy + // in our constructor; the MediaKeys must have shut down the CDM + // before we had a chance to start up the decoder. + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + gmp::CDMVideoDecoderConfig config; + if (MP4Decoder::IsH264(mConfig.mMimeType)) { + config.mCodec() = cdm::VideoCodec::kCodecH264; + config.mProfile() = + ToCDMH264Profile(mConfig.mExtraData->SafeElementAt(1, 0)); + config.mExtraData() = mConfig.mExtraData->Clone(); + mConvertToAnnexB = true; + } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { + config.mCodec() = cdm::VideoCodec::kCodecVp8; + config.mProfile() = cdm::VideoCodecProfile::kProfileNotNeeded; + } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { + config.mCodec() = cdm::VideoCodec::kCodecVp9; + config.mProfile() = cdm::VideoCodecProfile::kProfileNotNeeded; + } else { + return MediaDataDecoder::InitPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + config.mImageWidth() = mConfig.mImage.width; + config.mImageHeight() = mConfig.mImage.height; + config.mEncryptionScheme() = cdm::EncryptionScheme::kUnencrypted; + switch (mConfig.mCrypto.mCryptoScheme) { + case CryptoScheme::None: + break; + case CryptoScheme::Cenc: + config.mEncryptionScheme() = cdm::EncryptionScheme::kCenc; + break; + case CryptoScheme::Cbcs: + config.mEncryptionScheme() = cdm::EncryptionScheme::kCbcs; + break; + default: + MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type"); + break; + } + + RefPtr cdm = mCDMParent; + VideoInfo info = mConfig; + RefPtr imageContainer = mImageContainer; + RefPtr knowsCompositor = mKnowsCompositor; + return InvokeAsync(mGMPThread, __func__, + [cdm, config, info, imageContainer, knowsCompositor]() { + return cdm->InitializeVideoDecoder( + config, info, imageContainer, knowsCompositor); + }); +} + +nsCString ChromiumCDMVideoDecoder::GetDescriptionName() const { + return "chromium cdm video decoder"_ns; +} + +nsCString ChromiumCDMVideoDecoder::GetCodecName() const { + if (MP4Decoder::IsH264(mConfig.mMimeType)) { + return "h264"_ns; + } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { + return "vp8"_ns; + } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { + return "vp9"_ns; + } + return "unknown"_ns; +} + +MediaDataDecoder::ConversionRequired ChromiumCDMVideoDecoder::NeedsConversion() + const { + return mConvertToAnnexB ? ConversionRequired::kNeedAnnexB + : ConversionRequired::kNeedNone; +} + +RefPtr ChromiumCDMVideoDecoder::Decode( + MediaRawData* aSample) { + RefPtr cdm = mCDMParent; + RefPtr sample = aSample; + return InvokeAsync(mGMPThread, __func__, [cdm, sample]() { + return cdm->DecryptAndDecodeFrame(sample); + }); +} + +RefPtr ChromiumCDMVideoDecoder::Flush() { + MOZ_ASSERT(mCDMParent); + RefPtr cdm = mCDMParent; + return InvokeAsync(mGMPThread, __func__, + [cdm]() { return cdm->FlushVideoDecoder(); }); +} + +RefPtr ChromiumCDMVideoDecoder::Drain() { + MOZ_ASSERT(mCDMParent); + RefPtr cdm = mCDMParent; + return InvokeAsync(mGMPThread, __func__, [cdm]() { return cdm->Drain(); }); +} + +RefPtr ChromiumCDMVideoDecoder::Shutdown() { + if (!mCDMParent) { + // Must have failed to get the CDMParent from the ChromiumCDMProxy + // in our constructor; the MediaKeys must have shut down the CDM + // before we had a chance to start up the decoder. + return ShutdownPromise::CreateAndResolve(true, __func__); + } + RefPtr cdm = mCDMParent; + return InvokeAsync(mGMPThread, __func__, + [cdm]() { return cdm->ShutdownVideoDecoder(); }); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h new file mode 100644 index 0000000000..c177bf2e48 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +#ifndef ChromiumCDMVideoDecoder_h_ +#define ChromiumCDMVideoDecoder_h_ + +#include "ChromiumCDMParent.h" +#include "PlatformDecoderModule.h" +#include "mozilla/layers/KnowsCompositor.h" + +namespace mozilla { + +class CDMProxy; +struct GMPVideoDecoderParams; + +DDLoggedTypeDeclNameAndBase(ChromiumCDMVideoDecoder, MediaDataDecoder); + +class ChromiumCDMVideoDecoder final + : public MediaDataDecoder, + public DecoderDoctorLifeLogger { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMVideoDecoder, final); + + ChromiumCDMVideoDecoder(const GMPVideoDecoderParams& aParams, + CDMProxy* aCDMProxy); + + RefPtr Init() override; + RefPtr Decode(MediaRawData* aSample) override; + RefPtr Flush() override; + RefPtr Drain() override; + RefPtr Shutdown() override; + nsCString GetDescriptionName() const override; + nsCString GetCodecName() const override; + ConversionRequired NeedsConversion() const override; + + private: + ~ChromiumCDMVideoDecoder(); + + RefPtr mCDMParent; + const VideoInfo mConfig; + RefPtr mCrashHelper; + nsCOMPtr mGMPThread; + RefPtr mImageContainer; + RefPtr mKnowsCompositor; + MozPromiseHolder mInitPromise; + bool mConvertToAnnexB = false; +}; + +} // namespace mozilla + +#endif // ChromiumCDMVideoDecoder_h_ diff --git a/dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h b/dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h new file mode 100644 index 0000000000..bafb387f83 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DecryptThroughputLimit_h +#define DecryptThroughputLimit_h + +#include + +#include "MediaTimer.h" +#include "PlatformDecoderModule.h" + +namespace mozilla { + +// We throttle our decrypt so that we don't decrypt more than a certain +// duration of samples per second. This is to work around bugs in the +// Widevine CDM. See bugs 1338924, 1342822, 1718223. +class DecryptThroughputLimit { + public: + explicit DecryptThroughputLimit(nsISerialEventTarget* aTargetThread, + uint32_t aMaxThroughputMs) + : mThrottleScheduler(aTargetThread), + mMaxThroughput(aMaxThroughputMs / 1000.0) {} + + typedef MozPromise, MediaResult, true> ThrottlePromise; + + // Resolves promise after a delay if necessary in order to reduce the + // throughput of samples sent through the CDM for decryption. + RefPtr Throttle(MediaRawData* aSample) { + // We should only have one decrypt request being processed at once. + MOZ_RELEASE_ASSERT(!mThrottleScheduler.IsScheduled()); + + const TimeDuration WindowSize = TimeDuration::FromSeconds(0.1); + const TimeDuration MaxThroughput = + TimeDuration::FromSeconds(mMaxThroughput); + + // Forget decrypts that happened before the start of our window. + const TimeStamp now = TimeStamp::Now(); + while (!mDecrypts.empty() && + mDecrypts.front().mTimestamp < now - WindowSize) { + mDecrypts.pop_front(); + } + + // How much time duration of the media would we have decrypted inside the + // time window if we did decrypt this block? + TimeDuration sampleDuration = aSample->mDuration.ToTimeDuration(); + TimeDuration durationDecrypted = sampleDuration; + for (const DecryptedJob& job : mDecrypts) { + durationDecrypted += job.mSampleDuration; + } + + if (durationDecrypted < MaxThroughput) { + // If we decrypted a sample of this duration, we would *not* have + // decrypted more than our threshold for max throughput, over the + // preceding wall time window. So we're safe to proceed with this + // decrypt. + mDecrypts.push_back(DecryptedJob({now, sampleDuration})); + return ThrottlePromise::CreateAndResolve(aSample, __func__); + } + + // Otherwise, we need to delay until decrypting won't exceed our + // throughput threshold. + + RefPtr p = mPromiseHolder.Ensure(__func__); + + TimeDuration delay = durationDecrypted - MaxThroughput; + TimeStamp target = now + delay; + RefPtr sample(aSample); + mThrottleScheduler.Ensure( + target, + [this, sample, sampleDuration]() { + mThrottleScheduler.CompleteRequest(); + mDecrypts.push_back(DecryptedJob({TimeStamp::Now(), sampleDuration})); + mPromiseHolder.Resolve(sample, __func__); + }, + []() { MOZ_DIAGNOSTIC_ASSERT(false); }); + + return p; + } + + void Flush() { + mThrottleScheduler.Reset(); + mPromiseHolder.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + } + + private: + DelayedScheduler mThrottleScheduler; + MozPromiseHolder mPromiseHolder; + + double mMaxThroughput; + + struct DecryptedJob { + TimeStamp mTimestamp; + TimeDuration mSampleDuration; + }; + std::deque mDecrypts; +}; + +} // namespace mozilla + +#endif // DecryptThroughputLimit_h diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp new file mode 100644 index 0000000000..d4477bd6cd --- /dev/null +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp @@ -0,0 +1,479 @@ +/* -*- 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 diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h new file mode 100644 index 0000000000..06fea6e650 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h @@ -0,0 +1,79 @@ +/* -*- 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/. */ + +#if !defined(EMEDecoderModule_h_) +# define EMEDecoderModule_h_ + +# include "MediaDataDecoderProxy.h" +# include "PlatformDecoderModule.h" +# include "SamplesWaitingForKey.h" + +namespace mozilla { + +class CDMProxy; +class PDMFactory; + +class EMEDecoderModule : public PlatformDecoderModule { + public: + EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM); + + protected: + RefPtr AsyncCreateDecoder( + const CreateDecoderParams& aParams) override; + + // Decode thread. + already_AddRefed CreateVideoDecoder( + const CreateDecoderParams& aParams) override { + MOZ_CRASH("Not used"); + } + + // Decode thread. + already_AddRefed CreateAudioDecoder( + const CreateDecoderParams& aParams) override { + MOZ_CRASH("Not used"); + } + + media::DecodeSupportSet SupportsMimeType( + const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + private: + virtual ~EMEDecoderModule(); + RefPtr mProxy; + // Will be null if CDM has decoding capability. + RefPtr mPDM; +}; + +DDLoggedTypeDeclNameAndBase(EMEMediaDataDecoderProxy, MediaDataDecoderProxy); + +class EMEMediaDataDecoderProxy + : public MediaDataDecoderProxy, + public DecoderDoctorLifeLogger { + public: + EMEMediaDataDecoderProxy(const CreateDecoderParams& aParams, + already_AddRefed aProxyDecoder, + already_AddRefed aProxyThread, + CDMProxy* aProxy); + EMEMediaDataDecoderProxy(const CreateDecoderParams& aParams, + already_AddRefed aProxyDecoder, + CDMProxy* aProxy); + + RefPtr Decode(MediaRawData* aSample) override; + RefPtr Flush() override; + RefPtr Shutdown() override; + + private: + nsCOMPtr mThread; + RefPtr mSamplesWaitingForKey; + MozPromiseRequestHolder mKeyRequest; + MozPromiseHolder mDecodePromise; + MozPromiseRequestHolder mDecodeRequest; + RefPtr mProxy; +}; + +} // namespace mozilla + +#endif // EMEDecoderModule_h_ diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp new file mode 100644 index 0000000000..23d79ce56a --- /dev/null +++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SamplesWaitingForKey.h" + +#include "MediaData.h" +#include "MediaEventSource.h" +#include "mozilla/CDMCaps.h" +#include "mozilla/CDMProxy.h" +#include "mozilla/TaskQueue.h" + +namespace mozilla { + +SamplesWaitingForKey::SamplesWaitingForKey( + CDMProxy* aProxy, TrackInfo::TrackType aType, + const std::function*()>& + aOnWaitingForKeyEvent) + : mMutex("SamplesWaitingForKey"), + mProxy(aProxy), + mType(aType), + mOnWaitingForKeyEvent(aOnWaitingForKeyEvent) {} + +SamplesWaitingForKey::~SamplesWaitingForKey() { Flush(); } + +RefPtr +SamplesWaitingForKey::WaitIfKeyNotUsable(MediaRawData* aSample) { + if (!aSample || !aSample->mCrypto.IsEncrypted() || !mProxy) { + return WaitForKeyPromise::CreateAndResolve(aSample, __func__); + } + auto caps = mProxy->Capabilites().Lock(); + const auto& keyid = aSample->mCrypto.mKeyId; + if (caps->IsKeyUsable(keyid)) { + return WaitForKeyPromise::CreateAndResolve(aSample, __func__); + } + SampleEntry entry; + entry.mSample = aSample; + RefPtr p = entry.mPromise.Ensure(__func__); + { + MutexAutoLock lock(mMutex); + mSamples.AppendElement(std::move(entry)); + } + if (mOnWaitingForKeyEvent && mOnWaitingForKeyEvent()) { + mOnWaitingForKeyEvent()->Notify(mType); + } + caps->NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this); + return p; +} + +void SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId) { + MutexAutoLock lock(mMutex); + size_t i = 0; + while (i < mSamples.Length()) { + auto& entry = mSamples[i]; + if (aKeyId == entry.mSample->mCrypto.mKeyId) { + entry.mPromise.Resolve(entry.mSample, __func__); + mSamples.RemoveElementAt(i); + } else { + i++; + } + } +} + +void SamplesWaitingForKey::Flush() { + MutexAutoLock lock(mMutex); + for (auto& sample : mSamples) { + sample.mPromise.Reject(true, __func__); + } + mSamples.Clear(); +} + +void SamplesWaitingForKey::BreakCycles() { + MutexAutoLock lock(mMutex); + mProxy = nullptr; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h new file mode 100644 index 0000000000..06d72e3aae --- /dev/null +++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#ifndef SamplesWaitingForKey_h_ +#define SamplesWaitingForKey_h_ + +#include + +#include "MediaInfo.h" +#include "mozilla/MozPromise.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +typedef nsTArray CencKeyId; + +class CDMProxy; +template +class MediaEventProducer; +class MediaRawData; + +// Encapsulates the task of waiting for the CDMProxy to have the necessary +// keys to decrypt a given sample. +class SamplesWaitingForKey { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey) + + typedef MozPromise, bool, /* IsExclusive = */ true> + WaitForKeyPromise; + + SamplesWaitingForKey( + CDMProxy* aProxy, TrackInfo::TrackType aType, + const std::function*()>& + aOnWaitingForKeyEvent); + + // Returns a promise that will be resolved if or when a key for decoding the + // sample becomes usable. + RefPtr WaitIfKeyNotUsable(MediaRawData* aSample); + + void NotifyUsable(const CencKeyId& aKeyId); + + void Flush(); + + void BreakCycles(); + + protected: + ~SamplesWaitingForKey(); + + private: + Mutex mMutex MOZ_UNANNOTATED; + RefPtr mProxy; + struct SampleEntry { + RefPtr mSample; + MozPromiseHolder mPromise; + }; + nsTArray mSamples; + const TrackInfo::TrackType mType; + const std::function*()> + mOnWaitingForKeyEvent; +}; + +} // namespace mozilla + +#endif // SamplesWaitingForKey_h_ diff --git a/dom/media/platforms/agnostic/eme/moz.build b/dom/media/platforms/agnostic/eme/moz.build new file mode 100644 index 0000000000..34f0007b3b --- /dev/null +++ b/dom/media/platforms/agnostic/eme/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + "ChromiumCDMVideoDecoder.h", + "DecryptThroughputLimit.h", + "EMEDecoderModule.h", + "SamplesWaitingForKey.h", +] + +UNIFIED_SOURCES += [ + "ChromiumCDMVideoDecoder.cpp", + "EMEDecoderModule.cpp", + "SamplesWaitingForKey.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" -- cgit v1.2.3