--- 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 upload it 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);