Bug 1342822 - Move Widevine decryption throughput limit into Gecko. r=gerald
authorChris Pearce <cpearce@mozilla.com>
Mon, 27 Feb 2017 15:57:41 +1300
changeset 374148 3dc5e735df4f967e09af426ffcfc84e830732fce
parent 374147 af9dd74bba62799c680d3a2f3d2bd2b7379bab62
child 374149 682ee6f4802745005eccdb88f831828051ae4c1b
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerald
bugs1342822
milestone54.0a1
Bug 1342822 - Move Widevine decryption throughput limit into Gecko. r=gerald This means each stream that's being decrypted will have its own throughput limiting, independent of other streams. MozReview-Commit-ID: DiOiRx19OuC
dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h
dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
dom/media/platforms/agnostic/eme/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h
@@ -0,0 +1,106 @@
+/* -*- 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 "PlatformDecoderModule.h"
+#include "MediaTimer.h"
+#include <deque>
+
+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 bug 1338924 and bug 1342822.
+class DecryptThroughputLimit
+{
+public:
+
+  explicit DecryptThroughputLimit(AbstractThread* aTargetThread)
+    : mThrottleScheduler(aTargetThread)
+  {
+  }
+
+  typedef MozPromise<RefPtr<MediaRawData>, 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<ThrottlePromise>
+  Throttle(MediaRawData* aSample)
+  {
+    // We should only have one decrypt request being processed at once.
+    MOZ_RELEASE_ASSERT(!mThrottleScheduler.IsScheduled());
+
+    const TimeDuration WindowSize = TimeDuration::FromSeconds(1.0);
+    const TimeDuration MaxThroughput = TimeDuration::FromSeconds(2.0);
+
+    // 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 = TimeDuration::FromMicroseconds(aSample->mDuration);
+    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<ThrottlePromise> p = mPromiseHolder.Ensure(__func__);
+
+    TimeDuration delay = durationDecrypted - MaxThroughput;
+    TimeStamp target = now + delay;
+    RefPtr<MediaRawData> 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<ThrottlePromise> mPromiseHolder;
+
+  struct DecryptedJob
+  {
+    TimeStamp mTimestamp;
+    TimeDuration mSampleDuration;
+  };
+  std::deque<DecryptedJob> mDecrypts;
+};
+
+}
+
+#endif // DecryptThroughputLimit_h
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -15,16 +15,17 @@
 #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
 {
@@ -32,63 +33,83 @@ 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__);
-    AttemptDecode(aSample);
+
+    RefPtr<EMEDecryptor> self = this;
+    mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)
+      ->Then(mTaskQueue, __func__,
+             [self, this](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] (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;
     }
 
-    RefPtr<EMEDecryptor> self = this;
-    mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)
-      ->Then(mTaskQueue, __func__,
-             [self, this](MediaRawData* aSample) {
-               mKeyRequest.Complete();
-               nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
-               mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
-                                             writer->mCrypto.mSessionIds);
+    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));
-             },
-             [self, this]() { mKeyRequest.Complete(); })
-      ->Track(mKeyRequest);
+    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;
@@ -137,18 +158,20 @@ public:
     }
   }
 
   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<EMEDecryptor> self = this;
     return mDecoder->Flush()->Then(mTaskQueue, __func__,
                                    [self, this]() {
@@ -197,16 +220,18 @@ public:
 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;
 };
 
--- a/dom/media/platforms/agnostic/eme/moz.build
+++ b/dom/media/platforms/agnostic/eme/moz.build
@@ -1,15 +1,16 @@
 # -*- 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 += [
+    'DecryptThroughputLimit.h',
     'EMEDecoderModule.h',
     'EMEVideoDecoder.h',
     'SamplesWaitingForKey.h',
 ]
 
 UNIFIED_SOURCES += [
     'EMEDecoderModule.cpp',
     'EMEVideoDecoder.cpp',