summaryrefslogtreecommitdiffstats
path: root/dom/media/systemservices/CamerasParent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/systemservices/CamerasParent.cpp')
-rw-r--r--dom/media/systemservices/CamerasParent.cpp1094
1 files changed, 1094 insertions, 0 deletions
diff --git a/dom/media/systemservices/CamerasParent.cpp b/dom/media/systemservices/CamerasParent.cpp
new file mode 100644
index 000000000..808a076d7
--- /dev/null
+++ b/dom/media/systemservices/CamerasParent.cpp
@@ -0,0 +1,1094 @@
+/* -*- 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 <process.h>
+#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<InputObserver> self(this);
+ RefPtr<nsIRunnable> 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<CamerasParent> 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<FrameSizeChangeRunnable> 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<CamerasParent> mParent;
+ CaptureEngine mCapEngine;
+ int mCapId;
+ ShmemBuffer mBuffer;
+ mozilla::UniquePtr<unsigned char[]> 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<nsIObserverService> 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<Runnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> webrtc_runnable =
+ media::NewRunnableFrom([self]() -> nsresult {
+ MonitorAutoLock lock(self->mThreadMonitor);
+ self->CloseEngines();
+ self->mThreadMonitor.NotifyAll();
+ return NS_OK;
+ });
+ DebugOnly<nsresult> 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<Runnable> 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<DeliverFrameRunnable> 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<uint8_t*>(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<webrtc::CaptureDeviceInfo>(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<InputObserver>* 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<CaptureEngine>(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<CamerasParent> self(this);
+ RefPtr<Runnable> webrtc_runnable =
+ media::NewRunnableFrom([self, aCapEngine]() -> nsresult {
+ int num = -1;
+ if (self->EnsureInitialized(aCapEngine)) {
+ num = self->mEngines[aCapEngine].mPtrViECapture->NumberOfCaptureDevices();
+ }
+ RefPtr<nsIRunnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> webrtc_runnable =
+ media::NewRunnableFrom([self, aCapEngine]() -> nsresult {
+ bool result = self->EnsureInitialized(aCapEngine);
+
+ RefPtr<nsIRunnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> 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<nsIRunnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> 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<nsIRunnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> 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<nsIRunnable> 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<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> 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<nsIPermissionManager> mgr =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIIOService> ioServ(do_GetIOService());
+ nsCOMPtr<nsIURI> uri;
+ rv = ioServ->NewURI(aOrigin, nullptr, nullptr, getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) {
+ // Permanent permissions are only retrievable via principal, not uri
+ nsCOMPtr<nsIPrincipal> 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<CamerasParent> self(this);
+ RefPtr<Runnable> 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<Runnable> 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<nsIRunnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> webrtc_runnable =
+ media::NewRunnableFrom([self, aCapEngine, numdev]() -> nsresult {
+ int error = self->ReleaseCaptureDevice(aCapEngine, numdev);
+ RefPtr<nsIRunnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> 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<CaptureEngine>(aCapEngine), capnum, self));
+ render = static_cast<webrtc::ExternalRenderer*>(*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<webrtc::RawVideoType>(ipcCaps.rawType());
+ capability.codecType = static_cast<webrtc::VideoCodecType>(ipcCaps.codecType());
+ capability.interlaced = ipcCaps.interlaced();
+
+ if (!error) {
+ error = helper->mPtrViECapture->StartCapture(capnum, capability);
+ }
+ if (!error) {
+ helper->mEngineIsRunning = true;
+ }
+ }
+ RefPtr<nsIRunnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> 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<CamerasParent> self(this);
+ RefPtr<Runnable> threadStart =
+ media::NewRunnableFrom([self]() -> nsresult {
+ // Register thread shutdown observer
+ nsCOMPtr<nsIObserverService> 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>
+CamerasParent::Create() {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ RefPtr<CamerasParent> camerasParent = new CamerasParent();
+ return camerasParent.forget();
+}
+
+}
+}