/* -*- 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