Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set sw=2 ts=8 et ft=cpp : */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "nsIPrefService.h"
8 : #include "nsIPrefBranch.h"
9 :
10 : #include "CSFLog.h"
11 : #include "prenv.h"
12 :
13 : #include "mozilla/Logging.h"
14 :
15 : static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia");
16 :
17 : #include "MediaEngineWebRTC.h"
18 : #include "ImageContainer.h"
19 : #include "nsIComponentRegistrar.h"
20 : #include "MediaEngineTabVideoSource.h"
21 : #include "MediaEngineRemoteVideoSource.h"
22 : #include "CamerasChild.h"
23 : #include "nsITabSource.h"
24 : #include "MediaTrackConstraints.h"
25 :
26 : #ifdef MOZ_WIDGET_ANDROID
27 : #include "VideoEngine.h"
28 : #include "AndroidJNIWrapper.h"
29 : #include "AndroidBridge.h"
30 : #endif
31 :
32 : #undef LOG
33 : #define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args)
34 :
35 : namespace mozilla {
36 :
37 : // statics from AudioInputCubeb
38 : nsTArray<int>* AudioInputCubeb::mDeviceIndexes;
39 : int AudioInputCubeb::mDefaultDevice = -1;
40 : nsTArray<nsCString>* AudioInputCubeb::mDeviceNames;
41 : cubeb_device_collection AudioInputCubeb::mDevices = { nullptr, 0 };
42 : bool AudioInputCubeb::mAnyInUse = false;
43 3 : StaticMutex AudioInputCubeb::sMutex;
44 : uint32_t AudioInputCubeb::sUserChannelCount = 0;
45 :
46 : // AudioDeviceID is an annoying opaque value that's really a string
47 : // pointer, and is freed when the cubeb_device_collection is destroyed
48 :
49 0 : void AudioInputCubeb::UpdateDeviceList()
50 : {
51 0 : cubeb* cubebContext = CubebUtils::GetCubebContext();
52 0 : if (!cubebContext) {
53 0 : return;
54 : }
55 :
56 0 : cubeb_device_collection devices = { nullptr, 0 };
57 :
58 0 : if (CUBEB_OK != cubeb_enumerate_devices(cubebContext,
59 : CUBEB_DEVICE_TYPE_INPUT,
60 : &devices)) {
61 0 : return;
62 : }
63 :
64 0 : for (auto& device_index : (*mDeviceIndexes)) {
65 0 : device_index = -1; // unmapped
66 : }
67 : // We keep all the device names, but wipe the mappings and rebuild them
68 :
69 : // Calculate translation from existing mDevices to new devices. Note we
70 : // never end up with less devices than before, since people have
71 : // stashed indexes.
72 : // For some reason the "fake" device for automation is marked as DISABLED,
73 : // so white-list it.
74 0 : mDefaultDevice = -1;
75 0 : for (uint32_t i = 0; i < devices.count; i++) {
76 0 : LOG(("Cubeb device %u: type 0x%x, state 0x%x, name %s, id %p",
77 : i, devices.device[i].type, devices.device[i].state,
78 : devices.device[i].friendly_name, devices.device[i].device_id));
79 0 : if (devices.device[i].type == CUBEB_DEVICE_TYPE_INPUT && // paranoia
80 0 : (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED ||
81 0 : (devices.device[i].state == CUBEB_DEVICE_STATE_DISABLED &&
82 0 : devices.device[i].friendly_name &&
83 0 : strcmp(devices.device[i].friendly_name, "Sine source at 440 Hz") == 0)))
84 : {
85 0 : auto j = mDeviceNames->IndexOf(devices.device[i].device_id);
86 0 : if (j != nsTArray<nsCString>::NoIndex) {
87 : // match! update the mapping
88 0 : (*mDeviceIndexes)[j] = i;
89 : } else {
90 : // new device, add to the array
91 0 : mDeviceIndexes->AppendElement(i);
92 0 : mDeviceNames->AppendElement(devices.device[i].device_id);
93 0 : j = mDeviceIndexes->Length()-1;
94 : }
95 0 : if (devices.device[i].preferred & CUBEB_DEVICE_PREF_VOICE) {
96 : // There can be only one... we hope
97 0 : NS_ASSERTION(mDefaultDevice == -1, "multiple default cubeb input devices!");
98 0 : mDefaultDevice = j;
99 : }
100 : }
101 : }
102 0 : LOG(("Cubeb default input device %d", mDefaultDevice));
103 0 : StaticMutexAutoLock lock(sMutex);
104 : // swap state
105 0 : cubeb_device_collection_destroy(cubebContext, &mDevices);
106 0 : mDevices = devices;
107 : }
108 :
109 0 : MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
110 : : mMutex("mozilla::MediaEngineWebRTC"),
111 : mVoiceEngine(nullptr),
112 : mAudioInput(nullptr),
113 0 : mFullDuplex(aPrefs.mFullDuplex),
114 0 : mHasTabVideoSource(false)
115 : {
116 0 : nsCOMPtr<nsIComponentRegistrar> compMgr;
117 0 : NS_GetComponentRegistrar(getter_AddRefs(compMgr));
118 0 : if (compMgr) {
119 0 : compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
120 : }
121 :
122 0 : camera::GetChildAndCall(
123 : &camera::CamerasChild::AddDeviceChangeCallback,
124 0 : this);
125 0 : }
126 :
127 : void
128 0 : MediaEngineWebRTC::SetFakeDeviceChangeEvents()
129 : {
130 0 : camera::GetChildAndCall(
131 0 : &camera::CamerasChild::SetFakeDeviceChangeEvents);
132 0 : }
133 :
134 : void
135 0 : MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
136 : nsTArray<RefPtr<MediaEngineVideoSource> >* aVSources)
137 : {
138 : // We spawn threads to handle gUM runnables, so we must protect the member vars
139 0 : MutexAutoLock lock(mMutex);
140 :
141 0 : mozilla::camera::CaptureEngine capEngine = mozilla::camera::InvalidEngine;
142 :
143 : #ifdef MOZ_WIDGET_ANDROID
144 : // get the JVM
145 : JavaVM* jvm;
146 : JNIEnv* const env = jni::GetEnvForThread();
147 : MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
148 :
149 : if (!jvm || mozilla::camera::VideoEngine::SetAndroidObjects(jvm)) {
150 : LOG(("VideoEngine::SetAndroidObjects Failed"));
151 : return;
152 : }
153 : #endif
154 0 : bool scaryKind = false; // flag sources with cross-origin exploit potential
155 :
156 0 : switch (aMediaSource) {
157 : case dom::MediaSourceEnum::Window:
158 0 : capEngine = mozilla::camera::WinEngine;
159 0 : break;
160 : case dom::MediaSourceEnum::Application:
161 0 : capEngine = mozilla::camera::AppEngine;
162 0 : break;
163 : case dom::MediaSourceEnum::Screen:
164 0 : capEngine = mozilla::camera::ScreenEngine;
165 0 : scaryKind = true;
166 0 : break;
167 : case dom::MediaSourceEnum::Browser:
168 0 : capEngine = mozilla::camera::BrowserEngine;
169 0 : scaryKind = true;
170 0 : break;
171 : case dom::MediaSourceEnum::Camera:
172 0 : capEngine = mozilla::camera::CameraEngine;
173 0 : break;
174 : default:
175 : // BOOM
176 0 : MOZ_CRASH("No valid video engine");
177 : break;
178 : }
179 :
180 : /**
181 : * We still enumerate every time, in case a new device was plugged in since
182 : * the last call. TODO: Verify that WebRTC actually does deal with hotplugging
183 : * new devices (with or without new engine creation) and accordingly adjust.
184 : * Enumeration is not neccessary if GIPS reports the same set of devices
185 : * for a given instance of the engine. Likewise, if a device was plugged out,
186 : * mVideoSources must be updated.
187 : */
188 : int num;
189 0 : num = mozilla::camera::GetChildAndCall(
190 : &mozilla::camera::CamerasChild::NumberOfCaptureDevices,
191 0 : capEngine);
192 :
193 0 : for (int i = 0; i < num; i++) {
194 : char deviceName[MediaEngineSource::kMaxDeviceNameLength];
195 : char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
196 0 : bool scarySource = false;
197 :
198 : // paranoia
199 0 : deviceName[0] = '\0';
200 0 : uniqueId[0] = '\0';
201 : int error;
202 :
203 0 : error = mozilla::camera::GetChildAndCall(
204 : &mozilla::camera::CamerasChild::GetCaptureDevice,
205 : capEngine,
206 : i, deviceName,
207 0 : sizeof(deviceName), uniqueId,
208 0 : sizeof(uniqueId),
209 0 : &scarySource);
210 0 : if (error) {
211 0 : LOG(("camera:GetCaptureDevice: Failed %d", error ));
212 0 : continue;
213 : }
214 : #ifdef DEBUG
215 0 : LOG((" Capture Device Index %d, Name %s", i, deviceName));
216 :
217 0 : webrtc::CaptureCapability cap;
218 0 : int numCaps = mozilla::camera::GetChildAndCall(
219 : &mozilla::camera::CamerasChild::NumberOfCapabilities,
220 : capEngine,
221 0 : uniqueId);
222 0 : LOG(("Number of Capabilities %d", numCaps));
223 0 : for (int j = 0; j < numCaps; j++) {
224 0 : if (mozilla::camera::GetChildAndCall(
225 : &mozilla::camera::CamerasChild::GetCaptureCapability,
226 : capEngine,
227 : uniqueId,
228 : j, cap) != 0) {
229 0 : break;
230 : }
231 0 : LOG(("type=%d width=%d height=%d maxFPS=%d",
232 : cap.rawType, cap.width, cap.height, cap.maxFPS ));
233 : }
234 : #endif
235 :
236 0 : if (uniqueId[0] == '\0') {
237 : // In case a device doesn't set uniqueId!
238 0 : strncpy(uniqueId, deviceName, sizeof(uniqueId));
239 0 : uniqueId[sizeof(uniqueId)-1] = '\0'; // strncpy isn't safe
240 : }
241 :
242 0 : RefPtr<MediaEngineVideoSource> vSource;
243 0 : NS_ConvertUTF8toUTF16 uuid(uniqueId);
244 0 : if (mVideoSources.Get(uuid, getter_AddRefs(vSource))) {
245 : // We've already seen this device, just refresh and append.
246 0 : static_cast<MediaEngineRemoteVideoSource*>(vSource.get())->Refresh(i);
247 0 : aVSources->AppendElement(vSource.get());
248 : } else {
249 : vSource = new MediaEngineRemoteVideoSource(i, capEngine, aMediaSource,
250 0 : scaryKind || scarySource);
251 0 : mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
252 0 : aVSources->AppendElement(vSource);
253 : }
254 : }
255 :
256 0 : if (mHasTabVideoSource || dom::MediaSourceEnum::Browser == aMediaSource) {
257 0 : aVSources->AppendElement(new MediaEngineTabVideoSource());
258 : }
259 0 : }
260 :
261 : bool
262 0 : MediaEngineWebRTC::SupportsDuplex()
263 : {
264 0 : return mFullDuplex;
265 : }
266 :
267 : void
268 0 : MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
269 : nsTArray<RefPtr<MediaEngineAudioSource> >* aASources)
270 : {
271 0 : ScopedCustomReleasePtr<webrtc::VoEBase> ptrVoEBase;
272 : // We spawn threads to handle gUM runnables, so we must protect the member vars
273 0 : MutexAutoLock lock(mMutex);
274 :
275 0 : if (aMediaSource == dom::MediaSourceEnum::AudioCapture) {
276 : RefPtr<MediaEngineWebRTCAudioCaptureSource> audioCaptureSource =
277 0 : new MediaEngineWebRTCAudioCaptureSource(nullptr);
278 0 : aASources->AppendElement(audioCaptureSource);
279 0 : return;
280 : }
281 :
282 : #ifdef MOZ_WIDGET_ANDROID
283 : jobject context = mozilla::AndroidBridge::Bridge()->GetGlobalContextRef();
284 :
285 : // get the JVM
286 : JavaVM* jvm;
287 : JNIEnv* const env = jni::GetEnvForThread();
288 : MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
289 :
290 : if (webrtc::VoiceEngine::SetAndroidObjects(jvm, (void*)context) != 0) {
291 : LOG(("VoiceEngine:SetAndroidObjects Failed"));
292 : return;
293 : }
294 : #endif
295 :
296 0 : if (!mVoiceEngine) {
297 0 : mVoiceEngine = webrtc::VoiceEngine::Create(/*mConfig*/);
298 0 : if (!mVoiceEngine) {
299 0 : return;
300 : }
301 : }
302 :
303 0 : ptrVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
304 0 : if (!ptrVoEBase) {
305 0 : return;
306 : }
307 :
308 : // Always re-init the voice engine, since if we close the last use we
309 : // DeInitEngine() and Terminate(), which shuts down Process() - but means
310 : // we have to Init() again before using it. Init() when already inited is
311 : // just a no-op, so call always.
312 0 : if (ptrVoEBase->Init() < 0) {
313 0 : return;
314 : }
315 :
316 0 : if (!mAudioInput) {
317 0 : if (SupportsDuplex()) {
318 : // The platform_supports_full_duplex.
319 0 : mAudioInput = new mozilla::AudioInputCubeb(mVoiceEngine);
320 : } else {
321 0 : mAudioInput = new mozilla::AudioInputWebRTC(mVoiceEngine);
322 : }
323 : }
324 :
325 0 : int nDevices = 0;
326 0 : mAudioInput->GetNumOfRecordingDevices(nDevices);
327 : int i;
328 : #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
329 : i = 0; // Bug 1037025 - let the OS handle defaulting for now on android/b2g
330 : #else
331 : // -1 is "default communications device" depending on OS in webrtc.org code
332 0 : i = -1;
333 : #endif
334 0 : for (; i < nDevices; i++) {
335 : // We use constants here because GetRecordingDeviceName takes char[128].
336 : char deviceName[128];
337 : char uniqueId[128];
338 : // paranoia; jingle doesn't bother with this
339 0 : deviceName[0] = '\0';
340 0 : uniqueId[0] = '\0';
341 :
342 0 : int error = mAudioInput->GetRecordingDeviceName(i, deviceName, uniqueId);
343 0 : if (error) {
344 0 : LOG((" VoEHardware:GetRecordingDeviceName: Failed %d", error));
345 0 : continue;
346 : }
347 :
348 0 : if (uniqueId[0] == '\0') {
349 : // Mac and Linux don't set uniqueId!
350 : MOZ_ASSERT(sizeof(deviceName) == sizeof(uniqueId)); // total paranoia
351 0 : strcpy(uniqueId, deviceName); // safe given assert and initialization/error-check
352 : }
353 :
354 0 : RefPtr<MediaEngineAudioSource> aSource;
355 0 : NS_ConvertUTF8toUTF16 uuid(uniqueId);
356 0 : if (mAudioSources.Get(uuid, getter_AddRefs(aSource))) {
357 : // We've already seen this device, just append.
358 0 : aASources->AppendElement(aSource.get());
359 : } else {
360 0 : AudioInput* audioinput = mAudioInput;
361 0 : if (SupportsDuplex()) {
362 : // The platform_supports_full_duplex.
363 :
364 : // For cubeb, it has state (the selected ID)
365 : // XXX just use the uniqueID for cubeb and support it everywhere, and get rid of this
366 : // XXX Small window where the device list/index could change!
367 0 : audioinput = new mozilla::AudioInputCubeb(mVoiceEngine, i);
368 : }
369 : aSource = new MediaEngineWebRTCMicrophoneSource(mVoiceEngine, audioinput,
370 0 : i, deviceName, uniqueId);
371 0 : mAudioSources.Put(uuid, aSource); // Hashtable takes ownership.
372 0 : aASources->AppendElement(aSource);
373 : }
374 : }
375 : }
376 :
377 : void
378 0 : MediaEngineWebRTC::Shutdown()
379 : {
380 : // This is likely paranoia
381 0 : MutexAutoLock lock(mMutex);
382 :
383 0 : if (camera::GetCamerasChildIfExists()) {
384 0 : camera::GetChildAndCall(
385 0 : &camera::CamerasChild::RemoveDeviceChangeCallback, this);
386 : }
387 :
388 0 : LOG(("%s", __FUNCTION__));
389 : // Shutdown all the sources, since we may have dangling references to the
390 : // sources in nsDOMUserMediaStreams waiting for GC/CC
391 0 : for (auto iter = mVideoSources.Iter(); !iter.Done(); iter.Next()) {
392 0 : MediaEngineVideoSource* source = iter.UserData();
393 0 : if (source) {
394 0 : source->Shutdown();
395 : }
396 : }
397 0 : for (auto iter = mAudioSources.Iter(); !iter.Done(); iter.Next()) {
398 0 : MediaEngineAudioSource* source = iter.UserData();
399 0 : if (source) {
400 0 : source->Shutdown();
401 : }
402 : }
403 0 : mVideoSources.Clear();
404 0 : mAudioSources.Clear();
405 :
406 0 : if (mVoiceEngine) {
407 0 : mVoiceEngine->SetTraceCallback(nullptr);
408 0 : webrtc::VoiceEngine::Delete(mVoiceEngine);
409 : }
410 :
411 0 : mVoiceEngine = nullptr;
412 :
413 0 : mozilla::camera::Shutdown();
414 0 : AudioInputCubeb::CleanupGlobalData();
415 0 : }
416 :
417 9 : }
|