/* -*- 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 "MediaEngineSource.h" #include "MediaUtils.h" #include "VideoFrameUtils.h" #include "mozilla/Assertions.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/Preferences.h" #include "mozilla/StaticPrefs_permissions.h" #include "nsIPermissionManager.h" #include "nsThreadUtils.h" #include "nsNetUtil.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #if defined(_WIN32) # include # 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_VERBOSE(...) \ MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Verbose, (__VA_ARGS__)) #define LOG_ENABLED() MOZ_LOG_TEST(gCamerasParentLog, mozilla::LogLevel::Debug) namespace mozilla { using media::GetShutdownBarrier; using media::NewRunnableFrom; namespace camera { std::map sDeviceUniqueIDs; std::map 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); } StaticRefPtr CamerasParent::sEngines[CaptureEngine::MaxEngine]; int32_t CamerasParent::sNumOfOpenCamerasParentEngines = 0; int32_t CamerasParent::sNumOfCamerasParents = 0; base::Thread* CamerasParent::sVideoCaptureThread = nullptr; Monitor* CamerasParent::sThreadMonitor = nullptr; StaticMutex CamerasParent::sMutex; // 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. // InputObserver is owned by CamerasParent, and it has a ref to CamerasParent void InputObserver::OnDeviceChange() { LOG("%s", __PRETTY_FUNCTION__); MOZ_ASSERT(mParent); RefPtr self(this); RefPtr ipc_runnable = NewRunnableFrom([self]() { if (self->mParent->IsShuttingDown()) { LOG("OnDeviceChanged failure: parent shutting down."); return NS_ERROR_FAILURE; } Unused << self->mParent->SendDeviceChange(); return NS_OK; }); nsIEventTarget* target = mParent->GetBackgroundEventTarget(); MOZ_ASSERT(target != nullptr); target->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); }; class DeliverFrameRunnable : public mozilla::Runnable { public: DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine, uint32_t aStreamId, const webrtc::VideoFrame& aFrame, const VideoFrameProperties& aProperties) : Runnable("camera::DeliverFrameRunnable"), mParent(aParent), mCapEngine(aEngine), mStreamId(aStreamId), 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. mAlternateBuffer.reset(new unsigned char[aProperties.bufferSize()]); VideoFrameUtils::CopyVideoFrameBuffers(mAlternateBuffer.get(), aProperties.bufferSize(), aFrame); } DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine, uint32_t aStreamId, ShmemBuffer aBuffer, VideoFrameProperties& aProperties) : Runnable("camera::DeliverFrameRunnable"), mParent(aParent), mCapEngine(aEngine), mStreamId(aStreamId), mBuffer(std::move(aBuffer)), mProperties(aProperties), mResult(0){}; NS_IMETHOD Run() override { if (mParent->IsShuttingDown()) { // Communication channel is being torn down mResult = 0; return NS_OK; } if (!mParent->DeliverFrameOverIPC(mCapEngine, mStreamId, std::move(mBuffer), mAlternateBuffer.get(), mProperties)) { mResult = -1; } else { mResult = 0; } return NS_OK; } int GetResult() { return mResult; } private: RefPtr mParent; CaptureEngine mCapEngine; uint32_t mStreamId; ShmemBuffer mBuffer; UniquePtr mAlternateBuffer; VideoFrameProperties mProperties; int mResult; }; NS_IMPL_ISUPPORTS(CamerasParent, nsIAsyncShutdownBlocker) nsresult CamerasParent::DispatchToVideoCaptureThread(RefPtr event) { // Don't try to dispatch if we're already on the right thread. // There's a potential deadlock because the sThreadMonitor is likely // to be taken already. MonitorAutoLock lock(*sThreadMonitor); MOZ_ASSERT(!sVideoCaptureThread || sVideoCaptureThread->thread_id() != PlatformThread::CurrentId()); while (mChildIsAlive && mWebRTCAlive && (!sVideoCaptureThread || !sVideoCaptureThread->IsRunning())) { sThreadMonitor->Wait(); } if (!sVideoCaptureThread || !sVideoCaptureThread->IsRunning()) { LOG("Can't dispatch to video capture thread: thread not present or not " "running"); return NS_ERROR_FAILURE; } sVideoCaptureThread->message_loop()->PostTask(event.forget()); return NS_OK; } void CamerasParent::StopVideoCapture() { LOG("%s", __PRETTY_FUNCTION__); // We are called from the main thread (xpcom-shutdown) or // from PBackground (when the Actor shuts down). // Shut down the WebRTC stack (on the capture thread) RefPtr self(this); DebugOnly rv = DispatchToVideoCaptureThread(NewRunnableFrom([self]() { MonitorAutoLock lock(*(self->sThreadMonitor)); self->CloseEngines(); // After closing the WebRTC stack, clean up the // VideoCapture thread. base::Thread* thread = nullptr; if (sNumOfOpenCamerasParentEngines == 0 && self->sVideoCaptureThread) { thread = self->sVideoCaptureThread; self->sVideoCaptureThread = nullptr; } nsresult rv = NS_DispatchToMainThread(NewRunnableFrom([self, thread]() { if (thread) { if (thread->IsRunning()) { thread->Stop(); } delete thread; } nsresult rv = GetShutdownBarrier()->RemoveBlocker(self); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; return NS_OK; })); if (NS_FAILED(rv)) { LOG("Could not dispatch VideoCaptureThread destruction"); } return rv; })); #ifdef DEBUG // It's ok for the dispatch to fail if the cleanup it has to do // has been done already. MOZ_ASSERT(NS_SUCCEEDED(rv) || !mWebRTCAlive); #endif } int CamerasParent::DeliverFrameOverIPC(CaptureEngine capEng, uint32_t aStreamId, ShmemBuffer buffer, unsigned char* altbuffer, 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 (altbuffer != 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; } // get() and Size() check for proper alignment of the segment memcpy(shMemBuff.GetBytes(), altbuffer, aProps.bufferSize()); if (!SendDeliverFrame(capEng, aStreamId, std::move(shMemBuff.Get()), aProps)) { return -1; } } else { MOZ_ASSERT(buffer.Valid()); // ShmemBuffer was available, we're all good. A single copy happened // in the original webrtc callback. if (!SendDeliverFrame(capEng, aStreamId, std::move(buffer.Get()), aProps)) { return -1; } } return 0; } ShmemBuffer CamerasParent::GetBuffer(size_t aSize) { return mShmemPool.GetIfAvailable(aSize); } void CallbackHelper::OnFrame(const webrtc::VideoFrame& aVideoFrame) { LOG_VERBOSE("%s", __PRETTY_FUNCTION__); RefPtr 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. VideoFrameUtils::CopyVideoFrameBuffers( shMemBuffer.GetBytes(), properties.bufferSize(), aVideoFrame); runnable = new DeliverFrameRunnable(mParent, mCapEngine, mStreamId, std::move(shMemBuffer), properties); } if (!runnable) { runnable = new DeliverFrameRunnable(mParent, mCapEngine, mStreamId, aVideoFrame, properties); } MOZ_ASSERT(mParent); nsIEventTarget* target = mParent->GetBackgroundEventTarget(); MOZ_ASSERT(target != nullptr); target->Dispatch(runnable, NS_DISPATCH_NORMAL); } mozilla::ipc::IPCResult CamerasParent::RecvReleaseFrame( mozilla::ipc::Shmem&& s) { mShmemPool.Put(ShmemBuffer(s)); return IPC_OK(); } bool CamerasParent::SetupEngine(CaptureEngine aCapEngine) { LOG("%s", __PRETTY_FUNCTION__); StaticRefPtr& engine = sEngines[aCapEngine]; if (!engine) { UniquePtr captureDeviceInfo; auto config = MakeUnique(); switch (aCapEngine) { case ScreenEngine: captureDeviceInfo = MakeUnique( webrtc::CaptureDeviceType::Screen); break; case BrowserEngine: captureDeviceInfo = MakeUnique( webrtc::CaptureDeviceType::Browser); break; case WinEngine: captureDeviceInfo = MakeUnique( webrtc::CaptureDeviceType::Window); break; case CameraEngine: captureDeviceInfo = MakeUnique( webrtc::CaptureDeviceType::Camera); break; default: LOG("Invalid webrtc Video engine"); MOZ_CRASH(); break; } config->Set(captureDeviceInfo.release()); engine = VideoEngine::Create(std::move(config)); if (!engine) { LOG("VideoEngine::Create failed"); return false; } } if (aCapEngine == CameraEngine && !mCameraObserver) { mCameraObserver = new InputObserver(this); auto device_info = engine->GetOrCreateVideoCaptureDeviceInfo(); MOZ_ASSERT(device_info); if (device_info) { device_info->RegisterVideoInputFeedBack(mCameraObserver); } } return true; } void CamerasParent::CloseEngines() { LOG("%s", __PRETTY_FUNCTION__); if (!mWebRTCAlive) { return; } MOZ_ASSERT(sVideoCaptureThread->thread_id() == PlatformThread::CurrentId()); // Stop the callers while (mCallbacks.Length()) { auto capEngine = mCallbacks[0]->mCapEngine; auto streamNum = mCallbacks[0]->mStreamId; LOG("Forcing shutdown of engine %d, capturer %d", capEngine, streamNum); StopCapture(capEngine, streamNum); Unused << ReleaseCaptureDevice(capEngine, streamNum); } StaticRefPtr& engine = sEngines[CameraEngine]; if (engine && mCameraObserver) { auto device_info = engine->GetOrCreateVideoCaptureDeviceInfo(); MOZ_ASSERT(device_info); if (device_info) { device_info->DeRegisterVideoInputFeedBack(mCameraObserver); } mCameraObserver = nullptr; } // CloseEngines() is protected by sThreadMonitor sNumOfOpenCamerasParentEngines--; if (sNumOfOpenCamerasParentEngines == 0) { for (StaticRefPtr& engine : sEngines) { if (engine) { VideoEngine::Delete(engine); engine = nullptr; } } } mWebRTCAlive = false; } VideoEngine* CamerasParent::EnsureInitialized(int aEngine) { LOG_VERBOSE("%s", __PRETTY_FUNCTION__); // We're shutting down, don't try to do new WebRTC ops. if (!mWebRTCAlive) { return nullptr; } CaptureEngine capEngine = static_cast(aEngine); if (!SetupEngine(capEngine)) { LOG("CamerasParent failed to initialize engine"); return nullptr; } return sEngines[aEngine]; } // 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. mozilla::ipc::IPCResult CamerasParent::RecvNumberOfCaptureDevices( const CaptureEngine& aCapEngine) { LOG("%s", __PRETTY_FUNCTION__); LOG("CaptureEngine=%d", aCapEngine); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine]() { int num = -1; if (auto engine = self->EnsureInitialized(aCapEngine)) { if (auto devInfo = engine->GetOrCreateVideoCaptureDeviceInfo()) { num = devInfo->NumberOfDevices(); } } RefPtr ipc_runnable = NewRunnableFrom([self, num]() { if (!self->mChildIsAlive) { LOG("RecvNumberOfCaptureDevices failure: child not alive"); return NS_ERROR_FAILURE; } if (num < 0) { LOG("RecvNumberOfCaptureDevices couldn't find devices"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } LOG("RecvNumberOfCaptureDevices: %d", num); Unused << self->SendReplyNumberOfCaptureDevices(num); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvEnsureInitialized( const CaptureEngine& aCapEngine) { LOG("%s", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine]() { bool result = self->EnsureInitialized(aCapEngine); RefPtr ipc_runnable = NewRunnableFrom([self, result]() { if (!self->mChildIsAlive) { LOG("RecvEnsureInitialized: child not alive"); return NS_ERROR_FAILURE; } if (!result) { LOG("RecvEnsureInitialized failed"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } LOG("RecvEnsureInitialized succeeded"); Unused << self->SendReplySuccess(); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvNumberOfCapabilities( const CaptureEngine& aCapEngine, const nsCString& unique_id) { LOG("%s", __PRETTY_FUNCTION__); LOG("Getting caps for %s", unique_id.get()); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, unique_id, aCapEngine]() { int num = -1; if (auto engine = self->EnsureInitialized(aCapEngine)) { if (auto devInfo = engine->GetOrCreateVideoCaptureDeviceInfo()) { num = devInfo->NumberOfCapabilities(unique_id.get()); } } RefPtr ipc_runnable = NewRunnableFrom([self, num]() { if (!self->mChildIsAlive) { LOG("RecvNumberOfCapabilities: child not alive"); return NS_ERROR_FAILURE; } if (num < 0) { LOG("RecvNumberOfCapabilities couldn't find capabilities"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } LOG("RecvNumberOfCapabilities: %d", num); Unused << self->SendReplyNumberOfCapabilities(num); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvGetCaptureCapability( const CaptureEngine& aCapEngine, const nsCString& unique_id, const int& num) { LOG("%s", __PRETTY_FUNCTION__); LOG("RecvGetCaptureCapability: %s %d", unique_id.get(), num); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, unique_id, aCapEngine, num]() { webrtc::VideoCaptureCapability webrtcCaps; int error = -1; if (auto engine = self->EnsureInitialized(aCapEngine)) { if (auto devInfo = engine->GetOrCreateVideoCaptureDeviceInfo()) { error = devInfo->GetCapability(unique_id.get(), num, webrtcCaps); } if (!error && aCapEngine == CameraEngine) { auto iter = self->mAllCandidateCapabilities.find(unique_id); if (iter == self->mAllCandidateCapabilities.end()) { std::map candidateCapabilities; candidateCapabilities.emplace(num, webrtcCaps); self->mAllCandidateCapabilities.emplace(nsCString(unique_id), candidateCapabilities); } else { (iter->second).emplace(num, webrtcCaps); } } } RefPtr ipc_runnable = NewRunnableFrom([self, webrtcCaps, error]() { if (!self->mChildIsAlive) { LOG("RecvGetCaptureCapability: child not alive"); return NS_ERROR_FAILURE; } VideoCaptureCapability capCap( webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS, static_cast(webrtcCaps.videoType), webrtcCaps.interlaced); LOG("Capability: %u %u %u %d %d", webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS, static_cast(webrtcCaps.videoType), webrtcCaps.interlaced); if (error) { LOG("RecvGetCaptureCapability: reply failure"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } Unused << self->SendReplyGetCaptureCapability(capCap); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvGetCaptureDevice( const CaptureEngine& aCapEngine, const int& aListNumber) { LOG("%s", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine, aListNumber]() { char deviceName[MediaEngineSource::kMaxDeviceNameLength]; char deviceUniqueId[MediaEngineSource::kMaxUniqueIdLength]; nsCString name; nsCString uniqueId; pid_t devicePid = 0; int error = -1; if (auto engine = self->EnsureInitialized(aCapEngine)) { if (auto devInfo = engine->GetOrCreateVideoCaptureDeviceInfo()) { error = devInfo->GetDeviceName( aListNumber, deviceName, sizeof(deviceName), deviceUniqueId, sizeof(deviceUniqueId), nullptr, 0, &devicePid); } } if (!error) { name.Assign(deviceName); uniqueId.Assign(deviceUniqueId); } RefPtr ipc_runnable = NewRunnableFrom([self, error, name, uniqueId, devicePid]() { if (!self->mChildIsAlive) { return NS_ERROR_FAILURE; } if (error) { LOG("GetCaptureDevice failed: %d", error); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } bool scary = (devicePid == getpid()); LOG("Returning %s name %s id (pid = %d)%s", name.get(), uniqueId.get(), devicePid, (scary ? " (scary)" : "")); Unused << self->SendReplyGetCaptureDevice(name, uniqueId, scary); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); 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 window = dom::WindowGlobalParent::GetByInnerWindowId(aWindowId); if (!window) { // Could not find window by id return false; } // If we delegate permission from first party, we should use the top level // window if (StaticPrefs::permissions_delegation_enabled()) { RefPtr 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 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; } mozilla::ipc::IPCResult CamerasParent::RecvAllocateCaptureDevice( const CaptureEngine& aCapEngine, const nsCString& unique_id, const uint64_t& aWindowID) { LOG("%s: Verifying permissions", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr mainthread_runnable = NewRunnableFrom([self, aCapEngine, unique_id, 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) || Preferences::GetBool("media.navigator.permission.fake")) { allowed = true; LOG("No permission but checks are disabled or fake sources active"); } else { LOG("No camera permission for this origin"); } } // After retrieving the permission (or not) on the main thread, // bounce to the WebRTC thread to allocate the device (or not), // then bounce back to the IPC thread for the reply to content. RefPtr webrtc_runnable = NewRunnableFrom([self, allowed, aCapEngine, unique_id]() { int numdev = -1; int error = -1; if (allowed && self->EnsureInitialized(aCapEngine)) { StaticRefPtr& engine = self->sEngines[aCapEngine]; engine->CreateVideoCapture(numdev, unique_id.get()); engine->WithEntry(numdev, [&error](VideoEngine::CaptureEntry& cap) { if (cap.VideoCapture()) { error = 0; } }); } RefPtr ipc_runnable = NewRunnableFrom([self, numdev, error]() { if (!self->mChildIsAlive) { LOG("RecvAllocateCaptureDevice: child not alive"); return NS_ERROR_FAILURE; } if (error) { Unused << self->SendReplyFailure(); LOG("RecvAllocateCaptureDevice: WithEntry error"); return NS_ERROR_FAILURE; } LOG("Allocated device nr %d", numdev); Unused << self->SendReplyAllocateCaptureDevice(numdev); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); self->DispatchToVideoCaptureThread(webrtc_runnable); return NS_OK; }); NS_DispatchToMainThread(mainthread_runnable); return IPC_OK(); } int CamerasParent::ReleaseCaptureDevice(const CaptureEngine& aCapEngine, const int& capnum) { int error = -1; if (auto engine = EnsureInitialized(aCapEngine)) { error = engine->ReleaseVideoCapture(capnum); } return error; } mozilla::ipc::IPCResult CamerasParent::RecvReleaseCaptureDevice( const CaptureEngine& aCapEngine, const int& numdev) { LOG("%s", __PRETTY_FUNCTION__); LOG("RecvReleaseCamera device nr %d", numdev); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine, numdev]() { int error = self->ReleaseCaptureDevice(aCapEngine, numdev); RefPtr ipc_runnable = NewRunnableFrom([self, error, numdev]() { if (!self->mChildIsAlive) { LOG("RecvReleaseCaptureDevice: child not alive"); return NS_ERROR_FAILURE; } if (error) { Unused << self->SendReplyFailure(); LOG("RecvReleaseCaptureDevice: Failed to free device nr %d", numdev); return NS_ERROR_FAILURE; } Unused << self->SendReplySuccess(); LOG("Freed device nr %d", numdev); return NS_OK; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvStartCapture( const CaptureEngine& aCapEngine, const int& capnum, const VideoCaptureCapability& ipcCaps) { LOG("%s", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine, capnum, ipcCaps]() { LOG("%s", __PRETTY_FUNCTION__); CallbackHelper** cbh; int error = -1; if (self->EnsureInitialized(aCapEngine)) { cbh = self->mCallbacks.AppendElement(new CallbackHelper( static_cast(aCapEngine), capnum, self)); self->sEngines[aCapEngine]->WithEntry( capnum, [&capnum, &aCapEngine, &error, &ipcCaps, &cbh, self](VideoEngine::CaptureEntry& cap) { webrtc::VideoCaptureCapability capability; capability.width = ipcCaps.width(); capability.height = ipcCaps.height(); capability.maxFPS = ipcCaps.maxFPS(); capability.videoType = static_cast(ipcCaps.videoType()); capability.interlaced = ipcCaps.interlaced(); MOZ_DIAGNOSTIC_ASSERT(sDeviceUniqueIDs.find(capnum) == sDeviceUniqueIDs.end()); sDeviceUniqueIDs.emplace(capnum, cap.VideoCapture()->CurrentDeviceName()); MOZ_DIAGNOSTIC_ASSERT(sAllRequestedCapabilities.find(capnum) == sAllRequestedCapabilities.end()); sAllRequestedCapabilities.emplace(capnum, 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 = self->mAllCandidateCapabilities.find( nsCString(cap.VideoCapture()->CurrentDeviceName())); if ((candidateCapabilities != self->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 = 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); } } } error = cap.VideoCapture()->StartCapture(capability); if (!error) { cap.VideoCapture()->RegisterCaptureDataCallback( static_cast*>( *cbh)); } else { sDeviceUniqueIDs.erase(capnum); sAllRequestedCapabilities.erase(capnum); } }); } RefPtr ipc_runnable = NewRunnableFrom([self, error]() { if (!self->mChildIsAlive) { LOG("RecvStartCapture failure: child is not alive"); return NS_ERROR_FAILURE; } if (!error) { Unused << self->SendReplySuccess(); return NS_OK; } LOG("RecvStartCapture failure: StartCapture failed"); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } mozilla::ipc::IPCResult CamerasParent::RecvFocusOnSelectedSource( const CaptureEngine& aCapEngine, const int& aCapNum) { LOG("%s", __PRETTY_FUNCTION__); RefPtr webrtc_runnable = NewRunnableFrom( [self = RefPtr(this), aCapEngine, aCapNum]() { if (auto engine = self->EnsureInitialized(aCapEngine)) { engine->WithEntry(aCapNum, [self](VideoEngine::CaptureEntry& cap) { if (cap.VideoCapture()) { bool result = cap.VideoCapture()->FocusOnSelectedSource(); RefPtr ipc_runnable = NewRunnableFrom([self, result]() { if (!self->mChildIsAlive) { LOG("RecvFocusOnSelectedSource failure: child is not alive"); return NS_ERROR_FAILURE; } if (result) { Unused << self->SendReplySuccess(); return NS_OK; } Unused << self->SendReplyFailure(); LOG("RecvFocusOnSelectedSource failure."); return NS_ERROR_FAILURE; }); self->mPBackgroundEventTarget->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); } }); } LOG("RecvFocusOnSelectedSource CameraParent not initialized"); return NS_ERROR_FAILURE; }); DispatchToVideoCaptureThread(webrtc_runnable); return IPC_OK(); } void CamerasParent::StopCapture(const CaptureEngine& aCapEngine, const int& capnum) { 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)capnum) { CallbackHelper* cbh = mCallbacks[i - 1]; engine->WithEntry(capnum, [cbh, &capnum](VideoEngine::CaptureEntry& cap) { if (cap.VideoCapture()) { cap.VideoCapture()->DeRegisterCaptureDataCallback( static_cast*>(cbh)); cap.VideoCapture()->StopCaptureIfAllClientsClose(); sDeviceUniqueIDs.erase(capnum); sAllRequestedCapabilities.erase(capnum); } }); delete mCallbacks[i - 1]; mCallbacks.RemoveElementAt(i - 1); break; } } } } mozilla::ipc::IPCResult CamerasParent::RecvStopCapture( const CaptureEngine& aCapEngine, const int& capnum) { LOG("%s", __PRETTY_FUNCTION__); RefPtr self(this); RefPtr webrtc_runnable = NewRunnableFrom([self, aCapEngine, capnum]() { self->StopCapture(aCapEngine, capnum); return NS_OK; }); nsresult rv = DispatchToVideoCaptureThread(webrtc_runnable); if (!self->mChildIsAlive) { 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::StopIPC() { MOZ_ASSERT(!mDestroyed); // 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. mChildIsAlive = false; mDestroyed = true; } mozilla::ipc::IPCResult CamerasParent::RecvAllDone() { LOG("%s", __PRETTY_FUNCTION__); // Don't try to send anything to the child now mChildIsAlive = false; IProtocol* mgr = Manager(); if (!Send__delete__(this)) { return IPC_FAIL_NO_REASON(mgr); } return IPC_OK(); } void CamerasParent::ActorDestroy(ActorDestroyReason aWhy) { // No more IPC from here LOG("%s", __PRETTY_FUNCTION__); StopIPC(); // Shut down WebRTC (if we're not in full shutdown, else this // will already have happened) StopVideoCapture(); } nsString CamerasParent::GetNewName() { static volatile uint64_t counter = 0; nsString name(u"CamerasParent "_ns); name.AppendInt(++counter); return name; } NS_IMETHODIMP CamerasParent::BlockShutdown(nsIAsyncShutdownClient*) { StopVideoCapture(); return NS_OK; } CamerasParent::CamerasParent() : mName(GetNewName()), mShmemPool(CaptureEngine::MaxEngine), mChildIsAlive(true), mDestroyed(false), mWebRTCAlive(true) { LOG("CamerasParent: %p", this); StaticMutexAutoLock slock(sMutex); if (sNumOfCamerasParents++ == 0) { sThreadMonitor = new Monitor("CamerasParent::sThreadMonitor"); } mPBackgroundEventTarget = GetCurrentSerialEventTarget(); MOZ_ASSERT(mPBackgroundEventTarget != nullptr, "GetCurrentThreadEventTarget failed"); LOG("Spinning up WebRTC Cameras Thread"); RefPtr self(this); NS_DispatchToMainThread(NewRunnableFrom([self]() { nsresult rv = GetShutdownBarrier()->AddBlocker( self, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); // Start the thread MonitorAutoLock lock(*(self->sThreadMonitor)); if (self->sVideoCaptureThread == nullptr) { MOZ_ASSERT(sNumOfOpenCamerasParentEngines == 0); self->sVideoCaptureThread = new base::Thread("VideoCapture"); base::Thread::Options options; #if defined(_WIN32) options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD; #else options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINTHREAD; #endif if (!self->sVideoCaptureThread->StartWithOptions(options)) { MOZ_CRASH(); } } sNumOfOpenCamerasParentEngines++; self->sThreadMonitor->NotifyAll(); return NS_OK; })); } CamerasParent::~CamerasParent() { LOG("~CamerasParent: %p", this); StaticMutexAutoLock slock(sMutex); if (--sNumOfCamerasParents == 0) { delete sThreadMonitor; sThreadMonitor = nullptr; } } already_AddRefed CamerasParent::Create() { mozilla::ipc::AssertIsOnBackgroundThread(); return MakeAndAddRef(); } } // namespace camera } // namespace mozilla