tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 9a220092ae144225bdffb3d0202ed9075949c674
parent 4efc2953eaac8b992c32818c905501fa56543215
Author: Andreas Pehrson <apehrson@mozilla.com>
Date:   Fri,  5 Dec 2025 16:34:21 +0000

Bug 1999970 - Retrofit a Skipped callback on top of GMP encoder API using input frame destroy calls. r=aosmond,media-playback-reviewers,webrtc-reviewers,bwc

This allows the GMP client (in this case the WebRTC GMP encoder) to know whether
the encoder plugin decided to drop a frame. This lets us avoid flushing the
encoder with more encode jobs than it can handle.

Differential Revision: https://phabricator.services.mozilla.com/D272613

Diffstat:
Mdom/media/gmp/GMPSharedMemManager.h | 4++++
Mdom/media/gmp/GMPVideoEncoderChild.cpp | 22++++++++++++++++++++--
Mdom/media/gmp/GMPVideoEncoderChild.h | 3+++
Mdom/media/gmp/GMPVideoEncoderParent.cpp | 8++++++++
Mdom/media/gmp/GMPVideoEncoderParent.h | 1+
Mdom/media/gmp/GMPVideoEncoderProxy.h | 1+
Mdom/media/gmp/GMPVideoHost.cpp | 3+++
Mdom/media/gmp/GMPVideoi420FrameImpl.cpp | 23+++++++++++++++++------
Mdom/media/gmp/GMPVideoi420FrameImpl.h | 30++++++++++++++++++++++--------
Mdom/media/gmp/PGMPVideoEncoder.ipdl | 1+
Mdom/media/platforms/agnostic/gmp/GMPVideoEncoder.cpp | 5+++++
Mdom/media/platforms/agnostic/gmp/GMPVideoEncoder.h | 1+
Mdom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp | 51++++++++++++++++++++++++++++++++++++++++++---------
Mdom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h | 2++
14 files changed, 130 insertions(+), 25 deletions(-)

diff --git a/dom/media/gmp/GMPSharedMemManager.h b/dom/media/gmp/GMPSharedMemManager.h @@ -11,6 +11,8 @@ namespace mozilla::gmp { +class GMPVideoi420FrameImpl; + enum class GMPSharedMemClass { Decoded, Encoded }; class GMPSharedMemManager { @@ -27,6 +29,8 @@ class GMPSharedMemManager { virtual bool MgrAllocShmem(size_t aSize, ipc::Shmem* aMem) { return false; } virtual void MgrDeallocShmem(ipc::Shmem& aMem) = 0; + virtual void MgrDecodedFrameDestroyed(GMPVideoi420FrameImpl* aFrame) {} + protected: virtual bool MgrIsOnOwningThread() const = 0; diff --git a/dom/media/gmp/GMPVideoEncoderChild.cpp b/dom/media/gmp/GMPVideoEncoderChild.cpp @@ -10,7 +10,6 @@ #include "GMPContentChild.h" #include "GMPPlatform.h" #include "GMPVideoEncodedFrameImpl.h" -#include "GMPVideoi420FrameImpl.h" #include "mozilla/StaticPrefs_media.h" #include "runnable_utils.h" @@ -68,9 +67,26 @@ void GMPVideoEncoderChild::Encoded(GMPVideoEncodedFrame* aEncodedFrame, MOZ_CRASH("Encoded without any frame data!"); } + mLatestEncodedTimestamp = frameData.mTimestamp(); + aEncodedFrame->Destroy(); } +void GMPVideoEncoderChild::MgrDecodedFrameDestroyed( + GMPVideoi420FrameImpl* aFrame) { + if (NS_WARN_IF(!mPlugin)) { + return; + } + + // The OpenH264 encoder destroys the input frame if it has skipped encoding + // it. When it has encoded it, it calls the Encoded() callback before + // destroying the frame. + MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current()); + if (aFrame->Timestamp() > mLatestEncodedTimestamp) { + (void)SendDroppedFrame(aFrame->Timestamp()); + } +} + void GMPVideoEncoderChild::Error(GMPErr aError) { if (NS_WARN_IF(!mPlugin)) { return; @@ -117,8 +133,10 @@ mozilla::ipc::IPCResult GMPVideoEncoderChild::RecvEncode( return IPC_FAIL(this, "!mVideoDecoder"); } + // The `this` destroyed callback outlives the frame, because `mVideoEncoder` + // is responsible for destroying the frame, and we outlive `mVideoEncoder`. auto* f = new GMPVideoi420FrameImpl(aInputFrame, std::move(aInputShmem), - &mVideoHost); + &mVideoHost, HostReportPolicy::Destroyed); // Ignore any return code. It is OK for this to fail without killing the // process. diff --git a/dom/media/gmp/GMPVideoEncoderChild.h b/dom/media/gmp/GMPVideoEncoderChild.h @@ -8,6 +8,7 @@ #include "GMPSharedMemManager.h" #include "GMPVideoHost.h" +#include "GMPVideoi420FrameImpl.h" #include "gmp-video-encode.h" #include "mozilla/gmp/PGMPVideoEncoderChild.h" #include "nsString.h" @@ -39,6 +40,7 @@ class GMPVideoEncoderChild final : public PGMPVideoEncoderChild, // GMPSharedMemManager void MgrDeallocShmem(Shmem& aMem) override { DeallocShmem(aMem); } + void MgrDecodedFrameDestroyed(GMPVideoi420FrameImpl* aFrame) override; protected: bool MgrIsOnOwningThread() const override; @@ -66,6 +68,7 @@ class GMPVideoEncoderChild final : public PGMPVideoEncoderChild, GMPContentChild* mPlugin; GMPVideoEncoder* mVideoEncoder; GMPVideoHostImpl mVideoHost; + uint64_t mLatestEncodedTimestamp = 0; }; } // namespace mozilla::gmp diff --git a/dom/media/gmp/GMPVideoEncoderParent.cpp b/dom/media/gmp/GMPVideoEncoderParent.cpp @@ -270,6 +270,14 @@ mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvEncodedData( return IPC_OK(); } +mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvDroppedFrame( + const uint64_t& aTimestamp) { + if (mCallback) { + mCallback->Dropped(aTimestamp); + } + return IPC_OK(); +} + mozilla::ipc::IPCResult GMPVideoEncoderParent::RecvError(const GMPErr& aError) { if (mCallback) { mCallback->Error(aError); diff --git a/dom/media/gmp/GMPVideoEncoderParent.h b/dom/media/gmp/GMPVideoEncoderParent.h @@ -73,6 +73,7 @@ class GMPVideoEncoderParent final : public GMPVideoEncoderProxy, const GMPVideoEncodedFrameData& aEncodedFrame, nsTArray<uint8_t>&& aEncodedData, nsTArray<uint8_t>&& aCodecSpecificInfo) override; + mozilla::ipc::IPCResult RecvDroppedFrame(const uint64_t& aTimestamp) override; mozilla::ipc::IPCResult RecvError(const GMPErr& aError) override; mozilla::ipc::IPCResult RecvShutdown() override; diff --git a/dom/media/gmp/GMPVideoEncoderProxy.h b/dom/media/gmp/GMPVideoEncoderProxy.h @@ -18,6 +18,7 @@ class GMPVideoEncoderCallbackProxy : public GMPCallbackBase { virtual ~GMPVideoEncoderCallbackProxy() = default; virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame, const nsTArray<uint8_t>& aCodecSpecificInfo) = 0; + virtual void Dropped(uint64_t aTimestamp) = 0; virtual void Error(GMPErr aError) = 0; }; diff --git a/dom/media/gmp/GMPVideoHost.cpp b/dom/media/gmp/GMPVideoHost.cpp @@ -94,6 +94,9 @@ void GMPVideoHostImpl::DecodedFrameCreated( void GMPVideoHostImpl::DecodedFrameDestroyed(GMPVideoi420FrameImpl* aFrame) { MOZ_ALWAYS_TRUE(mDecodedFrames.RemoveElement(aFrame)); + if (mSharedMemMgr && aFrame->mReportPolicy == HostReportPolicy::Destroyed) { + mSharedMemMgr->MgrDecodedFrameDestroyed(aFrame); + } } } // namespace mozilla::gmp diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.cpp b/dom/media/gmp/GMPVideoi420FrameImpl.cpp @@ -40,16 +40,25 @@ void GMPVideoi420FrameImpl::GMPFramePlane::Copy(uint8_t* aDst, } } -GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost) - : mHost(aHost), mWidth(0), mHeight(0), mTimestamp(0ll), mDuration(0ll) { +GMPVideoi420FrameImpl::GMPVideoi420FrameImpl( + GMPVideoHostImpl* aHost, + HostReportPolicy aReportPolicy /*= HostReportPolicy::None*/) + : mReportPolicy(aReportPolicy), + mHost(aHost), + mWidth(0), + mHeight(0), + mTimestamp(0ll), + mDuration(0ll) { MOZ_ASSERT(aHost); aHost->DecodedFrameCreated(this); } GMPVideoi420FrameImpl::GMPVideoi420FrameImpl( const GMPVideoi420FrameData& aFrameData, ipc::Shmem&& aShmemBuffer, - GMPVideoHostImpl* aHost) - : mHost(aHost), + GMPVideoHostImpl* aHost, + HostReportPolicy aReportPolicy /*= HostReportPolicy::None*/) + : mReportPolicy(aReportPolicy), + mHost(aHost), mShmemBuffer(std::move(aShmemBuffer)), mYPlane(aFrameData.mYPlane()), mUPlane(aFrameData.mUPlane()), @@ -65,8 +74,10 @@ GMPVideoi420FrameImpl::GMPVideoi420FrameImpl( GMPVideoi420FrameImpl::GMPVideoi420FrameImpl( const GMPVideoi420FrameData& aFrameData, nsTArray<uint8_t>&& aArrayBuffer, - GMPVideoHostImpl* aHost) - : mHost(aHost), + GMPVideoHostImpl* aHost, + HostReportPolicy aReportPolicy /*= HostReportPolicy::None*/) + : mReportPolicy(aReportPolicy), + mHost(aHost), mArrayBuffer(std::move(aArrayBuffer)), mYPlane(aFrameData.mYPlane()), mUPlane(aFrameData.mUPlane()), diff --git a/dom/media/gmp/GMPVideoi420FrameImpl.h b/dom/media/gmp/GMPVideoi420FrameImpl.h @@ -17,14 +17,24 @@ class GMPPlaneData; class GMPVideoi420FrameData; class GMPVideoHostImpl; -class GMPVideoi420FrameImpl final : public GMPVideoi420Frame { +enum class HostReportPolicy : uint8_t { + None, + Destroyed, +}; + +class GMPVideoi420FrameImpl : public GMPVideoi420Frame { public: - explicit GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost); - GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData, - ipc::Shmem&& aShmemBuffer, GMPVideoHostImpl* aHost); - GMPVideoi420FrameImpl(const GMPVideoi420FrameData& aFrameData, - nsTArray<uint8_t>&& aArrayBuffer, - GMPVideoHostImpl* aHost); + explicit GMPVideoi420FrameImpl( + GMPVideoHostImpl* aHost, + HostReportPolicy aReportPolicy = HostReportPolicy::None); + GMPVideoi420FrameImpl( + const GMPVideoi420FrameData& aFrameData, ipc::Shmem&& aShmemBuffer, + GMPVideoHostImpl* aHost, + HostReportPolicy aReportPolicy = HostReportPolicy::None); + GMPVideoi420FrameImpl( + const GMPVideoi420FrameData& aFrameData, nsTArray<uint8_t>&& aArrayBuffer, + GMPVideoHostImpl* aHost, + HostReportPolicy aReportPolicy = HostReportPolicy::None); virtual ~GMPVideoi420FrameImpl(); // This is called during a normal destroy sequence, which is @@ -75,7 +85,7 @@ class GMPVideoi420FrameImpl final : public GMPVideoi420Frame { const uint8_t* Buffer() const; int32_t AllocatedSize() const; - private: + protected: struct GMPFramePlane { explicit GMPFramePlane(const GMPPlaneData& aPlaneData); GMPFramePlane() = default; @@ -98,6 +108,10 @@ class GMPVideoi420FrameImpl final : public GMPVideoi420Frame { GMPErr MaybeResize(int32_t aNewSize); void DestroyBuffer(); + public: + const HostReportPolicy mReportPolicy; + + protected: GMPVideoHostImpl* mHost; nsTArray<uint8_t> mArrayBuffer; ipc::Shmem mShmemBuffer; diff --git a/dom/media/gmp/PGMPVideoEncoder.ipdl b/dom/media/gmp/PGMPVideoEncoder.ipdl @@ -43,6 +43,7 @@ parent: async EncodedData(GMPVideoEncodedFrameData aEncodedFrame, uint8_t[] aEncodedData, uint8_t[] aCodecSpecificInfo); + async DroppedFrame(uint64_t aTimestamp); async Error(GMPErr aErr); async Shutdown(); }; diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoEncoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoEncoder.cpp @@ -446,6 +446,11 @@ void GMPVideoEncoder::Encoded(GMPVideoEncodedFrame* aEncodedFrame, } } +void GMPVideoEncoder::Dropped(uint64_t aTimestamp) { + MOZ_ASSERT(IsOnGMPThread()); + // TODO: implement +} + void GMPVideoEncoder::Teardown(const MediaResult& aResult, StaticString aCallSite) { GMP_LOG_DEBUG("[%p] GMPVideoEncoder::Teardown", this); diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoEncoder.h b/dom/media/platforms/agnostic/gmp/GMPVideoEncoder.h @@ -39,6 +39,7 @@ class GMPVideoEncoder final : public MediaDataEncoder, void Encoded(GMPVideoEncodedFrame* aEncodedFrame, const nsTArray<uint8_t>& aCodecSpecificInfo) override; + void Dropped(uint64_t aTimestamp) override; void Error(GMPErr aError) override; void Terminated() override; diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp b/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp @@ -387,7 +387,9 @@ void WebrtcGmpVideoEncoder::Encode_g( GMP_LOG_DEBUG("GMP Encode: failed to create frame"); return; } - frame->SetTimestamp(AssertedCast<uint64_t>(aInputImage.ntp_time_ms() * 1000)); + const auto gmpTimestamp = + AssertedCast<uint64_t>(aInputImage.ntp_time_ms() * 1000); + frame->SetTimestamp(gmpTimestamp); GMPCodecSpecificInfo info{}; info.mCodecType = kGMPVideoCodecH264; @@ -422,7 +424,7 @@ void WebrtcGmpVideoEncoder::Encode_g( mInputImageMap.LastElement().ntp_timestamp_ms < aInputImage.ntp_time_ms()); mInputImageMap.AppendElement( - InputImageData{.gmp_timestamp_us = frame->Timestamp(), + InputImageData{.gmp_timestamp_us = gmpTimestamp, .ntp_timestamp_ms = aInputImage.ntp_time_ms(), .timestamp_us = aInputImage.timestamp_us(), .rtp_timestamp = aInputImage.rtp_timestamp(), @@ -526,20 +528,23 @@ void WebrtcGmpVideoEncoder::Terminated() { // Could now notify that it's dead } +static int32_t GmpTimestampComparator(const InputImageData& aA, + const InputImageData& aB) { + const auto& a = aA.gmp_timestamp_us; + const auto& b = aB.gmp_timestamp_us; + return a < b ? -1 : a != b; +} + void WebrtcGmpVideoEncoder::Encoded( GMPVideoEncodedFrame* aEncodedFrame, const nsTArray<uint8_t>& aCodecSpecificInfo) { MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); Maybe<InputImageData> data; - auto gmp_timestamp_comparator = [](const InputImageData& aA, - const InputImageData& aB) -> int32_t { - const auto& a = aA.gmp_timestamp_us; - const auto& b = aB.gmp_timestamp_us; - return a < b ? -1 : a != b; - }; + MOZ_ASSERT(!mInputImageMap.IsEmpty()); + MOZ_ASSERT(mInputImageMap.Length() <= kMaxImagesInFlight); size_t nextIdx = mInputImageMap.IndexOfFirstElementGt( InputImageData{.gmp_timestamp_us = aEncodedFrame->TimeStamp()}, - gmp_timestamp_comparator); + GmpTimestampComparator); const size_t numToRemove = nextIdx; size_t numFramesDropped = numToRemove; MOZ_ASSERT(nextIdx != 0); @@ -676,6 +681,34 @@ void WebrtcGmpVideoEncoder::Encoded( mCallback->OnEncodedImage(unit, &info); } +void WebrtcGmpVideoEncoder::Dropped(uint64_t aTimestamp) { + MOZ_ASSERT(mGMPThread->IsOnCurrentThread()); + MOZ_ASSERT(!mInputImageMap.IsEmpty()); + MOZ_ASSERT(mInputImageMap.Length() <= kMaxImagesInFlight); + + size_t nextIdx = mInputImageMap.IndexOfFirstElementGt( + InputImageData{.gmp_timestamp_us = aTimestamp}, GmpTimestampComparator); + const size_t numDropped = nextIdx; + MOZ_ASSERT(nextIdx != 0); + MOZ_ASSERT(mInputImageMap.ElementAt(nextIdx - 1).gmp_timestamp_us == + aTimestamp); + mInputImageMap.RemoveElementsAt(0, numDropped); + + GMP_LOG_DEBUG("GMP Dropped: %" PRIu64 + " dropped by encoder. Reporting %u frames dropped.", + aTimestamp, static_cast<uint32_t>(numDropped)); + + MutexAutoLock lock(mCallbackMutex); + if (!mCallback) { + return; + } + + for (size_t i = 0; i < numDropped; ++i) { + mCallback->OnDroppedFrame( + webrtc::EncodedImageCallback::DropReason::kDroppedByEncoder); + } +} + // Decoder. WebrtcGmpVideoDecoder::WebrtcGmpVideoDecoder(std::string aPCHandle, TrackingId aTrackingId) diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h b/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h @@ -201,6 +201,8 @@ class WebrtcGmpVideoEncoder final : public GMPVideoEncoderCallbackProxy, void Encoded(GMPVideoEncodedFrame* aEncodedFrame, const nsTArray<uint8_t>& aCodecSpecificInfo) override; + void Dropped(uint64_t aTimestamp) override; + void Error(GMPErr aError) override {} private: