gecko-dev/dom/media/webrtc/MediaEngineWebRTC.cpp

418 строки
13 KiB
C++

/* -*- 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"
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 "VideoEngine.h"
#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, 0 };
bool AudioInputCubeb::mAnyInUse = false;
StaticMutex AudioInputCubeb::sMutex;
uint32_t AudioInputCubeb::sUserChannelCount = 0;
// 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, 0 };
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
cubeb_device_collection_destroy(cubebContext, &mDevices);
mDevices = devices;
}
MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
: mMutex("mozilla::MediaEngineWebRTC"),
mVoiceEngine(nullptr),
mAudioInput(nullptr),
mFullDuplex(aPrefs.mFullDuplex),
mHasTabVideoSource(false)
{
nsCOMPtr<nsIComponentRegistrar> compMgr;
NS_GetComponentRegistrar(getter_AddRefs(compMgr));
if (compMgr) {
compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
}
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 (!jvm || mozilla::camera::VideoEngine::SetAndroidObjects(jvm)) {
LOG(("VideoEngine::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()
{
return mFullDuplex;
}
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) {
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)
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((" AudioInput::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();
}
}