diff options
Diffstat (limited to 'dom/media/systemservices')
41 files changed, 6784 insertions, 0 deletions
diff --git a/dom/media/systemservices/CamerasChild.cpp b/dom/media/systemservices/CamerasChild.cpp new file mode 100644 index 000000000..0f7d1c1df --- /dev/null +++ b/dom/media/systemservices/CamerasChild.cpp @@ -0,0 +1,724 @@ +/* -*- 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) { + 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<FakeOnDeviceChangeEventRunnable> 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<mozilla::camera::CamerasChild*>(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<InitializeIPCThread> runnable = new InitializeIPCThread(); + RefPtr<SyncRunnable> 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 T = int> +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); + nsCOMPtr<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine, nsCString> + (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<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine> + (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__)); + nsCOMPtr<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine> + (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)); + nsCString unique_id(unique_idUTF8); + nsCOMPtr<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine, nsCString, unsigned int> + (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<webrtc::RawVideoType>(ipcCapability.rawType()); + mReplyCapability.codecType = static_cast<webrtc::VideoCodecType>(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__)); + nsCOMPtr<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine, unsigned int> + (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__)); + nsCString unique_id(unique_idUTF8); + nsCString origin(aOrigin); + nsCOMPtr<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine, nsCString, nsCString> + (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__)); + nsCOMPtr<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine, int> + (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); + nsCOMPtr<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine, int, CaptureCapability> + (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__)); + nsCOMPtr<nsIRunnable> runnable = + mozilla::NewNonOwningRunnableMethod<CaptureEngine, int> + (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<Runnable>&& aReplyEvent) + : mReplyEvent(aReplyEvent) {}; + + NS_IMETHOD Run() override { + LOG(("Closing BackgroundChild")); + ipc::BackgroundChild::CloseForCurrentThread(); + + NS_DispatchToMainThread(mReplyEvent.forget()); + + return NS_OK; + } + +private: + RefPtr<Runnable> 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. + nsCOMPtr<nsIRunnable> 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<ShutdownRunnable> 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<ShutdownRunnable> 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<unsigned char>(); + 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<FakeOnDeviceChangeEventRunnable> 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)); + + { + 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; +} + +} +} diff --git a/dom/media/systemservices/CamerasChild.h b/dom/media/systemservices/CamerasChild.h new file mode 100644 index 000000000..1530714e9 --- /dev/null +++ b/dom/media/systemservices/CamerasChild.h @@ -0,0 +1,250 @@ +/* -*- 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/. */ + +#ifndef mozilla_CamerasChild_h +#define mozilla_CamerasChild_h + +#include "mozilla/Move.h" +#include "mozilla/Pair.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/camera/PCamerasChild.h" +#include "mozilla/camera/PCamerasParent.h" +#include "mozilla/media/DeviceChangeCallback.h" +#include "mozilla/Mutex.h" +#include "base/singleton.h" +#include "nsCOMPtr.h" + +// conflicts with #include of scoped_ptr.h +#undef FF +#include "webrtc/common.h" +// Video Engine +#include "webrtc/video_engine/include/vie_base.h" +#include "webrtc/video_engine/include/vie_capture.h" +#include "webrtc/video_engine/include/vie_render.h" + +namespace mozilla { + +namespace ipc { +class BackgroundChildImpl; +} + +namespace camera { + +struct CapturerElement { + CaptureEngine engine; + int id; + webrtc::ExternalRenderer* callback; +}; + +// Forward declaration so we can work with pointers to it. +class CamerasChild; +// Helper class in impl that we friend. +template <class T> class LockAndDispatch; + +// We emulate the sync webrtc.org API with the help of singleton +// CamerasSingleton, which manages a pointer to an IPC object, a thread +// where IPC operations should run on, and a mutex. +// The static function Cameras() will use that Singleton to set up, +// if needed, both the thread and the associated IPC objects and return +// a pointer to the IPC object. Users can then do IPC calls on that object +// after dispatching them to aforementioned thread. + +// 2 Threads are involved in this code: +// - the MediaManager thread, which will call the (static, sync API) functions +// through MediaEngineRemoteVideoSource +// - the Cameras IPC thread, which will be doing our IPC to the parent process +// via PBackground + +// Our main complication is that we emulate a sync API while (having to do) +// async messaging. We dispatch the messages to another thread to send them +// async and hold a Monitor to wait for the result to be asynchronously received +// again. The requirement for async messaging originates on the parent side: +// it's not reasonable to block all PBackground IPC there while waiting for +// something like device enumeration to complete. + +class CamerasSingleton { +public: + CamerasSingleton(); + ~CamerasSingleton(); + + static OffTheBooksMutex& Mutex() { + return gTheInstance.get()->mCamerasMutex; + } + + static CamerasChild*& Child() { + Mutex().AssertCurrentThreadOwns(); + return gTheInstance.get()->mCameras; + } + + static nsCOMPtr<nsIThread>& Thread() { + Mutex().AssertCurrentThreadOwns(); + return gTheInstance.get()->mCamerasChildThread; + } + + static nsCOMPtr<nsIThread>& FakeDeviceChangeEventThread() { + Mutex().AssertCurrentThreadOwns(); + return gTheInstance.get()->mFakeDeviceChangeEventThread; + } + +private: + static Singleton<CamerasSingleton> gTheInstance; + + // Reinitializing CamerasChild will change the pointers below. + // We don't want this to happen in the middle of preparing IPC. + // We will be alive on destruction, so this needs to be off the books. + mozilla::OffTheBooksMutex mCamerasMutex; + + // This is owned by the IPC code, and the same code controls the lifetime. + // It will set and clear this pointer as appropriate in setup/teardown. + // We'd normally make this a WeakPtr but unfortunately the IPC code already + // uses the WeakPtr mixin in a protected base class of CamerasChild, and in + // any case the object becomes unusable as soon as IPC is tearing down, which + // will be before actual destruction. + CamerasChild* mCameras; + nsCOMPtr<nsIThread> mCamerasChildThread; + nsCOMPtr<nsIThread> mFakeDeviceChangeEventThread; +}; + +// Get a pointer to a CamerasChild object we can use to do IPC with. +// This does everything needed to set up, including starting the IPC +// channel with PBackground, blocking until thats done, and starting the +// thread to do IPC on. This will fail if we're in shutdown. On success +// it will set up the CamerasSingleton. +CamerasChild* GetCamerasChild(); + +CamerasChild* GetCamerasChildIfExists(); + +// Shut down the IPC channel and everything associated, like WebRTC. +// This is a static call because the CamerasChild object may not even +// be alive when we're called. +void Shutdown(void); + +// Obtain the CamerasChild object (if possible, i.e. not shutting down), +// and maintain a grip on the object for the duration of the call. +template <class MEM_FUN, class... ARGS> +int GetChildAndCall(MEM_FUN&& f, ARGS&&... args) +{ + OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); + CamerasChild* child = GetCamerasChild(); + if (child) { + return (child->*f)(mozilla::Forward<ARGS>(args)...); + } else { + return -1; + } +} + +class CamerasChild final : public PCamerasChild + ,public DeviceChangeCallback +{ + friend class mozilla::ipc::BackgroundChildImpl; + template <class T> friend class mozilla::camera::LockAndDispatch; + +public: + // We are owned by the PBackground thread only. CamerasSingleton + // takes a non-owning reference. + NS_INLINE_DECL_REFCOUNTING(CamerasChild) + + // IPC messages recevied, received on the PBackground thread + // these are the actual callbacks with data + virtual bool RecvDeliverFrame(const CaptureEngine&, const int&, mozilla::ipc::Shmem&&, + const size_t&, const uint32_t&, const int64_t&, + const int64_t&) override; + virtual bool RecvFrameSizeChange(const CaptureEngine&, const int&, + const int& w, const int& h) override; + + virtual bool RecvDeviceChange() override; + virtual int AddDeviceChangeCallback(DeviceChangeCallback* aCallback) override; + int SetFakeDeviceChangeEvents(); + + // these are response messages to our outgoing requests + virtual bool RecvReplyNumberOfCaptureDevices(const int&) override; + virtual bool RecvReplyNumberOfCapabilities(const int&) override; + virtual bool RecvReplyAllocateCaptureDevice(const int&) override; + virtual bool RecvReplyGetCaptureCapability(const CaptureCapability& capability) override; + virtual bool RecvReplyGetCaptureDevice(const nsCString& device_name, + const nsCString& device_id, + const bool& scary) override; + virtual bool RecvReplyFailure(void) override; + virtual bool RecvReplySuccess(void) override; + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + // the webrtc.org ViECapture calls are mirrored here, but with access + // to a specific PCameras instance to communicate over. These also + // run on the MediaManager thread + int NumberOfCaptureDevices(CaptureEngine aCapEngine); + int NumberOfCapabilities(CaptureEngine aCapEngine, + const char* deviceUniqueIdUTF8); + int ReleaseCaptureDevice(CaptureEngine aCapEngine, + const int capture_id); + int StartCapture(CaptureEngine aCapEngine, + const int capture_id, webrtc::CaptureCapability& capability, + webrtc::ExternalRenderer* func); + int StopCapture(CaptureEngine aCapEngine, const int capture_id); + int AllocateCaptureDevice(CaptureEngine aCapEngine, + const char* unique_idUTF8, + const unsigned int unique_idUTF8Length, + int& capture_id, + const nsACString& aOrigin); + int GetCaptureCapability(CaptureEngine aCapEngine, + const char* unique_idUTF8, + const unsigned int capability_number, + webrtc::CaptureCapability& capability); + int 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 = nullptr); + void ShutdownAll(); + int EnsureInitialized(CaptureEngine aCapEngine); + + webrtc::ExternalRenderer* Callback(CaptureEngine aCapEngine, int capture_id); + +private: + CamerasChild(); + ~CamerasChild(); + // Dispatch a Runnable to the PCamerasParent, by executing it on the + // decidecated Cameras IPC/PBackground thread. + bool DispatchToParent(nsIRunnable* aRunnable, + MonitorAutoLock& aMonitor); + void AddCallback(const CaptureEngine aCapEngine, const int capture_id, + webrtc::ExternalRenderer* render); + void RemoveCallback(const CaptureEngine aCapEngine, const int capture_id); + void ShutdownParent(); + void ShutdownChild(); + + nsTArray<CapturerElement> mCallbacks; + // Protects the callback arrays + Mutex mCallbackMutex; + + bool mIPCIsAlive; + + // Hold to prevent multiple outstanding requests. We don't use + // request IDs so we only support one at a time. Don't want try + // to use the webrtc.org API from multiple threads simultanously. + // The monitor below isn't sufficient for this, as it will drop + // the lock when Wait-ing for a response, allowing us to send a new + // request. The Notify on receiving the response will then unblock + // both waiters and one will be guaranteed to get the wrong result. + // Take this one before taking mReplyMonitor. + Mutex mRequestMutex; + // Hold to wait for an async response to our calls + Monitor mReplyMonitor; + // Async response valid? + bool mReceivedReply; + // Async responses data contents; + bool mReplySuccess; + int mReplyInteger; + webrtc::CaptureCapability mReplyCapability; + nsCString mReplyDeviceName; + nsCString mReplyDeviceID; + bool mReplyScary; +}; + +} // namespace camera +} // namespace mozilla + +#endif // mozilla_CamerasChild_h 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(); +} + +} +} diff --git a/dom/media/systemservices/CamerasParent.h b/dom/media/systemservices/CamerasParent.h new file mode 100644 index 000000000..2c1869410 --- /dev/null +++ b/dom/media/systemservices/CamerasParent.h @@ -0,0 +1,187 @@ +/* -*- 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/. */ + +#ifndef mozilla_CamerasParent_h +#define mozilla_CamerasParent_h + +#include "nsIObserver.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/camera/PCamerasParent.h" +#include "mozilla/ipc/Shmem.h" +#include "mozilla/ShmemPool.h" +#include "mozilla/Atomics.h" + +// conflicts with #include of scoped_ptr.h +#undef FF +#include "webrtc/common.h" +// Video Engine +#include "webrtc/video_engine/include/vie_base.h" +#include "webrtc/video_engine/include/vie_capture.h" +#include "webrtc/video_engine/include/vie_render.h" +#include "CamerasChild.h" + +#include "base/thread.h" + +namespace mozilla { +namespace camera { + +class CamerasParent; + +class CallbackHelper : public webrtc::ExternalRenderer +{ +public: + CallbackHelper(CaptureEngine aCapEng, int aCapId, CamerasParent *aParent) + : mCapEngine(aCapEng), mCapturerId(aCapId), mParent(aParent) {}; + + // ViEExternalRenderer implementation. These callbacks end up + // running on the VideoCapture thread. + virtual int FrameSizeChange(unsigned int w, unsigned int h, + unsigned int streams) override; + virtual int DeliverFrame(unsigned char* buffer, + size_t size, + uint32_t time_stamp, + int64_t ntp_time, + int64_t render_time, + void *handle) override; + virtual int DeliverI420Frame(const webrtc::I420VideoFrame& webrtc_frame) override; + virtual bool IsTextureSupported() override { return false; }; + + friend CamerasParent; + +private: + CaptureEngine mCapEngine; + int mCapturerId; + CamerasParent *mParent; +}; + +class EngineHelper +{ +public: + EngineHelper() : + mEngine(nullptr), mPtrViEBase(nullptr), mPtrViECapture(nullptr), + mPtrViERender(nullptr), mEngineIsRunning(false) {}; + + webrtc::VideoEngine *mEngine; + webrtc::ViEBase *mPtrViEBase; + webrtc::ViECapture *mPtrViECapture; + webrtc::ViERender *mPtrViERender; + + // The webrtc code keeps a reference to this one. + webrtc::Config mConfig; + + // Engine alive + bool mEngineIsRunning; +}; + +class InputObserver : public webrtc::ViEInputObserver +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InputObserver) + + explicit InputObserver(CamerasParent* aParent) + : mParent(aParent) {}; + virtual void DeviceChange(); + + friend CamerasParent; + +private: + ~InputObserver() {} + + RefPtr<CamerasParent> mParent; +}; + +class CamerasParent : public PCamerasParent, + public nsIObserver +{ + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + +public: + static already_AddRefed<CamerasParent> Create(); + + // Messages received form the child. These run on the IPC/PBackground thread. + virtual bool RecvAllocateCaptureDevice(const CaptureEngine&, const nsCString&, + const nsCString&) override; + virtual bool RecvReleaseCaptureDevice(const CaptureEngine&, + const int&) override; + virtual bool RecvNumberOfCaptureDevices(const CaptureEngine&) override; + virtual bool RecvNumberOfCapabilities(const CaptureEngine&, + const nsCString&) override; + virtual bool RecvGetCaptureCapability(const CaptureEngine&, const nsCString&, + const int&) override; + virtual bool RecvGetCaptureDevice(const CaptureEngine&, const int&) override; + virtual bool RecvStartCapture(const CaptureEngine&, const int&, + const CaptureCapability&) override; + virtual bool RecvStopCapture(const CaptureEngine&, const int&) override; + virtual bool RecvReleaseFrame(mozilla::ipc::Shmem&&) override; + virtual bool RecvAllDone() override; + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual bool RecvEnsureInitialized(const CaptureEngine&) override; + + nsIThread* GetBackgroundThread() { return mPBackgroundThread; }; + bool IsShuttingDown() { return !mChildIsAlive + || mDestroyed + || !mWebRTCAlive; }; + ShmemBuffer GetBuffer(size_t aSize); + + // helper to forward to the PBackground thread + int DeliverFrameOverIPC(CaptureEngine capEng, + int cap_id, + ShmemBuffer buffer, + unsigned char* altbuffer, + size_t size, + uint32_t time_stamp, + int64_t ntp_time, + int64_t render_time); + + + CamerasParent(); + +protected: + virtual ~CamerasParent(); + + // We use these helpers for shutdown and for the respective IPC commands. + void StopCapture(const CaptureEngine& aCapEngine, const int& capnum); + int ReleaseCaptureDevice(const CaptureEngine& aCapEngine, const int& capnum); + + bool SetupEngine(CaptureEngine aCapEngine); + bool EnsureInitialized(int aEngine); + void CloseEngines(); + void StopIPC(); + void StopVideoCapture(); + // Can't take already_AddRefed because it can fail in stupid ways. + nsresult DispatchToVideoCaptureThread(Runnable* event); + + EngineHelper mEngines[CaptureEngine::MaxEngine]; + nsTArray<CallbackHelper*> mCallbacks; + + // image buffers + mozilla::ShmemPool mShmemPool; + + // PBackground parent thread + nsCOMPtr<nsIThread> mPBackgroundThread; + + // Monitors creation of the thread below + Monitor mThreadMonitor; + + // video processing thread - where webrtc.org capturer code runs + base::Thread* mVideoCaptureThread; + + // Shutdown handling + bool mChildIsAlive; + bool mDestroyed; + // Above 2 are PBackground only, but this is potentially + // read cross-thread. + mozilla::Atomic<bool> mWebRTCAlive; + nsTArray<RefPtr<InputObserver>> mObservers; +}; + +PCamerasParent* CreateCamerasParent(); + +} // namespace camera +} // namespace mozilla + +#endif // mozilla_CameraParent_h diff --git a/dom/media/systemservices/CamerasTypes.h b/dom/media/systemservices/CamerasTypes.h new file mode 100644 index 000000000..9ae564e9a --- /dev/null +++ b/dom/media/systemservices/CamerasTypes.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ + +#ifndef mozilla_CamerasTypes_h +#define mozilla_CamerasTypes_h + +#include "ipc/IPCMessageUtils.h" + +namespace mozilla { + +namespace camera { + +enum CaptureEngine : int { + InvalidEngine = 0, + ScreenEngine, + BrowserEngine, + WinEngine, + AppEngine, + CameraEngine, + MaxEngine +}; + +} // namespace camera +} // namespace mozilla + +namespace IPC { +template<> +struct ParamTraits<mozilla::camera::CaptureEngine> : + public ContiguousEnumSerializer<mozilla::camera::CaptureEngine, + mozilla::camera::CaptureEngine::InvalidEngine, + mozilla::camera::CaptureEngine::MaxEngine> +{ }; +} + +#endif // mozilla_CamerasTypes_h diff --git a/dom/media/systemservices/DeviceChangeCallback.h b/dom/media/systemservices/DeviceChangeCallback.h new file mode 100644 index 000000000..d153e9cd4 --- /dev/null +++ b/dom/media/systemservices/DeviceChangeCallback.h @@ -0,0 +1,61 @@ +/* -*- 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/. */ + +#ifndef mozilla_DeviceChangeCallback_h +#define mozilla_DeviceChangeCallback_h + +namespace mozilla { + +class DeviceChangeCallback +{ +public: + virtual void OnDeviceChange() + { + MutexAutoLock lock(mCallbackMutex); + for (DeviceChangeCallback* observer : mDeviceChangeCallbackList) + { + observer->OnDeviceChange(); + } + } + + virtual int AddDeviceChangeCallback(DeviceChangeCallback* aCallback) + { + MutexAutoLock lock(mCallbackMutex); + if (mDeviceChangeCallbackList.IndexOf(aCallback) == mDeviceChangeCallbackList.NoIndex) + mDeviceChangeCallbackList.AppendElement(aCallback); + return 0; + } + + virtual int RemoveDeviceChangeCallback(DeviceChangeCallback* aCallback) + { + MutexAutoLock lock(mCallbackMutex); + return RemoveDeviceChangeCallbackLocked(aCallback); + } + + virtual int RemoveDeviceChangeCallbackLocked(DeviceChangeCallback* aCallback) + { + mCallbackMutex.AssertCurrentThreadOwns(); + if (mDeviceChangeCallbackList.IndexOf(aCallback) != mDeviceChangeCallbackList.NoIndex) + mDeviceChangeCallbackList.RemoveElement(aCallback); + return 0; + } + + DeviceChangeCallback() : mCallbackMutex("mozilla::media::DeviceChangeCallback::mCallbackMutex") + { + } + + virtual ~DeviceChangeCallback() + { + } + +protected: + nsTArray<DeviceChangeCallback*> mDeviceChangeCallbackList; + Mutex mCallbackMutex; +}; + +} // namespace mozilla + +#endif // mozilla_DeviceChangeCallback_h diff --git a/dom/media/systemservices/LoadManager.cpp b/dom/media/systemservices/LoadManager.cpp new file mode 100644 index 000000000..f0f4f83a7 --- /dev/null +++ b/dom/media/systemservices/LoadManager.cpp @@ -0,0 +1,227 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "LoadManager.h" +#include "LoadMonitor.h" +#include "nsString.h" +#include "mozilla/Logging.h" +#include "prtime.h" +#include "prinrval.h" +#include "prsystem.h" + +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsReadableUtils.h" +#include "nsIObserverService.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ArrayUtils.h" + +// MOZ_LOG=LoadManager:5 +mozilla::LazyLogModule gLoadManagerLog("LoadManager"); +#undef LOG +#undef LOG_ENABLED +#define LOG(args) MOZ_LOG(gLoadManagerLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Verbose) + +namespace mozilla { + +/* static */ StaticRefPtr<LoadManagerSingleton> LoadManagerSingleton::sSingleton; + +NS_IMPL_ISUPPORTS(LoadManagerSingleton, nsIObserver) + + +LoadManagerSingleton::LoadManagerSingleton(bool aEncoderOnly, + int aLoadMeasurementInterval, + int aAveragingMeasurements, + float aHighLoadThreshold, + float aLowLoadThreshold) + : mLock("LoadManager"), + mCurrentState(webrtc::kLoadNormal), + mOveruseActive(false), + mLoadSum(0.0f), + mLoadSumMeasurements(0), + mLoadMeasurementInterval(aLoadMeasurementInterval), + mAveragingMeasurements(aAveragingMeasurements), + mHighLoadThreshold(aHighLoadThreshold), + mLowLoadThreshold(aLowLoadThreshold) +{ + LOG(("LoadManager - Initializing (%dms x %d, %f, %f)", + mLoadMeasurementInterval, mAveragingMeasurements, + mHighLoadThreshold, mLowLoadThreshold)); + MOZ_ASSERT(mHighLoadThreshold > mLowLoadThreshold); + if (!aEncoderOnly) { + mLoadMonitor = new LoadMonitor(mLoadMeasurementInterval); + mLoadMonitor->Init(mLoadMonitor); + mLoadMonitor->SetLoadChangeCallback(this); + } + + mLastStateChange = TimeStamp::Now(); + for (auto &in_state : mTimeInState) { + in_state = 0; + } +} + +LoadManagerSingleton::~LoadManagerSingleton() +{ + LOG(("LoadManager: shutting down LoadMonitor")); + MOZ_ASSERT(!mLoadMonitor, "why wasn't the LoadMonitor shut down in xpcom-shutdown?"); + if (mLoadMonitor) { + mLoadMonitor->Shutdown(); + } +} + +nsresult +LoadManagerSingleton::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + NS_ASSERTION(NS_IsMainThread(), "Observer invoked off the main thread"); + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + + if (!strcmp(aTopic, "xpcom-shutdown")) { + obs->RemoveObserver(this, "xpcom-shutdown"); + { + MutexAutoLock lock(mLock); + mObservers.Clear(); + } + if (mLoadMonitor) { + mLoadMonitor->Shutdown(); + mLoadMonitor = nullptr; + } + + LOG(("Releasing LoadManager singleton and thread")); + // Note: won't be released immediately as the Observer has a ref to us + sSingleton = nullptr; + } + return NS_OK; +} + +void +LoadManagerSingleton::LoadChanged(float aSystemLoad, float aProcesLoad) +{ + MutexAutoLock lock(mLock); + // Update total load, and total amount of measured seconds. + mLoadSum += aSystemLoad; + mLoadSumMeasurements++; + + if (mLoadSumMeasurements >= mAveragingMeasurements) { + double averagedLoad = mLoadSum / (float)mLoadSumMeasurements; + + webrtc::CPULoadState newState = mCurrentState; + + if (mOveruseActive || averagedLoad > mHighLoadThreshold) { + LOG(("LoadManager - LoadStressed")); + newState = webrtc::kLoadStressed; + } else if (averagedLoad < mLowLoadThreshold) { + LOG(("LoadManager - LoadRelaxed")); + newState = webrtc::kLoadRelaxed; + } else { + LOG(("LoadManager - LoadNormal")); + newState = webrtc::kLoadNormal; + } + + if (newState != mCurrentState) { + LoadHasChanged(newState); + } + + mLoadSum = 0; + mLoadSumMeasurements = 0; + } +} + +void +LoadManagerSingleton::OveruseDetected() +{ + LOG(("LoadManager - Overuse Detected")); + MutexAutoLock lock(mLock); + mOveruseActive = true; + if (mCurrentState != webrtc::kLoadStressed) { + LoadHasChanged(webrtc::kLoadStressed); + } +} + +void +LoadManagerSingleton::NormalUsage() +{ + LOG(("LoadManager - Overuse finished")); + MutexAutoLock lock(mLock); + mOveruseActive = false; +} + +void +LoadManagerSingleton::LoadHasChanged(webrtc::CPULoadState aNewState) +{ + mLock.AssertCurrentThreadOwns(); + LOG(("LoadManager - Signaling LoadHasChanged from %d to %d to %d listeners", + mCurrentState, aNewState, mObservers.Length())); + + // Record how long we spent in this state for later Telemetry or display + TimeStamp now = TimeStamp::Now(); + mTimeInState[mCurrentState] += (now - mLastStateChange).ToMilliseconds(); + mLastStateChange = now; + + mCurrentState = aNewState; + for (size_t i = 0; i < mObservers.Length(); i++) { + mObservers.ElementAt(i)->onLoadStateChanged(mCurrentState); + } +} + +void +LoadManagerSingleton::AddObserver(webrtc::CPULoadStateObserver * aObserver) +{ + LOG(("LoadManager - Adding Observer")); + MutexAutoLock lock(mLock); + mObservers.AppendElement(aObserver); +} + +void +LoadManagerSingleton::RemoveObserver(webrtc::CPULoadStateObserver * aObserver) +{ + LOG(("LoadManager - Removing Observer")); + MutexAutoLock lock(mLock); + if (!mObservers.RemoveElement(aObserver)) { + LOG(("LoadManager - Element to remove not found")); + } + if (mObservers.Length() == 0) { + // Record how long we spent in the final state for later Telemetry or display + TimeStamp now = TimeStamp::Now(); + mTimeInState[mCurrentState] += (now - mLastStateChange).ToMilliseconds(); + + float total = 0; + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mTimeInState); i++) { + total += mTimeInState[i]; + } + // Don't include short calls; we don't have reasonable load data, and + // such short calls rarely reach a stable state. Keep relatively + // short calls separate from longer ones + bool log = total > 5*PR_MSEC_PER_SEC; + bool small = log && total < 30*PR_MSEC_PER_SEC; + if (log) { + // Note: We don't care about rounding here; thus total may be < 100 + Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_RELAXED_SHORT : + Telemetry::WEBRTC_LOAD_STATE_RELAXED, + (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadRelaxed]/total * 100)); + Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_NORMAL_SHORT : + Telemetry::WEBRTC_LOAD_STATE_NORMAL, + (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadNormal]/total * 100)); + Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_STRESSED_SHORT : + Telemetry::WEBRTC_LOAD_STATE_STRESSED, + (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadStressed]/total * 100)); + } + for (auto &in_state : mTimeInState) { + in_state = 0; + } + + if (mLoadMonitor) { + // Dance to avoid deadlock on mLock! + RefPtr<LoadMonitor> loadMonitor = mLoadMonitor.forget(); + MutexAutoUnlock unlock(mLock); + + loadMonitor->Shutdown(); + } + } +} + + +} diff --git a/dom/media/systemservices/LoadManager.h b/dom/media/systemservices/LoadManager.h new file mode 100644 index 000000000..96824a308 --- /dev/null +++ b/dom/media/systemservices/LoadManager.h @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef _LOADMANAGER_H_ +#define _LOADMANAGER_H_ + +#include "LoadMonitor.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Services.h" +#include "nsTArray.h" +#include "nsIObserver.h" + +#include "webrtc/common_types.h" +#include "webrtc/video_engine/include/vie_base.h" + +extern mozilla::LazyLogModule gLoadManagerLog; + +namespace mozilla { + +class LoadManagerSingleton : public LoadNotificationCallback, + public webrtc::CPULoadStateCallbackInvoker, + public webrtc::CpuOveruseObserver, + public nsIObserver + +{ +public: + static LoadManagerSingleton* Get(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + // LoadNotificationCallback interface + void LoadChanged(float aSystemLoad, float aProcessLoad) override; + // CpuOveruseObserver interface + // Called as soon as an overuse is detected. + void OveruseDetected() override; + // Called periodically when the system is not overused any longer. + void NormalUsage() override; + // CPULoadStateCallbackInvoker interface + void AddObserver(webrtc::CPULoadStateObserver * aObserver) override; + void RemoveObserver(webrtc::CPULoadStateObserver * aObserver) override; + +private: + LoadManagerSingleton(bool aEncoderOnly, + int aLoadMeasurementInterval, + int aAveragingMeasurements, + float aHighLoadThreshold, + float aLowLoadThreshold); + ~LoadManagerSingleton(); + + void LoadHasChanged(webrtc::CPULoadState aNewState); + + RefPtr<LoadMonitor> mLoadMonitor; + + // This protects access to the mObservers list, the current state, and + // pretty much all the other members (below). + Mutex mLock; + nsTArray<webrtc::CPULoadStateObserver*> mObservers; + webrtc::CPULoadState mCurrentState; + TimeStamp mLastStateChange; + float mTimeInState[static_cast<int>(webrtc::kLoadLast)]; + + // Set when overuse was signaled to us, and hasn't been un-signaled yet. + bool mOveruseActive; + float mLoadSum; + int mLoadSumMeasurements; + // Load measurement settings + int mLoadMeasurementInterval; + int mAveragingMeasurements; + float mHighLoadThreshold; + float mLowLoadThreshold; + + static StaticRefPtr<LoadManagerSingleton> sSingleton; +}; + +class LoadManager final : public webrtc::CPULoadStateCallbackInvoker, + public webrtc::CpuOveruseObserver +{ +public: + explicit LoadManager(LoadManagerSingleton* aManager) + : mManager(aManager) + {} + ~LoadManager() {} + + void AddObserver(webrtc::CPULoadStateObserver * aObserver) override + { + mManager->AddObserver(aObserver); + } + void RemoveObserver(webrtc::CPULoadStateObserver * aObserver) override + { + mManager->RemoveObserver(aObserver); + } + void OveruseDetected() override + { + mManager->OveruseDetected(); + } + void NormalUsage() override + { + mManager->NormalUsage(); + } + +private: + RefPtr<LoadManagerSingleton> mManager; +}; + +} //namespace + +#endif /* _LOADMANAGER_H_ */ diff --git a/dom/media/systemservices/LoadManagerFactory.cpp b/dom/media/systemservices/LoadManagerFactory.cpp new file mode 100644 index 000000000..be22a75fe --- /dev/null +++ b/dom/media/systemservices/LoadManagerFactory.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "LoadManager.h" +#include "LoadManagerFactory.h" +#include "MainThreadUtils.h" +#include "nsIObserverService.h" + +#include "mozilla/Preferences.h" + +namespace mozilla { + +// Assume stored in an nsAutoPtr<> +LoadManager * +LoadManagerBuild(void) +{ + return new LoadManager(LoadManagerSingleton::Get()); +} + +/* static */ LoadManagerSingleton* +LoadManagerSingleton::Get() { + if (!sSingleton) { + MOZ_ASSERT(NS_IsMainThread()); + + bool loadEncoderOnly = + mozilla::Preferences::GetBool("media.navigator.load_adapt.encoder_only", true); + int loadMeasurementInterval = + mozilla::Preferences::GetInt("media.navigator.load_adapt.measure_interval", 1000); + int averagingSeconds = + mozilla::Preferences::GetInt("media.navigator.load_adapt.avg_seconds", 3); + float highLoadThreshold = + mozilla::Preferences::GetFloat("media.navigator.load_adapt.high_load", 0.90f); + float lowLoadThreshold = + mozilla::Preferences::GetFloat("media.navigator.load_adapt.low_load", 0.40f); + + sSingleton = new LoadManagerSingleton(loadEncoderOnly, + loadMeasurementInterval, + averagingSeconds, + highLoadThreshold, + lowLoadThreshold); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->AddObserver(sSingleton, "xpcom-shutdown", false); + } + } + return sSingleton; +} + +}; // namespace diff --git a/dom/media/systemservices/LoadManagerFactory.h b/dom/media/systemservices/LoadManagerFactory.h new file mode 100644 index 000000000..4868756f5 --- /dev/null +++ b/dom/media/systemservices/LoadManagerFactory.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef _LOADMANAGERFACTORY_H_ +#define _LOADMANAGERFACTORY_H_ + +namespace mozilla { + +class LoadManager; + +mozilla::LoadManager* LoadManagerBuild(); +void LoadManagerDestroy(mozilla::LoadManager* aLoadManager); + +} //namespace + +#endif /* _LOADMANAGERFACTORY_H_ */ diff --git a/dom/media/systemservices/LoadMonitor.cpp b/dom/media/systemservices/LoadMonitor.cpp new file mode 100644 index 000000000..7a64c4fb0 --- /dev/null +++ b/dom/media/systemservices/LoadMonitor.cpp @@ -0,0 +1,658 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "LoadMonitor.h" +#include "LoadManager.h" +#include "nsString.h" +#include "mozilla/Logging.h" +#include "prtime.h" +#include "prinrval.h" +#include "prsystem.h" +#include "prprf.h" + +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsReadableUtils.h" +#include "nsNetUtil.h" +#include "nsIInputStream.h" +#include "nsIFile.h" +#include "nsILineInputStream.h" +#include "nsIObserverService.h" +#include "nsIServiceManager.h" + +#include "mozilla/TimeStamp.h" +#include "mozilla/Services.h" + +#ifdef XP_UNIX +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#endif + +#ifdef XP_MACOSX +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#include <mach/host_info.h> +#endif + +#if defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/sysctl.h> +# if defined(__OpenBSD__) +#define KERN_CP_TIME KERN_CPTIME +# endif +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/sched.h> +#endif + +#ifdef XP_WIN +#include <pdh.h> +#include <tchar.h> +#pragma comment(lib, "pdh.lib") +#endif + +// MOZ_LOG=LoadManager:5 +#undef LOG +#undef LOG_ENABLED +#define LOG(args) MOZ_LOG(gLoadManagerLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Debug) +#define LOG_MANY_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Verbose) + +namespace mozilla { + +NS_IMPL_ISUPPORTS(LoadMonitor, nsIObserver) + +LoadMonitor::LoadMonitor(int aLoadUpdateInterval) + : mLoadUpdateInterval(aLoadUpdateInterval), + mLock("LoadMonitor.mLock"), + mCondVar(mLock, "LoadMonitor.mCondVar"), + mShutdownPending(false), + mLoadInfoThread(nullptr), + mSystemLoad(0.0f), + mProcessLoad(0.0f), + mLoadNotificationCallback(nullptr) +{ +} + +LoadMonitor::~LoadMonitor() +{ + Shutdown(); +} + +NS_IMETHODIMP +LoadMonitor::Observe(nsISupports* /* aSubject */, + const char* aTopic, + const char16_t* /* aData */) +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); + Shutdown(); + return NS_OK; +} + +class LoadMonitorAddObserver : public Runnable +{ +public: + explicit LoadMonitorAddObserver(RefPtr<LoadMonitor> loadMonitor) + { + mLoadMonitor = loadMonitor; + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + nsresult rv = observerService->AddObserver(mLoadMonitor, "xpcom-shutdown-threads", false); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + +private: + RefPtr<LoadMonitor> mLoadMonitor; +}; + +class LoadMonitorRemoveObserver : public Runnable +{ +public: + explicit LoadMonitorRemoveObserver(RefPtr<LoadMonitor> loadMonitor) + { + mLoadMonitor = loadMonitor; + } + + NS_IMETHOD Run() override + { + // remove xpcom shutdown observer + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (observerService) + observerService->RemoveObserver(mLoadMonitor, "xpcom-shutdown-threads"); + + return NS_OK; + } + +private: + RefPtr<LoadMonitor> mLoadMonitor; +}; + +void LoadMonitor::Shutdown() +{ + if (mLoadInfoThread) { + { + MutexAutoLock lock(mLock); + LOG(("LoadMonitor: shutting down")); + mShutdownPending = true; + mCondVar.Notify(); + } + + // Note: can't just call ->Shutdown() from here; that spins the event + // loop here, causing re-entrancy issues if we're invoked from cycle + // collection. Argh. + mLoadInfoThread = nullptr; + + RefPtr<LoadMonitorRemoveObserver> remObsRunner = new LoadMonitorRemoveObserver(this); + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(remObsRunner); + } else { + remObsRunner->Run(); + } + } +} + +#ifdef XP_WIN +static LPCTSTR TotalCounterPath = _T("\\Processor(_Total)\\% Processor Time"); + +class WinProcMon +{ +public: + WinProcMon(): + mQuery(0), mCounter(0) {}; + ~WinProcMon(); + nsresult Init(); + nsresult QuerySystemLoad(float* load_percent); + static const uint64_t TicksPerSec = 10000000; //100nsec tick (10MHz) +private: + PDH_HQUERY mQuery; + PDH_HCOUNTER mCounter; +}; + +WinProcMon::~WinProcMon() +{ + if (mQuery != 0) { + PdhCloseQuery(mQuery); + mQuery = 0; + } +} + +nsresult +WinProcMon::Init() +{ + PDH_HQUERY query; + PDH_HCOUNTER counter; + + // Get a query handle to the Performance Data Helper + PDH_STATUS status = PdhOpenQuery( + NULL, // No log file name: use real-time source + 0, // zero out user data token: unsued + &query); + + if (status != ERROR_SUCCESS) { + LOG(("PdhOpenQuery error = %X", status)); + return NS_ERROR_FAILURE; + } + + // Add a pre-defined high performance counter to the query. + // This one is for the total CPU usage. + status = PdhAddCounter(query, TotalCounterPath, 0, &counter); + + if (status != ERROR_SUCCESS) { + PdhCloseQuery(query); + LOG(("PdhAddCounter (_Total) error = %X", status)); + return NS_ERROR_FAILURE; + } + + // Need to make an initial query call to set up data capture. + status = PdhCollectQueryData(query); + + if (status != ERROR_SUCCESS) { + PdhCloseQuery(query); + LOG(("PdhCollectQueryData (init) error = %X", status)); + return NS_ERROR_FAILURE; + } + + mQuery = query; + mCounter = counter; + return NS_OK; +} + +nsresult WinProcMon::QuerySystemLoad(float* load_percent) +{ + *load_percent = 0; + + if (mQuery == 0) { + return NS_ERROR_FAILURE; + } + + // Update all counters associated with this query object. + PDH_STATUS status = PdhCollectQueryData(mQuery); + + if (status != ERROR_SUCCESS) { + LOG(("PdhCollectQueryData error = %X", status)); + return NS_ERROR_FAILURE; + } + + PDH_FMT_COUNTERVALUE counter; + // maximum is 100% regardless of CPU core count. + status = PdhGetFormattedCounterValue( + mCounter, + PDH_FMT_DOUBLE, + (LPDWORD)NULL, + &counter); + + if (ERROR_SUCCESS != status || + // There are multiple success return values. + !IsSuccessSeverity(counter.CStatus)) { + LOG(("PdhGetFormattedCounterValue error")); + return NS_ERROR_FAILURE; + } + + // The result is a percent value, reduce to match expected scale. + *load_percent = (float)(counter.doubleValue / 100.0f); + return NS_OK; +} +#endif + +// Use a non-generic class name, because otherwise we can get name collisions +// with other classes in the codebase. The normal way of dealing with that is +// to put the class in an anonymous namespace, but this class is used as a +// member of RTCLoadInfo, which can't be in the anonymous namespace, so it also +// can't be in an anonymous namespace: gcc warns about that setup and this +// directory is fail-on-warnings. +class RTCLoadStats +{ +public: + RTCLoadStats() : + mPrevTotalTimes(0), + mPrevCpuTimes(0), + mPrevLoad(0) {}; + + double GetLoad() { return (double)mPrevLoad; }; + + uint64_t mPrevTotalTimes; + uint64_t mPrevCpuTimes; + float mPrevLoad; // Previous load value. +}; + +// Use a non-generic class name, because otherwise we can get name collisions +// with other classes in the codebase. The normal way of dealing with that is +// to put the class in an anonymous namespace, but this class is used as a +// member of LoadInfoCollectRunner, which can't be in the anonymous namespace, +// so it also can't be in an anonymous namespace: gcc warns about that setup +// and this directory is fail-on-warnings. +class RTCLoadInfo final +{ +private: + ~RTCLoadInfo() {} + +public: + NS_INLINE_DECL_REFCOUNTING(RTCLoadInfo) + + RTCLoadInfo(): mLoadUpdateInterval(0) {}; + nsresult Init(int aLoadUpdateInterval); + double GetSystemLoad() { return mSystemLoad.GetLoad(); }; + double GetProcessLoad() { return mProcessLoad.GetLoad(); }; + nsresult UpdateSystemLoad(); + nsresult UpdateProcessLoad(); + +private: + void UpdateCpuLoad(uint64_t ticks_per_interval, + uint64_t current_total_times, + uint64_t current_cpu_times, + RTCLoadStats* loadStat); +#ifdef XP_WIN + WinProcMon mSysMon; + HANDLE mProcHandle; + int mNumProcessors; +#endif + RTCLoadStats mSystemLoad; + RTCLoadStats mProcessLoad; + uint64_t mTicksPerInterval; + int mLoadUpdateInterval; +}; + +nsresult RTCLoadInfo::Init(int aLoadUpdateInterval) +{ + mLoadUpdateInterval = aLoadUpdateInterval; +#ifdef XP_WIN + mTicksPerInterval = (WinProcMon::TicksPerSec /*Hz*/ + * mLoadUpdateInterval /*msec*/) / 1000 ; + mNumProcessors = PR_GetNumberOfProcessors(); + mProcHandle = GetCurrentProcess(); + return mSysMon.Init(); +#else + mTicksPerInterval = (sysconf(_SC_CLK_TCK) * mLoadUpdateInterval) / 1000; + return NS_OK; +#endif +} + +void RTCLoadInfo::UpdateCpuLoad(uint64_t ticks_per_interval, + uint64_t current_total_times, + uint64_t current_cpu_times, + RTCLoadStats *loadStat) { + // Check if we get an inconsistent number of ticks. + if (((current_total_times - loadStat->mPrevTotalTimes) + > (ticks_per_interval * 10)) + || current_total_times < loadStat->mPrevTotalTimes + || current_cpu_times < loadStat->mPrevCpuTimes) { + // Bug at least on the Nexus 4 and Galaxy S4 + // https://code.google.com/p/android/issues/detail?id=41630 + // We do need to update our previous times, or we can get stuck + // when there is a blip upwards and then we get a bunch of consecutive + // lower times. Just skip the load calculation. + LOG(("Inconsistent time values are passed. ignored")); + // Try to recover next tick + loadStat->mPrevTotalTimes = current_total_times; + loadStat->mPrevCpuTimes = current_cpu_times; + return; + } + + const uint64_t cpu_diff = current_cpu_times - loadStat->mPrevCpuTimes; + const uint64_t total_diff = current_total_times - loadStat->mPrevTotalTimes; + if (total_diff > 0) { +#ifdef XP_WIN + float result = (float)cpu_diff / (float)total_diff/ (float)mNumProcessors; +#else + float result = (float)cpu_diff / (float)total_diff; +#endif + loadStat->mPrevLoad = result; + } + loadStat->mPrevTotalTimes = current_total_times; + loadStat->mPrevCpuTimes = current_cpu_times; +} + +nsresult RTCLoadInfo::UpdateSystemLoad() +{ +#if defined(LINUX) || defined(ANDROID) + nsCOMPtr<nsIFile> procStatFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + procStatFile->InitWithPath(NS_LITERAL_STRING("/proc/stat")); + + nsCOMPtr<nsIInputStream> fileInputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), + procStatFile); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString buffer; + bool isMore = true; + lineInputStream->ReadLine(buffer, &isMore); + + uint64_t user; + uint64_t nice; + uint64_t system; + uint64_t idle; + if (PR_sscanf(buffer.get(), "cpu %llu %llu %llu %llu", + &user, &nice, + &system, &idle) != 4) { + LOG(("Error parsing /proc/stat")); + return NS_ERROR_FAILURE; + } + + const uint64_t cpu_times = nice + system + user; + const uint64_t total_times = cpu_times + idle; + + UpdateCpuLoad(mTicksPerInterval, + total_times, + cpu_times, + &mSystemLoad); + return NS_OK; +#elif defined(XP_MACOSX) + mach_msg_type_number_t info_cnt = HOST_CPU_LOAD_INFO_COUNT; + host_cpu_load_info_data_t load_info; + kern_return_t rv = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, + (host_info_t)(&load_info), &info_cnt); + + if (rv != KERN_SUCCESS || info_cnt != HOST_CPU_LOAD_INFO_COUNT) { + LOG(("Error from mach/host_statistics call")); + return NS_ERROR_FAILURE; + } + + const uint64_t cpu_times = load_info.cpu_ticks[CPU_STATE_NICE] + + load_info.cpu_ticks[CPU_STATE_SYSTEM] + + load_info.cpu_ticks[CPU_STATE_USER]; + const uint64_t total_times = cpu_times + load_info.cpu_ticks[CPU_STATE_IDLE]; + + UpdateCpuLoad(mTicksPerInterval, + total_times, + cpu_times, + &mSystemLoad); + return NS_OK; +#elif defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) +#if defined(__NetBSD__) + uint64_t cp_time[CPUSTATES]; +#else + long cp_time[CPUSTATES]; +#endif // __NetBSD__ + size_t sz = sizeof(cp_time); +#ifdef KERN_CP_TIME + int mib[] = { + CTL_KERN, + KERN_CP_TIME, + }; + u_int miblen = sizeof(mib) / sizeof(mib[0]); + if (sysctl(mib, miblen, &cp_time, &sz, nullptr, 0)) { +#else + if (sysctlbyname("kern.cp_time", &cp_time, &sz, nullptr, 0)) { +#endif // KERN_CP_TIME + LOG(("sysctl kern.cp_time failed")); + return NS_ERROR_FAILURE; + } + + const uint64_t cpu_times = cp_time[CP_NICE] + + cp_time[CP_SYS] + + cp_time[CP_INTR] + + cp_time[CP_USER]; + const uint64_t total_times = cpu_times + cp_time[CP_IDLE]; + + UpdateCpuLoad(mTicksPerInterval, + total_times, + cpu_times, + &mSystemLoad); + return NS_OK; +#elif defined(XP_WIN) + float load; + nsresult rv = mSysMon.QuerySystemLoad(&load); + + if (rv == NS_OK) { + mSystemLoad.mPrevLoad = load; + } + + return rv; +#else + // Not implemented + return NS_OK; +#endif +} + +nsresult RTCLoadInfo::UpdateProcessLoad() { +#if defined(XP_UNIX) + struct timeval tv; + gettimeofday(&tv, nullptr); + const uint64_t total_times = tv.tv_sec * PR_USEC_PER_SEC + tv.tv_usec; + + rusage usage; + if (getrusage(RUSAGE_SELF, &usage) < 0) { + LOG(("getrusage failed")); + return NS_ERROR_FAILURE; + } + + const uint64_t cpu_times = + (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * PR_USEC_PER_SEC + + usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; + + UpdateCpuLoad(PR_USEC_PER_MSEC * mLoadUpdateInterval, + total_times, + cpu_times, + &mProcessLoad); +#elif defined(XP_WIN) + FILETIME clk_time, sys_time, user_time; + uint64_t total_times, cpu_times; + + GetSystemTimeAsFileTime(&clk_time); + total_times = (((uint64_t)clk_time.dwHighDateTime) << 32) + + (uint64_t)clk_time.dwLowDateTime; + BOOL ok = GetProcessTimes(mProcHandle, &clk_time, &clk_time, &sys_time, &user_time); + + if (ok == 0) { + return NS_ERROR_FAILURE; + } + + cpu_times = (((uint64_t)sys_time.dwHighDateTime + + (uint64_t)user_time.dwHighDateTime) << 32) + + (uint64_t)sys_time.dwLowDateTime + + (uint64_t)user_time.dwLowDateTime; + + UpdateCpuLoad(mTicksPerInterval, + total_times, + cpu_times, + &mProcessLoad); +#endif + return NS_OK; +} + +// Note: This class can't be in the anonymous namespace, because then we can't +// declare it as a friend of LoadMonitor. +class LoadInfoCollectRunner : public Runnable +{ +public: + LoadInfoCollectRunner(RefPtr<LoadMonitor> loadMonitor, + RefPtr<RTCLoadInfo> loadInfo, + nsIThread *loadInfoThread) + : mThread(loadInfoThread), + mLoadUpdateInterval(loadMonitor->mLoadUpdateInterval), + mLoadNoiseCounter(0) + { + mLoadMonitor = loadMonitor; + mLoadInfo = loadInfo; + } + + NS_IMETHOD Run() override + { + if (NS_IsMainThread()) { + if (mThread) { + // Don't leak threads! + mThread->Shutdown(); // can't Shutdown from the thread itself, darn + // Don't null out mThread! + // See bug 999104. We must hold a ref to the thread across Dispatch() + // since the internal mThread ref could be released while processing + // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it + // assumes the caller does. + } + return NS_OK; + } + + MutexAutoLock lock(mLoadMonitor->mLock); + while (!mLoadMonitor->mShutdownPending) { + mLoadInfo->UpdateSystemLoad(); + mLoadInfo->UpdateProcessLoad(); + float sysLoad = mLoadInfo->GetSystemLoad(); + float procLoad = mLoadInfo->GetProcessLoad(); + + if ((++mLoadNoiseCounter % (LOG_MANY_ENABLED() ? 1 : 10)) == 0) { + LOG(("System Load: %f Process Load: %f", sysLoad, procLoad)); + mLoadNoiseCounter = 0; + } + mLoadMonitor->SetSystemLoad(sysLoad); + mLoadMonitor->SetProcessLoad(procLoad); + mLoadMonitor->FireCallbacks(); + + mLoadMonitor->mCondVar.Wait(PR_MillisecondsToInterval(mLoadUpdateInterval)); + } + // ok, we need to exit safely and can't shut ourselves down (DARN) + NS_DispatchToMainThread(this); + return NS_OK; + } + +private: + nsCOMPtr<nsIThread> mThread; + RefPtr<RTCLoadInfo> mLoadInfo; + RefPtr<LoadMonitor> mLoadMonitor; + int mLoadUpdateInterval; + int mLoadNoiseCounter; +}; + +void +LoadMonitor::SetProcessLoad(float load) { + mLock.AssertCurrentThreadOwns(); + mProcessLoad = load; +} + +void +LoadMonitor::SetSystemLoad(float load) { + mLock.AssertCurrentThreadOwns(); + mSystemLoad = load; +} + +float +LoadMonitor::GetProcessLoad() { + MutexAutoLock lock(mLock); + float load = mProcessLoad; + return load; +} + +void +LoadMonitor::FireCallbacks() { + if (mLoadNotificationCallback) { + mLoadNotificationCallback->LoadChanged(mSystemLoad, mProcessLoad); + } +} + +float +LoadMonitor::GetSystemLoad() { + MutexAutoLock lock(mLock); + float load = mSystemLoad; + return load; +} + +nsresult +LoadMonitor::Init(RefPtr<LoadMonitor> &self) +{ + LOG(("Initializing LoadMonitor")); + + RefPtr<RTCLoadInfo> load_info = new RTCLoadInfo(); + nsresult rv = load_info->Init(mLoadUpdateInterval); + + if (NS_FAILED(rv)) { + LOG(("RTCLoadInfo::Init error")); + return rv; + } + + RefPtr<LoadMonitorAddObserver> addObsRunner = new LoadMonitorAddObserver(self); + NS_DispatchToMainThread(addObsRunner); + + NS_NewNamedThread("Sys Load Info", getter_AddRefs(mLoadInfoThread)); + + RefPtr<LoadInfoCollectRunner> runner = + new LoadInfoCollectRunner(self, load_info, mLoadInfoThread); + mLoadInfoThread->Dispatch(runner, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void +LoadMonitor::SetLoadChangeCallback(LoadNotificationCallback* aCallback) +{ + mLoadNotificationCallback = aCallback; +} + +} diff --git a/dom/media/systemservices/LoadMonitor.h b/dom/media/systemservices/LoadMonitor.h new file mode 100644 index 000000000..3731b97dc --- /dev/null +++ b/dom/media/systemservices/LoadMonitor.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef _LOADMONITOR_H_ +#define _LOADMONITOR_H_ + +#include "mozilla/Mutex.h" +#include "mozilla/CondVar.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Atomics.h" +#include "nsCOMPtr.h" +#include "nsIThread.h" +#include "nsIObserver.h" + +namespace mozilla { +class LoadInfoCollectRunner; + +class LoadNotificationCallback +{ +public: + virtual void LoadChanged(float aSystemLoad, float aProcessLoad) = 0; +}; + +class LoadMonitor final : public nsIObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit LoadMonitor(int aLoadUpdateInterval); + + nsresult Init(RefPtr<LoadMonitor> &self); + void SetLoadChangeCallback(LoadNotificationCallback* aCallback); + void Shutdown(); + float GetSystemLoad(); + float GetProcessLoad(); + + friend class LoadInfoCollectRunner; + +private: + ~LoadMonitor(); + + void SetProcessLoad(float load); + void SetSystemLoad(float load); + void FireCallbacks(); + + int mLoadUpdateInterval; + mozilla::Mutex mLock; + mozilla::CondVar mCondVar; + bool mShutdownPending; + nsCOMPtr<nsIThread> mLoadInfoThread; + float mSystemLoad; + float mProcessLoad; + LoadNotificationCallback* mLoadNotificationCallback; +}; + +} //namespace + +#endif /* _LOADMONITOR_H_ */ diff --git a/dom/media/systemservices/MediaChild.cpp b/dom/media/systemservices/MediaChild.cpp new file mode 100644 index 000000000..327ea3c4a --- /dev/null +++ b/dom/media/systemservices/MediaChild.cpp @@ -0,0 +1,115 @@ +/* -*- 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 "MediaChild.h" +#include "MediaParent.h" + +#include "nsGlobalWindow.h" +#include "mozilla/MediaManager.h" +#include "mozilla/Logging.h" +#include "nsQueryObject.h" + +#undef LOG +mozilla::LazyLogModule gMediaChildLog("MediaChild"); +#define LOG(args) MOZ_LOG(gMediaChildLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { +namespace media { + +already_AddRefed<Pledge<nsCString>> +GetOriginKey(const nsCString& aOrigin, bool aPrivateBrowsing, bool aPersist) +{ + RefPtr<MediaManager> mgr = MediaManager::GetInstance(); + MOZ_ASSERT(mgr); + + RefPtr<Pledge<nsCString>> p = new Pledge<nsCString>(); + uint32_t id = mgr->mGetOriginKeyPledges.Append(*p); + + if (XRE_GetProcessType() == GeckoProcessType_Default) { + mgr->GetNonE10sParent()->RecvGetOriginKey(id, aOrigin, aPrivateBrowsing, + aPersist); + } else { + Child::Get()->SendGetOriginKey(id, aOrigin, aPrivateBrowsing, aPersist); + } + return p.forget(); +} + +void +SanitizeOriginKeys(const uint64_t& aSinceWhen, bool aOnlyPrivateBrowsing) +{ + LOG(("SanitizeOriginKeys since %llu %s", aSinceWhen, + (aOnlyPrivateBrowsing? "in Private Browsing." : "."))); + + if (XRE_GetProcessType() == GeckoProcessType_Default) { + // Avoid opening MediaManager in this case, since this is called by + // sanitize.js when cookies are cleared, which can happen on startup. + RefPtr<Parent<NonE10s>> tmpParent = new Parent<NonE10s>(); + tmpParent->RecvSanitizeOriginKeys(aSinceWhen, aOnlyPrivateBrowsing); + } else { + Child::Get()->SendSanitizeOriginKeys(aSinceWhen, aOnlyPrivateBrowsing); + } +} + +static Child* sChild; + +Child* Child::Get() +{ + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content); + MOZ_ASSERT(NS_IsMainThread()); + if (!sChild) { + sChild = static_cast<Child*>(dom::ContentChild::GetSingleton()->SendPMediaConstructor()); + } + return sChild; +} + +Child::Child() + : mActorDestroyed(false) +{ + LOG(("media::Child: %p", this)); + MOZ_COUNT_CTOR(Child); +} + +Child::~Child() +{ + LOG(("~media::Child: %p", this)); + sChild = nullptr; + MOZ_COUNT_DTOR(Child); +} + +void Child::ActorDestroy(ActorDestroyReason aWhy) +{ + mActorDestroyed = true; +} + +bool +Child::RecvGetOriginKeyResponse(const uint32_t& aRequestId, const nsCString& aKey) +{ + RefPtr<MediaManager> mgr = MediaManager::GetInstance(); + if (!mgr) { + return false; + } + RefPtr<Pledge<nsCString>> pledge = mgr->mGetOriginKeyPledges.Remove(aRequestId); + if (pledge) { + pledge->Resolve(aKey); + } + return true; +} + +PMediaChild* +AllocPMediaChild() +{ + return new Child(); +} + +bool +DeallocPMediaChild(media::PMediaChild *aActor) +{ + delete static_cast<Child*>(aActor); + return true; +} + +} // namespace media +} // namespace mozilla diff --git a/dom/media/systemservices/MediaChild.h b/dom/media/systemservices/MediaChild.h new file mode 100644 index 000000000..b013c4a5f --- /dev/null +++ b/dom/media/systemservices/MediaChild.h @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +#ifndef mozilla_MediaChild_h +#define mozilla_MediaChild_h + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/media/PMediaChild.h" +#include "mozilla/media/PMediaParent.h" +#include "MediaUtils.h" + +namespace mozilla { +namespace media { + +// media::Child implements proxying to the chrome process for some media-related +// functions, for the moment just: +// +// GetOriginKey() - get a cookie-like persisted unique key for a given origin. +// SanitizeOriginKeys() - reset persisted unique keys. + +// GetOriginKey and SanitizeOriginKeys are asynchronous APIs that return pledges +// (promise-like objects) with the future value. Use pledge.Then(func) to access. + +already_AddRefed<Pledge<nsCString>> +GetOriginKey(const nsCString& aOrigin, bool aPrivateBrowsing, bool aPersist); + +void +SanitizeOriginKeys(const uint64_t& aSinceWhen, bool aOnlyPrivateBrowsing); + +class Child : public PMediaChild +{ +public: + static Child* Get(); + + Child(); + + bool RecvGetOriginKeyResponse(const uint32_t& aRequestId, const nsCString& aKey) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + virtual ~Child(); +private: + + bool mActorDestroyed; +}; + +PMediaChild* AllocPMediaChild(); +bool DeallocPMediaChild(PMediaChild *aActor); + +} // namespace media +} // namespace mozilla + +#endif // mozilla_MediaChild_h diff --git a/dom/media/systemservices/MediaParent.cpp b/dom/media/systemservices/MediaParent.cpp new file mode 100644 index 000000000..109a44a28 --- /dev/null +++ b/dom/media/systemservices/MediaParent.cpp @@ -0,0 +1,516 @@ +/* -*- 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 "MediaParent.h" + +#include "mozilla/Base64.h" +#include <mozilla/StaticMutex.h> + +#include "MediaUtils.h" +#include "MediaEngine.h" +#include "VideoUtils.h" +#include "nsAutoPtr.h" +#include "nsThreadUtils.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIInputStream.h" +#include "nsILineInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsISupportsImpl.h" +#include "mozilla/Logging.h" + +#undef LOG +mozilla::LazyLogModule gMediaParentLog("MediaParent"); +#define LOG(args) MOZ_LOG(gMediaParentLog, mozilla::LogLevel::Debug, args) + +// A file in the profile dir is used to persist mOriginKeys used to anonymize +// deviceIds to be unique per origin, to avoid them being supercookies. + +#define ORIGINKEYS_FILE "enumerate_devices.txt" +#define ORIGINKEYS_VERSION "1" + +namespace mozilla { +namespace media { + +static OriginKeyStore* sOriginKeyStore = nullptr; + +class OriginKeyStore : public nsISupports +{ + NS_DECL_THREADSAFE_ISUPPORTS + class OriginKey + { + public: + static const size_t DecodedLength = 18; + static const size_t EncodedLength = DecodedLength * 4 / 3; + + explicit OriginKey(const nsACString& aKey, int64_t aSecondsStamp = 0) // 0 = temporal + : mKey(aKey) + , mSecondsStamp(aSecondsStamp) {} + + nsCString mKey; // Base64 encoded. + int64_t mSecondsStamp; + }; + + class OriginKeysTable + { + public: + OriginKeysTable() : mPersistCount(0) {} + + nsresult + GetOriginKey(const nsACString& aOrigin, nsCString& aResult, bool aPersist = false) + { + OriginKey* key; + if (!mKeys.Get(aOrigin, &key)) { + nsCString salt; // Make a new one + nsresult rv = GenerateRandomName(salt, key->EncodedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + key = new OriginKey(salt); + mKeys.Put(aOrigin, key); + } + if (aPersist && !key->mSecondsStamp) { + key->mSecondsStamp = PR_Now() / PR_USEC_PER_SEC; + mPersistCount++; + } + aResult = key->mKey; + return NS_OK; + } + + void Clear(int64_t aSinceWhen) + { + // Avoid int64_t* <-> void* casting offset + OriginKey since(nsCString(), aSinceWhen / PR_USEC_PER_SEC); + for (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<OriginKey>& originKey = iter.Data(); + LOG((((originKey->mSecondsStamp >= since.mSecondsStamp)? + "%s: REMOVE %lld >= %lld" : + "%s: KEEP %lld < %lld"), + __FUNCTION__, originKey->mSecondsStamp, since.mSecondsStamp)); + + if (originKey->mSecondsStamp >= since.mSecondsStamp) { + iter.Remove(); + } + } + mPersistCount = 0; + } + + protected: + nsClassHashtable<nsCStringHashKey, OriginKey> mKeys; + size_t mPersistCount; + }; + + class OriginKeysLoader : public OriginKeysTable + { + public: + OriginKeysLoader() {} + + nsresult + GetOriginKey(const nsACString& aOrigin, nsCString& aResult, bool aPersist) + { + auto before = mPersistCount; + OriginKeysTable::GetOriginKey(aOrigin, aResult, aPersist); + if (mPersistCount != before) { + Save(); + } + return NS_OK; + } + + already_AddRefed<nsIFile> + GetFile() + { + MOZ_ASSERT(mProfileDir); + nsCOMPtr<nsIFile> file; + nsresult rv = mProfileDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + file->Append(NS_LITERAL_STRING(ORIGINKEYS_FILE)); + return file.forget(); + } + + // Format of file is key secondsstamp origin (first line is version #): + // + // 1 + // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424733961 http://fiddle.jshell.net + // rOMAAbFujNwKyIpj4RJ3Wt5Q 1424734841 http://mozilla.github.io + // etc. + + nsresult Read() + { + nsCOMPtr<nsIFile> file = GetFile(); + if (NS_WARN_IF(!file)) { + return NS_ERROR_UNEXPECTED; + } + bool exists; + nsresult rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!exists) { + return NS_OK; + } + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + nsCOMPtr<nsILineInputStream> i = do_QueryInterface(stream); + MOZ_ASSERT(i); + MOZ_ASSERT(!mPersistCount); + + nsCString line; + bool hasMoreLines; + rv = i->ReadLine(line, &hasMoreLines); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!line.EqualsLiteral(ORIGINKEYS_VERSION)) { + // If version on disk is newer than we can understand then ignore it. + return NS_OK; + } + + while (hasMoreLines) { + rv = i->ReadLine(line, &hasMoreLines); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // Read key secondsstamp origin. + // Ignore any lines that don't fit format in the comment above exactly. + int32_t f = line.FindChar(' '); + if (f < 0) { + continue; + } + const nsACString& key = Substring(line, 0, f); + const nsACString& s = Substring(line, f+1); + f = s.FindChar(' '); + if (f < 0) { + continue; + } + int64_t secondsstamp = nsCString(Substring(s, 0, f)).ToInteger64(&rv); + if (NS_FAILED(rv)) { + continue; + } + const nsACString& origin = Substring(s, f+1); + + // Validate key + if (key.Length() != OriginKey::EncodedLength) { + continue; + } + nsCString dummy; + rv = Base64Decode(key, dummy); + if (NS_FAILED(rv)) { + continue; + } + mKeys.Put(origin, new OriginKey(key, secondsstamp)); + } + mPersistCount = mKeys.Count(); + return NS_OK; + } + + nsresult + Write() + { + nsCOMPtr<nsIFile> file = GetFile(); + if (NS_WARN_IF(!file)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIOutputStream> stream; + nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString versionBuffer; + versionBuffer.AppendLiteral(ORIGINKEYS_VERSION); + versionBuffer.Append('\n'); + + uint32_t count; + rv = stream->Write(versionBuffer.Data(), versionBuffer.Length(), &count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (count != versionBuffer.Length()) { + return NS_ERROR_UNEXPECTED; + } + for (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) { + const nsACString& origin = iter.Key(); + OriginKey* originKey = iter.UserData(); + + if (!originKey->mSecondsStamp) { + continue; // don't write temporal ones + } + + nsCString originBuffer; + originBuffer.Append(originKey->mKey); + originBuffer.Append(' '); + originBuffer.AppendInt(originKey->mSecondsStamp); + originBuffer.Append(' '); + originBuffer.Append(origin); + originBuffer.Append('\n'); + + rv = stream->Write(originBuffer.Data(), originBuffer.Length(), &count); + if (NS_WARN_IF(NS_FAILED(rv)) || count != originBuffer.Length()) { + break; + } + } + + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream); + MOZ_ASSERT(safeStream); + + rv = safeStream->Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + nsresult Load() + { + nsresult rv = Read(); + if (NS_WARN_IF(NS_FAILED(rv))) { + Delete(); + } + return rv; + } + + nsresult Save() + { + nsresult rv = Write(); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_WARNING("Failed to write data for EnumerateDevices id-persistence."); + Delete(); + } + return rv; + } + + void Clear(int64_t aSinceWhen) + { + OriginKeysTable::Clear(aSinceWhen); + Delete(); + Save(); + } + + nsresult Delete() + { + nsCOMPtr<nsIFile> file = GetFile(); + if (NS_WARN_IF(!file)) { + return NS_ERROR_UNEXPECTED; + } + nsresult rv = file->Remove(false); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + return NS_OK; + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + } + + void + SetProfileDir(nsIFile* aProfileDir) + { + MOZ_ASSERT(!NS_IsMainThread()); + bool first = !mProfileDir; + mProfileDir = aProfileDir; + // Load from disk when we first get a profileDir, but not subsequently. + if (first) { + Load(); + } + } + private: + nsCOMPtr<nsIFile> mProfileDir; + }; + +private: + virtual ~OriginKeyStore() + { + sOriginKeyStore = nullptr; + LOG((__FUNCTION__)); + } + +public: + static OriginKeyStore* Get() + { + MOZ_ASSERT(NS_IsMainThread()); + if (!sOriginKeyStore) { + sOriginKeyStore = new OriginKeyStore(); + } + return sOriginKeyStore; + } + + // Only accessed on StreamTS thread + OriginKeysLoader mOriginKeys; + OriginKeysTable mPrivateBrowsingOriginKeys; +}; + +NS_IMPL_ISUPPORTS0(OriginKeyStore) + +bool NonE10s::SendGetOriginKeyResponse(const uint32_t& aRequestId, + nsCString aKey) { + MediaManager* mgr = MediaManager::GetIfExists(); + if (!mgr) { + return false; + } + RefPtr<Pledge<nsCString>> pledge = mgr->mGetOriginKeyPledges.Remove(aRequestId); + if (pledge) { + pledge->Resolve(aKey); + } + return true; +} + +template<class Super> bool +Parent<Super>::RecvGetOriginKey(const uint32_t& aRequestId, + const nsCString& aOrigin, + const bool& aPrivateBrowsing, + const bool& aPersist) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // First, get profile dir. + + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIFile> profileDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + // Then over to stream-transport thread to do the actual file io. + // Stash a pledge to hold the answer and get an id for this request. + + RefPtr<Pledge<nsCString>> p = new Pledge<nsCString>(); + uint32_t id = mOutstandingPledges.Append(*p); + + nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(sts); + RefPtr<Parent<Super>> that(this); + + rv = sts->Dispatch(NewRunnableFrom([this, that, id, profileDir, aOrigin, + aPrivateBrowsing, aPersist]() -> nsresult { + MOZ_ASSERT(!NS_IsMainThread()); + mOriginKeyStore->mOriginKeys.SetProfileDir(profileDir); + nsCString result; + if (aPrivateBrowsing) { + mOriginKeyStore->mPrivateBrowsingOriginKeys.GetOriginKey(aOrigin, result); + } else { + mOriginKeyStore->mOriginKeys.GetOriginKey(aOrigin, result, aPersist); + } + + // Pass result back to main thread. + nsresult rv; + rv = NS_DispatchToMainThread(NewRunnableFrom([this, that, id, + result]() -> nsresult { + if (mDestroyed) { + return NS_OK; + } + RefPtr<Pledge<nsCString>> p = mOutstandingPledges.Remove(id); + if (!p) { + return NS_ERROR_UNEXPECTED; + } + p->Resolve(result); + return NS_OK; + }), NS_DISPATCH_NORMAL); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; + }), NS_DISPATCH_NORMAL); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + p->Then([this, that, aRequestId](const nsCString& aKey) mutable { + if (mDestroyed) { + return NS_OK; + } + Unused << this->SendGetOriginKeyResponse(aRequestId, aKey); + return NS_OK; + }); + return true; +} + +template<class Super> bool +Parent<Super>::RecvSanitizeOriginKeys(const uint64_t& aSinceWhen, + const bool& aOnlyPrivateBrowsing) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIFile> profileDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + // Over to stream-transport thread to do the file io. + + nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(sts); + RefPtr<OriginKeyStore> store(mOriginKeyStore); + + rv = sts->Dispatch(NewRunnableFrom([profileDir, store, aSinceWhen, + aOnlyPrivateBrowsing]() -> nsresult { + MOZ_ASSERT(!NS_IsMainThread()); + store->mPrivateBrowsingOriginKeys.Clear(aSinceWhen); + if (!aOnlyPrivateBrowsing) { + store->mOriginKeys.SetProfileDir(profileDir); + store->mOriginKeys.Clear(aSinceWhen); + } + return NS_OK; + }), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + return true; +} + +template<class Super> void +Parent<Super>::ActorDestroy(ActorDestroyReason aWhy) +{ + // No more IPC from here + mDestroyed = true; + LOG((__FUNCTION__)); +} + +template<class Super> +Parent<Super>::Parent() + : mOriginKeyStore(OriginKeyStore::Get()) + , mDestroyed(false) +{ + LOG(("media::Parent: %p", this)); +} + +template<class Super> +Parent<Super>::~Parent() +{ + LOG(("~media::Parent: %p", this)); +} + +PMediaParent* +AllocPMediaParent() +{ + Parent<PMediaParent>* obj = new Parent<PMediaParent>(); + obj->AddRef(); + return obj; +} + +bool +DeallocPMediaParent(media::PMediaParent *aActor) +{ + static_cast<Parent<PMediaParent>*>(aActor)->Release(); + return true; +} + +} // namespace media +} // namespace mozilla + +// Instantiate templates to satisfy linker +template class mozilla::media::Parent<mozilla::media::NonE10s>; diff --git a/dom/media/systemservices/MediaParent.h b/dom/media/systemservices/MediaParent.h new file mode 100644 index 000000000..b5dcd84ad --- /dev/null +++ b/dom/media/systemservices/MediaParent.h @@ -0,0 +1,77 @@ +/* -*- 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/. */ + +#ifndef mozilla_MediaParent_h +#define mozilla_MediaParent_h + +#include "MediaChild.h" + +#include "mozilla/dom/ContentParent.h" +#include "mozilla/media/PMediaParent.h" + +namespace mozilla { +namespace media { + +// media::Parent implements the chrome-process side of ipc for media::Child APIs +// A same-process version may also be created to service non-e10s calls. + +class OriginKeyStore; + +class NonE10s +{ + typedef mozilla::ipc::IProtocol::ActorDestroyReason + ActorDestroyReason; +public: + virtual ~NonE10s() {} +protected: + virtual bool RecvGetOriginKey(const uint32_t& aRequestId, + const nsCString& aOrigin, + const bool& aPrivateBrowsing, + const bool& aPersist) = 0; + virtual bool RecvSanitizeOriginKeys(const uint64_t& aSinceWhen, + const bool& aOnlyPrivateBrowsing) = 0; + virtual void + ActorDestroy(ActorDestroyReason aWhy) = 0; + + bool SendGetOriginKeyResponse(const uint32_t& aRequestId, + nsCString aKey); +}; + +// Super = PMediaParent or NonE10s + +template<class Super> +class Parent : public Super +{ + typedef mozilla::ipc::IProtocol::ActorDestroyReason + ActorDestroyReason; +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Parent<Super>) + + virtual bool RecvGetOriginKey(const uint32_t& aRequestId, + const nsCString& aOrigin, + const bool& aPrivateBrowsing, + const bool& aPersist) override; + virtual bool RecvSanitizeOriginKeys(const uint64_t& aSinceWhen, + const bool& aOnlyPrivateBrowsing) override; + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + Parent(); +private: + virtual ~Parent(); + + RefPtr<OriginKeyStore> mOriginKeyStore; + bool mDestroyed; + + CoatCheck<Pledge<nsCString>> mOutstandingPledges; +}; + +PMediaParent* AllocPMediaParent(); +bool DeallocPMediaParent(PMediaParent *aActor); + +} // namespace media +} // namespace mozilla + +#endif // mozilla_MediaParent_h diff --git a/dom/media/systemservices/MediaSystemResourceClient.cpp b/dom/media/systemservices/MediaSystemResourceClient.cpp new file mode 100644 index 000000000..e1ad38aef --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceClient.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "mozilla/Monitor.h" +#include "mozilla/ReentrantMonitor.h" + +#include "MediaSystemResourceClient.h" + +namespace mozilla { + +Atomic<uint32_t> MediaSystemResourceClient::sSerialCounter(0); + +MediaSystemResourceClient::MediaSystemResourceClient(MediaSystemResourceType aReourceType) + : mResourceType(aReourceType) + , mId(++sSerialCounter) + , mListener(nullptr) + , mResourceState(RESOURCE_STATE_START) + , mIsSync(false) + , mAcquireSyncWaitMonitor(nullptr) + , mAcquireSyncWaitDone(nullptr) +{ + mManager = MediaSystemResourceManager::Get(); + if (mManager) { + mManager->Register(this); + } +} + +MediaSystemResourceClient::~MediaSystemResourceClient() +{ + ReleaseResource(); + if (mManager) { + mManager->Unregister(this); + } +} + +bool +MediaSystemResourceClient::SetListener(MediaSystemResourceReservationListener* aListener) +{ + if (!mManager) { + return false; + } + return mManager->SetListener(this, aListener); +} + +void +MediaSystemResourceClient::Acquire() +{ + if (!mManager) { + return; + } + mManager->Acquire(this); +} + +bool +MediaSystemResourceClient::AcquireSyncNoWait() +{ + if (!mManager) { + return false; + } + return mManager->AcquireSyncNoWait(this); +} + +void +MediaSystemResourceClient::ReleaseResource() +{ + if (!mManager) { + return; + } + mManager->ReleaseResource(this); +} + +} // namespace mozilla diff --git a/dom/media/systemservices/MediaSystemResourceClient.h b/dom/media/systemservices/MediaSystemResourceClient.h new file mode 100644 index 000000000..29e769a92 --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceClient.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#if !defined(MediaSystemResourceClient_h_) +#define MediaSystemResourceClient_h_ + +#include "MediaSystemResourceManager.h" +#include "MediaSystemResourceTypes.h" +#include "mozilla/Atomics.h" +#include "mozilla/media/MediaSystemResourceTypes.h" +#include "mozilla/Monitor.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class MediaSystemResourceManager; + + +/** + * This is a base class for listener callbacks. + * This callback is invoked when the media system resource reservation state + * is changed. + */ +class MediaSystemResourceReservationListener { +public: + virtual void ResourceReserved() = 0; + virtual void ResourceReserveFailed() = 0; +}; + +/** + * MediaSystemResourceClient is used to reserve a media system resource + * like hw decoder. When system has a limitation of a media resource, + * use this class to mediate use rights of the resource. + */ +class MediaSystemResourceClient +{ +public: + + // Enumeration for the valid decoding states + enum ResourceState { + RESOURCE_STATE_START, + RESOURCE_STATE_WAITING, + RESOURCE_STATE_ACQUIRED, + RESOURCE_STATE_NOT_ACQUIRED, + RESOURCE_STATE_END + }; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSystemResourceClient) + + explicit MediaSystemResourceClient(MediaSystemResourceType aReourceType); + + bool SetListener(MediaSystemResourceReservationListener* aListener); + + // Try to acquire media resource asynchronously. + // If the resource is used by others, wait until acquired. + void Acquire(); + + // Try to acquire media resource synchronously. If the resource is not immediately + // available, fail to acquire it. + // return false if resource is not acquired. + // return true if resource is acquired. + // + // This function should not be called on ImageBridge thread. + // It should be used only for compatibility with legacy code. + bool AcquireSyncNoWait(); + + void ReleaseResource(); + +private: + ~MediaSystemResourceClient(); + + RefPtr<MediaSystemResourceManager> mManager; + const MediaSystemResourceType mResourceType; + const uint32_t mId; + + // Modified only by MediaSystemResourceManager. + // Accessed and modified with MediaSystemResourceManager::mReentrantMonitor held. + MediaSystemResourceReservationListener* mListener; + ResourceState mResourceState; + bool mIsSync; + ReentrantMonitor* mAcquireSyncWaitMonitor; + bool* mAcquireSyncWaitDone; + + static mozilla::Atomic<uint32_t> sSerialCounter; + + friend class MediaSystemResourceManager; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/systemservices/MediaSystemResourceManager.cpp b/dom/media/systemservices/MediaSystemResourceManager.cpp new file mode 100644 index 000000000..29db0ef3e --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceManager.cpp @@ -0,0 +1,387 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "mozilla/TaskQueue.h" + +#include "gfxPrefs.h" +#include "MediaSystemResourceManagerChild.h" +#include "mozilla/layers/ImageBridgeChild.h" + +#include "MediaSystemResourceManager.h" + +namespace mozilla { + +using namespace mozilla::ipc; +using namespace mozilla::layers; + +/* static */ StaticRefPtr<MediaSystemResourceManager> MediaSystemResourceManager::sSingleton; + +/* static */ MediaSystemResourceManager* +MediaSystemResourceManager::Get() +{ + if (sSingleton) { + return sSingleton; + } + MediaSystemResourceManager::Init(); + return sSingleton; +} + +/* static */ void +MediaSystemResourceManager::Shutdown() +{ + MOZ_ASSERT(InImageBridgeChildThread()); + if (sSingleton) { + sSingleton->CloseIPC(); + sSingleton = nullptr; + } +} + +/* static */ void +MediaSystemResourceManager::Init() +{ + RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton(); + if (!imageBridge) { + NS_WARNING("ImageBridge does not exist"); + return; + } + + if (InImageBridgeChildThread()) { + if (!sSingleton) { +#ifdef DEBUG + static int timesCreated = 0; + timesCreated++; + MOZ_ASSERT(timesCreated == 1); +#endif + sSingleton = new MediaSystemResourceManager(); + } + return; + } + + ReentrantMonitor barrier("MediaSystemResourceManager::Init"); + ReentrantMonitorAutoEnter mainThreadAutoMon(barrier); + bool done = false; + + RefPtr<Runnable> runnable = + NS_NewRunnableFunction([&]() { + if (!sSingleton) { + sSingleton = new MediaSystemResourceManager(); + } + ReentrantMonitorAutoEnter childThreadAutoMon(barrier); + done = true; + barrier.NotifyAll(); + }); + + imageBridge->GetMessageLoop()->PostTask(runnable.forget()); + + // should stop the thread until done. + while (!done) { + barrier.Wait(); + } +} + +MediaSystemResourceManager::MediaSystemResourceManager() + : mReentrantMonitor("MediaSystemResourceManager.mReentrantMonitor") + , mShutDown(false) + , mChild(nullptr) +{ + MOZ_ASSERT(InImageBridgeChildThread()); + OpenIPC(); +} + +MediaSystemResourceManager::~MediaSystemResourceManager() +{ + MOZ_ASSERT(IsIpcClosed()); +} + +void +MediaSystemResourceManager::OpenIPC() +{ + MOZ_ASSERT(InImageBridgeChildThread()); + MOZ_ASSERT(!mChild); + + media::PMediaSystemResourceManagerChild* child = + ImageBridgeChild::GetSingleton()->SendPMediaSystemResourceManagerConstructor(); + mChild = static_cast<media::MediaSystemResourceManagerChild*>(child); + mChild->SetManager(this); +} + +void +MediaSystemResourceManager::CloseIPC() +{ + MOZ_ASSERT(InImageBridgeChildThread()); + + if (!mChild) { + return; + } + mChild->Destroy(); + mChild = nullptr; + mShutDown = true; +} + +void +MediaSystemResourceManager::OnIpcClosed() +{ + mChild = nullptr; +} + +bool +MediaSystemResourceManager::IsIpcClosed() +{ + return mChild ? true : false; +} + +void +MediaSystemResourceManager::Register(MediaSystemResourceClient* aClient) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MOZ_ASSERT(aClient); + MOZ_ASSERT(!mResourceClients.Get(aClient->mId)); + + mResourceClients.Put(aClient->mId, aClient); +} + +void +MediaSystemResourceManager::Unregister(MediaSystemResourceClient* aClient) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MOZ_ASSERT(aClient); + MOZ_ASSERT(mResourceClients.Get(aClient->mId)); + MOZ_ASSERT(mResourceClients.Get(aClient->mId) == aClient); + + mResourceClients.Remove(aClient->mId); +} + +bool +MediaSystemResourceManager::SetListener(MediaSystemResourceClient* aClient, + MediaSystemResourceReservationListener* aListener) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MOZ_ASSERT(aClient); + + MediaSystemResourceClient* client = mResourceClients.Get(aClient->mId); + MOZ_ASSERT(client); + + if (!client) { + return false; + } + // State Check + if (aClient->mResourceState != MediaSystemResourceClient::RESOURCE_STATE_START) { + return false; + } + aClient->mListener = aListener; + return true; +} + +void +MediaSystemResourceManager::Acquire(MediaSystemResourceClient* aClient) +{ + MOZ_ASSERT(aClient); + MOZ_ASSERT(!InImageBridgeChildThread()); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MediaSystemResourceClient* client = mResourceClients.Get(aClient->mId); + MOZ_ASSERT(client); + MOZ_ASSERT(client == aClient); + + aClient->mIsSync = false; // async request + + if (!client) { + HandleAcquireResult(aClient->mId, false); + return; + } + // State Check + if (aClient->mResourceState != MediaSystemResourceClient::RESOURCE_STATE_START) { + HandleAcquireResult(aClient->mId, false); + return; + } + aClient->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_WAITING; + ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask( + NewRunnableMethod<uint32_t>( + this, + &MediaSystemResourceManager::DoAcquire, + aClient->mId)); +} + +bool +MediaSystemResourceManager::AcquireSyncNoWait(MediaSystemResourceClient* aClient) +{ + MOZ_ASSERT(aClient); + MOZ_ASSERT(!InImageBridgeChildThread()); + + ReentrantMonitor barrier("MediaSystemResourceManager::AcquireSyncNoWait"); + ReentrantMonitorAutoEnter autoMon(barrier); + bool done = false; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MediaSystemResourceClient* client = mResourceClients.Get(aClient->mId); + MOZ_ASSERT(client); + MOZ_ASSERT(client == aClient); + + aClient->mIsSync = true; // sync request + + if (InImageBridgeChildThread()) { + HandleAcquireResult(aClient->mId, false); + return false; + } + if (!client || client != aClient) { + HandleAcquireResult(aClient->mId, false); + return false; + } + // State Check + if (aClient->mResourceState != MediaSystemResourceClient::RESOURCE_STATE_START) { + HandleAcquireResult(aClient->mId, false); + return false; + } + // Hold barrier Monitor until acquire task end. + aClient->mAcquireSyncWaitMonitor = &barrier; + aClient->mAcquireSyncWaitDone = &done; + aClient->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_WAITING; + } + + ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask( + NewRunnableMethod<uint32_t>( + this, + &MediaSystemResourceManager::DoAcquire, + aClient->mId)); + + // should stop the thread until done. + while (!done) { + barrier.Wait(); + } + + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + if (aClient->mResourceState != MediaSystemResourceClient::RESOURCE_STATE_ACQUIRED) { + return false; + } + return true; + } +} + +void +MediaSystemResourceManager::DoAcquire(uint32_t aId) +{ + MOZ_ASSERT(InImageBridgeChildThread()); + if (mShutDown || !mChild) { + HandleAcquireResult(aId, false); + return; + } + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MediaSystemResourceClient* client = mResourceClients.Get(aId); + MOZ_ASSERT(client); + + if (!client || + client->mResourceState != MediaSystemResourceClient::RESOURCE_STATE_WAITING) { + HandleAcquireResult(aId, false); + return; + } + MOZ_ASSERT(aId == client->mId); + bool willWait = !client->mAcquireSyncWaitMonitor ? true : false; + mChild->SendAcquire(client->mId, + client->mResourceType, + willWait); + } +} + +void +MediaSystemResourceManager::ReleaseResource(MediaSystemResourceClient* aClient) +{ + MOZ_ASSERT(aClient); + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MediaSystemResourceClient* client = mResourceClients.Get(aClient->mId); + MOZ_ASSERT(client); + MOZ_ASSERT(client == aClient); + + if (!client || + client != aClient || + aClient->mResourceState == MediaSystemResourceClient::RESOURCE_STATE_START || + aClient->mResourceState == MediaSystemResourceClient::RESOURCE_STATE_END) { + + aClient->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_END; + return; + } + + aClient->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_END; + + ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask( + NewRunnableMethod<uint32_t>( + this, + &MediaSystemResourceManager::DoRelease, + aClient->mId)); + } +} + +void +MediaSystemResourceManager::DoRelease(uint32_t aId) +{ + MOZ_ASSERT(InImageBridgeChildThread()); + if (mShutDown || !mChild) { + return; + } + mChild->SendRelease(aId); +} + +void +MediaSystemResourceManager::RecvResponse(uint32_t aId, bool aSuccess) +{ + HandleAcquireResult(aId, aSuccess); +} + +void +MediaSystemResourceManager::HandleAcquireResult(uint32_t aId, bool aSuccess) +{ + if (!InImageBridgeChildThread()) { + ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask( + NewRunnableMethod<uint32_t, bool>( + this, + &MediaSystemResourceManager::HandleAcquireResult, + aId, + aSuccess)); + return; + } + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MediaSystemResourceClient* client = mResourceClients.Get(aId); + if (!client) { + // Client was already unregistered. + return; + } + if (client->mResourceState != MediaSystemResourceClient::RESOURCE_STATE_WAITING) { + return; + } + + // Update state + if (aSuccess) { + client->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_ACQUIRED; + } else { + client->mResourceState = MediaSystemResourceClient::RESOURCE_STATE_NOT_ACQUIRED; + } + + if (client->mIsSync) { + if (client->mAcquireSyncWaitMonitor) { + // Notify AcquireSync() complete + MOZ_ASSERT(client->mAcquireSyncWaitDone); + ReentrantMonitorAutoEnter autoMon(*client->mAcquireSyncWaitMonitor); + *client->mAcquireSyncWaitDone = true; + client->mAcquireSyncWaitMonitor->NotifyAll(); + client->mAcquireSyncWaitMonitor = nullptr; + client->mAcquireSyncWaitDone = nullptr; + } + } else { + // Notify Acquire() result + if (client->mListener) { + if (aSuccess) { + client->mListener->ResourceReserved(); + } else { + client->mListener->ResourceReserveFailed(); + } + } + } +} + +} // namespace mozilla diff --git a/dom/media/systemservices/MediaSystemResourceManager.h b/dom/media/systemservices/MediaSystemResourceManager.h new file mode 100644 index 000000000..36869e202 --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceManager.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#if !defined(MediaSystemResourceManager_h_) +#define MediaSystemResourceManager_h_ + +#include <queue> + +#include "MediaSystemResourceTypes.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/StaticPtr.h" +#include "nsDataHashtable.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +namespace media { +class MediaSystemResourceManagerChild; +} // namespace media + +class MediaSystemResourceClient; +class MediaSystemResourceReservationListener; +class ReentrantMonitor; +class TaskQueue; + +/** + * Manage media system resource allocation requests within a process. + */ +class MediaSystemResourceManager +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSystemResourceManager) + + static MediaSystemResourceManager* Get(); + static void Init(); + static void Shutdown(); + + void OnIpcClosed(); + + void Register(MediaSystemResourceClient* aClient); + void Unregister(MediaSystemResourceClient* aClient); + + bool SetListener(MediaSystemResourceClient* aClient, + MediaSystemResourceReservationListener* aListener); + + void Acquire(MediaSystemResourceClient* aClient); + bool AcquireSyncNoWait(MediaSystemResourceClient* aClient); + void ReleaseResource(MediaSystemResourceClient* aClient); + + void RecvResponse(uint32_t aId, bool aSuccess); + +private: + MediaSystemResourceManager(); + virtual ~MediaSystemResourceManager(); + + void OpenIPC(); + void CloseIPC(); + bool IsIpcClosed(); + + void DoAcquire(uint32_t aId); + + void DoRelease(uint32_t aId); + + void HandleAcquireResult(uint32_t aId, bool aSuccess); + + ReentrantMonitor mReentrantMonitor; + + bool mShutDown; + + media::MediaSystemResourceManagerChild* mChild; + + nsDataHashtable<nsUint32HashKey, MediaSystemResourceClient*> mResourceClients; + + static StaticRefPtr<MediaSystemResourceManager> sSingleton; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/systemservices/MediaSystemResourceManagerChild.cpp b/dom/media/systemservices/MediaSystemResourceManagerChild.cpp new file mode 100644 index 000000000..284473821 --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceManagerChild.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "MediaSystemResourceManager.h" + +#include "MediaSystemResourceManagerChild.h" + +namespace mozilla { +namespace media { + +MediaSystemResourceManagerChild::MediaSystemResourceManagerChild() + : mDestroyed(false) +{ +} + +MediaSystemResourceManagerChild::~MediaSystemResourceManagerChild() +{ +} + +bool +MediaSystemResourceManagerChild::RecvResponse(const uint32_t& aId, + const bool& aSuccess) +{ + if (mManager) { + mManager->RecvResponse(aId, aSuccess); + } + return true; +} + +void +MediaSystemResourceManagerChild::ActorDestroy(ActorDestroyReason aActorDestroyReason) +{ + MOZ_ASSERT(!mDestroyed); + if (mManager) { + mManager->OnIpcClosed(); + } + mDestroyed = true; +} + +void +MediaSystemResourceManagerChild::Destroy() +{ + if (mDestroyed) { + return; + } + SendRemoveResourceManager(); + // WARNING: |this| is dead, hands off +} + +} // namespace media +} // namespace mozilla diff --git a/dom/media/systemservices/MediaSystemResourceManagerChild.h b/dom/media/systemservices/MediaSystemResourceManagerChild.h new file mode 100644 index 000000000..51d4de8d1 --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceManagerChild.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#if !defined(MediaSystemResourceManagerChild_h_) +#define MediaSystemResourceManagerChild_h_ + +#include "mozilla/media/PMediaSystemResourceManagerChild.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +class MediaSystemResourceManager; + +namespace ipc { +class BackgroundChildImpl; +} // namespace ipc + +namespace media { + +/** + * Handle MediaSystemResourceManager's IPC + */ +class MediaSystemResourceManagerChild final : public PMediaSystemResourceManagerChild +{ +public: + struct ResourceListener { + /* The resource is reserved and can be granted. + * The client can allocate the requested resource. + */ + virtual void resourceReserved() = 0; + /* The resource is not reserved any more. + * The client should release the resource as soon as possible if the + * resource is still being held. + */ + virtual void resourceCanceled() = 0; + }; + + MediaSystemResourceManagerChild(); + virtual ~MediaSystemResourceManagerChild(); + + void Destroy(); + + void SetManager(MediaSystemResourceManager* aManager) + { + mManager = aManager; + } + +protected: + bool RecvResponse(const uint32_t& aId, + const bool& aSuccess) override; + +private: + void ActorDestroy(ActorDestroyReason aActorDestroyReason) override; + + bool mDestroyed; + MediaSystemResourceManager* mManager; + + friend class mozilla::ipc::BackgroundChildImpl; +}; + +} // namespace media +} // namespace mozilla + +#endif diff --git a/dom/media/systemservices/MediaSystemResourceManagerParent.cpp b/dom/media/systemservices/MediaSystemResourceManagerParent.cpp new file mode 100644 index 000000000..9e15c0c48 --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceManagerParent.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/Unused.h" + +#include "MediaSystemResourceManagerParent.h" + +namespace mozilla { +namespace media { + +using namespace ipc; + +MediaSystemResourceManagerParent::MediaSystemResourceManagerParent() + : mDestroyed(false) +{ + mMediaSystemResourceService = MediaSystemResourceService::Get(); +} + +MediaSystemResourceManagerParent::~MediaSystemResourceManagerParent() +{ + MOZ_ASSERT(mDestroyed); +} + +bool +MediaSystemResourceManagerParent::RecvAcquire(const uint32_t& aId, + const MediaSystemResourceType& aResourceType, + const bool& aWillWait) +{ + MediaSystemResourceRequest* request = mResourceRequests.Get(aId); + MOZ_ASSERT(!request); + if (request) { + // Send fail response + mozilla::Unused << SendResponse(aId, false /* fail */); + return true; + } + + request = new MediaSystemResourceRequest(aId, aResourceType); + mResourceRequests.Put(aId, request); + mMediaSystemResourceService->Acquire(this, aId, aResourceType, aWillWait); + return true; +} + +bool +MediaSystemResourceManagerParent::RecvRelease(const uint32_t& aId) +{ + MediaSystemResourceRequest* request = mResourceRequests.Get(aId); + if (!request) { + return true; + } + + mMediaSystemResourceService->ReleaseResource(this, aId, request->mResourceType); + mResourceRequests.Remove(aId); + return true; +} + +bool +MediaSystemResourceManagerParent::RecvRemoveResourceManager() +{ + return PMediaSystemResourceManagerParent::Send__delete__(this); +} + +void +MediaSystemResourceManagerParent::ActorDestroy(ActorDestroyReason aReason) +{ + MOZ_ASSERT(!mDestroyed); + + // Release all resource requests of the MediaSystemResourceManagerParent. + // Clears all remaining pointers to this object. + mMediaSystemResourceService->ReleaseResource(this); + + mDestroyed = true; +} + +} // namespace media +} // namespace mozilla diff --git a/dom/media/systemservices/MediaSystemResourceManagerParent.h b/dom/media/systemservices/MediaSystemResourceManagerParent.h new file mode 100644 index 000000000..9080cb385 --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceManagerParent.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#if !defined(MediaSystemResourceManagerParent_h_) +#define MediaSystemResourceManagerParent_h_ + +#include "MediaSystemResourceManager.h" +#include "MediaSystemResourceService.h" +#include "MediaSystemResourceTypes.h" +#include "mozilla/media/PMediaSystemResourceManagerParent.h" + +namespace mozilla { +namespace media { + +/** + * Handle MediaSystemResourceManager's IPC + */ +class MediaSystemResourceManagerParent final : public PMediaSystemResourceManagerParent +{ +public: + MediaSystemResourceManagerParent(); + virtual ~MediaSystemResourceManagerParent(); + +protected: + bool RecvAcquire(const uint32_t& aId, + const MediaSystemResourceType& aResourceType, + const bool& aWillWait) override; + + bool RecvRelease(const uint32_t& aId) override; + + bool RecvRemoveResourceManager() override; + +private: + void ActorDestroy(ActorDestroyReason aActorDestroyReason) override; + + struct MediaSystemResourceRequest { + MediaSystemResourceRequest() + : mId(-1), mResourceType(MediaSystemResourceType::INVALID_RESOURCE) {} + MediaSystemResourceRequest(uint32_t aId, MediaSystemResourceType aResourceType) + : mId(aId), mResourceType(aResourceType) {} + int32_t mId; + MediaSystemResourceType mResourceType; + }; + + bool mDestroyed; + + RefPtr<MediaSystemResourceService> mMediaSystemResourceService; + + nsClassHashtable<nsUint32HashKey, MediaSystemResourceRequest> mResourceRequests; +}; + +} // namespace media +} // namespace mozilla + +#endif diff --git a/dom/media/systemservices/MediaSystemResourceMessageUtils.h b/dom/media/systemservices/MediaSystemResourceMessageUtils.h new file mode 100644 index 000000000..851fbb253 --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceMessageUtils.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#if !defined(MediaSystemResourceMessageUtils_h_) +#define MediaSystemResourceMessageUtils_h_ + +#include "ipc/IPCMessageUtils.h" +#include "MediaSystemResourceTypes.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::MediaSystemResourceType> + : public ContiguousEnumSerializer< + mozilla::MediaSystemResourceType, + mozilla::MediaSystemResourceType::VIDEO_DECODER, + mozilla::MediaSystemResourceType::INVALID_RESOURCE> +{}; + +} // namespace IPC + +#endif diff --git a/dom/media/systemservices/MediaSystemResourceService.cpp b/dom/media/systemservices/MediaSystemResourceService.cpp new file mode 100644 index 000000000..0e5d6a50c --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceService.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "MediaSystemResourceManagerParent.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/Unused.h" + +#include "MediaSystemResourceService.h" + +using namespace mozilla::layers; + +namespace mozilla { + +/* static */ StaticRefPtr<MediaSystemResourceService> MediaSystemResourceService::sSingleton; + +/* static */ MediaSystemResourceService* +MediaSystemResourceService::Get() +{ + if (sSingleton) { + return sSingleton; + } + Init(); + return sSingleton; +} + +/* static */ void +MediaSystemResourceService::Init() +{ + if (!sSingleton) { + sSingleton = new MediaSystemResourceService(); + } +} + +/* static */ void +MediaSystemResourceService::Shutdown() +{ + if (sSingleton) { + sSingleton->Destroy(); + sSingleton = nullptr; + } +} + +MediaSystemResourceService::MediaSystemResourceService() + : mDestroyed(false) +{ + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); +#ifdef MOZ_WIDGET_GONK + // The maximum number of hardware resoureces available. + // XXX need to hange to a dynamic way. + enum + { + VIDEO_DECODER_COUNT = 1, + VIDEO_ENCODER_COUNT = 1 + }; + + MediaSystemResource* resource; + + resource = new MediaSystemResource(VIDEO_DECODER_COUNT); + mResources.Put(static_cast<uint32_t>(MediaSystemResourceType::VIDEO_DECODER), resource); + + resource = new MediaSystemResource(VIDEO_ENCODER_COUNT); + mResources.Put(static_cast<uint32_t>(MediaSystemResourceType::VIDEO_ENCODER), resource); +#endif +} + +MediaSystemResourceService::~MediaSystemResourceService() +{ +} + +void +MediaSystemResourceService::Destroy() +{ + mDestroyed = true; +} + +void +MediaSystemResourceService::Acquire(media::MediaSystemResourceManagerParent* aParent, + uint32_t aId, + MediaSystemResourceType aResourceType, + bool aWillWait) +{ + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(aParent); + + if (mDestroyed) { + return; + } + + MediaSystemResource* resource = mResources.Get(static_cast<uint32_t>(aResourceType)); + + if (!resource || + resource->mResourceCount == 0) { + // Resource does not exit + // Send fail response + mozilla::Unused << aParent->SendResponse(aId, false /* fail */); + return; + } + + // Try to acquire a resource + if (resource->mAcquiredRequests.size() < resource->mResourceCount) { + // Resource is available + resource->mAcquiredRequests.push_back( + MediaSystemResourceRequest(aParent, aId)); + // Send success response + mozilla::Unused << aParent->SendResponse(aId, true /* success */); + return; + } else if (!aWillWait) { + // Resource is not available and do not wait. + // Send fail response + mozilla::Unused << aParent->SendResponse(aId, false /* fail */); + return; + } + // Wait until acquire. + resource->mWaitingRequests.push_back( + MediaSystemResourceRequest(aParent, aId)); +} + +void +MediaSystemResourceService::ReleaseResource(media::MediaSystemResourceManagerParent* aParent, + uint32_t aId, + MediaSystemResourceType aResourceType) +{ + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(aParent); + + if (mDestroyed) { + return; + } + + MediaSystemResource* resource = mResources.Get(static_cast<uint32_t>(aResourceType)); + + if (!resource || + resource->mResourceCount == 0) { + // Resource does not exit + return; + } + RemoveRequest(aParent, aId, aResourceType); + UpdateRequests(aResourceType); +} + +void +MediaSystemResourceService::ReleaseResource(media::MediaSystemResourceManagerParent* aParent) +{ + MOZ_ASSERT(aParent); + + if (mDestroyed) { + return; + } + + for (auto iter = mResources.Iter(); !iter.Done(); iter.Next()) { + const uint32_t& key = iter.Key(); + RemoveRequests(aParent, static_cast<MediaSystemResourceType>(key)); + UpdateRequests(static_cast<MediaSystemResourceType>(key)); + } +} + +void +MediaSystemResourceService::RemoveRequest(media::MediaSystemResourceManagerParent* aParent, + uint32_t aId, + MediaSystemResourceType aResourceType) +{ + MOZ_ASSERT(aParent); + + MediaSystemResource* resource = mResources.Get(static_cast<uint32_t>(aResourceType)); + if (!resource) { + return; + } + + std::deque<MediaSystemResourceRequest>::iterator it; + std::deque<MediaSystemResourceRequest>& acquiredRequests = + resource->mAcquiredRequests; + for (it = acquiredRequests.begin(); it != acquiredRequests.end(); it++) { + if (((*it).mParent == aParent) && ((*it).mId == aId)) { + acquiredRequests.erase(it); + return; + } + } + + std::deque<MediaSystemResourceRequest>& waitingRequests = + resource->mWaitingRequests; + for (it = waitingRequests.begin(); it != waitingRequests.end(); it++) { + if (((*it).mParent == aParent) && ((*it).mId == aId)) { + waitingRequests.erase(it); + return; + } + } +} + +void +MediaSystemResourceService::RemoveRequests(media::MediaSystemResourceManagerParent* aParent, + MediaSystemResourceType aResourceType) +{ + MOZ_ASSERT(aParent); + + MediaSystemResource* resource = mResources.Get(static_cast<uint32_t>(aResourceType)); + + if (!resource || + resource->mResourceCount == 0) { + // Resource does not exit + return; + } + + std::deque<MediaSystemResourceRequest>::iterator it; + std::deque<MediaSystemResourceRequest>& acquiredRequests = + resource->mAcquiredRequests; + for (it = acquiredRequests.begin(); it != acquiredRequests.end();) { + if ((*it).mParent == aParent) { + it = acquiredRequests.erase(it); + } else { + it++; + } + } + + std::deque<MediaSystemResourceRequest>& waitingRequests = + resource->mWaitingRequests; + for (it = waitingRequests.begin(); it != waitingRequests.end();) { + if ((*it).mParent == aParent) { + it = waitingRequests.erase(it); + } else { + it++; + } + } +} + +void +MediaSystemResourceService::UpdateRequests(MediaSystemResourceType aResourceType) +{ + MediaSystemResource* resource = mResources.Get(static_cast<uint32_t>(aResourceType)); + + if (!resource || + resource->mResourceCount == 0) { + // Resource does not exit + return; + } + + std::deque<MediaSystemResourceRequest>& acquiredRequests = + resource->mAcquiredRequests; + std::deque<MediaSystemResourceRequest>& waitingRequests = + resource->mWaitingRequests; + + while ((acquiredRequests.size() < resource->mResourceCount) && + (waitingRequests.size() > 0)) { + MediaSystemResourceRequest& request = waitingRequests.front(); + MOZ_ASSERT(request.mParent); + // Send response + mozilla::Unused << request.mParent->SendResponse(request.mId, true /* success */); + // Move request to mAcquiredRequests + acquiredRequests.push_back(waitingRequests.front()); + waitingRequests.pop_front(); + } +} + +} // namespace mozilla diff --git a/dom/media/systemservices/MediaSystemResourceService.h b/dom/media/systemservices/MediaSystemResourceService.h new file mode 100644 index 000000000..06e1b537c --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceService.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#if !defined(MediaSystemResourceService_h_) +#define MediaSystemResourceService_h_ + +#include <deque> + +#include "MediaSystemResourceTypes.h" +#include "mozilla/StaticPtr.h" +#include "nsClassHashtable.h" + +namespace mozilla { + +namespace media { +class MediaSystemResourceManagerParent; +} // namespace media + +/** + * Manage media system resource allocation requests within system. + */ +class MediaSystemResourceService +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSystemResourceService) + + static MediaSystemResourceService* Get(); + static void Init(); + static void Shutdown(); + + void Acquire(media::MediaSystemResourceManagerParent* aParent, + uint32_t aId, + MediaSystemResourceType aResourceType, + bool aWillWait); + + void ReleaseResource(media::MediaSystemResourceManagerParent* aParent, + uint32_t aId, + MediaSystemResourceType aResourceType); + + void ReleaseResource(media::MediaSystemResourceManagerParent* aParent); + +private: + MediaSystemResourceService(); + ~MediaSystemResourceService(); + + struct MediaSystemResourceRequest { + MediaSystemResourceRequest() + : mParent(nullptr), mId(-1) {} + MediaSystemResourceRequest(media::MediaSystemResourceManagerParent* aParent, uint32_t aId) + : mParent(aParent), mId(aId) {} + media::MediaSystemResourceManagerParent* mParent; + uint32_t mId; + }; + + struct MediaSystemResource { + MediaSystemResource() + : mResourceCount(0) {} + explicit MediaSystemResource(uint32_t aResourceCount) + : mResourceCount(aResourceCount) {} + + std::deque<MediaSystemResourceRequest> mWaitingRequests; + std::deque<MediaSystemResourceRequest> mAcquiredRequests; + uint32_t mResourceCount; + }; + + void Destroy(); + + void RemoveRequest(media::MediaSystemResourceManagerParent* aParent, + uint32_t aId, + MediaSystemResourceType aResourceType); + + void RemoveRequests(media::MediaSystemResourceManagerParent* aParent, + MediaSystemResourceType aResourceType); + + void UpdateRequests(MediaSystemResourceType aResourceType); + + bool mDestroyed; + + nsClassHashtable<nsUint32HashKey, MediaSystemResource> mResources; + + static StaticRefPtr<MediaSystemResourceService> sSingleton; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/systemservices/MediaSystemResourceTypes.h b/dom/media/systemservices/MediaSystemResourceTypes.h new file mode 100644 index 000000000..3257d89d2 --- /dev/null +++ b/dom/media/systemservices/MediaSystemResourceTypes.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#if !defined(MediaSystemResourceTypes_h_) +#define MediaSystemResourceTypes_h_ + +namespace mozilla { + +enum class MediaSystemResourceType : uint32_t { + VIDEO_DECODER = 0, + AUDIO_DECODER, // Not supported currently. + VIDEO_ENCODER, + AUDIO_ENCODER, // Not supported currently. + CAMERA, // Not supported currently. + INVALID_RESOURCE, +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/systemservices/MediaTaskUtils.h b/dom/media/systemservices/MediaTaskUtils.h new file mode 100644 index 000000000..c9b64fd2a --- /dev/null +++ b/dom/media/systemservices/MediaTaskUtils.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef mozilla_MediaTaskUtils_h +#define mozilla_MediaTaskUtils_h + +#include "nsThreadUtils.h" + +// The main reason this file is separate from MediaUtils.h +#include "base/task.h" + +namespace mozilla { +namespace media { + +/* media::NewTaskFrom() - Create a Task from a lambda. + * + * Similar to media::NewRunnableFrom() - Create an nsRunnable from a lambda. + */ + +template<typename OnRunType> +class LambdaTask : public Runnable +{ +public: + explicit LambdaTask(OnRunType&& aOnRun) : mOnRun(Move(aOnRun)) {} +private: + NS_IMETHOD + Run() override + { + mOnRun(); + return NS_OK; + } + OnRunType mOnRun; +}; + +template<typename OnRunType> +already_AddRefed<LambdaTask<OnRunType>> +NewTaskFrom(OnRunType&& aOnRun) +{ + typedef LambdaTask<OnRunType> LambdaType; + RefPtr<LambdaType> lambda = new LambdaType(Forward<OnRunType>(aOnRun)); + return lambda.forget(); +} + +} // namespace media +} // namespace mozilla + +#endif // mozilla_MediaTaskUtils_h diff --git a/dom/media/systemservices/MediaUtils.cpp b/dom/media/systemservices/MediaUtils.cpp new file mode 100644 index 000000000..a77e3404a --- /dev/null +++ b/dom/media/systemservices/MediaUtils.cpp @@ -0,0 +1,15 @@ +/* -*- 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 "MediaUtils.h" + +namespace mozilla { +namespace media { + +NS_IMPL_ISUPPORTS(ShutdownBlocker, nsIAsyncShutdownBlocker) + +} // namespace media +} // namespace mozilla diff --git a/dom/media/systemservices/MediaUtils.h b/dom/media/systemservices/MediaUtils.h new file mode 100644 index 000000000..18f7d3e41 --- /dev/null +++ b/dom/media/systemservices/MediaUtils.h @@ -0,0 +1,373 @@ +/* -*- 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/. */ + +#ifndef mozilla_MediaUtils_h +#define mozilla_MediaUtils_h + +#include "nsThreadUtils.h" +#include "nsIAsyncShutdown.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace media { + +/* + * media::Pledge - A promise-like pattern for c++ that takes lambda functions. + * + * Asynchronous APIs that proxy to another thread or to the chrome process and + * back may find it useful to return a pledge to callers who then use + * pledge.Then(func) to specify a lambda function to be invoked with the result + * later back on this same thread. + * + * Callers will enjoy that lambdas allow "capturing" of local variables, much + * like closures in JavaScript (safely by-copy by default). + * + * Callers will also enjoy that they do not need to be thread-safe (their code + * runs on the same thread after all). + * + * Advantageously, pledges are non-threadsafe by design (because locking and + * event queues are redundant). This means none of the lambdas you pass in, + * or variables you lambda-capture into them, need be threasafe or support + * threadsafe refcounting. After all, they'll run later on the same thread. + * + * RefPtr<media::Pledge<Foo>> p = GetFooAsynchronously(); // returns a pledge + * p->Then([](const Foo& foo) { + * // use foo here (same thread. Need not be thread-safe!) + * }); + * + * See media::CoatCheck below for an example of GetFooAsynchronously(). + */ + +class PledgeBase +{ +public: + NS_INLINE_DECL_REFCOUNTING(PledgeBase); +protected: + virtual ~PledgeBase() {}; +}; + +template<typename ValueType, typename ErrorType = nsresult> +class Pledge : public PledgeBase +{ + // TODO: Remove workaround once mozilla allows std::function from <functional> + // wo/std::function support, do template + virtual trick to accept lambdas + class FunctorsBase + { + public: + FunctorsBase() {} + virtual void Succeed(ValueType& result) = 0; + virtual void Fail(ErrorType& error) = 0; + virtual ~FunctorsBase() {}; + }; + +public: + explicit Pledge() : mDone(false), mRejected(false) {} + Pledge(const Pledge& aOther) = delete; + Pledge& operator = (const Pledge&) = delete; + + template<typename OnSuccessType> + void Then(OnSuccessType&& aOnSuccess) + { + Then(Forward<OnSuccessType>(aOnSuccess), [](ErrorType&){}); + } + + template<typename OnSuccessType, typename OnFailureType> + void Then(OnSuccessType&& aOnSuccess, OnFailureType&& aOnFailure) + { + class Functors : public FunctorsBase + { + public: + Functors(OnSuccessType&& aOnSuccessRef, OnFailureType&& aOnFailureRef) + : mOnSuccess(Move(aOnSuccessRef)), mOnFailure(Move(aOnFailureRef)) {} + + void Succeed(ValueType& result) + { + mOnSuccess(result); + } + void Fail(ErrorType& error) + { + mOnFailure(error); + }; + + OnSuccessType mOnSuccess; + OnFailureType mOnFailure; + }; + mFunctors = MakeUnique<Functors>(Forward<OnSuccessType>(aOnSuccess), + Forward<OnFailureType>(aOnFailure)); + if (mDone) { + if (!mRejected) { + mFunctors->Succeed(mValue); + } else { + mFunctors->Fail(mError); + } + } + } + + void Resolve(const ValueType& aValue) + { + mValue = aValue; + Resolve(); + } + + void Reject(ErrorType rv) + { + if (!mDone) { + mDone = mRejected = true; + mError = rv; + if (mFunctors) { + mFunctors->Fail(mError); + } + } + } + +protected: + void Resolve() + { + if (!mDone) { + mDone = true; + MOZ_ASSERT(!mRejected); + if (mFunctors) { + mFunctors->Succeed(mValue); + } + } + } + + ValueType mValue; +private: + ~Pledge() {}; + bool mDone; + bool mRejected; + ErrorType mError; + UniquePtr<FunctorsBase> mFunctors; +}; + +/* media::NewRunnableFrom() - Create a Runnable from a lambda. + * + * Passing variables (closures) to an async function is clunky with Runnable: + * + * void Foo() + * { + * class FooRunnable : public Runnable + * { + * public: + * FooRunnable(const Bar &aBar) : mBar(aBar) {} + * NS_IMETHOD Run() override + * { + * // Use mBar + * } + * private: + * RefPtr<Bar> mBar; + * }; + * + * RefPtr<Bar> bar = new Bar(); + * NS_DispatchToMainThread(new FooRunnable(bar); + * } + * + * It's worse with more variables. Lambdas have a leg up with variable capture: + * + * void Foo() + * { + * RefPtr<Bar> bar = new Bar(); + * NS_DispatchToMainThread(media::NewRunnableFrom([bar]() mutable { + * // use bar + * }); + * } + * + * Capture is by-copy by default, so the nsRefPtr 'bar' is safely copied for + * access on the other thread (threadsafe refcounting in bar is assumed). + * + * The 'mutable' keyword is only needed for non-const access to bar. + */ + +template<typename OnRunType> +class LambdaRunnable : public Runnable +{ +public: + explicit LambdaRunnable(OnRunType&& aOnRun) : mOnRun(Move(aOnRun)) {} +private: + NS_IMETHODIMP + Run() override + { + return mOnRun(); + } + OnRunType mOnRun; +}; + +template<typename OnRunType> +already_AddRefed<LambdaRunnable<OnRunType>> +NewRunnableFrom(OnRunType&& aOnRun) +{ + typedef LambdaRunnable<OnRunType> LambdaType; + RefPtr<LambdaType> lambda = new LambdaType(Forward<OnRunType>(aOnRun)); + return lambda.forget(); +} + +/* media::CoatCheck - There and back again. Park an object in exchange for an id. + * + * A common problem with calling asynchronous functions that do work on other + * threads or processes is how to pass in a heap object for use once the + * function completes, without requiring that object to have threadsafe + * refcounting, contain mutexes, be marshaled, or leak if things fail + * (or worse, intermittent use-after-free because of lifetime issues). + * + * One solution is to set up a coat-check on the caller side, park your object + * in exchange for an id, and send the id. Common in IPC, but equally useful + * for same-process thread-hops, because by never leaving the thread there's + * no need for objects to be threadsafe or use threadsafe refcounting. E.g. + * + * class FooDoer + * { + * CoatCheck<Foo> mOutstandingFoos; + * + * public: + * void DoFoo() + * { + * RefPtr<Foo> foo = new Foo(); + * uint32_t requestId = mOutstandingFoos.Append(*foo); + * sChild->SendFoo(requestId); + * } + * + * void RecvFooResponse(uint32_t requestId) + * { + * RefPtr<Foo> foo = mOutstandingFoos.Remove(requestId); + * if (foo) { + * // use foo + * } + * } + * }; + * + * If you read media::Pledge earlier, here's how this is useful for pledges: + * + * class FooGetter + * { + * CoatCheck<Pledge<Foo>> mOutstandingPledges; + * + * public: + * already_addRefed<Pledge<Foo>> GetFooAsynchronously() + * { + * RefPtr<Pledge<Foo>> p = new Pledge<Foo>(); + * uint32_t requestId = mOutstandingPledges.Append(*p); + * sChild->SendFoo(requestId); + * return p.forget(); + * } + * + * void RecvFooResponse(uint32_t requestId, const Foo& fooResult) + * { + * RefPtr<Foo> p = mOutstandingPledges.Remove(requestId); + * if (p) { + * p->Resolve(fooResult); + * } + * } + * }; + * + * This helper is currently optimized for very small sets (i.e. not optimized). + * It is also not thread-safe as the whole point is to stay on the same thread. + */ + +template<class T> +class CoatCheck +{ +public: + typedef std::pair<uint32_t, RefPtr<T>> Element; + + uint32_t Append(T& t) + { + uint32_t id = GetNextId(); + mElements.AppendElement(Element(id, RefPtr<T>(&t))); + return id; + } + + already_AddRefed<T> Remove(uint32_t aId) + { + for (auto& element : mElements) { + if (element.first == aId) { + RefPtr<T> ref; + ref.swap(element.second); + mElements.RemoveElement(element); + return ref.forget(); + } + } + MOZ_ASSERT_UNREACHABLE("Received id with no matching parked object!"); + return nullptr; + } + +private: + static uint32_t GetNextId() + { + static uint32_t counter = 0; + return ++counter; + }; + AutoTArray<Element, 3> mElements; +}; + +/* media::Refcountable - Add threadsafe ref-counting to something that isn't. + * + * Often, reference counting is the most practical way to share an object with + * another thread without imposing lifetime restrictions, even if there's + * otherwise no concurrent access happening on the object. For instance, an + * algorithm on another thread may find it more expedient to modify a passed-in + * object, rather than pass expensive copies back and forth. + * + * Lists in particular often aren't ref-countable, yet are expensive to copy, + * e.g. nsTArray<RefPtr<Foo>>. Refcountable can be used to make such objects + * (or owning smart-pointers to such objects) refcountable. + * + * Technical limitation: A template specialization is needed for types that take + * a constructor. Please add below (UniquePtr covers a lot of ground though). + */ + +template<typename T> +class Refcountable : public T +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Refcountable<T>) +private: + ~Refcountable<T>() {} +}; + +template<typename T> +class Refcountable<UniquePtr<T>> : public UniquePtr<T> +{ +public: + explicit Refcountable<UniquePtr<T>>(T* aPtr) : UniquePtr<T>(aPtr) {} + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Refcountable<T>) +private: + ~Refcountable<UniquePtr<T>>() {} +}; + +/* media::ShutdownBlocker - Async shutdown helper. + */ + +class ShutdownBlocker : public nsIAsyncShutdownBlocker +{ +public: + ShutdownBlocker(const nsString& aName) : mName(aName) {} + + NS_IMETHOD + BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override = 0; + + NS_IMETHOD GetName(nsAString& aName) override + { + aName = mName; + return NS_OK; + } + + NS_IMETHOD GetState(nsIPropertyBag**) override + { + return NS_OK; + } + + NS_DECL_ISUPPORTS +protected: + virtual ~ShutdownBlocker() {} +private: + const nsString mName; +}; + +} // namespace media +} // namespace mozilla + +#endif // mozilla_MediaUtils_h diff --git a/dom/media/systemservices/OSXRunLoopSingleton.cpp b/dom/media/systemservices/OSXRunLoopSingleton.cpp new file mode 100644 index 000000000..6211d5c12 --- /dev/null +++ b/dom/media/systemservices/OSXRunLoopSingleton.cpp @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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 "OSXRunLoopSingleton.h" +#include <mozilla/StaticMutex.h> + +#include <AudioUnit/AudioUnit.h> +#include <CoreAudio/AudioHardware.h> +#include <CoreAudio/HostTime.h> +#include <CoreFoundation/CoreFoundation.h> + +static bool gRunLoopSet = false; +static mozilla::StaticMutex gMutex; + +void mozilla_set_coreaudio_notification_runloop_if_needed() +{ + mozilla::StaticMutexAutoLock lock(gMutex); + if (gRunLoopSet) { + return; + } + + /* This is needed so that AudioUnit listeners get called on this thread, and + * not the main thread. If we don't do that, they are not called, or a crash + * occur, depending on the OSX version. */ + AudioObjectPropertyAddress runloop_address = { + kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + CFRunLoopRef run_loop = nullptr; + + OSStatus r; + r = AudioObjectSetPropertyData(kAudioObjectSystemObject, + &runloop_address, + 0, NULL, sizeof(CFRunLoopRef), &run_loop); + if (r != noErr) { + NS_WARNING("Could not make global CoreAudio notifications use their own thread."); + } + + gRunLoopSet = true; +} diff --git a/dom/media/systemservices/OSXRunLoopSingleton.h b/dom/media/systemservices/OSXRunLoopSingleton.h new file mode 100644 index 000000000..d06365e14 --- /dev/null +++ b/dom/media/systemservices/OSXRunLoopSingleton.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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/. */ + +#ifndef OSXRUNLOOPSINGLETON_H_ +#define OSXRUNLOOPSINGLETON_H_ + +#include <mozilla/Types.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +/* This function tells CoreAudio to use its own thread for device change + * notifications, and can be called from any thread without external + * synchronization. */ +void MOZ_EXPORT +mozilla_set_coreaudio_notification_runloop_if_needed(); + +#if defined(__cplusplus) +} +#endif + +#endif // OSXRUNLOOPSINGLETON_H_ diff --git a/dom/media/systemservices/OpenSLESProvider.cpp b/dom/media/systemservices/OpenSLESProvider.cpp new file mode 100644 index 000000000..c7348afa0 --- /dev/null +++ b/dom/media/systemservices/OpenSLESProvider.cpp @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "OpenSLESProvider.h" +#include "mozilla/Logging.h" +#include "nsDebug.h" + +#include <dlfcn.h> +#include <SLES/OpenSLES_Android.h> +#include <SLES/OpenSLES_AndroidConfiguration.h> + +// MOZ_LOG=OpenSLESProvider:5 +#undef LOG +#undef LOG_ENABLED +mozilla::LazyLogModule gOpenSLESProviderLog("OpenSLESProvider"); +#define LOG(args) MOZ_LOG(gOpenSLESProviderLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gOpenSLESProviderLog, mozilla::LogLevel::Debug) + +namespace mozilla { + +OpenSLESProvider::OpenSLESProvider() + : mLock("OpenSLESProvider.mLock"), + mSLEngine(nullptr), + mSLEngineUsers(0), + mIsRealized(false), + mOpenSLESLib(nullptr) +{ + LOG(("OpenSLESProvider being initialized")); +} + +OpenSLESProvider::~OpenSLESProvider() +{ + if (mOpenSLESLib) { + LOG(("OpenSLES Engine was not properly Destroyed")); + (void)dlclose(mOpenSLESLib); + } +} + +/* static */ +OpenSLESProvider& OpenSLESProvider::getInstance() +{ + // This doesn't need a Mutex in C++11 or GCC 4.3+, see N2660 and + // https://gcc.gnu.org/projects/cxx0x.html + static OpenSLESProvider instance; + return instance; +} + +/* static */ +SLresult OpenSLESProvider::Get(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions) +{ + OpenSLESProvider& provider = OpenSLESProvider::getInstance(); + return provider.GetEngine(aObjectm, aOptionCount, aOptions); +} + +SLresult OpenSLESProvider::GetEngine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions) +{ + MutexAutoLock lock(mLock); + LOG(("Getting OpenSLES engine")); + // Bug 1042051: Validate options are the same + if (mSLEngine != nullptr) { + *aObjectm = mSLEngine; + mSLEngineUsers++; + LOG(("Returning existing engine, %d users", mSLEngineUsers)); + return SL_RESULT_SUCCESS; + } else { + int res = ConstructEngine(aObjectm, aOptionCount, aOptions); + if (res == SL_RESULT_SUCCESS) { + // Bug 1042051: Store engine options + mSLEngine = *aObjectm; + mSLEngineUsers++; + LOG(("Returning new engine")); + } else { + LOG(("Error getting engine: %d", res)); + } + return res; + } +} + +SLresult OpenSLESProvider::ConstructEngine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions) +{ + mLock.AssertCurrentThreadOwns(); + + if (!mOpenSLESLib) { + mOpenSLESLib = dlopen("libOpenSLES.so", RTLD_LAZY); + if (!mOpenSLESLib) { + LOG(("Failed to dlopen OpenSLES library")); + return SL_RESULT_MEMORY_FAILURE; + } + } + + typedef SLresult (*slCreateEngine_t)(SLObjectItf *, + SLuint32, + const SLEngineOption *, + SLuint32, + const SLInterfaceID *, + const SLboolean *); + + slCreateEngine_t f_slCreateEngine = + (slCreateEngine_t)dlsym(mOpenSLESLib, "slCreateEngine"); + int result = f_slCreateEngine(aObjectm, aOptionCount, aOptions, 0, NULL, NULL); + return result; +} + +/* static */ +void OpenSLESProvider::Destroy(SLObjectItf * aObjectm) +{ + OpenSLESProvider& provider = OpenSLESProvider::getInstance(); + provider.DestroyEngine(aObjectm); +} + +void OpenSLESProvider::DestroyEngine(SLObjectItf * aObjectm) +{ + MutexAutoLock lock(mLock); + NS_ASSERTION(mOpenSLESLib, "OpenSLES destroy called but library is not open"); + + mSLEngineUsers--; + LOG(("Freeing engine, %d users left", mSLEngineUsers)); + if (mSLEngineUsers) { + return; + } + + (*(*aObjectm))->Destroy(*aObjectm); + // This assumes SLObjectItf is a pointer, but given the previous line, + // that's a given. + *aObjectm = nullptr; + + (void)dlclose(mOpenSLESLib); + mOpenSLESLib = nullptr; + mIsRealized = false; +} + +/* static */ +SLresult OpenSLESProvider::Realize(SLObjectItf aObjectm) +{ + OpenSLESProvider& provider = OpenSLESProvider::getInstance(); + return provider.RealizeEngine(aObjectm); +} + +SLresult OpenSLESProvider::RealizeEngine(SLObjectItf aObjectm) +{ + MutexAutoLock lock(mLock); + NS_ASSERTION(mOpenSLESLib, "OpenSLES realize called but library is not open"); + NS_ASSERTION(aObjectm != nullptr, "OpenSLES realize engine with empty ObjectItf"); + + if (mIsRealized) { + LOG(("Not realizing already realized engine")); + return SL_RESULT_SUCCESS; + } else { + SLresult res = (*aObjectm)->Realize(aObjectm, SL_BOOLEAN_FALSE); + if (res != SL_RESULT_SUCCESS) { + LOG(("Error realizing OpenSLES engine: %d", res)); + } else { + LOG(("Realized OpenSLES engine")); + mIsRealized = true; + } + return res; + } +} + +} // namespace mozilla + +extern "C" { +SLresult mozilla_get_sles_engine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions) +{ + return mozilla::OpenSLESProvider::Get(aObjectm, aOptionCount, aOptions); +} + +void mozilla_destroy_sles_engine(SLObjectItf * aObjectm) +{ + mozilla::OpenSLESProvider::Destroy(aObjectm); +} + +SLresult mozilla_realize_sles_engine(SLObjectItf aObjectm) +{ + return mozilla::OpenSLESProvider::Realize(aObjectm); +} + +} + diff --git a/dom/media/systemservices/OpenSLESProvider.h b/dom/media/systemservices/OpenSLESProvider.h new file mode 100644 index 000000000..6253e9519 --- /dev/null +++ b/dom/media/systemservices/OpenSLESProvider.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef _OPENSLESPROVIDER_H_ +#define _OPENSLESPROVIDER_H_ + +#include <SLES/OpenSLES.h> +#include <mozilla/Types.h> + +#ifdef __cplusplus +extern "C" { +#endif +extern MOZ_EXPORT +SLresult mozilla_get_sles_engine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions); +extern MOZ_EXPORT +void mozilla_destroy_sles_engine(SLObjectItf * aObjectm); +/* Realize is always in synchronous mode. */ +extern MOZ_EXPORT +SLresult mozilla_realize_sles_engine(SLObjectItf aObjectm); +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +#include "mozilla/Mutex.h" + +extern mozilla::LazyLogModule gOpenSLESProviderLog; + +namespace mozilla { + +class OpenSLESProvider { +public: + static SLresult Get(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions); + static void Destroy(SLObjectItf * aObjectm); + static SLresult Realize(SLObjectItf aObjectm); +private: + OpenSLESProvider(); + ~OpenSLESProvider(); + OpenSLESProvider(OpenSLESProvider const&); // NO IMPLEMENTATION + void operator=(OpenSLESProvider const&); // NO IMPLEMENTATION + static OpenSLESProvider& getInstance(); + SLresult GetEngine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions); + SLresult ConstructEngine(SLObjectItf * aObjectm, + SLuint32 aOptionCount, + const SLEngineOption *aOptions); + SLresult RealizeEngine(SLObjectItf aObjectm); + void DestroyEngine(SLObjectItf * aObjectm); + + // Protect all our internal variables + mozilla::Mutex mLock; + SLObjectItf mSLEngine; + int mSLEngineUsers; + bool mIsRealized; + void *mOpenSLESLib; +}; + +} //namespace +#endif // cplusplus + +#endif /* _OPENSLESPROVIDER_H_ */ diff --git a/dom/media/systemservices/PCameras.ipdl b/dom/media/systemservices/PCameras.ipdl new file mode 100644 index 000000000..b9fa58329 --- /dev/null +++ b/dom/media/systemservices/PCameras.ipdl @@ -0,0 +1,65 @@ +/* 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 protocol PContent; +include protocol PBackground; + +using mozilla::camera::CaptureEngine from "mozilla/media/CamerasTypes.h"; + +namespace mozilla { +namespace camera { + +struct CaptureCapability +{ + int width; + int height; + int maxFPS; + int expectedCaptureDelay; + int rawType; + int codecType; + bool interlaced; +}; + +async protocol PCameras +{ + manager PBackground; + +child: + async FrameSizeChange(CaptureEngine capEngine, int cap_id, int w, int h); + // transfers ownership of |buffer| from parent to child + async DeliverFrame(CaptureEngine capEngine, int cap_id, + Shmem buffer, size_t size, uint32_t time_stamp, + int64_t ntp_time, int64_t render_time); + async DeviceChange(); + async ReplyNumberOfCaptureDevices(int numdev); + async ReplyNumberOfCapabilities(int numdev); + async ReplyAllocateCaptureDevice(int numdev); + async ReplyGetCaptureCapability(CaptureCapability cap); + async ReplyGetCaptureDevice(nsCString device_name, nsCString device_id, bool scary); + async ReplyFailure(); + async ReplySuccess(); + async __delete__(); + +parent: + async NumberOfCaptureDevices(CaptureEngine engine); + async NumberOfCapabilities(CaptureEngine engine, nsCString deviceUniqueIdUTF8); + + async GetCaptureCapability(CaptureEngine engine, nsCString unique_idUTF8, int capability_number); + async GetCaptureDevice(CaptureEngine engine, int num); + + async AllocateCaptureDevice(CaptureEngine engine, nsCString unique_idUTF8, nsCString origin); + async ReleaseCaptureDevice(CaptureEngine engine, int numdev); + async StartCapture(CaptureEngine engine, int numdev, CaptureCapability capability); + async StopCapture(CaptureEngine engine, int numdev); + // transfers frame back + async ReleaseFrame(Shmem s); + + // Ask parent to delete us + async AllDone(); + // setup camera engine + async EnsureInitialized(CaptureEngine engine); +}; + +} // namespace camera +} // namespace mozilla diff --git a/dom/media/systemservices/PMedia.ipdl b/dom/media/systemservices/PMedia.ipdl new file mode 100644 index 000000000..c060f030e --- /dev/null +++ b/dom/media/systemservices/PMedia.ipdl @@ -0,0 +1,51 @@ +/* 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 protocol PContent; + +namespace mozilla { +namespace media { + +protocol PMedia +{ + manager PContent; + +parent: + /** + * Requests a potentially persistent unique secret key for each origin. + * Has no expiry, but is cleared by age along with cookies. + * This is needed by mediaDevices.enumerateDevices() to produce persistent + * deviceIds that wont work cross-origin. + * + * If aPrivateBrowsing is false, a key for this origin is returned from a + * primary pool of temporal in-memory keys and persistent keys read from disk. + * If no key exists, a temporal one is created. + * If aPersist is true and key is temporal, the key is promoted to persistent. + * Once persistent, a key cannot become temporal again. + * + * If aPrivateBrowsing is true, a different key for this origin is returned + * from a secondary pool that is never persisted to disk, and aPersist is + * ignored. + */ + async GetOriginKey(uint32_t aRequestId, nsCString aOrigin, bool aPrivateBrowsing, + bool aPersist); + + /** + * Clear per-orgin list of persistent deviceIds stored for enumerateDevices + * Fire and forget. + * + * aSinceTime - milliseconds since 1 January 1970 00:00:00 UTC. 0 = clear all + * + * aOnlyPrivateBrowsing - if true then only purge the separate in-memory + * per-origin list used in Private Browsing. + */ + async SanitizeOriginKeys(uint64_t aSinceWhen, bool aOnlyPrivateBrowsing); + +child: + async GetOriginKeyResponse(uint32_t aRequestId, nsCString key); + async __delete__(); +}; + +} // namespace media +} // namespace mozilla diff --git a/dom/media/systemservices/PMediaSystemResourceManager.ipdl b/dom/media/systemservices/PMediaSystemResourceManager.ipdl new file mode 100644 index 000000000..8ba546765 --- /dev/null +++ b/dom/media/systemservices/PMediaSystemResourceManager.ipdl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 protocol PImageBridge; +include "mozilla/media/MediaSystemResourceMessageUtils.h"; + +using mozilla::MediaSystemResourceType from "mozilla/media/MediaSystemResourceTypes.h"; + +namespace mozilla { +namespace media { + +/* + * The PMediaSystemResourceManager is a sub-protocol in PImageBridge + */ +sync protocol PMediaSystemResourceManager +{ + manager PImageBridge; + +child: + async Response(uint32_t aId, bool aSuccess); + async __delete__(); + +parent: + async Acquire(uint32_t aId, MediaSystemResourceType aResourceType, bool aWillWait); + async Release(uint32_t aId); + + /** + * Asynchronously tell the parent side to remove the PMediaSystemResourceManager. + */ + async RemoveResourceManager(); +}; + +} // namespace media +} // namespace mozilla + diff --git a/dom/media/systemservices/ShmemPool.cpp b/dom/media/systemservices/ShmemPool.cpp new file mode 100644 index 000000000..334a94e35 --- /dev/null +++ b/dom/media/systemservices/ShmemPool.cpp @@ -0,0 +1,82 @@ +/* -*- 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 "mozilla/Assertions.h" +#include "mozilla/Logging.h" +#include "mozilla/ShmemPool.h" +#include "mozilla/Move.h" + +namespace mozilla { + +ShmemPool::ShmemPool(size_t aPoolSize) + : mMutex("mozilla::ShmemPool"), + mPoolFree(aPoolSize) +#ifdef DEBUG + ,mMaxPoolUse(0) +#endif +{ + mShmemPool.SetLength(aPoolSize); +} + +mozilla::ShmemBuffer ShmemPool::GetIfAvailable(size_t aSize) +{ + MutexAutoLock lock(mMutex); + + // Pool is empty, don't block caller. + if (mPoolFree == 0) { + // This isn't initialized, so will be understood as an error. + return ShmemBuffer(); + } + + ShmemBuffer& res = mShmemPool[mPoolFree - 1]; + + if (!res.mInitialized) { + LOG(("No free preallocated Shmem")); + return ShmemBuffer(); + } + + MOZ_ASSERT(res.mShmem.IsWritable(), "Pool in Shmem is not writable?"); + + if (res.mShmem.Size<char>() < aSize) { + LOG(("Free Shmem but not of the right size")); + return ShmemBuffer(); + } + + mPoolFree--; +#ifdef DEBUG + size_t poolUse = mShmemPool.Length() - mPoolFree; + if (poolUse > mMaxPoolUse) { + mMaxPoolUse = poolUse; + LOG(("Maximum ShmemPool use increased: %d buffers", mMaxPoolUse)); + } +#endif + return Move(res); +} + +void ShmemPool::Put(ShmemBuffer&& aShmem) +{ + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mPoolFree < mShmemPool.Length()); + mShmemPool[mPoolFree] = Move(aShmem); + mPoolFree++; +#ifdef DEBUG + size_t poolUse = mShmemPool.Length() - mPoolFree; + if (poolUse > 0) { + LOG(("ShmemPool usage reduced to %d buffers", poolUse)); + } +#endif +} + +ShmemPool::~ShmemPool() +{ +#ifdef DEBUG + for (size_t i = 0; i < mShmemPool.Length(); i++) { + MOZ_ASSERT(!mShmemPool[i].Valid()); + } +#endif +} + +} // namespace mozilla diff --git a/dom/media/systemservices/ShmemPool.h b/dom/media/systemservices/ShmemPool.h new file mode 100644 index 000000000..95901ffa0 --- /dev/null +++ b/dom/media/systemservices/ShmemPool.h @@ -0,0 +1,153 @@ +/* -*- 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/. */ + +#ifndef mozilla_ShmemPool_h +#define mozilla_ShmemPool_h + +#include "mozilla/ipc/Shmem.h" +#include "mozilla/Mutex.h" + +#undef LOG +#undef LOG_ENABLED +extern mozilla::LazyLogModule gCamerasParentLog; +#define LOG(args) MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gCamerasParentLog, mozilla::LogLevel::Debug) + +namespace mozilla { + +class ShmemPool; + +class ShmemBuffer { +public: + ShmemBuffer() : mInitialized(false) {} + explicit ShmemBuffer(mozilla::ipc::Shmem aShmem) { + mInitialized = true; + mShmem = aShmem; + } + + ShmemBuffer(ShmemBuffer&& rhs) { + mInitialized = rhs.mInitialized; + mShmem = Move(rhs.mShmem); + } + + ShmemBuffer& operator=(ShmemBuffer&& rhs) { + MOZ_ASSERT(&rhs != this, "self-moves are prohibited"); + mInitialized = rhs.mInitialized; + mShmem = Move(rhs.mShmem); + return *this; + } + + // No copies allowed + ShmemBuffer(const ShmemBuffer&) = delete; + ShmemBuffer& operator=(const ShmemBuffer&) = delete; + + bool Valid() { + return mInitialized; + } + + char* GetBytes() { + return mShmem.get<char>(); + } + + mozilla::ipc::Shmem& Get() { + return mShmem; + } + +private: + friend class ShmemPool; + + bool mInitialized; + mozilla::ipc::Shmem mShmem; +}; + +class ShmemPool { +public: + explicit ShmemPool(size_t aPoolSize); + ~ShmemPool(); + // Get/GetIfAvailable differ in what thread they can run on. GetIfAvailable + // can run anywhere but won't allocate if the right size isn't available. + ShmemBuffer GetIfAvailable(size_t aSize); + void Put(ShmemBuffer&& aShmem); + + // We need to use the allocation/deallocation functions + // of a specific IPC child/parent instance. + template <class T> + void Cleanup(T* aInstance) + { + MutexAutoLock lock(mMutex); + for (size_t i = 0; i < mShmemPool.Length(); i++) { + if (mShmemPool[i].mInitialized) { + aInstance->DeallocShmem(mShmemPool[i].Get()); + mShmemPool[i].mInitialized = false; + } + } + } + + template <class T> + ShmemBuffer Get(T* aInstance, size_t aSize) + { + MutexAutoLock lock(mMutex); + + // Pool is empty, don't block caller. + if (mPoolFree == 0) { + // This isn't initialized, so will be understood as an error. + return ShmemBuffer(); + } + + ShmemBuffer& res = mShmemPool[mPoolFree - 1]; + + if (!res.mInitialized) { + LOG(("Initializing new Shmem in pool")); + if (!aInstance->AllocShmem(aSize, ipc::SharedMemory::TYPE_BASIC, &res.mShmem)) { + LOG(("Failure allocating new Shmem buffer")); + return ShmemBuffer(); + } + res.mInitialized = true; + } + + MOZ_ASSERT(res.mShmem.IsWritable(), "Shmem in Pool is not writable?"); + + // Prepare buffer, increase size if needed (we never shrink as we don't + // maintain seperate sized pools and we don't want to keep reallocating) + if (res.mShmem.Size<char>() < aSize) { + LOG(("Size change/increase in Shmem Pool")); + aInstance->DeallocShmem(res.mShmem); + res.mInitialized = false; + // this may fail; always check return value + if (!aInstance->AllocShmem(aSize, ipc::SharedMemory::TYPE_BASIC, &res.mShmem)) { + LOG(("Failure allocating resized Shmem buffer")); + return ShmemBuffer(); + } else { + res.mInitialized = true; + } + } + + MOZ_ASSERT(res.mShmem.IsWritable(), "Shmem in Pool is not writable post resize?"); + + mPoolFree--; +#ifdef DEBUG + size_t poolUse = mShmemPool.Length() - mPoolFree; + if (poolUse > mMaxPoolUse) { + mMaxPoolUse = poolUse; + LOG(("Maximum ShmemPool use increased: %d buffers", mMaxPoolUse)); + } +#endif + return Move(res); + } + +private: + Mutex mMutex; + size_t mPoolFree; +#ifdef DEBUG + size_t mMaxPoolUse; +#endif + nsTArray<ShmemBuffer> mShmemPool; +}; + + +} // namespace mozilla + +#endif // mozilla_ShmemPool_h diff --git a/dom/media/systemservices/moz.build b/dom/media/systemservices/moz.build new file mode 100644 index 000000000..33e5ed1f1 --- /dev/null +++ b/dom/media/systemservices/moz.build @@ -0,0 +1,104 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG['MOZ_WEBRTC']: + EXPORTS += [ + 'CamerasChild.h', + 'CamerasParent.h', + 'LoadManager.h', + 'LoadManagerFactory.h', + 'LoadMonitor.h', + ] + UNIFIED_SOURCES += [ + 'CamerasChild.cpp', + 'CamerasParent.cpp', + 'LoadManager.cpp', + 'LoadManagerFactory.cpp', + 'LoadMonitor.cpp', + 'ShmemPool.cpp', + ] + LOCAL_INCLUDES += [ + '/media/webrtc/signaling', + '/media/webrtc/trunk', + ] +if CONFIG['OS_TARGET'] == 'WINNT': + DEFINES['WEBRTC_WIN'] = True +else: + DEFINES['WEBRTC_POSIX'] = True + + +if CONFIG['OS_TARGET'] == 'Android': + EXPORTS += [ + 'OpenSLESProvider.h' + ] + UNIFIED_SOURCES += [ + 'OpenSLESProvider.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + UNIFIED_SOURCES += ['OSXRunLoopSingleton.cpp'] + EXPORTS += ['OSXRunLoopSingleton.h'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + if CONFIG['ANDROID_VERSION'] >= '17': + LOCAL_INCLUDES += [ + '%' + '%s/frameworks/wilhelm/include' % CONFIG['ANDROID_SOURCE'], + ] + else: + LOCAL_INCLUDES += [ + '%' + '%s/system/media/wilhelm/include' % CONFIG['ANDROID_SOURCE'], + ] + +if CONFIG['_MSC_VER']: + DEFINES['__PRETTY_FUNCTION__'] = '__FUNCSIG__' + + # This is intended as a temporary workaround to enable building with VS2015. + # media\webrtc\trunk\webrtc/base/criticalsection.h(59): warning C4312: + # 'reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size + CXXFLAGS += ['-wd4312'] + +EXPORTS.mozilla += ['ShmemPool.h',] + +EXPORTS.mozilla.media += ['CamerasTypes.h', + 'DeviceChangeCallback.h', + 'MediaChild.h', + 'MediaParent.h', + 'MediaSystemResourceClient.h', + 'MediaSystemResourceManager.h', + 'MediaSystemResourceManagerChild.h', + 'MediaSystemResourceManagerParent.h', + 'MediaSystemResourceMessageUtils.h', + 'MediaSystemResourceService.h', + 'MediaSystemResourceTypes.h', + 'MediaTaskUtils.h', + 'MediaUtils.h', +] +UNIFIED_SOURCES += [ + 'MediaChild.cpp', + 'MediaParent.cpp', + 'MediaSystemResourceClient.cpp', + 'MediaSystemResourceManager.cpp', + 'MediaSystemResourceManagerChild.cpp', + 'MediaSystemResourceManagerParent.cpp', + 'MediaSystemResourceService.cpp', + 'MediaUtils.cpp', +] +IPDL_SOURCES += [ + 'PCameras.ipdl', + 'PMedia.ipdl', + 'PMediaSystemResourceManager.ipdl', +] +# /dom/base needed for nsGlobalWindow.h in MediaChild.cpp +LOCAL_INCLUDES += [ + '/dom/base', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] |