/* -*- 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 "CamerasChild.h" #include "webrtc/video_engine/include/vie_capture.h" #undef FF #include "mozilla/Assertions.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/PBackgroundChild.h" #include "mozilla/Logging.h" #include "mozilla/SyncRunnable.h" #include "mozilla/WeakPtr.h" #include "mozilla/Unused.h" #include "MediaUtils.h" #include "nsThreadUtils.h" #undef LOG #undef LOG_ENABLED mozilla::LazyLogModule gCamerasChildLog("CamerasChild"); #define LOG(args) MOZ_LOG(gCamerasChildLog, mozilla::LogLevel::Debug, args) #define LOG_ENABLED() MOZ_LOG_TEST(gCamerasChildLog, mozilla::LogLevel::Debug) #define FAKE_ONDEVICECHANGE_EVENT_PERIOD_IN_MS 5000 #define FAKE_ONDEVICECHANGE_EVENT_REPEAT_COUNT 30 namespace mozilla { namespace camera { CamerasSingleton::CamerasSingleton() : mCamerasMutex("CamerasSingleton::mCamerasMutex"), mCameras(nullptr), mCamerasChildThread(nullptr), mFakeDeviceChangeEventThread(nullptr), mInShutdown(false) { LOG(("CamerasSingleton: %p", this)); } CamerasSingleton::~CamerasSingleton() { LOG(("~CamerasSingleton: %p", this)); } class FakeOnDeviceChangeEventRunnable : public Runnable { public: explicit FakeOnDeviceChangeEventRunnable(uint8_t counter) : mCounter(counter) {} NS_IMETHOD Run() override { OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); CamerasChild* child = CamerasSingleton::Child(); if (child) { child->OnDeviceChange(); if (mCounter++ < FAKE_ONDEVICECHANGE_EVENT_REPEAT_COUNT) { RefPtr evt = new FakeOnDeviceChangeEventRunnable(mCounter); CamerasSingleton::FakeDeviceChangeEventThread()->DelayedDispatch(evt.forget(), FAKE_ONDEVICECHANGE_EVENT_PERIOD_IN_MS); } } return NS_OK; } private: uint8_t mCounter; }; class InitializeIPCThread : public Runnable { public: InitializeIPCThread() : mCamerasChild(nullptr) {} NS_IMETHOD Run() override { // Try to get the PBackground handle ipc::PBackgroundChild* existingBackgroundChild = ipc::BackgroundChild::GetForCurrentThread(); // If it's not spun up yet, block until it is, and retry if (!existingBackgroundChild) { LOG(("No existingBackgroundChild")); existingBackgroundChild = ipc::BackgroundChild::SynchronouslyCreateForCurrentThread(); LOG(("BackgroundChild: %p", existingBackgroundChild)); if (!existingBackgroundChild) { return NS_ERROR_FAILURE; } } // Create CamerasChild // We will be returning the resulting pointer (synchronously) to our caller. mCamerasChild = static_cast(existingBackgroundChild->SendPCamerasConstructor()); return NS_OK; } CamerasChild* GetCamerasChild() { return mCamerasChild; } private: CamerasChild* mCamerasChild; }; CamerasChild* GetCamerasChild() { CamerasSingleton::Mutex().AssertCurrentThreadOwns(); if (!CamerasSingleton::Child()) { MOZ_ASSERT(!NS_IsMainThread(), "Should not be on the main Thread"); MOZ_ASSERT(!CamerasSingleton::Thread()); LOG(("No sCameras, setting up IPC Thread")); nsresult rv = NS_NewNamedThread("Cameras IPC", getter_AddRefs(CamerasSingleton::Thread())); if (NS_FAILED(rv)) { LOG(("Error launching IPC Thread")); return nullptr; } // At this point we are in the MediaManager thread, and the thread we are // dispatching to is the specific Cameras IPC thread that was just made // above, so now we will fire off a runnable to run // BackgroundChild::SynchronouslyCreateForCurrentThread there, while we // block in this thread. // We block until the following happens in the Cameras IPC thread: // 1) Creation of PBackground finishes // 2) Creation of PCameras finishes by sending a message to the parent RefPtr runnable = new InitializeIPCThread(); RefPtr sr = new SyncRunnable(runnable); sr->DispatchToThread(CamerasSingleton::Thread()); CamerasSingleton::Child() = runnable->GetCamerasChild(); } if (!CamerasSingleton::Child()) { LOG(("Failed to set up CamerasChild, are we in shutdown?")); } return CamerasSingleton::Child(); } CamerasChild* GetCamerasChildIfExists() { OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); return CamerasSingleton::Child(); } int CamerasChild::AddDeviceChangeCallback(DeviceChangeCallback* aCallback) { // According to the spec, if the script sets // navigator.mediaDevices.ondevicechange and the permission state is // "always granted", the User Agent MUST fires a devicechange event when // a new media input device is made available, even the script never // call getusermedia or enumerateDevices. // In order to detect the event, we need to init the camera engine. // Currently EnsureInitialized(aCapEngine) is only called when one of // CamerasaParent api, e.g., RecvNumberOfCaptureDevices(), is called. // So here we setup camera engine via EnsureInitialized(aCapEngine) EnsureInitialized(CameraEngine); return DeviceChangeCallback::AddDeviceChangeCallback(aCallback); } bool CamerasChild::RecvReplyFailure(void) { LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = false; monitor.Notify(); return true; } bool CamerasChild::RecvReplySuccess(void) { LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; monitor.Notify(); return true; } bool CamerasChild::RecvReplyNumberOfCapabilities(const int& numdev) { LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; mReplyInteger = numdev; monitor.Notify(); return true; } // Helper function to dispatch calls to the IPC Thread and // CamerasChild object. Takes the needed locks and dispatches. // Takes a "failed" value and a reference to the output variable // as parameters, will return the right one depending on whether // dispatching succeeded. template class LockAndDispatch { public: LockAndDispatch(CamerasChild* aCamerasChild, const char* aRequestingFunc, nsIRunnable *aRunnable, const T& aFailureValue = T(-1), const T& aSuccessValue = T(0)) : mCamerasChild(aCamerasChild), mRequestingFunc(aRequestingFunc), mRunnable(aRunnable), mReplyLock(aCamerasChild->mReplyMonitor), mRequestLock(aCamerasChild->mRequestMutex), mSuccess(true), mFailureValue(aFailureValue), mSuccessValue(aSuccessValue) { Dispatch(); } const T& ReturnValue() const { if (mSuccess) { return mSuccessValue; } else { return mFailureValue; } } const bool& Success() const { return mSuccess; } private: void Dispatch() { if (!mCamerasChild->DispatchToParent(mRunnable, mReplyLock)) { LOG(("Cameras dispatch for IPC failed in %s", mRequestingFunc)); mSuccess = false; } } CamerasChild* mCamerasChild; const char* mRequestingFunc; nsIRunnable* mRunnable; // Prevent concurrent use of the reply variables by holding // the mReplyMonitor. Note that this is unlocked while waiting for // the reply to be filled in, necessitating the additional mRequestLock/Mutex; MonitorAutoLock mReplyLock; MutexAutoLock mRequestLock; bool mSuccess; const T& mFailureValue; const T& mSuccessValue; }; bool CamerasChild::DispatchToParent(nsIRunnable* aRunnable, MonitorAutoLock& aMonitor) { CamerasSingleton::Mutex().AssertCurrentThreadOwns(); CamerasSingleton::Thread()->Dispatch(aRunnable, NS_DISPATCH_NORMAL); // We can't see if the send worked, so we need to be able to bail // out on shutdown (when it failed and we won't get a reply). if (!mIPCIsAlive) { return false; } // Guard against spurious wakeups. mReceivedReply = false; // Wait for a reply do { aMonitor.Wait(); } while (!mReceivedReply && mIPCIsAlive); if (!mReplySuccess) { return false; } return true; } int CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine, const char* deviceUniqueIdUTF8) { LOG((__PRETTY_FUNCTION__)); LOG(("NumberOfCapabilities for %s", deviceUniqueIdUTF8)); nsCString unique_id(deviceUniqueIdUTF8); RefPtr deathGrip = this; nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendNumberOfCapabilities, aCapEngine, unique_id); LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); LOG(("Capture capability count: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } int CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine) { LOG((__PRETTY_FUNCTION__)); nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendNumberOfCaptureDevices, aCapEngine); LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); LOG(("Capture Devices: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } bool CamerasChild::RecvReplyNumberOfCaptureDevices(const int& numdev) { LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; mReplyInteger = numdev; monitor.Notify(); return true; } int CamerasChild::EnsureInitialized(CaptureEngine aCapEngine) { LOG((__PRETTY_FUNCTION__)); RefPtr deathGrip = this; nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendEnsureInitialized, aCapEngine); LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); LOG(("Capture Devices: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } int CamerasChild::GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int capability_number, webrtc::CaptureCapability& capability) { LOG(("GetCaptureCapability: %s %d", unique_idUTF8, capability_number)); RefPtr deathGrip = this; nsCString unique_id(unique_idUTF8); nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendGetCaptureCapability, aCapEngine, unique_id, capability_number); LockAndDispatch<> dispatcher(this, __func__, runnable); if (dispatcher.Success()) { capability = mReplyCapability; } return dispatcher.ReturnValue(); } bool CamerasChild::RecvReplyGetCaptureCapability(const CaptureCapability& ipcCapability) { LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; mReplyCapability.width = ipcCapability.width(); mReplyCapability.height = ipcCapability.height(); mReplyCapability.maxFPS = ipcCapability.maxFPS(); mReplyCapability.expectedCaptureDelay = ipcCapability.expectedCaptureDelay(); mReplyCapability.rawType = static_cast(ipcCapability.rawType()); mReplyCapability.codecType = static_cast(ipcCapability.codecType()); mReplyCapability.interlaced = ipcCapability.interlaced(); monitor.Notify(); return true; } int CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine, unsigned int list_number, char* device_nameUTF8, const unsigned int device_nameUTF8Length, char* unique_idUTF8, const unsigned int unique_idUTF8Length, bool* scary) { LOG((__PRETTY_FUNCTION__)); RefPtr deathGrip = this; nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendGetCaptureDevice, aCapEngine, list_number); LockAndDispatch<> dispatcher(this, __func__, runnable); if (dispatcher.Success()) { base::strlcpy(device_nameUTF8, mReplyDeviceName.get(), device_nameUTF8Length); base::strlcpy(unique_idUTF8, mReplyDeviceID.get(), unique_idUTF8Length); if (scary) { *scary = mReplyScary; } LOG(("Got %s name %s id", device_nameUTF8, unique_idUTF8)); } return dispatcher.ReturnValue(); } bool CamerasChild::RecvReplyGetCaptureDevice(const nsCString& device_name, const nsCString& device_id, const bool& scary) { LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; mReplyDeviceName = device_name; mReplyDeviceID = device_id; mReplyScary = scary; monitor.Notify(); return true; } int CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int unique_idUTF8Length, int& capture_id, const nsACString& aOrigin) { LOG((__PRETTY_FUNCTION__)); RefPtr deathGrip = this; nsCString unique_id(unique_idUTF8); nsCString origin(aOrigin); nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendAllocateCaptureDevice, aCapEngine, unique_id, origin); LockAndDispatch<> dispatcher(this, __func__, runnable); if (dispatcher.Success()) { LOG(("Capture Device allocated: %d", mReplyInteger)); capture_id = mReplyInteger; } return dispatcher.ReturnValue(); } bool CamerasChild::RecvReplyAllocateCaptureDevice(const int& numdev) { LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; mReplyInteger = numdev; monitor.Notify(); return true; } int CamerasChild::ReleaseCaptureDevice(CaptureEngine aCapEngine, const int capture_id) { LOG((__PRETTY_FUNCTION__)); RefPtr deathGrip = this; nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendReleaseCaptureDevice, aCapEngine, capture_id); LockAndDispatch<> dispatcher(this, __func__, runnable); return dispatcher.ReturnValue(); } void CamerasChild::AddCallback(const CaptureEngine aCapEngine, const int capture_id, webrtc::ExternalRenderer* render) { MutexAutoLock lock(mCallbackMutex); CapturerElement ce; ce.engine = aCapEngine; ce.id = capture_id; ce.callback = render; mCallbacks.AppendElement(ce); } void CamerasChild::RemoveCallback(const CaptureEngine aCapEngine, const int capture_id) { MutexAutoLock lock(mCallbackMutex); for (unsigned int i = 0; i < mCallbacks.Length(); i++) { CapturerElement ce = mCallbacks[i]; if (ce.engine == aCapEngine && ce.id == capture_id) { mCallbacks.RemoveElementAt(i); break; } } } int CamerasChild::StartCapture(CaptureEngine aCapEngine, const int capture_id, webrtc::CaptureCapability& webrtcCaps, webrtc::ExternalRenderer* cb) { LOG((__PRETTY_FUNCTION__)); AddCallback(aCapEngine, capture_id, cb); CaptureCapability capCap(webrtcCaps.width, webrtcCaps.height, webrtcCaps.maxFPS, webrtcCaps.expectedCaptureDelay, webrtcCaps.rawType, webrtcCaps.codecType, webrtcCaps.interlaced); RefPtr deathGrip = this; nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendStartCapture, aCapEngine, capture_id, capCap); LockAndDispatch<> dispatcher(this, __func__, runnable); return dispatcher.ReturnValue(); } int CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id) { LOG((__PRETTY_FUNCTION__)); RefPtr deathGrip = this; nsCOMPtr runnable = mozilla::NewNonOwningRunnableMethod (this, &CamerasChild::SendStopCapture, aCapEngine, capture_id); LockAndDispatch<> dispatcher(this, __func__, runnable); if (dispatcher.Success()) { RemoveCallback(aCapEngine, capture_id); } return dispatcher.ReturnValue(); } void Shutdown(void) { OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); CamerasChild* child = CamerasSingleton::Child(); if (!child) { // We don't want to cause everything to get fired up if we're // really already shut down. LOG(("Shutdown when already shut down")); return; } child->ShutdownAll(); } class ShutdownRunnable : public Runnable { public: explicit ShutdownRunnable(already_AddRefed&& aReplyEvent) : mReplyEvent(aReplyEvent) {}; NS_IMETHOD Run() override { LOG(("Closing BackgroundChild")); ipc::BackgroundChild::CloseForCurrentThread(); NS_DispatchToMainThread(mReplyEvent.forget()); return NS_OK; } private: RefPtr mReplyEvent; }; void CamerasChild::ShutdownAll() { // Called with CamerasSingleton::Mutex() held ShutdownParent(); ShutdownChild(); } void CamerasChild::ShutdownParent() { // Called with CamerasSingleton::Mutex() held { MonitorAutoLock monitor(mReplyMonitor); mIPCIsAlive = false; monitor.NotifyAll(); } if (CamerasSingleton::Thread()) { LOG(("Dispatching actor deletion")); // Delete the parent actor. // CamerasChild (this) will remain alive and is only deleted by the // IPC layer when SendAllDone returns. RefPtr deathGrip = this; nsCOMPtr deleteRunnable = mozilla::NewNonOwningRunnableMethod(this, &CamerasChild::SendAllDone); CamerasSingleton::Thread()->Dispatch(deleteRunnable, NS_DISPATCH_NORMAL); } else { LOG(("ShutdownParent called without PBackground thread")); } } void CamerasChild::ShutdownChild() { // Called with CamerasSingleton::Mutex() held if (CamerasSingleton::Thread()) { LOG(("PBackground thread exists, dispatching close")); // Dispatch closing the IPC thread back to us when the // BackgroundChild is closed. RefPtr runnable = new ShutdownRunnable(NewRunnableMethod(CamerasSingleton::Thread(), &nsIThread::Shutdown)); CamerasSingleton::Thread()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); } else { LOG(("Shutdown called without PBackground thread")); } LOG(("Erasing sCameras & thread refs (original thread)")); CamerasSingleton::Child() = nullptr; CamerasSingleton::Thread() = nullptr; if (CamerasSingleton::FakeDeviceChangeEventThread()) { RefPtr runnable = new ShutdownRunnable(NewRunnableMethod(CamerasSingleton::FakeDeviceChangeEventThread(), &nsIThread::Shutdown)); CamerasSingleton::FakeDeviceChangeEventThread()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); } CamerasSingleton::FakeDeviceChangeEventThread() = nullptr; } bool CamerasChild::RecvDeliverFrame(const CaptureEngine& capEngine, const int& capId, mozilla::ipc::Shmem&& shmem, const size_t& size, const uint32_t& time_stamp, const int64_t& ntp_time, const int64_t& render_time) { MutexAutoLock lock(mCallbackMutex); if (Callback(capEngine, capId)) { unsigned char* image = shmem.get(); Callback(capEngine, capId)->DeliverFrame(image, size, time_stamp, ntp_time, render_time, nullptr); } else { LOG(("DeliverFrame called with dead callback")); } SendReleaseFrame(shmem); return true; } bool CamerasChild::RecvDeviceChange() { this->OnDeviceChange(); return true; } int CamerasChild::SetFakeDeviceChangeEvents() { CamerasSingleton::Mutex().AssertCurrentThreadOwns(); if(!CamerasSingleton::FakeDeviceChangeEventThread()) { nsresult rv = NS_NewNamedThread("Fake DC Event", getter_AddRefs(CamerasSingleton::FakeDeviceChangeEventThread())); if (NS_FAILED(rv)) { LOG(("Error launching Fake OnDeviceChange Event Thread")); return -1; } } // To simulate the devicechange event in mochitest, // we fire a fake devicechange event in Camera IPC thread periodically RefPtr evt = new FakeOnDeviceChangeEventRunnable(0); CamerasSingleton::FakeDeviceChangeEventThread()->Dispatch(evt.forget(), NS_DISPATCH_NORMAL); return 0; } bool CamerasChild::RecvFrameSizeChange(const CaptureEngine& capEngine, const int& capId, const int& w, const int& h) { LOG((__PRETTY_FUNCTION__)); MutexAutoLock lock(mCallbackMutex); if (Callback(capEngine, capId)) { Callback(capEngine, capId)->FrameSizeChange(w, h, 0); } else { LOG(("Frame size change with dead callback")); } return true; } void CamerasChild::ActorDestroy(ActorDestroyReason aWhy) { MonitorAutoLock monitor(mReplyMonitor); mIPCIsAlive = false; // Hopefully prevent us from getting stuck // on replies that'll never come. monitor.NotifyAll(); } CamerasChild::CamerasChild() : mCallbackMutex("mozilla::cameras::CamerasChild::mCallbackMutex"), mIPCIsAlive(true), mRequestMutex("mozilla::cameras::CamerasChild::mRequestMutex"), mReplyMonitor("mozilla::cameras::CamerasChild::mReplyMonitor") { LOG(("CamerasChild: %p", this)); MOZ_COUNT_CTOR(CamerasChild); } CamerasChild::~CamerasChild() { LOG(("~CamerasChild: %p", this)); if (!CamerasSingleton::InShutdown()) { OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); // In normal circumstances we've already shut down and the // following does nothing. But on fatal IPC errors we will // get destructed immediately, and should not try to reach // the parent. ShutdownChild(); } MOZ_COUNT_DTOR(CamerasChild); } webrtc::ExternalRenderer* CamerasChild::Callback(CaptureEngine aCapEngine, int capture_id) { for (unsigned int i = 0; i < mCallbacks.Length(); i++) { CapturerElement ce = mCallbacks[i]; if (ce.engine == aCapEngine && ce.id == capture_id) { return ce.callback; } } return nullptr; } } }