1358 lines
50 KiB
C++
1358 lines
50 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 ts=8 et ft=cpp : */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "CamerasParent.h"
|
|
|
|
#include <atomic>
|
|
#include "CamerasTypes.h"
|
|
#include "MediaEngineSource.h"
|
|
#include "PerformanceRecorder.h"
|
|
#include "VideoEngine.h"
|
|
#include "VideoFrameUtils.h"
|
|
|
|
#include "common/browser_logging/WebRtcLog.h"
|
|
#include "mozilla/AppShutdown.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/ProfilerMarkers.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/ipc/BackgroundParent.h"
|
|
#include "mozilla/ipc/PBackgroundParent.h"
|
|
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
|
#include "mozilla/dom/WindowGlobalParent.h"
|
|
#include "mozilla/media/MediaUtils.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/StaticPrefs_media.h"
|
|
#include "mozilla/StaticPrefs_permissions.h"
|
|
#include "nsIPermissionManager.h"
|
|
#include "nsIThread.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsNetUtil.h"
|
|
#include "video_engine/desktop_capture_impl.h"
|
|
#include "video_engine/video_capture_factory.h"
|
|
|
|
#include "api/video/video_frame_buffer.h"
|
|
#include "common_video/libyuv/include/webrtc_libyuv.h"
|
|
|
|
#if defined(_WIN32)
|
|
# include <process.h>
|
|
# define getpid() _getpid()
|
|
#endif
|
|
|
|
#undef LOG
|
|
#undef LOG_VERBOSE
|
|
#undef LOG_ENABLED
|
|
mozilla::LazyLogModule gCamerasParentLog("CamerasParent");
|
|
#define LOG(...) \
|
|
MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
|
#define LOG_FUNCTION() \
|
|
MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Debug, \
|
|
("CamerasParent(%p)::%s", this, __func__))
|
|
#define LOG_VERBOSE(...) \
|
|
MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Verbose, (__VA_ARGS__))
|
|
#define LOG_ENABLED() MOZ_LOG_TEST(gCamerasParentLog, mozilla::LogLevel::Debug)
|
|
|
|
namespace mozilla {
|
|
using media::ShutdownBlockingTicket;
|
|
namespace camera {
|
|
|
|
MOZ_RUNINIT std::map<uint32_t, const char*> sDeviceUniqueIDs;
|
|
MOZ_RUNINIT std::map<uint32_t, webrtc::VideoCaptureCapability>
|
|
sAllRequestedCapabilities;
|
|
|
|
uint32_t ResolutionFeasibilityDistance(int32_t candidate, int32_t requested) {
|
|
// The purpose of this function is to find a smallest resolution
|
|
// which is larger than all requested capabilities.
|
|
// Then we can use down-scaling to fulfill each request.
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(candidate >= 0, "Candidate unexpectedly negative");
|
|
MOZ_DIAGNOSTIC_ASSERT(requested >= 0, "Requested unexpectedly negative");
|
|
|
|
if (candidate == 0) {
|
|
// Treat width|height capability of 0 as "can do any".
|
|
// This allows for orthogonal capabilities that are not in discrete steps.
|
|
return 0;
|
|
}
|
|
|
|
uint32_t distance =
|
|
std::abs(candidate - requested) * 1000 / std::max(candidate, requested);
|
|
if (candidate >= requested) {
|
|
// This is a good case, the candidate covers the requested resolution.
|
|
return distance;
|
|
}
|
|
|
|
// This is a bad case, the candidate is lower than the requested resolution.
|
|
// This is penalized with an added weight of 10000.
|
|
return 10000 + distance;
|
|
}
|
|
|
|
uint32_t FeasibilityDistance(int32_t candidate, int32_t requested) {
|
|
MOZ_DIAGNOSTIC_ASSERT(candidate >= 0, "Candidate unexpectedly negative");
|
|
MOZ_DIAGNOSTIC_ASSERT(requested >= 0, "Requested unexpectedly negative");
|
|
|
|
if (candidate == 0) {
|
|
// Treat maxFPS capability of 0 as "can do any".
|
|
// This allows for orthogonal capabilities that are not in discrete steps.
|
|
return 0;
|
|
}
|
|
|
|
return std::abs(candidate - requested) * 1000 /
|
|
std::max(candidate, requested);
|
|
}
|
|
|
|
class CamerasParent::VideoEngineArray
|
|
: public media::Refcountable<nsTArray<RefPtr<VideoEngine>>> {};
|
|
|
|
// Singleton video engines. The sEngines RefPtr is IPC background thread only
|
|
// and outlives the CamerasParent instances. The array elements are video
|
|
// capture thread only.
|
|
using VideoEngineArray = CamerasParent::VideoEngineArray;
|
|
static StaticRefPtr<VideoEngineArray> sEngines;
|
|
// Number of CamerasParents instances in the current process for which
|
|
// mVideoCaptureThread has been set. IPC background thread only.
|
|
static int32_t sNumCamerasParents = 0;
|
|
// Video processing thread - where webrtc.org capturer code runs. Outlives the
|
|
// CamerasParent instances. IPC background thread only.
|
|
static StaticRefPtr<nsIThread> sVideoCaptureThread;
|
|
// Main VideoCaptureFactory used to create and manage all capture related
|
|
// objects. IPC background thread only. Outlives the CamerasParent instances.
|
|
static StaticRefPtr<VideoCaptureFactory> sVideoCaptureFactory;
|
|
|
|
static VideoCaptureFactory* EnsureVideoCaptureFactory() {
|
|
ipc::AssertIsOnBackgroundThread();
|
|
|
|
if (sVideoCaptureFactory) {
|
|
return sVideoCaptureFactory;
|
|
}
|
|
|
|
sVideoCaptureFactory = MakeRefPtr<VideoCaptureFactory>();
|
|
NS_DispatchToMainThread(
|
|
NS_NewRunnableFunction("CamerasParent::EnsureVideoCaptureFactory",
|
|
[]() { ClearOnShutdown(&sVideoCaptureFactory); }));
|
|
return sVideoCaptureFactory;
|
|
}
|
|
|
|
static already_AddRefed<nsISerialEventTarget>
|
|
MakeAndAddRefVideoCaptureThreadAndSingletons() {
|
|
ipc::AssertIsOnBackgroundThread();
|
|
|
|
MOZ_ASSERT_IF(sVideoCaptureThread, sNumCamerasParents > 0);
|
|
MOZ_ASSERT_IF(!sVideoCaptureThread, sNumCamerasParents == 0);
|
|
|
|
if (!sVideoCaptureThread) {
|
|
LOG("Spinning up WebRTC Cameras Thread");
|
|
nsIThreadManager::ThreadCreationOptions options;
|
|
#ifdef XP_WIN
|
|
// Windows desktop capture needs a UI thread
|
|
options.isUiThread = true;
|
|
#endif
|
|
nsCOMPtr<nsIThread> videoCaptureThread;
|
|
if (NS_FAILED(NS_NewNamedThread("VideoCapture",
|
|
getter_AddRefs(videoCaptureThread), nullptr,
|
|
options))) {
|
|
return nullptr;
|
|
}
|
|
sVideoCaptureThread = videoCaptureThread.forget();
|
|
|
|
sEngines = MakeRefPtr<VideoEngineArray>();
|
|
sEngines->AppendElements(CaptureEngine::MaxEngine);
|
|
}
|
|
|
|
++sNumCamerasParents;
|
|
return do_AddRef(sVideoCaptureThread);
|
|
}
|
|
|
|
static void ReleaseVideoCaptureThreadAndSingletons() {
|
|
ipc::AssertIsOnBackgroundThread();
|
|
|
|
if (--sNumCamerasParents > 0) {
|
|
// Other CamerasParent instances are using the singleton classes.
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(sNumCamerasParents == 0, "Double release!");
|
|
|
|
// No other CamerasParent instances alive. Clean up.
|
|
LOG("Shutting down VideoEngines and the VideoCapture thread");
|
|
MOZ_ALWAYS_SUCCEEDS(sVideoCaptureThread->Dispatch(
|
|
NS_NewRunnableFunction(__func__, [engines = RefPtr(sEngines.forget())] {
|
|
for (RefPtr<VideoEngine>& engine : *engines) {
|
|
if (engine) {
|
|
VideoEngine::Delete(engine);
|
|
engine = nullptr;
|
|
}
|
|
}
|
|
})));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(RefPtr(sVideoCaptureThread.forget())->AsyncShutdown());
|
|
}
|
|
|
|
// 3 threads are involved in this code:
|
|
// - the main thread for some setups, and occassionally for video capture setup
|
|
// calls that don't work correctly elsewhere.
|
|
// - the IPC thread on which PBackground is running and which receives and
|
|
// sends messages
|
|
// - a thread which will execute the actual (possibly slow) camera access
|
|
// called "VideoCapture". On Windows this is a thread with an event loop
|
|
// suitable for UI access.
|
|
|
|
void CamerasParent::OnDeviceChange() {
|
|
LOG_FUNCTION();
|
|
|
|
mPBackgroundEventTarget->Dispatch(
|
|
NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)]() {
|
|
if (IsShuttingDown()) {
|
|
LOG("OnDeviceChanged failure: parent shutting down.");
|
|
return;
|
|
}
|
|
Unused << SendDeviceChange();
|
|
}));
|
|
};
|
|
|
|
class DeliverFrameRunnable : public mozilla::Runnable {
|
|
public:
|
|
DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine,
|
|
uint32_t aStreamId, const TrackingId& aTrackingId,
|
|
const webrtc::VideoFrame& aFrame,
|
|
const VideoFrameProperties& aProperties)
|
|
: Runnable("camera::DeliverFrameRunnable"),
|
|
mParent(aParent),
|
|
mCapEngine(aEngine),
|
|
mStreamId(aStreamId),
|
|
mTrackingId(aTrackingId),
|
|
mProperties(aProperties),
|
|
mResult(0) {
|
|
// No ShmemBuffer (of the right size) was available, so make an
|
|
// extra buffer here. We have no idea when we are going to run and
|
|
// it will be potentially long after the webrtc frame callback has
|
|
// returned, so the copy needs to be no later than here.
|
|
// We will need to copy this back into a Shmem later on so we prefer
|
|
// using ShmemBuffers to avoid the extra copy.
|
|
PerformanceRecorder<CopyVideoStage> rec(
|
|
"CamerasParent::VideoFrameToAltBuffer"_ns, aTrackingId, aFrame.width(),
|
|
aFrame.height());
|
|
mAlternateBuffer.reset(new unsigned char[aProperties.bufferSize()]);
|
|
VideoFrameUtils::CopyVideoFrameBuffers(mAlternateBuffer.get(),
|
|
aProperties.bufferSize(), aFrame);
|
|
rec.Record();
|
|
}
|
|
|
|
DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine,
|
|
uint32_t aStreamId, const TrackingId& aTrackingId,
|
|
ShmemBuffer aBuffer, VideoFrameProperties& aProperties)
|
|
: Runnable("camera::DeliverFrameRunnable"),
|
|
mParent(aParent),
|
|
mCapEngine(aEngine),
|
|
mStreamId(aStreamId),
|
|
mTrackingId(aTrackingId),
|
|
mBuffer(std::move(aBuffer)),
|
|
mProperties(aProperties),
|
|
mResult(0) {};
|
|
|
|
NS_IMETHOD Run() override {
|
|
// runs on BackgroundEventTarget
|
|
MOZ_ASSERT(GetCurrentSerialEventTarget() ==
|
|
mParent->mPBackgroundEventTarget);
|
|
if (mParent->IsShuttingDown()) {
|
|
// Communication channel is being torn down
|
|
mResult = 0;
|
|
return NS_OK;
|
|
}
|
|
if (!mParent->DeliverFrameOverIPC(mCapEngine, mStreamId, mTrackingId,
|
|
std::move(mBuffer),
|
|
mAlternateBuffer.get(), mProperties)) {
|
|
mResult = -1;
|
|
} else {
|
|
mResult = 0;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
int GetResult() { return mResult; }
|
|
|
|
private:
|
|
const RefPtr<CamerasParent> mParent;
|
|
const CaptureEngine mCapEngine;
|
|
const uint32_t mStreamId;
|
|
const TrackingId mTrackingId;
|
|
ShmemBuffer mBuffer;
|
|
UniquePtr<unsigned char[]> mAlternateBuffer;
|
|
const VideoFrameProperties mProperties;
|
|
int mResult;
|
|
};
|
|
|
|
int CamerasParent::DeliverFrameOverIPC(CaptureEngine aCapEngine,
|
|
uint32_t aStreamId,
|
|
const TrackingId& aTrackingId,
|
|
ShmemBuffer aBuffer,
|
|
unsigned char* aAltBuffer,
|
|
const VideoFrameProperties& aProps) {
|
|
// No ShmemBuffers were available, so construct one now of the right size
|
|
// and copy into it. That is an extra copy, but we expect this to be
|
|
// the exceptional case, because we just assured the next call *will* have a
|
|
// 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());
|
|
|
|
if (!shMemBuff.Valid()) {
|
|
LOG("No usable Video shmem in DeliverFrame (out of buffers?)");
|
|
// We can skip this frame if we run out of buffers, it's not a real error.
|
|
return 0;
|
|
}
|
|
|
|
PerformanceRecorder<CopyVideoStage> rec(
|
|
"CamerasParent::AltBufferToShmem"_ns, aTrackingId, aProps.width(),
|
|
aProps.height());
|
|
// get() and Size() check for proper alignment of the segment
|
|
memcpy(shMemBuff.GetBytes(), aAltBuffer, aProps.bufferSize());
|
|
rec.Record();
|
|
|
|
if (!SendDeliverFrame(aStreamId, 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(aStreamId, std::move(aBuffer.Get()), aProps)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ShmemBuffer CamerasParent::GetBuffer(size_t aSize) {
|
|
return mShmemPool.GetIfAvailable(aSize);
|
|
}
|
|
|
|
void CallbackHelper::OnCaptureEnded() {
|
|
nsIEventTarget* target = mParent->GetBackgroundEventTarget();
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(target->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [&] { Unused << mParent->SendCaptureEnded(mStreamId); })));
|
|
}
|
|
|
|
void CallbackHelper::OnFrame(const webrtc::VideoFrame& aVideoFrame) {
|
|
LOG_VERBOSE("CamerasParent(%p)::%s", mParent, __func__);
|
|
if (profiler_thread_is_being_profiled_for_markers()) {
|
|
PROFILER_MARKER_UNTYPED(
|
|
nsPrintfCString("CaptureVideoFrame %dx%d %s %s", aVideoFrame.width(),
|
|
aVideoFrame.height(),
|
|
webrtc::VideoFrameBufferTypeToString(
|
|
aVideoFrame.video_frame_buffer()->type()),
|
|
mTrackingId.ToString().get()),
|
|
MEDIA_RT);
|
|
}
|
|
RefPtr<DeliverFrameRunnable> runnable = nullptr;
|
|
// Get frame properties
|
|
camera::VideoFrameProperties properties;
|
|
VideoFrameUtils::InitFrameBufferProperties(aVideoFrame, properties);
|
|
// Get a shared memory buffer to copy the frame data into
|
|
ShmemBuffer shMemBuffer = mParent->GetBuffer(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");
|
|
// We will do the copy into a(n extra) temporary buffer inside
|
|
// the DeliverFrameRunnable constructor.
|
|
} else {
|
|
// Shared memory buffers of the right size are available, do the copy here.
|
|
PerformanceRecorder<CopyVideoStage> rec(
|
|
"CamerasParent::VideoFrameToShmem"_ns, mTrackingId, aVideoFrame.width(),
|
|
aVideoFrame.height());
|
|
VideoFrameUtils::CopyVideoFrameBuffers(
|
|
shMemBuffer.GetBytes(), properties.bufferSize(), aVideoFrame);
|
|
rec.Record();
|
|
runnable =
|
|
new DeliverFrameRunnable(mParent, mCapEngine, mStreamId, mTrackingId,
|
|
std::move(shMemBuffer), properties);
|
|
}
|
|
if (!runnable) {
|
|
runnable = new DeliverFrameRunnable(mParent, mCapEngine, mStreamId,
|
|
mTrackingId, aVideoFrame, properties);
|
|
}
|
|
MOZ_ASSERT(mParent);
|
|
nsIEventTarget* target = mParent->GetBackgroundEventTarget();
|
|
MOZ_ASSERT(target != nullptr);
|
|
target->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvReleaseFrame(ipc::Shmem&& aShmem) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
|
|
mShmemPool.Put(ShmemBuffer(aShmem));
|
|
return IPC_OK();
|
|
}
|
|
|
|
void CamerasParent::CloseEngines() {
|
|
MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread());
|
|
LOG_FUNCTION();
|
|
|
|
// Stop the callers
|
|
while (!mCallbacks.IsEmpty()) {
|
|
auto capEngine = mCallbacks[0]->mCapEngine;
|
|
auto streamNum = mCallbacks[0]->mStreamId;
|
|
LOG("Forcing shutdown of engine %d, capturer %d", capEngine, streamNum);
|
|
StopCapture(capEngine, streamNum);
|
|
Unused << ReleaseCapture(capEngine, streamNum);
|
|
}
|
|
|
|
auto device_info = GetDeviceInfo(CameraEngine);
|
|
MOZ_ASSERT(device_info);
|
|
if (device_info) {
|
|
device_info->DeRegisterVideoInputFeedBack(this);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<webrtc::VideoCaptureModule::DeviceInfo>
|
|
CamerasParent::GetDeviceInfo(int aEngine) {
|
|
MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread());
|
|
LOG_VERBOSE("CamerasParent(%p)::%s", this, __func__);
|
|
|
|
auto* engine = EnsureInitialized(aEngine);
|
|
if (!engine) {
|
|
return nullptr;
|
|
}
|
|
return engine->GetOrCreateVideoCaptureDeviceInfo(this);
|
|
}
|
|
|
|
VideoEngine* CamerasParent::EnsureInitialized(int aEngine) {
|
|
MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread());
|
|
LOG_VERBOSE("CamerasParent(%p)::%s", this, __func__);
|
|
CaptureEngine capEngine = static_cast<CaptureEngine>(aEngine);
|
|
|
|
if (VideoEngine* engine = mEngines->ElementAt(capEngine); engine) {
|
|
return engine;
|
|
}
|
|
|
|
CaptureDeviceType captureDeviceType = CaptureDeviceType::Camera;
|
|
switch (capEngine) {
|
|
case ScreenEngine:
|
|
captureDeviceType = CaptureDeviceType::Screen;
|
|
break;
|
|
case BrowserEngine:
|
|
captureDeviceType = CaptureDeviceType::Browser;
|
|
break;
|
|
case WinEngine:
|
|
captureDeviceType = CaptureDeviceType::Window;
|
|
break;
|
|
case CameraEngine:
|
|
captureDeviceType = CaptureDeviceType::Camera;
|
|
break;
|
|
default:
|
|
LOG("Invalid webrtc Video engine");
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<VideoEngine> engine =
|
|
VideoEngine::Create(captureDeviceType, mVideoCaptureFactory);
|
|
if (!engine) {
|
|
LOG("VideoEngine::Create failed");
|
|
return nullptr;
|
|
}
|
|
|
|
return mEngines->ElementAt(capEngine) = std::move(engine);
|
|
}
|
|
|
|
// Dispatch the runnable to do the camera operation on the
|
|
// specific Cameras thread, preventing us from blocking, and
|
|
// chain a runnable to send back the result on the IPC thread.
|
|
// It would be nice to get rid of the code duplication here,
|
|
// perhaps via Promises.
|
|
ipc::IPCResult CamerasParent::RecvNumberOfCaptureDevices(
|
|
const CaptureEngine& aCapEngine) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
LOG("CaptureEngine=%d", aCapEngine);
|
|
|
|
using Promise = MozPromise<int, bool, true>;
|
|
InvokeAsync(mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), aCapEngine] {
|
|
int num = -1;
|
|
if (auto devInfo = GetDeviceInfo(aCapEngine)) {
|
|
num = static_cast<int>(devInfo->NumberOfDevices());
|
|
}
|
|
|
|
return Promise::CreateAndResolve(
|
|
num, "CamerasParent::RecvNumberOfCaptureDevices");
|
|
})
|
|
->Then(
|
|
mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this)](Promise::ResolveOrRejectValue&& aValue) {
|
|
int nrDevices = aValue.ResolveValue();
|
|
|
|
if (mDestroyed) {
|
|
LOG("RecvNumberOfCaptureDevices failure: child not alive");
|
|
return;
|
|
}
|
|
|
|
if (nrDevices < 0) {
|
|
LOG("RecvNumberOfCaptureDevices couldn't find devices");
|
|
Unused << SendReplyFailure();
|
|
return;
|
|
}
|
|
|
|
LOG("RecvNumberOfCaptureDevices: %d", nrDevices);
|
|
Unused << SendReplyNumberOfCaptureDevices(nrDevices);
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvEnsureInitialized(
|
|
const CaptureEngine& aCapEngine) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
|
|
using Promise = MozPromise<bool, bool, true>;
|
|
InvokeAsync(mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), aCapEngine] {
|
|
return Promise::CreateAndResolve(
|
|
EnsureInitialized(aCapEngine),
|
|
"CamerasParent::RecvEnsureInitialized");
|
|
})
|
|
->Then(
|
|
mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this)](Promise::ResolveOrRejectValue&& aValue) {
|
|
bool result = aValue.ResolveValue();
|
|
|
|
if (mDestroyed) {
|
|
LOG("RecvEnsureInitialized: child not alive");
|
|
return;
|
|
}
|
|
|
|
if (!result) {
|
|
LOG("RecvEnsureInitialized failed");
|
|
Unused << SendReplyFailure();
|
|
return;
|
|
}
|
|
|
|
LOG("RecvEnsureInitialized succeeded");
|
|
Unused << SendReplySuccess();
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvNumberOfCapabilities(
|
|
const CaptureEngine& aCapEngine, const nsACString& aUniqueId) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
LOG("Getting caps for %s", PromiseFlatCString(aUniqueId).get());
|
|
|
|
using Promise = MozPromise<int, bool, true>;
|
|
InvokeAsync(
|
|
mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), id = nsCString(aUniqueId), aCapEngine]() {
|
|
int num = -1;
|
|
if (auto devInfo = GetDeviceInfo(aCapEngine)) {
|
|
num = devInfo->NumberOfCapabilities(id.get());
|
|
}
|
|
return Promise::CreateAndResolve(
|
|
num, "CamerasParent::RecvNumberOfCapabilities");
|
|
})
|
|
->Then(
|
|
mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this)](Promise::ResolveOrRejectValue&& aValue) {
|
|
int aNrCapabilities = aValue.ResolveValue();
|
|
|
|
if (mDestroyed) {
|
|
LOG("RecvNumberOfCapabilities: child not alive");
|
|
return;
|
|
}
|
|
|
|
if (aNrCapabilities < 0) {
|
|
LOG("RecvNumberOfCapabilities couldn't find capabilities");
|
|
Unused << SendReplyFailure();
|
|
return;
|
|
}
|
|
|
|
LOG("RecvNumberOfCapabilities: %d", aNrCapabilities);
|
|
Unused << SendReplyNumberOfCapabilities(aNrCapabilities);
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvGetCaptureCapability(
|
|
const CaptureEngine& aCapEngine, const nsACString& aUniqueId,
|
|
const int& aIndex) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
LOG("RecvGetCaptureCapability: %s %d", PromiseFlatCString(aUniqueId).get(),
|
|
aIndex);
|
|
|
|
using Promise = MozPromise<webrtc::VideoCaptureCapability, int, true>;
|
|
InvokeAsync(mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), id = nsCString(aUniqueId), aCapEngine,
|
|
aIndex] {
|
|
webrtc::VideoCaptureCapability webrtcCaps;
|
|
int error = -1;
|
|
if (auto devInfo = GetDeviceInfo(aCapEngine)) {
|
|
error = devInfo->GetCapability(id.get(), aIndex, webrtcCaps);
|
|
}
|
|
|
|
if (!error && aCapEngine == CameraEngine) {
|
|
auto iter = mAllCandidateCapabilities.find(id);
|
|
if (iter == mAllCandidateCapabilities.end()) {
|
|
std::map<uint32_t, webrtc::VideoCaptureCapability>
|
|
candidateCapabilities;
|
|
candidateCapabilities.emplace(aIndex, webrtcCaps);
|
|
mAllCandidateCapabilities.emplace(id,
|
|
candidateCapabilities);
|
|
} else {
|
|
(iter->second).emplace(aIndex, webrtcCaps);
|
|
}
|
|
}
|
|
if (error) {
|
|
return Promise::CreateAndReject(
|
|
error, "CamerasParent::RecvGetCaptureCapability");
|
|
}
|
|
return Promise::CreateAndResolve(
|
|
webrtcCaps, "CamerasParent::RecvGetCaptureCapability");
|
|
})
|
|
->Then(
|
|
mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this)](Promise::ResolveOrRejectValue&& aValue) {
|
|
if (mDestroyed) {
|
|
LOG("RecvGetCaptureCapability: child not alive");
|
|
return;
|
|
}
|
|
|
|
if (aValue.IsReject()) {
|
|
LOG("RecvGetCaptureCapability: reply failure");
|
|
Unused << SendReplyFailure();
|
|
return;
|
|
}
|
|
|
|
auto webrtcCaps = aValue.ResolveValue();
|
|
VideoCaptureCapability capCap(
|
|
webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS,
|
|
static_cast<int>(webrtcCaps.videoType), webrtcCaps.interlaced);
|
|
LOG("Capability: %u %u %u %d %d", webrtcCaps.width,
|
|
webrtcCaps.height, webrtcCaps.maxFPS,
|
|
static_cast<int>(webrtcCaps.videoType), webrtcCaps.interlaced);
|
|
Unused << SendReplyGetCaptureCapability(capCap);
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvGetCaptureDevice(
|
|
const CaptureEngine& aCapEngine, const int& aDeviceIndex) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
|
|
using Data = std::tuple<nsCString, nsCString, pid_t, bool, int>;
|
|
using Promise = MozPromise<Data, bool, true>;
|
|
InvokeAsync(mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), aCapEngine, aDeviceIndex] {
|
|
char deviceName[MediaEngineSource::kMaxDeviceNameLength];
|
|
char deviceUniqueId[MediaEngineSource::kMaxUniqueIdLength];
|
|
nsCString name;
|
|
nsCString uniqueId;
|
|
pid_t devicePid = 0;
|
|
bool placeholder = false;
|
|
int error = -1;
|
|
if (auto devInfo = GetDeviceInfo(aCapEngine)) {
|
|
error = devInfo->GetDeviceName(
|
|
aDeviceIndex, deviceName, sizeof(deviceName),
|
|
deviceUniqueId, sizeof(deviceUniqueId), nullptr, 0,
|
|
&devicePid, &placeholder);
|
|
}
|
|
|
|
if (error == 0) {
|
|
name.Assign(deviceName);
|
|
uniqueId.Assign(deviceUniqueId);
|
|
}
|
|
|
|
return Promise::CreateAndResolve(
|
|
std::make_tuple(std::move(name), std::move(uniqueId),
|
|
devicePid, placeholder, error),
|
|
"CamerasParent::RecvGetCaptureDevice");
|
|
})
|
|
->Then(
|
|
mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this)](Promise::ResolveOrRejectValue&& aValue) {
|
|
const auto& [name, uniqueId, devicePid, placeholder, error] =
|
|
aValue.ResolveValue();
|
|
if (mDestroyed) {
|
|
return;
|
|
}
|
|
if (error != 0) {
|
|
LOG("GetCaptureDevice failed: %d", error);
|
|
Unused << SendReplyFailure();
|
|
return;
|
|
}
|
|
bool scary = (devicePid == getpid());
|
|
|
|
LOG("Returning %s name %s id (pid = %d)%s", name.get(),
|
|
uniqueId.get(), devicePid, (scary ? " (scary)" : ""));
|
|
Unused << SendReplyGetCaptureDevice(name, uniqueId, scary,
|
|
placeholder);
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
// Find out whether the given window with id has permission to use the
|
|
// camera. If the permission is not persistent, we'll make it a one-shot by
|
|
// removing the (session) permission.
|
|
static bool HasCameraPermission(const uint64_t& aWindowId) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<dom::WindowGlobalParent> window =
|
|
dom::WindowGlobalParent::GetByInnerWindowId(aWindowId);
|
|
if (!window) {
|
|
// Could not find window by id
|
|
return false;
|
|
}
|
|
|
|
// when we delegate permission from first party, we should use the top level
|
|
// window
|
|
RefPtr<dom::BrowsingContext> topBC = window->BrowsingContext()->Top();
|
|
window = topBC->Canonical()->GetCurrentWindowGlobal();
|
|
|
|
// Return false if the window is not the currently-active window for its
|
|
// BrowsingContext.
|
|
if (!window || !window->IsCurrentGlobal()) {
|
|
return false;
|
|
}
|
|
|
|
nsIPrincipal* principal = window->DocumentPrincipal();
|
|
if (principal->GetIsNullPrincipal()) {
|
|
return false;
|
|
}
|
|
|
|
if (principal->IsSystemPrincipal()) {
|
|
return true;
|
|
}
|
|
|
|
MOZ_ASSERT(principal->GetIsContentPrincipal());
|
|
|
|
nsresult rv;
|
|
// Name used with nsIPermissionManager
|
|
static const nsLiteralCString cameraPermission = "MediaManagerVideo"_ns;
|
|
nsCOMPtr<nsIPermissionManager> mgr =
|
|
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t video = nsIPermissionManager::UNKNOWN_ACTION;
|
|
rv = mgr->TestExactPermissionFromPrincipal(principal, cameraPermission,
|
|
&video);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
bool allowed = (video == nsIPermissionManager::ALLOW_ACTION);
|
|
|
|
// Session permissions are removed after one use.
|
|
if (allowed) {
|
|
mgr->RemoveFromPrincipal(principal, cameraPermission);
|
|
}
|
|
|
|
return allowed;
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvAllocateCapture(
|
|
const CaptureEngine& aCapEngine, const nsACString& aUniqueIdUTF8,
|
|
const uint64_t& aWindowID) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG("CamerasParent(%p)::%s: Verifying permissions", this, __func__);
|
|
|
|
using Promise1 = MozPromise<bool, bool, true>;
|
|
using Data = std::tuple<int, int>;
|
|
using Promise2 = MozPromise<Data, bool, true>;
|
|
InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
|
|
[aWindowID] {
|
|
// Verify whether the claimed origin has received permission
|
|
// to use the camera, either persistently or this session (one
|
|
// shot).
|
|
bool allowed = HasCameraPermission(aWindowID);
|
|
if (!allowed) {
|
|
// Developer preference for turning off permission check.
|
|
if (Preferences::GetBool(
|
|
"media.navigator.permission.disabled", false)) {
|
|
allowed = true;
|
|
LOG("No permission but checks are disabled");
|
|
} else {
|
|
LOG("No camera permission for this origin");
|
|
}
|
|
}
|
|
return Promise1::CreateAndResolve(
|
|
allowed, "CamerasParent::RecvAllocateCapture");
|
|
})
|
|
->Then(mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), aCapEngine,
|
|
unique_id = nsCString(aUniqueIdUTF8)](
|
|
Promise1::ResolveOrRejectValue&& aValue) {
|
|
bool allowed = aValue.ResolveValue();
|
|
int captureId = -1;
|
|
int error = -1;
|
|
if (allowed && EnsureInitialized(aCapEngine)) {
|
|
VideoEngine* engine = mEngines->ElementAt(aCapEngine);
|
|
captureId = engine->CreateVideoCapture(unique_id.get());
|
|
engine->WithEntry(captureId,
|
|
[&error](VideoEngine::CaptureEntry& cap) {
|
|
if (cap.VideoCapture()) {
|
|
error = 0;
|
|
}
|
|
});
|
|
}
|
|
return Promise2::CreateAndResolve(
|
|
std::make_tuple(captureId, error),
|
|
"CamerasParent::RecvAllocateCapture");
|
|
})
|
|
->Then(
|
|
mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this)](Promise2::ResolveOrRejectValue&& aValue) {
|
|
const auto [captureId, error] = aValue.ResolveValue();
|
|
if (mDestroyed) {
|
|
LOG("RecvAllocateCapture: child not alive");
|
|
return;
|
|
}
|
|
|
|
if (error != 0) {
|
|
Unused << SendReplyFailure();
|
|
LOG("RecvAllocateCapture: WithEntry error");
|
|
return;
|
|
}
|
|
|
|
LOG("Allocated device nr %d", captureId);
|
|
Unused << SendReplyAllocateCapture(captureId);
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
int CamerasParent::ReleaseCapture(const CaptureEngine& aCapEngine,
|
|
int aCaptureId) {
|
|
MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread());
|
|
int error = -1;
|
|
if (auto* engine = EnsureInitialized(aCapEngine)) {
|
|
error = engine->ReleaseVideoCapture(aCaptureId);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvReleaseCapture(
|
|
const CaptureEngine& aCapEngine, const int& aCaptureId) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
LOG("RecvReleaseCamera device nr %d", aCaptureId);
|
|
|
|
using Promise = MozPromise<int, bool, true>;
|
|
InvokeAsync(mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), aCapEngine, aCaptureId] {
|
|
return Promise::CreateAndResolve(
|
|
ReleaseCapture(aCapEngine, aCaptureId),
|
|
"CamerasParent::RecvReleaseCapture");
|
|
})
|
|
->Then(mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this),
|
|
aCaptureId](Promise::ResolveOrRejectValue&& aValue) {
|
|
int error = aValue.ResolveValue();
|
|
|
|
if (mDestroyed) {
|
|
LOG("RecvReleaseCapture: child not alive");
|
|
return;
|
|
}
|
|
|
|
if (error != 0) {
|
|
Unused << SendReplyFailure();
|
|
LOG("RecvReleaseCapture: Failed to free device nr %d",
|
|
aCaptureId);
|
|
return;
|
|
}
|
|
|
|
Unused << SendReplySuccess();
|
|
LOG("Freed device nr %d", aCaptureId);
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvStartCapture(
|
|
const CaptureEngine& aCapEngine, const int& aCaptureId,
|
|
const VideoCaptureCapability& aIpcCaps) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
|
|
using Promise = MozPromise<int, bool, true>;
|
|
InvokeAsync(
|
|
mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), aCapEngine, aCaptureId, aIpcCaps] {
|
|
LOG_FUNCTION();
|
|
int error = -1;
|
|
|
|
if (!EnsureInitialized(aCapEngine)) {
|
|
return Promise::CreateAndResolve(error,
|
|
"CamerasParent::RecvStartCapture");
|
|
}
|
|
|
|
mEngines->ElementAt(aCapEngine)
|
|
->WithEntry(aCaptureId, [&](VideoEngine::CaptureEntry& cap) {
|
|
webrtc::VideoCaptureCapability capability;
|
|
capability.width = aIpcCaps.width();
|
|
capability.height = aIpcCaps.height();
|
|
capability.maxFPS = aIpcCaps.maxFPS();
|
|
capability.videoType =
|
|
static_cast<webrtc::VideoType>(aIpcCaps.videoType());
|
|
capability.interlaced = aIpcCaps.interlaced();
|
|
|
|
if (sDeviceUniqueIDs.find(aCaptureId) == sDeviceUniqueIDs.end()) {
|
|
sDeviceUniqueIDs.emplace(
|
|
aCaptureId, cap.VideoCapture()->CurrentDeviceName());
|
|
sAllRequestedCapabilities.emplace(aCaptureId, capability);
|
|
} else {
|
|
// Starting capture for an id that already exists. Update its
|
|
// requested capability.
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
strcmp(sDeviceUniqueIDs[aCaptureId],
|
|
cap.VideoCapture()->CurrentDeviceName()) == 0);
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
sAllRequestedCapabilities.find(aCaptureId) !=
|
|
sAllRequestedCapabilities.end());
|
|
sAllRequestedCapabilities[aCaptureId] = capability;
|
|
}
|
|
|
|
if (aCapEngine == CameraEngine) {
|
|
for (const auto& it : sDeviceUniqueIDs) {
|
|
if (strcmp(it.second,
|
|
cap.VideoCapture()->CurrentDeviceName()) == 0) {
|
|
capability.width =
|
|
std::max(capability.width,
|
|
sAllRequestedCapabilities[it.first].width);
|
|
capability.height =
|
|
std::max(capability.height,
|
|
sAllRequestedCapabilities[it.first].height);
|
|
capability.maxFPS =
|
|
std::max(capability.maxFPS,
|
|
sAllRequestedCapabilities[it.first].maxFPS);
|
|
}
|
|
}
|
|
|
|
auto candidateCapabilities = mAllCandidateCapabilities.find(
|
|
nsCString(cap.VideoCapture()->CurrentDeviceName()));
|
|
if ((candidateCapabilities !=
|
|
mAllCandidateCapabilities.end()) &&
|
|
(!candidateCapabilities->second.empty())) {
|
|
int32_t minIdx = -1;
|
|
uint64_t minDistance = UINT64_MAX;
|
|
|
|
for (auto& candidateCapability :
|
|
candidateCapabilities->second) {
|
|
if (candidateCapability.second.videoType !=
|
|
capability.videoType) {
|
|
continue;
|
|
}
|
|
// The first priority is finding a suitable resolution.
|
|
// So here we raise the weight of width and height
|
|
uint64_t distance = uint64_t(ResolutionFeasibilityDistance(
|
|
candidateCapability.second.width,
|
|
capability.width)) +
|
|
uint64_t(ResolutionFeasibilityDistance(
|
|
candidateCapability.second.height,
|
|
capability.height)) +
|
|
uint64_t(FeasibilityDistance(
|
|
candidateCapability.second.maxFPS,
|
|
capability.maxFPS));
|
|
if (distance < minDistance) {
|
|
minIdx = static_cast<int32_t>(candidateCapability.first);
|
|
minDistance = distance;
|
|
}
|
|
}
|
|
MOZ_ASSERT(minIdx != -1);
|
|
capability = candidateCapabilities->second[minIdx];
|
|
}
|
|
} else if (aCapEngine == ScreenEngine ||
|
|
aCapEngine == BrowserEngine ||
|
|
aCapEngine == WinEngine) {
|
|
for (const auto& it : sDeviceUniqueIDs) {
|
|
if (strcmp(it.second,
|
|
cap.VideoCapture()->CurrentDeviceName()) == 0) {
|
|
capability.maxFPS =
|
|
std::max(capability.maxFPS,
|
|
sAllRequestedCapabilities[it.first].maxFPS);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool cbhExists = false;
|
|
CallbackHelper** cbh = nullptr;
|
|
for (auto* cb : mCallbacks) {
|
|
if (cb->mCapEngine == aCapEngine &&
|
|
cb->mStreamId == (uint32_t)aCaptureId) {
|
|
cbhExists = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!cbhExists) {
|
|
cbh = mCallbacks.AppendElement(new CallbackHelper(
|
|
static_cast<CaptureEngine>(aCapEngine), aCaptureId, this));
|
|
cap.VideoCapture()->SetTrackingId(
|
|
(*cbh)->mTrackingId.mUniqueInProcId);
|
|
}
|
|
|
|
error = cap.VideoCapture()->StartCapture(capability);
|
|
|
|
if (!error) {
|
|
if (cbh) {
|
|
cap.VideoCapture()->RegisterCaptureDataCallback(
|
|
static_cast<rtc::VideoSinkInterface<webrtc::VideoFrame>*>(
|
|
*cbh));
|
|
if (auto* event = cap.CaptureEndedEvent();
|
|
event && !(*cbh)->mConnectedToCaptureEnded) {
|
|
(*cbh)->mCaptureEndedListener =
|
|
event->Connect(mVideoCaptureThread, (*cbh),
|
|
&CallbackHelper::OnCaptureEnded);
|
|
(*cbh)->mConnectedToCaptureEnded = true;
|
|
}
|
|
}
|
|
} else {
|
|
sDeviceUniqueIDs.erase(aCaptureId);
|
|
sAllRequestedCapabilities.erase(aCaptureId);
|
|
}
|
|
});
|
|
|
|
return Promise::CreateAndResolve(error,
|
|
"CamerasParent::RecvStartCapture");
|
|
})
|
|
->Then(
|
|
mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this)](Promise::ResolveOrRejectValue&& aValue) {
|
|
int error = aValue.ResolveValue();
|
|
|
|
if (mDestroyed) {
|
|
LOG("RecvStartCapture failure: child is not alive");
|
|
return;
|
|
}
|
|
|
|
if (error != 0) {
|
|
LOG("RecvStartCapture failure: StartCapture failed");
|
|
Unused << SendReplyFailure();
|
|
return;
|
|
}
|
|
|
|
Unused << SendReplySuccess();
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvFocusOnSelectedSource(
|
|
const CaptureEngine& aCapEngine, const int& aCaptureId) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
|
|
using Promise = MozPromise<bool, bool, true>;
|
|
InvokeAsync(mVideoCaptureThread, __func__,
|
|
[this, self = RefPtr(this), aCapEngine, aCaptureId] {
|
|
bool result = false;
|
|
if (auto* engine = EnsureInitialized(aCapEngine)) {
|
|
engine->WithEntry(
|
|
aCaptureId, [&](VideoEngine::CaptureEntry& cap) {
|
|
if (cap.VideoCapture()) {
|
|
result = cap.VideoCapture()->FocusOnSelectedSource();
|
|
}
|
|
});
|
|
}
|
|
return Promise::CreateAndResolve(
|
|
result, "CamerasParent::RecvFocusOnSelectedSource");
|
|
})
|
|
->Then(
|
|
mPBackgroundEventTarget, __func__,
|
|
[this, self = RefPtr(this)](Promise::ResolveOrRejectValue&& aValue) {
|
|
bool result = aValue.ResolveValue();
|
|
if (mDestroyed) {
|
|
LOG("RecvFocusOnSelectedSource failure: child is not alive");
|
|
return;
|
|
}
|
|
|
|
if (!result) {
|
|
Unused << SendReplyFailure();
|
|
LOG("RecvFocusOnSelectedSource failure.");
|
|
return;
|
|
}
|
|
|
|
Unused << SendReplySuccess();
|
|
});
|
|
return IPC_OK();
|
|
}
|
|
|
|
void CamerasParent::StopCapture(const CaptureEngine& aCapEngine,
|
|
int aCaptureId) {
|
|
MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread());
|
|
if (auto* engine = EnsureInitialized(aCapEngine)) {
|
|
// we're removing elements, iterate backwards
|
|
for (size_t i = mCallbacks.Length(); i > 0; i--) {
|
|
if (mCallbacks[i - 1]->mCapEngine == aCapEngine &&
|
|
mCallbacks[i - 1]->mStreamId == (uint32_t)aCaptureId) {
|
|
CallbackHelper* cbh = mCallbacks[i - 1];
|
|
engine->WithEntry(aCaptureId, [cbh, &aCaptureId](
|
|
VideoEngine::CaptureEntry& cap) {
|
|
if (cap.VideoCapture()) {
|
|
cap.VideoCapture()->DeRegisterCaptureDataCallback(
|
|
static_cast<rtc::VideoSinkInterface<webrtc::VideoFrame>*>(cbh));
|
|
cap.VideoCapture()->StopCaptureIfAllClientsClose();
|
|
|
|
sDeviceUniqueIDs.erase(aCaptureId);
|
|
sAllRequestedCapabilities.erase(aCaptureId);
|
|
}
|
|
});
|
|
cbh->mCaptureEndedListener.DisconnectIfExists();
|
|
delete mCallbacks[i - 1];
|
|
mCallbacks.RemoveElementAt(i - 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ipc::IPCResult CamerasParent::RecvStopCapture(const CaptureEngine& aCapEngine,
|
|
const int& aCaptureId) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
MOZ_ASSERT(!mDestroyed);
|
|
|
|
LOG_FUNCTION();
|
|
|
|
nsresult rv = mVideoCaptureThread->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [this, self = RefPtr(this), aCapEngine, aCaptureId] {
|
|
StopCapture(aCapEngine, aCaptureId);
|
|
}));
|
|
|
|
if (mDestroyed) {
|
|
if (NS_FAILED(rv)) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
} else {
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (!SendReplySuccess()) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
} else {
|
|
if (!SendReplyFailure()) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
}
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
void CamerasParent::ActorDestroy(ActorDestroyReason aWhy) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
LOG_FUNCTION();
|
|
|
|
// Release shared memory now, it's our last chance
|
|
mShmemPool.Cleanup(this);
|
|
// We don't want to receive callbacks or anything if we can't
|
|
// forward them anymore anyway.
|
|
mDestroyed = true;
|
|
// We don't need to listen for shutdown any longer. Disconnect the request.
|
|
// This breaks the reference cycle between CamerasParent and the shutdown
|
|
// promise's Then handler.
|
|
mShutdownRequest.DisconnectIfExists();
|
|
|
|
if (mVideoCaptureThread) {
|
|
// Shut down the WebRTC stack, on the video capture thread.
|
|
MOZ_ALWAYS_SUCCEEDS(mVideoCaptureThread->Dispatch(
|
|
NewRunnableMethod(__func__, this, &CamerasParent::CloseEngines)));
|
|
}
|
|
}
|
|
|
|
void CamerasParent::OnShutdown() {
|
|
ipc::AssertIsOnBackgroundThread();
|
|
LOG("CamerasParent(%p) ShutdownEvent", this);
|
|
mShutdownRequest.Complete();
|
|
(void)Send__delete__(this);
|
|
}
|
|
|
|
CamerasParent::CamerasParent()
|
|
: mShutdownBlocker(ShutdownBlockingTicket::Create(
|
|
u"CamerasParent"_ns, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
|
|
__LINE__)),
|
|
mVideoCaptureThread(mShutdownBlocker
|
|
? MakeAndAddRefVideoCaptureThreadAndSingletons()
|
|
: nullptr),
|
|
mEngines(sEngines),
|
|
mVideoCaptureFactory(EnsureVideoCaptureFactory()),
|
|
mShmemPool(CaptureEngine::MaxEngine),
|
|
mPBackgroundEventTarget(GetCurrentSerialEventTarget()),
|
|
mDestroyed(false) {
|
|
MOZ_ASSERT(mPBackgroundEventTarget != nullptr,
|
|
"GetCurrentThreadEventTarget failed");
|
|
LOG("CamerasParent: %p", this);
|
|
|
|
// Don't dispatch from the constructor a runnable that may toggle the
|
|
// reference count, because the IPC thread does not get a reference until
|
|
// after the constructor returns.
|
|
}
|
|
|
|
/* static */
|
|
auto CamerasParent::RequestCameraAccess(bool aAllowPermissionRequest)
|
|
-> RefPtr<CameraAccessRequestPromise> {
|
|
ipc::AssertIsOnBackgroundThread();
|
|
|
|
// Special case for PipeWire where we at this point just need to make sure
|
|
// we have information about camera availabilty through the camera portal
|
|
if (!aAllowPermissionRequest) {
|
|
return EnsureVideoCaptureFactory()->UpdateCameraAvailability()->Then(
|
|
GetCurrentSerialEventTarget(),
|
|
"CamerasParent::RequestCameraAccess update camera availability",
|
|
[](const VideoCaptureFactory::UpdateCameraAvailabilityPromise::
|
|
ResolveOrRejectValue& aValue) {
|
|
LOG("Camera availability updated to %s",
|
|
aValue.IsResolve()
|
|
? aValue.ResolveValue() ==
|
|
VideoCaptureFactory::CameraAvailability::Available
|
|
? "available"
|
|
: "not available"
|
|
: "still unknown");
|
|
return CameraAccessRequestPromise::CreateAndResolve(
|
|
CamerasAccessStatus::RequestRequired,
|
|
"CamerasParent::RequestCameraAccess camera availability updated");
|
|
});
|
|
}
|
|
|
|
static StaticRefPtr<CameraAccessRequestPromise> sCameraAccessRequestPromise;
|
|
if (!sCameraAccessRequestPromise) {
|
|
sCameraAccessRequestPromise = RefPtr<CameraAccessRequestPromise>(
|
|
EnsureVideoCaptureFactory()->InitCameraBackend()->Then(
|
|
GetCurrentSerialEventTarget(),
|
|
"CamerasParent::RequestCameraAccess camera backend init handler",
|
|
[](nsresult aRv) mutable {
|
|
MOZ_ASSERT(NS_SUCCEEDED(aRv));
|
|
if (sVideoCaptureThread) {
|
|
MOZ_ASSERT(sEngines);
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
sVideoCaptureThread->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [engines = RefPtr(sEngines.get())] {
|
|
if (VideoEngine* engine =
|
|
engines->ElementAt(CameraEngine)) {
|
|
engine->ClearVideoCaptureDeviceInfo();
|
|
}
|
|
})));
|
|
}
|
|
return CameraAccessRequestPromise::CreateAndResolve(
|
|
CamerasAccessStatus::Granted,
|
|
"CamerasParent::RequestCameraAccess camera backend init "
|
|
"resolve");
|
|
},
|
|
[](nsresult aRv) mutable {
|
|
MOZ_ASSERT(NS_FAILED(aRv));
|
|
return CameraAccessRequestPromise::CreateAndResolve(
|
|
aRv == NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR
|
|
? CamerasAccessStatus::Rejected
|
|
: CamerasAccessStatus::Error,
|
|
"CamerasParent::RequestCameraAccess camera backend init "
|
|
"reject");
|
|
}));
|
|
static nsresult clearingRv = NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
__func__, [] { ClearOnShutdown(&sCameraAccessRequestPromise); }));
|
|
Unused << clearingRv;
|
|
}
|
|
|
|
// If camera acess is granted, all is jolly. But we need to handle rejection.
|
|
return sCameraAccessRequestPromise->Then(
|
|
GetCurrentSerialEventTarget(),
|
|
"CamerasParent::CameraAccessRequestPromise rejection handler",
|
|
[](CamerasAccessStatus aStatus) {
|
|
return CameraAccessRequestPromise::CreateAndResolve(
|
|
aStatus, "CamerasParent::RequestCameraAccess resolve");
|
|
},
|
|
[promise = RefPtr(sCameraAccessRequestPromise.get()),
|
|
aAllowPermissionRequest](void_t aRv) {
|
|
if (promise == sCameraAccessRequestPromise) {
|
|
sCameraAccessRequestPromise = nullptr;
|
|
return CameraAccessRequestPromise::CreateAndResolve(
|
|
CamerasAccessStatus::Error,
|
|
"CamerasParent::RequestCameraAccess reject");
|
|
}
|
|
return CamerasParent::RequestCameraAccess(aAllowPermissionRequest);
|
|
});
|
|
}
|
|
|
|
// RecvPCamerasConstructor() is used because IPC messages, for
|
|
// Send__delete__(), cannot be sent from AllocPCamerasParent().
|
|
ipc::IPCResult CamerasParent::RecvPCamerasConstructor() {
|
|
MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread());
|
|
|
|
// AsyncShutdown barriers are available only for ShutdownPhases as late as
|
|
// XPCOMWillShutdown. The IPC background thread shuts down during
|
|
// XPCOMShutdownThreads, so actors may be created when AsyncShutdown barriers
|
|
// are no longer available. Should shutdown be past XPCOMWillShutdown we end
|
|
// up with a null mShutdownBlocker.
|
|
|
|
if (!mShutdownBlocker) {
|
|
LOG("CamerasParent(%p) Got no ShutdownBlockingTicket. We are already in "
|
|
"shutdown. Deleting.",
|
|
this);
|
|
return Send__delete__(this) ? IPC_OK() : IPC_FAIL(this, "Failed to send");
|
|
}
|
|
|
|
if (!mVideoCaptureThread) {
|
|
return Send__delete__(this) ? IPC_OK() : IPC_FAIL(this, "Failed to send");
|
|
}
|
|
|
|
NS_DispatchToMainThread(
|
|
NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)] {
|
|
mLogHandle = new nsMainThreadPtrHolder<WebrtcLogSinkHandle>(
|
|
"CamerasParent::mLogHandle", EnsureWebrtcLogging());
|
|
}));
|
|
|
|
MOZ_ASSERT(mEngines);
|
|
|
|
mShutdownBlocker->ShutdownPromise()
|
|
->Then(mPBackgroundEventTarget, "CamerasParent OnShutdown",
|
|
[this, self = RefPtr(this)](
|
|
const ShutdownPromise::ResolveOrRejectValue& aValue) {
|
|
MOZ_ASSERT(aValue.IsResolve(),
|
|
"ShutdownBlockingTicket must have been destroyed "
|
|
"without us disconnecting the shutdown request");
|
|
OnShutdown();
|
|
})
|
|
->Track(mShutdownRequest);
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
CamerasParent::~CamerasParent() {
|
|
ipc::AssertIsOnBackgroundThread();
|
|
LOG_FUNCTION();
|
|
|
|
if (!mVideoCaptureThread) {
|
|
// No video engines or video capture thread to shutdown here.
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mShutdownBlocker,
|
|
"A ShutdownBlocker is a prerequisite for mVideoCaptureThread");
|
|
|
|
ReleaseVideoCaptureThreadAndSingletons();
|
|
}
|
|
|
|
already_AddRefed<CamerasParent> CamerasParent::Create() {
|
|
ipc::AssertIsOnBackgroundThread();
|
|
return MakeAndAddRef<CamerasParent>();
|
|
}
|
|
|
|
} // namespace camera
|
|
} // namespace mozilla
|