/* -*- 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 "MediaEngine.h" #include "MediaUtils.h" #include "mozilla/Assertions.h" #include "mozilla/Unused.h" #include "mozilla/Services.h" #include "mozilla/Logging.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/Preferences.h" #include "nsIPermissionManager.h" #include "nsThreadUtils.h" #include "nsXPCOM.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_ENABLED mozilla::LazyLogModule gCamerasParentLog("CamerasParent"); #define LOG(args) MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Debug, args) #define LOG_ENABLED() MOZ_LOG_TEST(gCamerasParentLog, mozilla::LogLevel::Debug) namespace mozilla { namespace camera { // 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::DeviceChange() { LOG((__PRETTY_FUNCTION__)); MOZ_ASSERT(mParent); RefPtr self(this); RefPtr ipc_runnable = media::NewRunnableFrom([self]() -> nsresult { if (self->mParent->IsShuttingDown()) { return NS_ERROR_FAILURE; } Unused << self->mParent->SendDeviceChange(); return NS_OK; }); nsIThread* thread = mParent->GetBackgroundThread(); MOZ_ASSERT(thread != nullptr); thread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); }; class FrameSizeChangeRunnable : public Runnable { public: FrameSizeChangeRunnable(CamerasParent *aParent, CaptureEngine capEngine, int cap_id, unsigned int aWidth, unsigned int aHeight) : mParent(aParent), mCapEngine(capEngine), mCapId(cap_id), mWidth(aWidth), mHeight(aHeight) {} NS_IMETHOD Run() override { if (mParent->IsShuttingDown()) { // Communication channel is being torn down LOG(("FrameSizeChangeRunnable is active without active Child")); mResult = 0; return NS_OK; } if (!mParent->SendFrameSizeChange(mCapEngine, mCapId, mWidth, mHeight)) { mResult = -1; } else { mResult = 0; } return NS_OK; } int GetResult() { return mResult; } private: RefPtr mParent; CaptureEngine mCapEngine; int mCapId; unsigned int mWidth; unsigned int mHeight; int mResult; }; int CallbackHelper::FrameSizeChange(unsigned int w, unsigned int h, unsigned int streams) { LOG(("CallbackHelper Video FrameSizeChange: %ux%u", w, h)); RefPtr runnable = new FrameSizeChangeRunnable(mParent, mCapEngine, mCapturerId, w, h); MOZ_ASSERT(mParent); nsIThread * thread = mParent->GetBackgroundThread(); MOZ_ASSERT(thread != nullptr); thread->Dispatch(runnable, NS_DISPATCH_NORMAL); return 0; } class DeliverFrameRunnable : public Runnable { public: DeliverFrameRunnable(CamerasParent *aParent, CaptureEngine engine, int cap_id, ShmemBuffer buffer, unsigned char* altbuffer, size_t size, uint32_t time_stamp, int64_t ntp_time, int64_t render_time) : mParent(aParent), mCapEngine(engine), mCapId(cap_id), mBuffer(Move(buffer)), mSize(size), mTimeStamp(time_stamp), mNtpTime(ntp_time), mRenderTime(render_time) { // 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. if (altbuffer != nullptr) { mAlternateBuffer.reset(new unsigned char[size]); memcpy(mAlternateBuffer.get(), altbuffer, size); } }; NS_IMETHOD Run() override { if (mParent->IsShuttingDown()) { // Communication channel is being torn down mResult = 0; return NS_OK; } if (!mParent->DeliverFrameOverIPC(mCapEngine, mCapId, Move(mBuffer), mAlternateBuffer.get(), mSize, mTimeStamp, mNtpTime, mRenderTime)) { mResult = -1; } else { mResult = 0; } return NS_OK; } int GetResult() { return mResult; } private: RefPtr mParent; CaptureEngine mCapEngine; int mCapId; ShmemBuffer mBuffer; mozilla::UniquePtr mAlternateBuffer; size_t mSize; uint32_t mTimeStamp; int64_t mNtpTime; int64_t mRenderTime; int mResult; }; NS_IMPL_ISUPPORTS(CamerasParent, nsIObserver) NS_IMETHODIMP CamerasParent::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)); nsCOMPtr obs = services::GetObserverService(); MOZ_ASSERT(obs); obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); StopVideoCapture(); return NS_OK; } nsresult CamerasParent::DispatchToVideoCaptureThread(Runnable* event) { // Don't try to dispatch if we're already on the right thread. // There's a potential deadlock because the mThreadMonitor is likely // to be taken already. MOZ_ASSERT(!mVideoCaptureThread || mVideoCaptureThread->thread_id() != PlatformThread::CurrentId()); MonitorAutoLock lock(mThreadMonitor); while(mChildIsAlive && mWebRTCAlive && (!mVideoCaptureThread || !mVideoCaptureThread->IsRunning())) { mThreadMonitor.Wait(); } if (!mVideoCaptureThread || !mVideoCaptureThread->IsRunning()) { return NS_ERROR_FAILURE; } RefPtr addrefedEvent = event; mVideoCaptureThread->message_loop()->PostTask(addrefedEvent.forget()); return NS_OK; } void CamerasParent::StopVideoCapture() { LOG((__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); RefPtr webrtc_runnable = media::NewRunnableFrom([self]() -> nsresult { MonitorAutoLock lock(self->mThreadMonitor); self->CloseEngines(); self->mThreadMonitor.NotifyAll(); return NS_OK; }); DebugOnly rv = DispatchToVideoCaptureThread(webrtc_runnable); #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 // Hold here until the WebRTC thread is gone. We need to dispatch // the thread deletion *now*, or there will be no more possibility // to get to the main thread. MonitorAutoLock lock(mThreadMonitor); while (mWebRTCAlive) { mThreadMonitor.Wait(); } // After closing the WebRTC stack, clean up the // VideoCapture thread. if (self->mVideoCaptureThread) { base::Thread *thread = self->mVideoCaptureThread; self->mVideoCaptureThread = nullptr; RefPtr threadShutdown = media::NewRunnableFrom([thread]() -> nsresult { if (thread->IsRunning()) { thread->Stop(); } delete thread; return NS_OK; }); if (NS_FAILED(NS_DispatchToMainThread(threadShutdown))) { LOG(("Could not dispatch VideoCaptureThread destruction")); } } } int CamerasParent::DeliverFrameOverIPC(CaptureEngine cap_engine, int cap_id, ShmemBuffer buffer, unsigned char* altbuffer, size_t size, uint32_t time_stamp, int64_t ntp_time, int64_t render_time) { // 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, size); 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, size); if (!SendDeliverFrame(cap_engine, cap_id, shMemBuff.Get(), size, time_stamp, ntp_time, render_time)) { 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(cap_engine, cap_id, buffer.Get(), size, time_stamp, ntp_time, render_time)) { return -1; } } return 0; } ShmemBuffer CamerasParent::GetBuffer(size_t aSize) { return mShmemPool.GetIfAvailable(aSize); } int CallbackHelper::DeliverFrame(unsigned char* buffer, size_t size, uint32_t time_stamp, int64_t ntp_time, int64_t render_time, void *handle) { // Get a shared memory buffer to copy the frame data into ShmemBuffer shMemBuffer = mParent->GetBuffer(size); 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. memcpy(shMemBuffer.GetBytes(), buffer, size); // Mark the original buffer as cleared. buffer = nullptr; } RefPtr runnable = new DeliverFrameRunnable(mParent, mCapEngine, mCapturerId, Move(shMemBuffer), buffer, size, time_stamp, ntp_time, render_time); MOZ_ASSERT(mParent); nsIThread* thread = mParent->GetBackgroundThread(); MOZ_ASSERT(thread != nullptr); thread->Dispatch(runnable, NS_DISPATCH_NORMAL); return 0; } // XXX!!! FIX THIS -- we should move to pure DeliverI420Frame int CallbackHelper::DeliverI420Frame(const webrtc::I420VideoFrame& webrtc_frame) { return DeliverFrame(const_cast(webrtc_frame.buffer(webrtc::kYPlane)), CalcBufferSize(webrtc::kI420, webrtc_frame.width(), webrtc_frame.height()), webrtc_frame.timestamp(), webrtc_frame.ntp_time_ms(), webrtc_frame.render_time_ms(), (void*) webrtc_frame.native_handle()); } bool CamerasParent::RecvReleaseFrame(mozilla::ipc::Shmem&& s) { mShmemPool.Put(ShmemBuffer(s)); return true; } bool CamerasParent::SetupEngine(CaptureEngine aCapEngine) { MOZ_ASSERT(mVideoCaptureThread->thread_id() == PlatformThread::CurrentId()); EngineHelper *helper = &mEngines[aCapEngine]; // Already initialized if (helper->mEngine) { return true; } webrtc::CaptureDeviceInfo *captureDeviceInfo = nullptr; switch (aCapEngine) { case ScreenEngine: captureDeviceInfo = new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Screen); break; case BrowserEngine: captureDeviceInfo = new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Browser); break; case WinEngine: captureDeviceInfo = new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Window); break; case AppEngine: captureDeviceInfo = new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Application); break; case CameraEngine: captureDeviceInfo = new webrtc::CaptureDeviceInfo(webrtc::CaptureDeviceType::Camera); break; default: LOG(("Invalid webrtc Video engine")); MOZ_CRASH(); break; } helper->mConfig.Set(captureDeviceInfo); helper->mEngine = webrtc::VideoEngine::Create(helper->mConfig); if (!helper->mEngine) { LOG(("VideoEngine::Create failed")); return false; } helper->mPtrViEBase = webrtc::ViEBase::GetInterface(helper->mEngine); if (!helper->mPtrViEBase) { LOG(("ViEBase::GetInterface failed")); return false; } if (helper->mPtrViEBase->Init() < 0) { LOG(("ViEBase::Init failed")); return false; } helper->mPtrViECapture = webrtc::ViECapture::GetInterface(helper->mEngine); if (!helper->mPtrViECapture) { LOG(("ViECapture::GetInterface failed")); return false; } RefPtr* observer = mObservers.AppendElement(new InputObserver(this)); #ifdef DEBUG MOZ_ASSERT(0 == helper->mPtrViECapture->RegisterInputObserver(observer->get())); #else helper->mPtrViECapture->RegisterInputObserver(observer->get()); #endif helper->mPtrViERender = webrtc::ViERender::GetInterface(helper->mEngine); if (!helper->mPtrViERender) { LOG(("ViERender::GetInterface failed")); return false; } return true; } void CamerasParent::CloseEngines() { LOG((__PRETTY_FUNCTION__)); if (!mWebRTCAlive) { return; } MOZ_ASSERT(mVideoCaptureThread->thread_id() == PlatformThread::CurrentId()); // Stop the callers while (mCallbacks.Length()) { auto capEngine = mCallbacks[0]->mCapEngine; auto capNum = mCallbacks[0]->mCapturerId; LOG(("Forcing shutdown of engine %d, capturer %d", capEngine, capNum)); StopCapture(capEngine, capNum); Unused << ReleaseCaptureDevice(capEngine, capNum); } for (int i = 0; i < CaptureEngine::MaxEngine; i++) { if (mEngines[i].mEngineIsRunning) { LOG(("Being closed down while engine %d is running!", i)); } if (mEngines[i].mPtrViERender) { mEngines[i].mPtrViERender->Release(); mEngines[i].mPtrViERender = nullptr; } if (mEngines[i].mPtrViECapture) { #ifdef DEBUG MOZ_ASSERT(0 == mEngines[i].mPtrViECapture->DeregisterInputObserver()); #else mEngines[i].mPtrViECapture->DeregisterInputObserver(); #endif mEngines[i].mPtrViECapture->Release(); mEngines[i].mPtrViECapture = nullptr; } if(mEngines[i].mPtrViEBase) { mEngines[i].mPtrViEBase->Release(); mEngines[i].mPtrViEBase = nullptr; } if (mEngines[i].mEngine) { mEngines[i].mEngine->SetTraceCallback(nullptr); webrtc::VideoEngine::Delete(mEngines[i].mEngine); mEngines[i].mEngine = nullptr; } } mObservers.Clear(); mWebRTCAlive = false; } bool CamerasParent::EnsureInitialized(int aEngine) { LOG((__PRETTY_FUNCTION__)); // We're shutting down, don't try to do new WebRTC ops. if (!mWebRTCAlive) { return false; } CaptureEngine capEngine = static_cast(aEngine); if (!SetupEngine(capEngine)) { LOG(("CamerasParent failed to initialize engine")); return false; } return true; } // 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. bool CamerasParent::RecvNumberOfCaptureDevices(const CaptureEngine& aCapEngine) { LOG((__PRETTY_FUNCTION__)); RefPtr self(this); RefPtr webrtc_runnable = media::NewRunnableFrom([self, aCapEngine]() -> nsresult { int num = -1; if (self->EnsureInitialized(aCapEngine)) { num = self->mEngines[aCapEngine].mPtrViECapture->NumberOfCaptureDevices(); } RefPtr ipc_runnable = media::NewRunnableFrom([self, num]() -> nsresult { if (self->IsShuttingDown()) { return NS_ERROR_FAILURE; } if (num < 0) { LOG(("RecvNumberOfCaptureDevices couldn't find devices")); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } else { LOG(("RecvNumberOfCaptureDevices: %d", num)); Unused << self->SendReplyNumberOfCaptureDevices(num); return NS_OK; } }); self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return true; } bool CamerasParent::RecvEnsureInitialized(const CaptureEngine& aCapEngine) { LOG((__PRETTY_FUNCTION__)); RefPtr self(this); RefPtr webrtc_runnable = media::NewRunnableFrom([self, aCapEngine]() -> nsresult { bool result = self->EnsureInitialized(aCapEngine); RefPtr ipc_runnable = media::NewRunnableFrom([self, result]() -> nsresult { if (self->IsShuttingDown()) { return NS_ERROR_FAILURE; } if (!result) { LOG(("RecvEnsureInitialized failed")); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } else { LOG(("RecvEnsureInitialized succeeded")); Unused << self->SendReplySuccess(); return NS_OK; } }); self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return true; } bool CamerasParent::RecvNumberOfCapabilities(const CaptureEngine& aCapEngine, const nsCString& unique_id) { LOG((__PRETTY_FUNCTION__)); LOG(("Getting caps for %s", unique_id.get())); RefPtr self(this); RefPtr webrtc_runnable = media::NewRunnableFrom([self, unique_id, aCapEngine]() -> nsresult { int num = -1; if (self->EnsureInitialized(aCapEngine)) { num = self->mEngines[aCapEngine].mPtrViECapture->NumberOfCapabilities( unique_id.get(), MediaEngineSource::kMaxUniqueIdLength); } RefPtr ipc_runnable = media::NewRunnableFrom([self, num]() -> nsresult { if (self->IsShuttingDown()) { return NS_ERROR_FAILURE; } if (num < 0) { LOG(("RecvNumberOfCapabilities couldn't find capabilities")); Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } else { LOG(("RecvNumberOfCapabilities: %d", num)); } Unused << self->SendReplyNumberOfCapabilities(num); return NS_OK; }); self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return true; } bool CamerasParent::RecvGetCaptureCapability(const CaptureEngine& aCapEngine, const nsCString& unique_id, const int& num) { LOG((__PRETTY_FUNCTION__)); LOG(("RecvGetCaptureCapability: %s %d", unique_id.get(), num)); RefPtr self(this); RefPtr webrtc_runnable = media::NewRunnableFrom([self, unique_id, aCapEngine, num]() -> nsresult { webrtc::CaptureCapability webrtcCaps; int error = -1; if (self->EnsureInitialized(aCapEngine)) { error = self->mEngines[aCapEngine].mPtrViECapture->GetCaptureCapability( unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, num, webrtcCaps); } RefPtr ipc_runnable = media::NewRunnableFrom([self, webrtcCaps, error]() -> nsresult { if (self->IsShuttingDown()) { return NS_ERROR_FAILURE; } CaptureCapability capCap(webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS, webrtcCaps.expectedCaptureDelay, webrtcCaps.rawType, webrtcCaps.codecType, webrtcCaps.interlaced); LOG(("Capability: %u %u %u %u %d %d", webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS, webrtcCaps.expectedCaptureDelay, webrtcCaps.rawType, webrtcCaps.codecType)); if (error) { Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } Unused << self->SendReplyGetCaptureCapability(capCap); return NS_OK; }); self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return true; } bool CamerasParent::RecvGetCaptureDevice(const CaptureEngine& aCapEngine, const int& aListNumber) { LOG((__PRETTY_FUNCTION__)); RefPtr self(this); RefPtr webrtc_runnable = media::NewRunnableFrom([self, aCapEngine, aListNumber]() -> nsresult { char deviceName[MediaEngineSource::kMaxDeviceNameLength]; char deviceUniqueId[MediaEngineSource::kMaxUniqueIdLength]; nsCString name; nsCString uniqueId; int devicePid = 0; int error = -1; if (self->EnsureInitialized(aCapEngine)) { error = self->mEngines[aCapEngine].mPtrViECapture->GetCaptureDevice(aListNumber, deviceName, sizeof(deviceName), deviceUniqueId, sizeof(deviceUniqueId), &devicePid); } if (!error) { name.Assign(deviceName); uniqueId.Assign(deviceUniqueId); } RefPtr ipc_runnable = media::NewRunnableFrom([self, error, name, uniqueId, devicePid]() { if (self->IsShuttingDown()) { 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->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return true; } static nsresult GetPrincipalFromOrigin(const nsACString& aOrigin, nsIPrincipal** aPrincipal) { nsAutoCString originNoSuffix; mozilla::PrincipalOriginAttributes attrs; if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) { return NS_ERROR_FAILURE; } nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs); principal.forget(aPrincipal); return NS_OK; } // Find out whether the given origin 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 nsCString& aOrigin) { // Name used with nsIPermissionManager static const char* cameraPermission = "MediaManagerVideo"; bool allowed = false; nsresult rv; nsCOMPtr mgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { nsCOMPtr ioServ(do_GetIOService()); nsCOMPtr uri; rv = ioServ->NewURI(aOrigin, nullptr, nullptr, getter_AddRefs(uri)); if (NS_SUCCEEDED(rv)) { // Permanent permissions are only retrievable via principal, not uri nsCOMPtr principal; rv = GetPrincipalFromOrigin(aOrigin, getter_AddRefs(principal)); if (NS_SUCCEEDED(rv)) { uint32_t video = nsIPermissionManager::UNKNOWN_ACTION; rv = mgr->TestExactPermissionFromPrincipal(principal, cameraPermission, &video); if (NS_SUCCEEDED(rv)) { allowed = (video == nsIPermissionManager::ALLOW_ACTION); } // Session permissions are removed after one use. if (allowed) { mgr->RemoveFromPrincipal(principal, cameraPermission); } } } } return allowed; } bool CamerasParent::RecvAllocateCaptureDevice(const CaptureEngine& aCapEngine, const nsCString& unique_id, const nsCString& aOrigin) { LOG(("%s: Verifying permissions for %s", __PRETTY_FUNCTION__, aOrigin.get())); RefPtr self(this); RefPtr mainthread_runnable = media::NewRunnableFrom([self, aCapEngine, unique_id, aOrigin]() -> nsresult { // Verify whether the claimed origin has received permission // to use the camera, either persistently or this session (one shot). bool allowed = HasCameraPermission(aOrigin); 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 = media::NewRunnableFrom([self, allowed, aCapEngine, unique_id]() -> nsresult { int numdev = -1; int error = -1; if (allowed && self->EnsureInitialized(aCapEngine)) { error = self->mEngines[aCapEngine].mPtrViECapture->AllocateCaptureDevice( unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, numdev); } RefPtr ipc_runnable = media::NewRunnableFrom([self, numdev, error]() -> nsresult { if (self->IsShuttingDown()) { return NS_ERROR_FAILURE; } if (error) { Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } else { LOG(("Allocated device nr %d", numdev)); Unused << self->SendReplyAllocateCaptureDevice(numdev); return NS_OK; } }); self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); self->DispatchToVideoCaptureThread(webrtc_runnable); return NS_OK; }); NS_DispatchToMainThread(mainthread_runnable); return true; } int CamerasParent::ReleaseCaptureDevice(const CaptureEngine& aCapEngine, const int& capnum) { int error = -1; if (EnsureInitialized(aCapEngine)) { error = mEngines[aCapEngine].mPtrViECapture->ReleaseCaptureDevice(capnum); } return error; } bool CamerasParent::RecvReleaseCaptureDevice(const CaptureEngine& aCapEngine, const int& numdev) { LOG((__PRETTY_FUNCTION__)); LOG(("RecvReleaseCamera device nr %d", numdev)); RefPtr self(this); RefPtr webrtc_runnable = media::NewRunnableFrom([self, aCapEngine, numdev]() -> nsresult { int error = self->ReleaseCaptureDevice(aCapEngine, numdev); RefPtr ipc_runnable = media::NewRunnableFrom([self, error, numdev]() -> nsresult { if (self->IsShuttingDown()) { LOG(("In Shutdown, not Releasing")); return NS_ERROR_FAILURE; } if (error) { Unused << self->SendReplyFailure(); LOG(("Failed to free device nr %d", numdev)); return NS_ERROR_FAILURE; } else { Unused << self->SendReplySuccess(); LOG(("Freed device nr %d", numdev)); return NS_OK; } }); self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return true; } bool CamerasParent::RecvStartCapture(const CaptureEngine& aCapEngine, const int& capnum, const CaptureCapability& ipcCaps) { LOG((__PRETTY_FUNCTION__)); RefPtr self(this); RefPtr webrtc_runnable = media::NewRunnableFrom([self, aCapEngine, capnum, ipcCaps]() -> nsresult { CallbackHelper** cbh; webrtc::ExternalRenderer* render; EngineHelper* helper = nullptr; int error = -1; if (self->EnsureInitialized(aCapEngine)) { cbh = self->mCallbacks.AppendElement( new CallbackHelper(static_cast(aCapEngine), capnum, self)); render = static_cast(*cbh); helper = &self->mEngines[aCapEngine]; error = helper->mPtrViERender->AddRenderer(capnum, webrtc::kVideoI420, render); if (!error) { error = helper->mPtrViERender->StartRender(capnum); } webrtc::CaptureCapability capability; capability.width = ipcCaps.width(); capability.height = ipcCaps.height(); capability.maxFPS = ipcCaps.maxFPS(); capability.expectedCaptureDelay = ipcCaps.expectedCaptureDelay(); capability.rawType = static_cast(ipcCaps.rawType()); capability.codecType = static_cast(ipcCaps.codecType()); capability.interlaced = ipcCaps.interlaced(); if (!error) { error = helper->mPtrViECapture->StartCapture(capnum, capability); } if (!error) { helper->mEngineIsRunning = true; } } RefPtr ipc_runnable = media::NewRunnableFrom([self, error]() -> nsresult { if (self->IsShuttingDown()) { return NS_ERROR_FAILURE; } if (!error) { Unused << self->SendReplySuccess(); return NS_OK; } else { Unused << self->SendReplyFailure(); return NS_ERROR_FAILURE; } }); self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL); return NS_OK; }); DispatchToVideoCaptureThread(webrtc_runnable); return true; } void CamerasParent::StopCapture(const CaptureEngine& aCapEngine, const int& capnum) { if (EnsureInitialized(aCapEngine)) { mEngines[aCapEngine].mPtrViECapture->StopCapture(capnum); mEngines[aCapEngine].mPtrViERender->StopRender(capnum); mEngines[aCapEngine].mPtrViERender->RemoveRenderer(capnum); mEngines[aCapEngine].mEngineIsRunning = false; for (size_t i = 0; i < mCallbacks.Length(); i++) { if (mCallbacks[i]->mCapEngine == aCapEngine && mCallbacks[i]->mCapturerId == capnum) { delete mCallbacks[i]; mCallbacks.RemoveElementAt(i); break; } } } } bool CamerasParent::RecvStopCapture(const CaptureEngine& aCapEngine, const int& capnum) { LOG((__PRETTY_FUNCTION__)); RefPtr self(this); RefPtr webrtc_runnable = media::NewRunnableFrom([self, aCapEngine, capnum]() -> nsresult { self->StopCapture(aCapEngine, capnum); return NS_OK; }); nsresult rv = DispatchToVideoCaptureThread(webrtc_runnable); if (self->IsShuttingDown()) { return NS_SUCCEEDED(rv); } else { if (NS_SUCCEEDED(rv)) { return SendReplySuccess(); } else { return SendReplyFailure(); } } } 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; } bool CamerasParent::RecvAllDone() { LOG((__PRETTY_FUNCTION__)); // Don't try to send anything to the child now mChildIsAlive = false; return Send__delete__(this); } void CamerasParent::ActorDestroy(ActorDestroyReason aWhy) { // No more IPC from here LOG((__PRETTY_FUNCTION__)); StopIPC(); // Shut down WebRTC (if we're not in full shutdown, else this // will already have happened) StopVideoCapture(); } CamerasParent::CamerasParent() : mShmemPool(CaptureEngine::MaxEngine), mThreadMonitor("CamerasParent::mThreadMonitor"), mVideoCaptureThread(nullptr), mChildIsAlive(true), mDestroyed(false), mWebRTCAlive(true) { LOG(("CamerasParent: %p", this)); mPBackgroundThread = NS_GetCurrentThread(); MOZ_ASSERT(mPBackgroundThread != nullptr, "GetCurrentThread failed"); LOG(("Spinning up WebRTC Cameras Thread")); RefPtr self(this); RefPtr threadStart = media::NewRunnableFrom([self]() -> nsresult { // Register thread shutdown observer nsCOMPtr obs = services::GetObserverService(); if (NS_WARN_IF(!obs)) { return NS_ERROR_FAILURE; } nsresult rv = obs->AddObserver(self, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Start the thread MonitorAutoLock lock(self->mThreadMonitor); self->mVideoCaptureThread = 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->mVideoCaptureThread->StartWithOptions(options)) { MOZ_CRASH(); } self->mThreadMonitor.NotifyAll(); return NS_OK; }); NS_DispatchToMainThread(threadStart); MOZ_COUNT_CTOR(CamerasParent); } CamerasParent::~CamerasParent() { LOG(("~CamerasParent: %p", this)); MOZ_COUNT_DTOR(CamerasParent); #ifdef DEBUG // Verify we have shut down the webrtc engines, this is // supposed to happen in ActorDestroy. // That runnable takes a ref to us, so it must have finished // by the time we get here. for (int i = 0; i < CaptureEngine::MaxEngine; i++) { MOZ_ASSERT(!mEngines[i].mEngine); } #endif } already_AddRefed CamerasParent::Create() { mozilla::ipc::AssertIsOnBackgroundThread(); RefPtr camerasParent = new CamerasParent(); return camerasParent.forget(); } } }