Bug 1383580 - Add an explicit message to increase CDM-Firefox shmem pool. r=gerald
The strategy we were using to expand the pool of shmems we use to shuffle video
frames between the CDM and content processses is to increase the size of the
pool if the content process receives a video frame in a non-shmem. However the
CDM process will send a frame in a non-shmem if none of the shmems in the pool
are big enough to fit the frame the CDM produces. This happens if we
underestimate the size required for video frames. This causes the
ChromiumCDMParent to increase the number of shmems in the pool every time we
rate switch, until we eventually hit the limit of 50, whereupon playback fails.
So we need to disambiguate between these two cases; the first being we have a
pool of shmems, but they're the wrong size, the second being our shmems are the
right size, but we've run out and we need to expand the shmem pool. The only
place where we know this is in the CDM process. So this commit adds a new
message to PChromiumCDM through which the ChromiumCDMChild can report to the
parent that it needs more shmems in the pool.
The new Widevine CDM has a new video decoder which allocates video frames less
optimally than the previous, which causes us to hit this more often in Nightly.
Our telemetry also indicates we hit this rarely in Beta with the old CDM.
MozReview-Commit-ID: LoSvVhxHQxn
--- a/dom/media/gmp/ChromiumCDMChild.cpp
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -134,16 +134,20 @@ ToString(const nsTArray<ipc::Shmem>& aBu
cdm::Buffer*
ChromiumCDMChild::Allocate(uint32_t aCapacity)
{
GMP_LOG("ChromiumCDMChild::Allocate(capacity=%" PRIu32 ") bufferSizes={%s}",
aCapacity,
ToString(mBuffers).get());
MOZ_ASSERT(IsOnMessageLoopThread());
+ if (mBuffers.IsEmpty()) {
+ Unused << SendIncreaseShmemPoolSize();
+ }
+
// Find the shmem with the least amount of wasted space if we were to
// select it for this sized allocation. We need to do this because shmems
// for decrypted audio as well as video frames are both stored in this
// list, and we don't want to use the video frame shmems for audio samples.
const size_t invalid = std::numeric_limits<size_t>::max();
size_t best = invalid;
auto wastedSpace = [this, aCapacity](size_t index) {
return mBuffers[index].Size<uint8_t>() - aCapacity;
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -203,16 +203,17 @@ ChromiumCDMParent::InitCDMInputBuffer(gm
crypto.mEncryptedSizes,
crypto.mValid);
return true;
}
bool
ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes)
{
+ GMP_LOG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32, aSizeInBytes);
Shmem shmem;
if (!AllocShmem(aSizeInBytes, Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
return false;
}
if (!SendGiveBuffer(shmem)) {
DeallocShmem(shmem);
return false;
}
@@ -612,16 +613,46 @@ ChromiumCDMParent::RecvDecrypted(const u
MakeSpan<const uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
mDecrypts.RemoveElementAt(i);
break;
}
}
return IPC_OK();
}
+ipc::IPCResult
+ChromiumCDMParent::RecvIncreaseShmemPoolSize()
+{
+ GMP_LOG("%s(this=%p) limit=%" PRIu32 " active=%" PRIu32,
+ __func__,
+ this,
+ mVideoShmemLimit,
+ mVideoShmemsActive);
+
+ // 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) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
+ __func__);
+ Shutdown();
+ return IPC_OK();
+ }
+ mVideoShmemLimit++;
+
+ EnsureSufficientShmems(mVideoFrameBufferSize);
+
+ return IPC_OK();
+}
+
bool
ChromiumCDMParent::PurgeShmems()
{
GMP_LOG("ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%" PRIuSIZE
" limit=%" PRIu32 " active=%" PRIu32,
this,
mVideoFrameBufferSize,
mVideoShmemLimit,
@@ -670,44 +701,33 @@ ChromiumCDMParent::EnsureSufficientShmem
// 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.
+ // in an nsTArray<uint8_t> instead of a shmem. The child then sends a
+ // message to the parent asking it to increase the number of shmems in
+ // the pool. 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++;
}
@@ -717,17 +737,19 @@ ChromiumCDMParent::EnsureSufficientShmem
return true;
}
ipc::IPCResult
ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame,
nsTArray<uint8_t>&& aData)
{
- GMP_LOG("ChromiumCDMParent::RecvDecodedData(this=%p)", this);
+ GMP_LOG("ChromiumCDMParent::RecvDecodedData(this=%p) time=%" PRId64,
+ this,
+ aFrame.mTimestamp());
if (mIsShutdown || mDecodePromise.IsEmpty()) {
return IPC_OK();
}
if (!EnsureSufficientShmems(aData.Length())) {
mDecodePromise.RejectIfExists(
MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -121,16 +121,17 @@ protected:
ipc::IPCResult RecvDecodedShmem(const CDMVideoFrame& aFrame,
ipc::Shmem&& aShmem) override;
ipc::IPCResult RecvDecodedData(const CDMVideoFrame& aFrame,
nsTArray<uint8_t>&& aData) override;
ipc::IPCResult RecvDecodeFailed(const uint32_t& aStatus) override;
ipc::IPCResult RecvShutdown() override;
ipc::IPCResult RecvResetVideoDecoderComplete() override;
ipc::IPCResult RecvDrainComplete() override;
+ ipc::IPCResult RecvIncreaseShmemPoolSize() override;
void ActorDestroy(ActorDestroyReason aWhy) override;
bool SendBufferToCDM(uint32_t aSizeInBytes);
void ReorderAndReturnOutput(RefPtr<VideoData>&& aFrame);
void RejectPromise(uint32_t aPromiseId,
nsresult aError,
const nsCString& aErrorMessage);
--- a/dom/media/gmp/PChromiumCDM.ipdl
+++ b/dom/media/gmp/PChromiumCDM.ipdl
@@ -101,12 +101,14 @@ parent:
async DecodedData(CDMVideoFrame aFrame, uint8_t[] aData);
async DecodeFailed(uint32_t aStatus);
async ResetVideoDecoderComplete();
async DrainComplete();
async Shutdown();
+
+ async IncreaseShmemPoolSize();
};
} // namespace gmp
} // namespace mozilla