Bug 1357133 - Recover from incorrectly guessing the CDM's shmem sizes. r?gerald draft
authorChris Pearce <cpearce@mozilla.com>
Fri, 28 Apr 2017 08:55:28 +1200
changeset 569884 e27c16459b026b27a5ff017dc725773ea2719e7a
parent 569701 2cca333f546f38860f84940d4c72d7470a3410f4
child 626317 9f84062ca95a684340efdab295f585682e0d505e
push id56297
push userbmo:cpearce@mozilla.com
push dateFri, 28 Apr 2017 00:10:45 +0000
reviewersgerald
bugs1357133
milestone55.0a1
Bug 1357133 - Recover from incorrectly guessing the CDM's shmem sizes. r?gerald We are pre-allocating shmems in the content process for use by the CDM in the GMP process. We guess the size of shmem required. However if we guess wrong, currently we always end up taking the non-shmem path for video frames to return to the content process, which results in us sending another shmem (of the wrong size) to the CDM, and this continues until we hit the limit on the number of shmems that we tolerate the CDM asking for. So in this patch, I change our behaviour to detect when we're allocating shmems that are too small, whereupon we purge the existing shmems and switch to allocating them at the size being requested by the CDM. This means we recover from incorrectly guessing the size of shmems required by the CDM. The overhead of an incorrect guess should be one video frame transferred via the nsTArray path. MozReview-Commit-ID: 8o1s7FI2UBd
dom/media/gmp/ChromiumCDMChild.cpp
dom/media/gmp/ChromiumCDMChild.h
dom/media/gmp/ChromiumCDMParent.cpp
dom/media/gmp/ChromiumCDMParent.h
dom/media/gmp/GMPUtils.cpp
dom/media/gmp/GMPUtils.h
dom/media/gmp/PChromiumCDM.ipdl
--- a/dom/media/gmp/ChromiumCDMChild.cpp
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -366,16 +366,32 @@ ChromiumCDMChild::~ChromiumCDMChild()
 }
 
 bool
 ChromiumCDMChild::IsOnMessageLoopThread()
 {
   return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
 }
 
+void
+ChromiumCDMChild::PurgeShmems()
+{
+  for (ipc::Shmem& shmem : mBuffers) {
+    DeallocShmem(shmem);
+  }
+  mBuffers.Clear();
+}
+
+ipc::IPCResult
+ChromiumCDMChild::RecvPurgeShmems()
+{
+  PurgeShmems();
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult
 ChromiumCDMChild::RecvInit(const bool& aAllowDistinctiveIdentifier,
                            const bool& aAllowPersistentState)
 {
   MOZ_ASSERT(IsOnMessageLoopThread());
   GMP_LOG("ChromiumCDMChild::RecvInit(distinctiveId=%d, persistentState=%d)",
           aAllowDistinctiveIdentifier,
           aAllowPersistentState);
@@ -634,20 +650,17 @@ ChromiumCDMChild::RecvDeinitializeVideoD
 {
   MOZ_ASSERT(IsOnMessageLoopThread());
   GMP_LOG("ChromiumCDMChild::RecvDeinitializeVideoDecoder()");
   MOZ_ASSERT(mDecoderInitialized);
   if (mDecoderInitialized) {
     mDecoderInitialized = false;
     mCDM->DeinitializeDecoder(cdm::kStreamTypeVideo);
   }
-  for (ipc::Shmem& shmem : mBuffers) {
-    DeallocShmem(shmem);
-  }
-  mBuffers.Clear();
+  PurgeShmems();
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ChromiumCDMChild::RecvResetVideoDecoder()
 {
   MOZ_ASSERT(IsOnMessageLoopThread());
   GMP_LOG("ChromiumCDMChild::RecvResetVideoDecoder()");
--- a/dom/media/gmp/ChromiumCDMChild.h
+++ b/dom/media/gmp/ChromiumCDMChild.h
@@ -77,17 +77,18 @@ public:
   void GiveBuffer(ipc::Shmem&& aBuffer);
 
 protected:
   ~ChromiumCDMChild();
 
   bool IsOnMessageLoopThread();
 
   ipc::IPCResult RecvGiveBuffer(ipc::Shmem&& aShmem) override;
-
+  ipc::IPCResult RecvPurgeShmems() override;
+  void PurgeShmems();
   ipc::IPCResult RecvInit(const bool& aAllowDistinctiveIdentifier,
                           const bool& aAllowPersistentState) override;
   ipc::IPCResult RecvSetServerCertificate(
     const uint32_t& aPromiseId,
     nsTArray<uint8_t>&& aServerCert) override;
   ipc::IPCResult RecvCreateSessionAndGenerateRequest(
     const uint32_t& aPromiseId,
     const uint32_t& aSessionType,
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -17,17 +17,17 @@
 
 namespace mozilla {
 namespace gmp {
 
 ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
                                      uint32_t aPluginId)
   : mPluginId(aPluginId)
   , mContentParent(aContentParent)
-  , mVideoShmemCount(MediaPrefs::EMEChromiumAPIVideoShmemCount())
+  , mVideoShmemLimit(MediaPrefs::EMEChromiumAPIVideoShmemCount())
 {
   GMP_LOG(
     "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, id=%u)",
     this,
     aContentParent,
     aPluginId);
 }
 
@@ -595,75 +595,193 @@ ChromiumCDMParent::RecvDecrypted(const u
         MakeSpan<const uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
       mDecrypts.RemoveElementAt(i);
       break;
     }
   }
   return IPC_OK();
 }
 
+bool
+ChromiumCDMParent::PurgeShmems()
+{
+  GMP_LOG("ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%" PRIuSIZE
+          " limit=%" PRIu32 " active=%" PRIu32,
+          this,
+          mVideoFrameBufferSize,
+          mVideoShmemLimit,
+          mVideoShmemsActive);
+
+  if (mVideoShmemsActive == 0) {
+    // We haven't allocated any shmems, nothing to do here.
+    return true;
+  }
+  if (!SendPurgeShmems()) {
+    return false;
+  }
+  mVideoShmemsActive = 0;
+  return true;
+}
+
+bool
+ChromiumCDMParent::EnsureSufficientShmems(size_t aVideoFrameSize)
+{
+  GMP_LOG("ChromiumCDMParent::EnsureSufficientShmems(this=%p) "
+          "size=%" PRIuSIZE " expected_size=%" PRIuSIZE " limit=%" PRIu32
+          " active=%" PRIu32,
+          this,
+          aVideoFrameSize,
+          mVideoFrameBufferSize,
+          mVideoShmemLimit,
+          mVideoShmemsActive);
+
+  // The Chromium CDM API requires us to implement a synchronous
+  // interface to supply buffers to the CDM for it to write decrypted samples
+  // into. We want our buffers to be backed by shmems, in order to reduce
+  // the overhead of transferring decoded frames. However due to sandboxing
+  // restrictions, the CDM process cannot allocate shmems itself.
+  // We don't want to be doing synchronous IPC to request shmems from the
+  // content process, nor do we want to have to do intr IPC or make async
+  // IPC conform to the sync allocation interface. So instead we have the
+  // content process pre-allocate a set of shmems and give them to the CDM
+  // process in advance of them being needed.
+  //
+  // When the CDM needs to allocate a buffer for storing a decoded video
+  // frame, the CDM host gives it one of these shmems' buffers. When this
+  // is sent back to the content process, we uploading to a GPU surface,
+  // and send the shmem back to the CDM process so it can reuse it.
+  //
+  // Normally the CDM won't allocate more than one buffer at once, but
+  // we've seen cases where it allocates multiple buffers, returns one and
+  // holds onto the rest. So we need to ensure we have several extra
+  // shmems pre-allocated for the CDM. This threshold is set by the pref
+  // media.eme.chromium-api.video-shmems.
+  //
+  // We also have a failure recovery mechanism; if the CDM asks for more
+  // buffers than we have shmem's available, ChromiumCDMChild gives the
+  // CDM a non-shared memory buffer, and returns the frame to the parent
+  // in an nsTArray<uint8_t> instead of a shmem. Every time this happens,
+  // the parent sends an extra shmem to the CDM process for it to add to the
+  // set of shmems with which to return output. Via this mechanism we should
+  // recover from incorrectly predicting how many shmems to pre-allocate.
+  //
+  // At decoder start up, we guess how big the shmems need to be based on
+  // the video frame dimensions. If we guess wrong, the CDM will follow
+  // the non-shmem path, and we'll re-create the shmems of the correct size.
+  // This meanns we can recover from guessing the shmem size wrong.
+  // We must re-take this path after every decoder de-init/re-init, as the
+  // frame sizes should change every time we switch video stream.
+
+  if (mVideoFrameBufferSize < aVideoFrameSize) {
+    if (!PurgeShmems()) {
+      return false;
+    }
+    mVideoFrameBufferSize = aVideoFrameSize;
+  } else {
+    // Put an upper limit on the number of shmems we tolerate the CDM asking
+    // for, to prevent a memory blow-out. In practice, we expect the CDM to
+    // need less than 5, but some encodings require more.
+    // We'd expect CDMs to not have video frames larger than 720p-1080p
+    // (due to DRM robustness requirements), which is about 1.5MB-3MB per
+    // frame.
+    if (mVideoShmemLimit > 50) {
+      return false;
+    }
+    mVideoShmemLimit++;
+  }
+
+  while (mVideoShmemsActive < mVideoShmemLimit) {
+    if (!SendBufferToCDM(mVideoFrameBufferSize)) {
+      return false;
+    }
+    mVideoShmemsActive++;
+  }
+  return true;
+}
+
 ipc::IPCResult
 ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame,
                                    nsTArray<uint8_t>&& aData)
 {
-  GMP_LOG("ChromiumCDMParent::RecvDecodedData(this=%p) "
-          "mVideoShmemCount=%" PRIu32,
-          this,
-          mVideoShmemCount);
-  // We'd expect CDMs to not have video frames larger than 1280x720 (due to
-  // DRM robustness requirements), which is about 1.5MB per frame. So put an
-  // upper limit on the number of shmems we tolerate the CDM asking for. In
-  // practice, we expect the CDM to need less than 5, but some encodings
-  // require more.
-  Shmem shmem;
-  if (mVideoShmemCount >= 50 || !AllocShmem(mVideoFrameBufferSize,
-                                            Shmem::SharedMemory::TYPE_BASIC,
-                                            &shmem)) {
-    GMP_LOG("ChromiumCDMParent::RecvDecodedData(this=%p) "
-            "failed to allocate shmem for CDM.",
-            this);
-    mVideoDecoderInitialized = false;
+  GMP_LOG("ChromiumCDMParent::RecvDecodedData(this=%p)", this);
+
+  if (mIsShutdown || mDecodePromise.IsEmpty()) {
+    return IPC_OK();
+  }
+
+  if (!EnsureSufficientShmems(aData.Length())) {
     mDecodePromise.RejectIfExists(
-      MediaResult(
-        NS_ERROR_DOM_MEDIA_FATAL_ERR,
-        RESULT_DETAIL("Failled to send shmems to CDM after decode init.")),
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
       __func__);
     return IPC_OK();
   }
-  mVideoShmemCount++;
 
-  ProcessDecoded(aFrame, aData, Move(shmem));
+  RefPtr<VideoData> v = CreateVideoFrame(aFrame, aData);
+  if (!v) {
+    mDecodePromise.RejectIfExists(
+      MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                  RESULT_DETAIL("Can't create VideoData")),
+      __func__);
+    return IPC_OK();
+  }
+
+  mDecodePromise.ResolveIfExists({ Move(v) }, __func__);
 
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDecodedShmem(const CDMVideoFrame& aFrame,
                                     ipc::Shmem&& aShmem)
 {
-  ProcessDecoded(
-    aFrame,
-    MakeSpan<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()),
-    Move(aShmem));
+  GMP_LOG("ChromiumCDMParent::RecvDecodedShmem(this=%p)", this);
+
+  // On failure we need to deallocate the shmem we're to return to the
+  // CDM. On success we return it to the CDM to be reused.
+  auto autoDeallocateShmem =
+    MakeScopeExit([&, this] { this->DeallocShmem(aShmem); });
+
+  if (mIsShutdown || mDecodePromise.IsEmpty()) {
+    return IPC_OK();
+  }
+
+  RefPtr<VideoData> v = CreateVideoFrame(
+    aFrame, MakeSpan<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
+  if (!v) {
+    mDecodePromise.RejectIfExists(
+      MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                  RESULT_DETAIL("Can't create VideoData")),
+      __func__);
+    return IPC_OK();
+  }
+
+  // Return the shmem to the CDM so the shmem can be reused to send us
+  // another frame.
+  if (!SendGiveBuffer(aShmem)) {
+    mDecodePromise.RejectIfExists(
+      MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                  RESULT_DETAIL("Can't return shmem to CDM process")),
+      __func__);
+    return IPC_OK();
+  }
+
+  // Don't need to deallocate the shmem since the CDM process is responsible
+  // for it again.
+  autoDeallocateShmem.release();
+
+  mDecodePromise.ResolveIfExists({ Move(v) }, __func__);
+
   return IPC_OK();
 }
 
-void
-ChromiumCDMParent::ProcessDecoded(const CDMVideoFrame& aFrame,
-                                  Span<uint8_t> aData,
-                                  ipc::Shmem&& aGiftShmem)
+already_AddRefed<VideoData>
+ChromiumCDMParent::CreateVideoFrame(const CDMVideoFrame& aFrame,
+                                    Span<uint8_t> aData)
 {
-  // On failure we need to deallocate the shmem we're to return to the
-  // CDM. On success we return it to the CDM to be reused.
-  auto autoDeallocateShmem =
-    MakeScopeExit([&, this] { this->DeallocShmem(aGiftShmem); });
-
-  if (mIsShutdown || mDecodePromise.IsEmpty()) {
-    return;
-  }
   VideoData::YCbCrBuffer b;
   MOZ_ASSERT(aData.Length() > 0);
 
   b.mPlanes[0].mData = aData.Elements();
   b.mPlanes[0].mWidth = aFrame.mImageWidth();
   b.mPlanes[0].mHeight = aFrame.mImageHeight();
   b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
   b.mPlanes[0].mOffset = aFrame.mYPlane().mPlaneOffset();
@@ -690,37 +808,17 @@ ChromiumCDMParent::ProcessDecoded(const 
     mLastStreamOffset,
     media::TimeUnit::FromMicroseconds(aFrame.mTimestamp()),
     media::TimeUnit::FromMicroseconds(aFrame.mDuration()),
     b,
     false,
     media::TimeUnit::FromMicroseconds(-1),
     pictureRegion);
 
-  // Return the shmem to the CDM so the shmem can be reused to send us
-  // another frame.
-  if (!SendGiveBuffer(aGiftShmem)) {
-    mDecodePromise.RejectIfExists(
-      MediaResult(NS_ERROR_OUT_OF_MEMORY,
-                  RESULT_DETAIL("Can't return shmem to CDM process")),
-      __func__);
-    return;
-  }
-  // Don't need to deallocate the shmem since the CDM process is responsible
-  // for it again.
-  autoDeallocateShmem.release();
-
-  if (v) {
-    mDecodePromise.ResolveIfExists({ Move(v) }, __func__);
-  } else {
-    mDecodePromise.RejectIfExists(
-      MediaResult(NS_ERROR_OUT_OF_MEMORY,
-                  RESULT_DETAIL("CallBack::CreateAndCopyData")),
-      __func__);
-  }
+  return v.forget();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus)
 {
   if (mIsShutdown) {
     MOZ_ASSERT(mDecodePromise.IsEmpty());
     return IPC_OK();
@@ -782,25 +880,32 @@ ChromiumCDMParent::InitializeVideoDecode
 {
   if (mIsShutdown) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                   RESULT_DETAIL("ChromiumCDMParent is shutdown")),
       __func__);
   }
 
-  const int32_t bufferSize =
+  const size_t bufferSize =
     I420FrameBufferSizePadded(aInfo.mImage.width, aInfo.mImage.height);
   if (bufferSize <= 0) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                   RESULT_DETAIL("Video frame buffer size is invalid.")),
       __func__);
   }
 
+  if (!EnsureSufficientShmems(bufferSize)) {
+    return MediaDataDecoder::InitPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("Failed to init shmems for video decoder")),
+      __func__);
+  }
+
   if (!SendInitializeVideoDecoder(aConfig)) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                   RESULT_DETAIL("Failed to send init video decoder to CDM")),
       __func__);
   }
 
   mVideoDecoderInitialized = true;
@@ -817,61 +922,16 @@ ChromiumCDMParent::RecvOnDecoderInitDone
   GMP_LOG("ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%u)",
           this,
           aStatus);
   if (mIsShutdown) {
     MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
     return IPC_OK();
   }
   if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
-    // The Chromium CDM API requires us to implement a synchronous
-    // interface to supply buffers to the CDM for it to write decrypted samples
-    // into. We want our buffers to be backed by shmems, in order to reduce
-    // the overhead of transferring decoded frames. However due to sandboxing
-    // restrictions, the CDM process cannot allocate shmems itself.
-    // We don't want to be doing synchronous IPC to request shmems from the
-    // content process, nor do we want to have to do intr IPC or make async
-    // IPC conform to the sync allocation interface. So instead we have the
-    // content process pre-allocate a set of shmems and give them to the CDM
-    // process in advance of them being needed.
-    //
-    // When the CDM needs to allocate a buffer for storing a decrypted sample,
-    // the CDM host gives it one of these shmems' buffers. When this is sent
-    // back to the content process, we copy the result out (uploading to a
-    // GPU surface for video frames), and send the shmem back to the CDM
-    // process so it can reuse it.
-    //
-    // We predict the size of buffer the CDM will allocate, and prepopulate
-    // the CDM's list of shmems with shmems of at least that size, plus a bit
-    // of padding for safety.
-    //
-    // Normally the CDM won't allocate more than one buffer at once, but
-    // we've seen cases where it allocates multiple buffers, returns one and
-    // holds onto the rest. So we need to ensure we have a minimum number of
-    // shmems pre-allocated for the CDM. This minimum is set by the pref
-    // media.eme.chromium-api.video-shmems.
-    //
-    // We also have a failure recovery mechanism; if the CDM asks for more
-    // buffers than we have shmem's available, ChromiumCDMChild gives the
-    // CDM a non-shared memory buffer, and returns the frame to the parent
-    // in an nsTArray<uint8_t> instead of a shmem. Every time this happens,
-    // the parent sends an extra shmem to the CDM process for it to add to the
-    // set of shmems with which to return output. Via this mechanism we should
-    // recover from incorrectly predicting how many shmems to pre-allocate.
-    for (uint32_t i = 0; i < mVideoShmemCount; i++) {
-      if (!SendBufferToCDM(mVideoFrameBufferSize)) {
-        mVideoDecoderInitialized = false;
-        mInitVideoDecoderPromise.RejectIfExists(
-          MediaResult(
-            NS_ERROR_DOM_MEDIA_FATAL_ERR,
-            RESULT_DETAIL("Failled to send shmems to CDM after decode init.")),
-          __func__);
-        return IPC_OK();
-      }
-    }
     mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
   } else {
     mVideoDecoderInitialized = false;
     mInitVideoDecoderPromise.RejectIfExists(
       MediaResult(
         NS_ERROR_DOM_MEDIA_FATAL_ERR,
         RESULT_DETAIL("CDM init decode failed with %" PRIu32, aStatus)),
       __func__);
@@ -983,16 +1043,22 @@ ChromiumCDMParent::ShutdownVideoDecoder(
   mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
                                           __func__);
   mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
   if (!SendDeinitializeVideoDecoder()) {
     return ShutdownPromise::CreateAndResolve(true, __func__);
   }
   mVideoDecoderInitialized = false;
+
+  // The ChromiumCDMChild will purge its shmems, so if the decoder is
+  // reinitialized the shmems need to be re-allocated, and they may need
+  // to be a different size.
+  mVideoShmemsActive = 0;
+  mVideoFrameBufferSize = 0;
   return ShutdownPromise::CreateAndResolve(true, __func__);
 }
 
 void
 ChromiumCDMParent::Shutdown()
 {
   GMP_LOG("ChromiumCDMParent::Shutdown(this=%p)", this);
 
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -116,36 +116,36 @@ protected:
                                ipc::Shmem&& aData) override;
   ipc::IPCResult RecvDecryptFailed(const uint32_t& aId,
                                    const uint32_t& aStatus) override;
   ipc::IPCResult RecvOnDecoderInitDone(const uint32_t& aStatus) override;
   ipc::IPCResult RecvDecodedShmem(const CDMVideoFrame& aFrame,
                                   ipc::Shmem&& aShmem) override;
   ipc::IPCResult RecvDecodedData(const CDMVideoFrame& aFrame,
                                  nsTArray<uint8_t>&& aData) override;
-
-  void ProcessDecoded(const CDMVideoFrame& aFrame,
-                      Span<uint8_t> aData,
-                      ipc::Shmem&& aGiftShmem);
-
   ipc::IPCResult RecvDecodeFailed(const uint32_t& aStatus) override;
   ipc::IPCResult RecvShutdown() override;
   ipc::IPCResult RecvResetVideoDecoderComplete() override;
   ipc::IPCResult RecvDrainComplete() override;
   void ActorDestroy(ActorDestroyReason aWhy) override;
   bool SendBufferToCDM(uint32_t aSizeInBytes);
 
   void RejectPromise(uint32_t aPromiseId,
                      nsresult aError,
                      const nsCString& aErrorMessage);
 
   void ResolvePromise(uint32_t aPromiseId);
 
   bool InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer, MediaRawData* aSample);
 
+  bool PurgeShmems();
+  bool EnsureSufficientShmems(size_t aVideoFrameSize);
+  already_AddRefed<VideoData> CreateVideoFrame(const CDMVideoFrame& aFrame,
+                                               Span<uint8_t> aData);
+
   const uint32_t mPluginId;
   GMPContentParent* mContentParent;
   // Note: this pointer is a weak reference because otherwise it would cause
   // a cycle, as ChromiumCDMProxy has a strong reference to the
   // ChromiumCDMParent.
   ChromiumCDMProxy* mProxy = nullptr;
   nsDataHashtable<nsUint32HashKey, uint32_t> mPromiseToCreateSessionToken;
   nsTArray<RefPtr<DecryptJob>> mDecrypts;
@@ -154,21 +154,23 @@ protected:
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
 
   RefPtr<layers::ImageContainer> mImageContainer;
   VideoInfo mVideoInfo;
   uint64_t mLastStreamOffset = 0;
 
   MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushDecoderPromise;
 
-  int32_t mVideoFrameBufferSize = 0;
+  size_t mVideoFrameBufferSize = 0;
 
   // Count of the number of shmems in the set used to return decoded video
   // frames from the CDM to Gecko.
-  uint32_t mVideoShmemCount;
+  uint32_t mVideoShmemsActive = 0;
+  // Maximum number of shmems to use to return decoded video frames.
+  uint32_t mVideoShmemLimit;
 
   bool mIsShutdown = false;
   bool mVideoDecoderInitialized = false;
   bool mActorDestroyed = false;
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/GMPUtils.cpp
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -234,28 +234,28 @@ LogToConsole(const nsAString& aMsg)
 RefPtr<AbstractThread>
 GetGMPAbstractThread()
 {
   RefPtr<gmp::GeckoMediaPluginService> service =
     gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
   return service ? service->GetAbstractGMPThread() : nullptr;
 }
 
-static int32_t
-Align16(int32_t aNumber)
+static size_t
+Align16(size_t aNumber)
 {
   const uint32_t mask = 15; // Alignment - 1.
   return (aNumber + mask) & ~mask;
 }
 
-int32_t
+size_t
 I420FrameBufferSizePadded(int32_t aWidth, int32_t aHeight)
 {
   if (aWidth <= 0 || aHeight <= 0 || aWidth > MAX_VIDEO_WIDTH ||
       aHeight > MAX_VIDEO_HEIGHT) {
     return 0;
   }
 
-  int32_t ySize = Align16(aWidth) * Align16(aHeight);
+  size_t ySize = Align16(aWidth) * Align16(aHeight);
   return ySize + (ySize / 4) * 2;
 }
 
 } // namespace mozilla
--- a/dom/media/gmp/GMPUtils.h
+++ b/dom/media/gmp/GMPUtils.h
@@ -85,14 +85,14 @@ HaveGMPFor(const nsCString& aAPI,
 void
 LogToConsole(const nsAString& aMsg);
 
 RefPtr<AbstractThread>
 GetGMPAbstractThread();
 
 // Returns the number of bytes required to store an aWidth x aHeight image in
 // I420 format, padded so that the width and height are multiples of 16.
-int32_t
+size_t
 I420FrameBufferSizePadded(int32_t aWidth, int32_t aHeight);
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/gmp/PChromiumCDM.ipdl
+++ b/dom/media/gmp/PChromiumCDM.ipdl
@@ -51,16 +51,18 @@ child:
   async DecryptAndDecodeFrame(CDMInputBuffer aBuffer);
 
   async Drain();
 
   async Destroy();
 
   async GiveBuffer(Shmem aShmem);
 
+  async PurgeShmems();
+
 parent:
   async __delete__();
 
   // cdm::Host8
   async OnResolveNewSessionPromise(uint32_t aPromiseId, nsCString aSessionId);
 
   async OnResolvePromise(uint32_t aPromiseId);