summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/MediaEngineWebRTC.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc/MediaEngineWebRTC.cpp')
-rw-r--r--dom/media/webrtc/MediaEngineWebRTC.cpp431
1 files changed, 431 insertions, 0 deletions
diff --git a/dom/media/webrtc/MediaEngineWebRTC.cpp b/dom/media/webrtc/MediaEngineWebRTC.cpp
new file mode 100644
index 000000000..522f23f61
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -0,0 +1,431 @@
+/* -*- 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 "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+
+#include "CSFLog.h"
+#include "prenv.h"
+
+#include "mozilla/Logging.h"
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#endif
+
+static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia");
+
+#include "MediaEngineWebRTC.h"
+#include "ImageContainer.h"
+#include "nsIComponentRegistrar.h"
+#include "MediaEngineTabVideoSource.h"
+#include "MediaEngineRemoteVideoSource.h"
+#include "CamerasChild.h"
+#include "nsITabSource.h"
+#include "MediaTrackConstraints.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "AndroidJNIWrapper.h"
+#include "AndroidBridge.h"
+#endif
+
+#undef LOG
+#define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+
+// statics from AudioInputCubeb
+nsTArray<int>* AudioInputCubeb::mDeviceIndexes;
+int AudioInputCubeb::mDefaultDevice = -1;
+nsTArray<nsCString>* AudioInputCubeb::mDeviceNames;
+cubeb_device_collection* AudioInputCubeb::mDevices = nullptr;
+bool AudioInputCubeb::mAnyInUse = false;
+StaticMutex AudioInputCubeb::sMutex;
+
+// AudioDeviceID is an annoying opaque value that's really a string
+// pointer, and is freed when the cubeb_device_collection is destroyed
+
+void AudioInputCubeb::UpdateDeviceList()
+{
+ cubeb* cubebContext = CubebUtils::GetCubebContext();
+ if (!cubebContext) {
+ return;
+ }
+
+ cubeb_device_collection *devices = nullptr;
+
+ if (CUBEB_OK != cubeb_enumerate_devices(cubebContext,
+ CUBEB_DEVICE_TYPE_INPUT,
+ &devices)) {
+ return;
+ }
+
+ for (auto& device_index : (*mDeviceIndexes)) {
+ device_index = -1; // unmapped
+ }
+ // We keep all the device names, but wipe the mappings and rebuild them
+
+ // Calculate translation from existing mDevices to new devices. Note we
+ // never end up with less devices than before, since people have
+ // stashed indexes.
+ // For some reason the "fake" device for automation is marked as DISABLED,
+ // so white-list it.
+ mDefaultDevice = -1;
+ for (uint32_t i = 0; i < devices->count; i++) {
+ LOG(("Cubeb device %u: type 0x%x, state 0x%x, name %s, id %p",
+ i, devices->device[i]->type, devices->device[i]->state,
+ devices->device[i]->friendly_name, devices->device[i]->device_id));
+ if (devices->device[i]->type == CUBEB_DEVICE_TYPE_INPUT && // paranoia
+ (devices->device[i]->state == CUBEB_DEVICE_STATE_ENABLED ||
+ (devices->device[i]->state == CUBEB_DEVICE_STATE_DISABLED &&
+ devices->device[i]->friendly_name &&
+ strcmp(devices->device[i]->friendly_name, "Sine source at 440 Hz") == 0)))
+ {
+ auto j = mDeviceNames->IndexOf(devices->device[i]->device_id);
+ if (j != nsTArray<nsCString>::NoIndex) {
+ // match! update the mapping
+ (*mDeviceIndexes)[j] = i;
+ } else {
+ // new device, add to the array
+ mDeviceIndexes->AppendElement(i);
+ mDeviceNames->AppendElement(devices->device[i]->device_id);
+ j = mDeviceIndexes->Length()-1;
+ }
+ if (devices->device[i]->preferred & CUBEB_DEVICE_PREF_VOICE) {
+ // There can be only one... we hope
+ NS_ASSERTION(mDefaultDevice == -1, "multiple default cubeb input devices!");
+ mDefaultDevice = j;
+ }
+ }
+ }
+ LOG(("Cubeb default input device %d", mDefaultDevice));
+ StaticMutexAutoLock lock(sMutex);
+ // swap state
+ if (mDevices) {
+ cubeb_device_collection_destroy(mDevices);
+ }
+ mDevices = devices;
+}
+
+MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
+ : mMutex("mozilla::MediaEngineWebRTC"),
+ mVoiceEngine(nullptr),
+ mAudioInput(nullptr),
+ mFullDuplex(aPrefs.mFullDuplex),
+ mExtendedFilter(aPrefs.mExtendedFilter),
+ mDelayAgnostic(aPrefs.mDelayAgnostic),
+ mHasTabVideoSource(false)
+{
+ nsCOMPtr<nsIComponentRegistrar> compMgr;
+ NS_GetComponentRegistrar(getter_AddRefs(compMgr));
+ if (compMgr) {
+ compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
+ }
+ // XXX
+ gFarendObserver = new AudioOutputObserver();
+
+ camera::GetChildAndCall(
+ &camera::CamerasChild::AddDeviceChangeCallback,
+ this);
+}
+
+void
+MediaEngineWebRTC::SetFakeDeviceChangeEvents()
+{
+ camera::GetChildAndCall(
+ &camera::CamerasChild::SetFakeDeviceChangeEvents);
+}
+
+void
+MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
+ nsTArray<RefPtr<MediaEngineVideoSource> >* aVSources)
+{
+ // We spawn threads to handle gUM runnables, so we must protect the member vars
+ MutexAutoLock lock(mMutex);
+
+ mozilla::camera::CaptureEngine capEngine = mozilla::camera::InvalidEngine;
+
+#ifdef MOZ_WIDGET_ANDROID
+ // get the JVM
+ JavaVM* jvm;
+ JNIEnv* const env = jni::GetEnvForThread();
+ MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
+
+ if (webrtc::VideoEngine::SetAndroidObjects(jvm) != 0) {
+ LOG(("VieCapture:SetAndroidObjects Failed"));
+ return;
+ }
+#endif
+ bool scaryKind = false; // flag sources with cross-origin exploit potential
+
+ switch (aMediaSource) {
+ case dom::MediaSourceEnum::Window:
+ capEngine = mozilla::camera::WinEngine;
+ break;
+ case dom::MediaSourceEnum::Application:
+ capEngine = mozilla::camera::AppEngine;
+ break;
+ case dom::MediaSourceEnum::Screen:
+ capEngine = mozilla::camera::ScreenEngine;
+ scaryKind = true;
+ break;
+ case dom::MediaSourceEnum::Browser:
+ capEngine = mozilla::camera::BrowserEngine;
+ scaryKind = true;
+ break;
+ case dom::MediaSourceEnum::Camera:
+ capEngine = mozilla::camera::CameraEngine;
+ break;
+ default:
+ // BOOM
+ MOZ_CRASH("No valid video engine");
+ break;
+ }
+
+ /**
+ * We still enumerate every time, in case a new device was plugged in since
+ * the last call. TODO: Verify that WebRTC actually does deal with hotplugging
+ * new devices (with or without new engine creation) and accordingly adjust.
+ * Enumeration is not neccessary if GIPS reports the same set of devices
+ * for a given instance of the engine. Likewise, if a device was plugged out,
+ * mVideoSources must be updated.
+ */
+ int num;
+ num = mozilla::camera::GetChildAndCall(
+ &mozilla::camera::CamerasChild::NumberOfCaptureDevices,
+ capEngine);
+
+ for (int i = 0; i < num; i++) {
+ char deviceName[MediaEngineSource::kMaxDeviceNameLength];
+ char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
+ bool scarySource = false;
+
+ // paranoia
+ deviceName[0] = '\0';
+ uniqueId[0] = '\0';
+ int error;
+
+ error = mozilla::camera::GetChildAndCall(
+ &mozilla::camera::CamerasChild::GetCaptureDevice,
+ capEngine,
+ i, deviceName,
+ sizeof(deviceName), uniqueId,
+ sizeof(uniqueId),
+ &scarySource);
+ if (error) {
+ LOG(("camera:GetCaptureDevice: Failed %d", error ));
+ continue;
+ }
+#ifdef DEBUG
+ LOG((" Capture Device Index %d, Name %s", i, deviceName));
+
+ webrtc::CaptureCapability cap;
+ int numCaps = mozilla::camera::GetChildAndCall(
+ &mozilla::camera::CamerasChild::NumberOfCapabilities,
+ capEngine,
+ uniqueId);
+ LOG(("Number of Capabilities %d", numCaps));
+ for (int j = 0; j < numCaps; j++) {
+ if (mozilla::camera::GetChildAndCall(
+ &mozilla::camera::CamerasChild::GetCaptureCapability,
+ capEngine,
+ uniqueId,
+ j, cap) != 0) {
+ break;
+ }
+ LOG(("type=%d width=%d height=%d maxFPS=%d",
+ cap.rawType, cap.width, cap.height, cap.maxFPS ));
+ }
+#endif
+
+ if (uniqueId[0] == '\0') {
+ // In case a device doesn't set uniqueId!
+ strncpy(uniqueId, deviceName, sizeof(uniqueId));
+ uniqueId[sizeof(uniqueId)-1] = '\0'; // strncpy isn't safe
+ }
+
+ RefPtr<MediaEngineVideoSource> vSource;
+ NS_ConvertUTF8toUTF16 uuid(uniqueId);
+ if (mVideoSources.Get(uuid, getter_AddRefs(vSource))) {
+ // We've already seen this device, just refresh and append.
+ static_cast<MediaEngineRemoteVideoSource*>(vSource.get())->Refresh(i);
+ aVSources->AppendElement(vSource.get());
+ } else {
+ vSource = new MediaEngineRemoteVideoSource(i, capEngine, aMediaSource,
+ scaryKind || scarySource);
+ mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
+ aVSources->AppendElement(vSource);
+ }
+ }
+
+ if (mHasTabVideoSource || dom::MediaSourceEnum::Browser == aMediaSource) {
+ aVSources->AppendElement(new MediaEngineTabVideoSource());
+ }
+}
+
+bool
+MediaEngineWebRTC::SupportsDuplex()
+{
+#ifndef XP_WIN
+ return mFullDuplex;
+#else
+ return IsVistaOrLater() && mFullDuplex;
+#endif
+}
+
+void
+MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
+ nsTArray<RefPtr<MediaEngineAudioSource> >* aASources)
+{
+ ScopedCustomReleasePtr<webrtc::VoEBase> ptrVoEBase;
+ // We spawn threads to handle gUM runnables, so we must protect the member vars
+ MutexAutoLock lock(mMutex);
+
+ if (aMediaSource == dom::MediaSourceEnum::AudioCapture) {
+ RefPtr<MediaEngineWebRTCAudioCaptureSource> audioCaptureSource =
+ new MediaEngineWebRTCAudioCaptureSource(nullptr);
+ aASources->AppendElement(audioCaptureSource);
+ return;
+ }
+
+#ifdef MOZ_WIDGET_ANDROID
+ jobject context = mozilla::AndroidBridge::Bridge()->GetGlobalContextRef();
+
+ // get the JVM
+ JavaVM* jvm;
+ JNIEnv* const env = jni::GetEnvForThread();
+ MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
+
+ if (webrtc::VoiceEngine::SetAndroidObjects(jvm, (void*)context) != 0) {
+ LOG(("VoiceEngine:SetAndroidObjects Failed"));
+ return;
+ }
+#endif
+
+ if (!mVoiceEngine) {
+ mConfig.Set<webrtc::ExtendedFilter>(new webrtc::ExtendedFilter(mExtendedFilter));
+ mConfig.Set<webrtc::DelayAgnostic>(new webrtc::DelayAgnostic(mDelayAgnostic));
+
+ mVoiceEngine = webrtc::VoiceEngine::Create(mConfig);
+ if (!mVoiceEngine) {
+ return;
+ }
+ }
+
+ ptrVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
+ if (!ptrVoEBase) {
+ return;
+ }
+
+ // Always re-init the voice engine, since if we close the last use we
+ // DeInitEngine() and Terminate(), which shuts down Process() - but means
+ // we have to Init() again before using it. Init() when already inited is
+ // just a no-op, so call always.
+ if (ptrVoEBase->Init() < 0) {
+ return;
+ }
+
+ if (!mAudioInput) {
+ if (SupportsDuplex()) {
+ // The platform_supports_full_duplex.
+ mAudioInput = new mozilla::AudioInputCubeb(mVoiceEngine);
+ } else {
+ mAudioInput = new mozilla::AudioInputWebRTC(mVoiceEngine);
+ }
+ }
+
+ int nDevices = 0;
+ mAudioInput->GetNumOfRecordingDevices(nDevices);
+ int i;
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+ i = 0; // Bug 1037025 - let the OS handle defaulting for now on android/b2g
+#else
+ // -1 is "default communications device" depending on OS in webrtc.org code
+ i = -1;
+#endif
+ for (; i < nDevices; i++) {
+ // We use constants here because GetRecordingDeviceName takes char[128].
+ char deviceName[128];
+ char uniqueId[128];
+ // paranoia; jingle doesn't bother with this
+ deviceName[0] = '\0';
+ uniqueId[0] = '\0';
+
+ int error = mAudioInput->GetRecordingDeviceName(i, deviceName, uniqueId);
+ if (error) {
+ LOG((" VoEHardware:GetRecordingDeviceName: Failed %d", error));
+ continue;
+ }
+
+ if (uniqueId[0] == '\0') {
+ // Mac and Linux don't set uniqueId!
+ MOZ_ASSERT(sizeof(deviceName) == sizeof(uniqueId)); // total paranoia
+ strcpy(uniqueId, deviceName); // safe given assert and initialization/error-check
+ }
+
+ RefPtr<MediaEngineAudioSource> aSource;
+ NS_ConvertUTF8toUTF16 uuid(uniqueId);
+ if (mAudioSources.Get(uuid, getter_AddRefs(aSource))) {
+ // We've already seen this device, just append.
+ aASources->AppendElement(aSource.get());
+ } else {
+ AudioInput* audioinput = mAudioInput;
+ if (SupportsDuplex()) {
+ // The platform_supports_full_duplex.
+
+ // For cubeb, it has state (the selected ID)
+ // XXX just use the uniqueID for cubeb and support it everywhere, and get rid of this
+ // XXX Small window where the device list/index could change!
+ audioinput = new mozilla::AudioInputCubeb(mVoiceEngine, i);
+ }
+ aSource = new MediaEngineWebRTCMicrophoneSource(mVoiceEngine, audioinput,
+ i, deviceName, uniqueId);
+ mAudioSources.Put(uuid, aSource); // Hashtable takes ownership.
+ aASources->AppendElement(aSource);
+ }
+ }
+}
+
+void
+MediaEngineWebRTC::Shutdown()
+{
+ // This is likely paranoia
+ MutexAutoLock lock(mMutex);
+
+ if (camera::GetCamerasChildIfExists()) {
+ camera::GetChildAndCall(
+ &camera::CamerasChild::RemoveDeviceChangeCallback, this);
+ }
+
+ LOG(("%s", __FUNCTION__));
+ // Shutdown all the sources, since we may have dangling references to the
+ // sources in nsDOMUserMediaStreams waiting for GC/CC
+ for (auto iter = mVideoSources.Iter(); !iter.Done(); iter.Next()) {
+ MediaEngineVideoSource* source = iter.UserData();
+ if (source) {
+ source->Shutdown();
+ }
+ }
+ for (auto iter = mAudioSources.Iter(); !iter.Done(); iter.Next()) {
+ MediaEngineAudioSource* source = iter.UserData();
+ if (source) {
+ source->Shutdown();
+ }
+ }
+ mVideoSources.Clear();
+ mAudioSources.Clear();
+
+ if (mVoiceEngine) {
+ mVoiceEngine->SetTraceCallback(nullptr);
+ webrtc::VoiceEngine::Delete(mVoiceEngine);
+ }
+
+ mVoiceEngine = nullptr;
+
+ mozilla::camera::Shutdown();
+ AudioInputCubeb::CleanupGlobalData();
+}
+
+}