commit 5f499e32ad89732271d30db351ce4eb7c77bbf45
parent a5368601371aff4b8f49ddea75da77edde0ee24d
Author: Andreas Pehrson <apehrson@mozilla.com>
Date: Tue, 11 Nov 2025 08:20:25 +0000
Bug 1771789 - In CamerasParent use one ShmemPool per source. r=jib
This mitigates the risk of exhausting the ShmemPool when many distinct sources
are live.
Differential Revision: https://phabricator.services.mozilla.com/D266405
Diffstat:
5 files changed, 86 insertions(+), 37 deletions(-)
diff --git a/dom/media/systemservices/CamerasChild.cpp b/dom/media/systemservices/CamerasChild.cpp
@@ -490,18 +490,18 @@ mozilla::ipc::IPCResult CamerasChild::RecvCaptureEnded(
}
mozilla::ipc::IPCResult CamerasChild::RecvDeliverFrame(
- nsTArray<int>&& capIds, mozilla::ipc::Shmem&& shmem,
- const VideoFrameProperties& prop) {
+ const int& aCaptureId, nsTArray<int>&& aStreamIds,
+ mozilla::ipc::Shmem&& aShmem, const VideoFrameProperties& aProps) {
MutexAutoLock lock(mCallbackMutex);
- for (int capId : capIds) {
- if (auto* cb = Callback(capId)) {
- unsigned char* image = shmem.get<unsigned char>();
- cb->DeliverFrame(image, prop);
+ for (const int& streamId : aStreamIds) {
+ if (auto* cb = Callback(streamId)) {
+ unsigned char* image = aShmem.get<unsigned char>();
+ cb->DeliverFrame(image, aProps);
} else {
LOG(("DeliverFrame called with dead callback"));
}
}
- SendReleaseFrame(std::move(shmem));
+ SendReleaseFrame(aCaptureId, std::move(aShmem));
return IPC_OK();
}
diff --git a/dom/media/systemservices/CamerasChild.h b/dom/media/systemservices/CamerasChild.h
@@ -149,8 +149,9 @@ class CamerasChild final : public PCamerasChild {
mozilla::ipc::IPCResult RecvCaptureEnded(
nsTArray<int>&& aCaptureIds) override;
mozilla::ipc::IPCResult RecvDeliverFrame(
- nsTArray<int>&& capIds, mozilla::ipc::Shmem&& shmem,
- const VideoFrameProperties& prop) override;
+ const int& aCaptureId, nsTArray<int>&& aStreamIds,
+ mozilla::ipc::Shmem&& aShmem,
+ const VideoFrameProperties& aProps) override;
mozilla::ipc::IPCResult RecvDeviceChange() override;
diff --git a/dom/media/systemservices/CamerasParent.cpp b/dom/media/systemservices/CamerasParent.cpp
@@ -259,13 +259,14 @@ void CamerasParent::OnDeviceChange() {
class DeliverFrameRunnable : public mozilla::Runnable {
public:
DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine,
- nsTArray<int>&& aStreamIds,
+ int aCaptureId, nsTArray<int>&& aStreamIds,
const TrackingId& aTrackingId,
const webrtc::VideoFrame& aFrame,
const VideoFrameProperties& aProperties)
: Runnable("camera::DeliverFrameRunnable"),
mParent(aParent),
mCapEngine(aEngine),
+ mCaptureId(aCaptureId),
mStreamIds(std::move(aStreamIds)),
mTrackingId(aTrackingId),
mProperties(aProperties) {
@@ -285,12 +286,13 @@ class DeliverFrameRunnable : public mozilla::Runnable {
}
DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine,
- nsTArray<int>&& aStreamIds,
+ int aCaptureId, nsTArray<int>&& aStreamIds,
const TrackingId& aTrackingId, ShmemBuffer aBuffer,
VideoFrameProperties& aProperties)
: Runnable("camera::DeliverFrameRunnable"),
mParent(aParent),
mCapEngine(aEngine),
+ mCaptureId(aCaptureId),
mStreamIds(std::move(aStreamIds)),
mTrackingId(aTrackingId),
mBuffer(std::move(aBuffer)),
@@ -304,15 +306,16 @@ class DeliverFrameRunnable : public mozilla::Runnable {
// Communication channel is being torn down
return NS_OK;
}
- mParent->DeliverFrameOverIPC(mCapEngine, mStreamIds, mTrackingId,
- std::move(mBuffer), mAlternateBuffer.get(),
- mProperties);
+ mParent->DeliverFrameOverIPC(mCapEngine, mCaptureId, mStreamIds,
+ mTrackingId, std::move(mBuffer),
+ mAlternateBuffer.get(), mProperties);
return NS_OK;
}
private:
const RefPtr<CamerasParent> mParent;
const CaptureEngine mCapEngine;
+ const int mCaptureId;
const nsTArray<int> mStreamIds;
const TrackingId mTrackingId;
ShmemBuffer mBuffer;
@@ -320,7 +323,7 @@ class DeliverFrameRunnable : public mozilla::Runnable {
const VideoFrameProperties mProperties;
};
-int CamerasParent::DeliverFrameOverIPC(CaptureEngine aCapEngine,
+int CamerasParent::DeliverFrameOverIPC(CaptureEngine aCapEngine, int aCaptureId,
const Span<const int>& aStreamIds,
const TrackingId& aTrackingId,
ShmemBuffer aBuffer,
@@ -332,7 +335,15 @@ int CamerasParent::DeliverFrameOverIPC(CaptureEngine aCapEngine,
// buffer of the right size.
if (aAltBuffer != nullptr) {
// Get a shared memory buffer from the pool, at least size big
- ShmemBuffer shMemBuff = mShmemPool.Get(this, aProps.bufferSize());
+ ShmemBuffer shMemBuff;
+ {
+ auto guard = mShmemPools.Lock();
+ auto it = guard->find(aCaptureId);
+ if (it != guard->end()) {
+ auto& [_, pool] = *it;
+ shMemBuff = pool.Get(this, aProps.bufferSize());
+ }
+ }
if (!shMemBuff.Valid()) {
LOG("No usable Video shmem in DeliverFrame (out of buffers?)");
@@ -347,14 +358,16 @@ int CamerasParent::DeliverFrameOverIPC(CaptureEngine aCapEngine,
memcpy(shMemBuff.GetBytes(), aAltBuffer, aProps.bufferSize());
rec.Record();
- if (!SendDeliverFrame(aStreamIds, std::move(shMemBuff.Get()), aProps)) {
+ if (!SendDeliverFrame(aCaptureId, aStreamIds, std::move(shMemBuff.Get()),
+ aProps)) {
return -1;
}
} else {
MOZ_ASSERT(aBuffer.Valid());
// ShmemBuffer was available, we're all good. A single copy happened
// in the original webrtc callback.
- if (!SendDeliverFrame(aStreamIds, std::move(aBuffer.Get()), aProps)) {
+ if (!SendDeliverFrame(aCaptureId, aStreamIds, std::move(aBuffer.Get()),
+ aProps)) {
return -1;
}
}
@@ -379,8 +392,14 @@ bool CamerasParent::IsWindowCapturing(uint64_t aWindowId,
return false;
}
-ShmemBuffer CamerasParent::GetBuffer(size_t aSize) {
- return mShmemPool.GetIfAvailable(aSize);
+ShmemBuffer CamerasParent::GetBuffer(int aCaptureId, size_t aSize) {
+ auto guard = mShmemPools.Lock();
+ auto it = guard->find(aCaptureId);
+ if (it == guard->end()) {
+ return ShmemBuffer();
+ }
+ auto& [_, pool] = *it;
+ return pool.GetIfAvailable(aSize);
}
/*static*/
@@ -652,7 +671,8 @@ void AggregateCapturer::OnFrame(const webrtc::VideoFrame& aVideoFrame) {
LOG_VERBOSE("CamerasParent(%p)::%s", parent, __func__);
RefPtr<DeliverFrameRunnable> runnable = nullptr;
// Get a shared memory buffer to copy the frame data into
- ShmemBuffer shMemBuffer = parent->GetBuffer(properties.bufferSize());
+ ShmemBuffer shMemBuffer =
+ parent->GetBuffer(mCaptureId, properties.bufferSize());
if (!shMemBuffer.Valid()) {
// Either we ran out of buffers or they're not the right size yet
LOG("Correctly sized Video shmem not available in DeliverFrame");
@@ -667,23 +687,34 @@ void AggregateCapturer::OnFrame(const webrtc::VideoFrame& aVideoFrame) {
VideoFrameUtils::CopyVideoFrameBuffers(
shMemBuffer.GetBytes(), properties.bufferSize(), aVideoFrame);
rec.Record();
- runnable = new DeliverFrameRunnable(parent, mCapEngine, std::move(ids),
- mTrackingId, std::move(shMemBuffer),
- properties);
+ runnable = new DeliverFrameRunnable(parent, mCapEngine, mCaptureId,
+ std::move(ids), mTrackingId,
+ std::move(shMemBuffer), properties);
}
if (!runnable) {
- runnable = new DeliverFrameRunnable(parent, mCapEngine, std::move(ids),
- mTrackingId, aVideoFrame, properties);
+ runnable = new DeliverFrameRunnable(parent, mCapEngine, mCaptureId,
+ std::move(ids), mTrackingId,
+ aVideoFrame, properties);
}
nsIEventTarget* target = parent->GetBackgroundEventTarget();
target->Dispatch(runnable, NS_DISPATCH_NORMAL);
}
}
-ipc::IPCResult CamerasParent::RecvReleaseFrame(ipc::Shmem&& aShmem) {
+ipc::IPCResult CamerasParent::RecvReleaseFrame(const int& aCaptureId,
+ ipc::Shmem&& aShmem) {
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
- mShmemPool.Put(ShmemBuffer(aShmem));
+ auto guard = mShmemPools.Lock();
+ auto it = guard->find(aCaptureId);
+ if (it == guard->end()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Releasing shmem but pool is already gone. Shmem must have been "
+ "deallocated.");
+ return IPC_FAIL(this, "Shmem was already deallocated");
+ }
+ auto& [_, pool] = *it;
+ pool.Put(ShmemBuffer(aShmem));
return IPC_OK();
}
@@ -1320,12 +1351,18 @@ auto CamerasParent::GetOrCreateCapturer(
-> GetOrCreateCapturerResult {
MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread());
VideoEngine* engine = EnsureInitialized(aEngine);
+ const auto ensureShmemPool = [&](int aCaptureId) {
+ auto guard = mShmemPools.Lock();
+ constexpr size_t kMaxShmemBuffers = 1;
+ guard->try_emplace(aCaptureId, kMaxShmemBuffers);
+ };
for (auto& capturer : *mCapturers) {
if (capturer->mCapEngine != aEngine) {
continue;
}
if (capturer->mUniqueId.Equals(aUniqueId)) {
int streamId = engine->GenerateId();
+ ensureShmemPool(capturer->mCaptureId);
capturer->AddStream(this, streamId, aWindowId);
return {.mCapturer = capturer.get(), .mStreamId = streamId};
}
@@ -1333,6 +1370,7 @@ auto CamerasParent::GetOrCreateCapturer(
NotNull capturer = mCapturers->AppendElement(
AggregateCapturer::Create(mVideoCaptureThread, aEngine, engine, aUniqueId,
aWindowId, std::move(aCapabilities), this));
+ ensureShmemPool(capturer->get()->mCaptureId);
return {.mCapturer = capturer->get(),
.mStreamId = capturer->get()->mCaptureId};
}
@@ -1435,7 +1473,12 @@ void CamerasParent::ActorDestroy(ActorDestroyReason aWhy) {
LOG_FUNCTION();
// Release shared memory now, it's our last chance
- mShmemPool.Cleanup(this);
+ {
+ auto guard = mShmemPools.Lock();
+ for (auto& [captureId, pool] : *guard) {
+ pool.Cleanup(this);
+ }
+ }
// We don't want to receive callbacks or anything if we can't
// forward them anymore anyway.
mDestroyed = true;
@@ -1468,7 +1511,7 @@ CamerasParent::CamerasParent()
mEngines(sEngines),
mCapturers(sCapturers),
mVideoCaptureFactory(EnsureVideoCaptureFactory()),
- mShmemPool(CaptureEngine::MaxEngine),
+ mShmemPools("CamerasParent::mShmemPools"),
mPBackgroundEventTarget(GetCurrentSerialEventTarget()),
mDestroyed(false) {
MOZ_ASSERT(mPBackgroundEventTarget != nullptr,
diff --git a/dom/media/systemservices/CamerasParent.h b/dom/media/systemservices/CamerasParent.h
@@ -193,7 +193,7 @@ class CamerasParent final : public PCamerasParent {
mozilla::ipc::IPCResult RecvStopCapture(const CaptureEngine& aCapEngine,
const int& aStreamId) override;
mozilla::ipc::IPCResult RecvReleaseFrame(
- mozilla::ipc::Shmem&& aShmem) override;
+ const int& aCaptureId, mozilla::ipc::Shmem&& aShmem) override;
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvEnsureInitialized(
const CaptureEngine& aCapEngine) override;
@@ -207,10 +207,10 @@ class CamerasParent final : public PCamerasParent {
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
return mDestroyed;
};
- ShmemBuffer GetBuffer(size_t aSize);
+ ShmemBuffer GetBuffer(int aCaptureId, size_t aSize);
// helper to forward to the PBackground thread
- int DeliverFrameOverIPC(CaptureEngine aCapEngine,
+ int DeliverFrameOverIPC(CaptureEngine aCapEngine, int aCaptureId,
const Span<const int>& aStreamId,
const TrackingId& aTrackingId, ShmemBuffer aBuffer,
unsigned char* aAltBuffer,
@@ -272,8 +272,13 @@ class CamerasParent final : public PCamerasParent {
// capture thread only.
const RefPtr<VideoCaptureFactory> mVideoCaptureFactory;
- // image buffers
- ShmemPool mShmemPool;
+ // Image buffers. One pool per CamerasParent instance and capture id (i.e.
+ // unique source). Multiple CamerasParent instances capturing the same source
+ // need distinct ShmemPools as ShmemBuffers are tied to the IPC channel.
+ // Access is on the PBackground thread for mutations and
+ // allocating shmem buffers, and on the callback thread (varies by capture
+ // backend) for querying an existing pool for an available buffer.
+ DataMutex<std::map<int, ShmemPool>> mShmemPools;
// PBackgroundParent thread
const nsCOMPtr<nsISerialEventTarget> mPBackgroundEventTarget;
diff --git a/dom/media/systemservices/PCameras.ipdl b/dom/media/systemservices/PCameras.ipdl
@@ -63,7 +63,7 @@ async protocol PCameras
child:
async CaptureEnded(int[] streamIds);
// transfers ownership of |buffer| from parent to child
- async DeliverFrame(int[] streamIds, Shmem buffer, VideoFrameProperties props);
+ async DeliverFrame(int captureId, int[] streamIds, Shmem buffer, VideoFrameProperties props);
async DeviceChange();
async ReplyNumberOfCaptureDevices(int deviceCount);
async ReplyNumberOfCapabilities(int capabilityCount);
@@ -92,7 +92,7 @@ parent:
async FocusOnSelectedSource(CaptureEngine engine, int streamId);
async StopCapture(CaptureEngine engine, int streamId);
// transfers frame back
- async ReleaseFrame(Shmem s);
+ async ReleaseFrame(int captureId, Shmem s);
// setup camera engine
async EnsureInitialized(CaptureEngine engine);