diff options
Diffstat (limited to 'dom/media/webrtc/MediaEngineWebRTC.cpp')
-rw-r--r-- | dom/media/webrtc/MediaEngineWebRTC.cpp | 431 |
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(); +} + +} |