/src/mozilla-central/dom/media/webrtc/MediaEngineWebRTC.cpp
Line | Count | Source (jump to first uncovered line) |
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 "MediaEngineWebRTC.h" |
8 | | |
9 | | #include "AllocationHandle.h" |
10 | | #include "CamerasChild.h" |
11 | | #include "CSFLog.h" |
12 | | #include "MediaEngineTabVideoSource.h" |
13 | | #include "MediaEngineRemoteVideoSource.h" |
14 | | #include "MediaTrackConstraints.h" |
15 | | #include "mozilla/dom/MediaDeviceInfo.h" |
16 | | #include "mozilla/Logging.h" |
17 | | #include "nsIComponentRegistrar.h" |
18 | | #include "nsIPrefService.h" |
19 | | #include "nsIPrefBranch.h" |
20 | | #include "nsITabSource.h" |
21 | | #include "prenv.h" |
22 | | |
23 | | static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia"); |
24 | | #undef LOG |
25 | 0 | #define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args) |
26 | | |
27 | | namespace mozilla { |
28 | | |
29 | | using namespace CubebUtils; |
30 | | |
31 | | MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs) |
32 | | : mMutex("mozilla::MediaEngineWebRTC") |
33 | | , mDelayAgnostic(aPrefs.mDelayAgnostic) |
34 | | , mExtendedFilter(aPrefs.mExtendedFilter) |
35 | | , mHasTabVideoSource(false) |
36 | 0 | { |
37 | 0 | nsCOMPtr<nsIComponentRegistrar> compMgr; |
38 | 0 | NS_GetComponentRegistrar(getter_AddRefs(compMgr)); |
39 | 0 | if (compMgr) { |
40 | 0 | compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource); |
41 | 0 | } |
42 | 0 |
|
43 | 0 | camera::GetChildAndCall( |
44 | 0 | &camera::CamerasChild::AddDeviceChangeCallback, |
45 | 0 | this); |
46 | 0 | } |
47 | | |
48 | | void |
49 | | MediaEngineWebRTC::SetFakeDeviceChangeEvents() |
50 | 0 | { |
51 | 0 | camera::GetChildAndCall( |
52 | 0 | &camera::CamerasChild::SetFakeDeviceChangeEvents); |
53 | 0 | } |
54 | | |
55 | | void |
56 | | MediaEngineWebRTC::EnumerateVideoDevices(uint64_t aWindowId, |
57 | | dom::MediaSourceEnum aMediaSource, |
58 | | nsTArray<RefPtr<MediaDevice> >* aDevices) |
59 | 0 | { |
60 | 0 | mMutex.AssertCurrentThreadOwns(); |
61 | 0 |
|
62 | 0 | mozilla::camera::CaptureEngine capEngine = mozilla::camera::InvalidEngine; |
63 | 0 |
|
64 | 0 | bool scaryKind = false; // flag sources with cross-origin exploit potential |
65 | 0 |
|
66 | 0 | switch (aMediaSource) { |
67 | 0 | case dom::MediaSourceEnum::Window: |
68 | 0 | capEngine = mozilla::camera::WinEngine; |
69 | 0 | break; |
70 | 0 | case dom::MediaSourceEnum::Application: |
71 | 0 | capEngine = mozilla::camera::AppEngine; |
72 | 0 | break; |
73 | 0 | case dom::MediaSourceEnum::Screen: |
74 | 0 | capEngine = mozilla::camera::ScreenEngine; |
75 | 0 | scaryKind = true; |
76 | 0 | break; |
77 | 0 | case dom::MediaSourceEnum::Browser: |
78 | 0 | capEngine = mozilla::camera::BrowserEngine; |
79 | 0 | scaryKind = true; |
80 | 0 | break; |
81 | 0 | case dom::MediaSourceEnum::Camera: |
82 | 0 | capEngine = mozilla::camera::CameraEngine; |
83 | 0 | break; |
84 | 0 | default: |
85 | 0 | MOZ_CRASH("No valid video engine"); |
86 | 0 | break; |
87 | 0 | } |
88 | 0 | |
89 | 0 | /* |
90 | 0 | * We still enumerate every time, in case a new device was plugged in since |
91 | 0 | * the last call. TODO: Verify that WebRTC actually does deal with hotplugging |
92 | 0 | * new devices (with or without new engine creation) and accordingly adjust. |
93 | 0 | * Enumeration is not neccessary if GIPS reports the same set of devices |
94 | 0 | * for a given instance of the engine. Likewise, if a device was plugged out, |
95 | 0 | * mVideoSources must be updated. |
96 | 0 | */ |
97 | 0 | int num; |
98 | 0 | num = mozilla::camera::GetChildAndCall( |
99 | 0 | &mozilla::camera::CamerasChild::NumberOfCaptureDevices, |
100 | 0 | capEngine); |
101 | 0 |
|
102 | 0 | for (int i = 0; i < num; i++) { |
103 | 0 | char deviceName[MediaEngineSource::kMaxDeviceNameLength]; |
104 | 0 | char uniqueId[MediaEngineSource::kMaxUniqueIdLength]; |
105 | 0 | bool scarySource = false; |
106 | 0 |
|
107 | 0 | // paranoia |
108 | 0 | deviceName[0] = '\0'; |
109 | 0 | uniqueId[0] = '\0'; |
110 | 0 | int error; |
111 | 0 |
|
112 | 0 | error = mozilla::camera::GetChildAndCall( |
113 | 0 | &mozilla::camera::CamerasChild::GetCaptureDevice, |
114 | 0 | capEngine, |
115 | 0 | i, deviceName, |
116 | 0 | sizeof(deviceName), uniqueId, |
117 | 0 | sizeof(uniqueId), |
118 | 0 | &scarySource); |
119 | 0 | if (error) { |
120 | 0 | LOG(("camera:GetCaptureDevice: Failed %d", error )); |
121 | 0 | continue; |
122 | 0 | } |
123 | | #ifdef DEBUG |
124 | | LOG((" Capture Device Index %d, Name %s", i, deviceName)); |
125 | | |
126 | | webrtc::CaptureCapability cap; |
127 | | int numCaps = mozilla::camera::GetChildAndCall( |
128 | | &mozilla::camera::CamerasChild::NumberOfCapabilities, |
129 | | capEngine, |
130 | | uniqueId); |
131 | | LOG(("Number of Capabilities %d", numCaps)); |
132 | | for (int j = 0; j < numCaps; j++) { |
133 | | if (mozilla::camera::GetChildAndCall( |
134 | | &mozilla::camera::CamerasChild::GetCaptureCapability, |
135 | | capEngine, |
136 | | uniqueId, |
137 | | j, cap) != 0) { |
138 | | break; |
139 | | } |
140 | | LOG(("type=%d width=%d height=%d maxFPS=%d", |
141 | | cap.rawType, cap.width, cap.height, cap.maxFPS )); |
142 | | } |
143 | | #endif |
144 | | |
145 | 0 | if (uniqueId[0] == '\0') { |
146 | 0 | // In case a device doesn't set uniqueId! |
147 | 0 | strncpy(uniqueId, deviceName, sizeof(uniqueId)); |
148 | 0 | uniqueId[sizeof(uniqueId)-1] = '\0'; // strncpy isn't safe |
149 | 0 | } |
150 | 0 |
|
151 | 0 | NS_ConvertUTF8toUTF16 uuid(uniqueId); |
152 | 0 | RefPtr<MediaEngineSource> vSource; |
153 | 0 |
|
154 | 0 | nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>* |
155 | 0 | devicesForThisWindow = mVideoSources.LookupOrAdd(aWindowId); |
156 | 0 |
|
157 | 0 | if (devicesForThisWindow->Get(uuid, getter_AddRefs(vSource)) && |
158 | 0 | vSource->RequiresSharing()) { |
159 | 0 | // We've already seen this shared device, just refresh and append. |
160 | 0 | static_cast<MediaEngineRemoteVideoSource*>(vSource.get())->Refresh(i); |
161 | 0 | } else { |
162 | 0 | vSource = new MediaEngineRemoteVideoSource(i, capEngine, aMediaSource, |
163 | 0 | scaryKind || scarySource); |
164 | 0 | devicesForThisWindow->Put(uuid, vSource); |
165 | 0 | } |
166 | 0 | aDevices->AppendElement(MakeRefPtr<MediaDevice>( |
167 | 0 | vSource, |
168 | 0 | vSource->GetName(), |
169 | 0 | NS_ConvertUTF8toUTF16(vSource->GetUUID()))); |
170 | 0 | } |
171 | 0 |
|
172 | 0 | if (mHasTabVideoSource || dom::MediaSourceEnum::Browser == aMediaSource) { |
173 | 0 | RefPtr<MediaEngineSource> tabVideoSource = new MediaEngineTabVideoSource(); |
174 | 0 | aDevices->AppendElement(MakeRefPtr<MediaDevice>( |
175 | 0 | tabVideoSource, |
176 | 0 | tabVideoSource->GetName(), |
177 | 0 | NS_ConvertUTF8toUTF16(tabVideoSource->GetUUID()))); |
178 | 0 | } |
179 | 0 | } |
180 | | |
181 | | void |
182 | | MediaEngineWebRTC::EnumerateMicrophoneDevices(uint64_t aWindowId, |
183 | | nsTArray<RefPtr<MediaDevice> >* aDevices) |
184 | 0 | { |
185 | 0 | mMutex.AssertCurrentThreadOwns(); |
186 | 0 |
|
187 | 0 | if (!mEnumerator) { |
188 | 0 | mEnumerator.reset(new CubebDeviceEnumerator()); |
189 | 0 | } |
190 | 0 |
|
191 | 0 | nsTArray<RefPtr<AudioDeviceInfo>> devices; |
192 | 0 | mEnumerator->EnumerateAudioInputDevices(devices); |
193 | 0 |
|
194 | 0 | DebugOnly<bool> foundPreferredDevice = false; |
195 | 0 |
|
196 | 0 | for (uint32_t i = 0; i < devices.Length(); i++) { |
197 | 0 | #ifndef ANDROID |
198 | 0 | MOZ_ASSERT(devices[i]->DeviceID()); |
199 | 0 | #endif |
200 | 0 | LOG(("Cubeb device %u: type 0x%x, state 0x%x, name %s, id %p", |
201 | 0 | i, |
202 | 0 | devices[i]->Type(), |
203 | 0 | devices[i]->State(), |
204 | 0 | NS_ConvertUTF16toUTF8(devices[i]->Name()).get(), |
205 | 0 | devices[i]->DeviceID())); |
206 | 0 |
|
207 | 0 | if (devices[i]->State() == CUBEB_DEVICE_STATE_ENABLED) { |
208 | 0 | MOZ_ASSERT(devices[i]->Type() == CUBEB_DEVICE_TYPE_INPUT); |
209 | 0 | RefPtr<MediaEngineSource> source = |
210 | 0 | new MediaEngineWebRTCMicrophoneSource( |
211 | 0 | devices[i], |
212 | 0 | devices[i]->Name(), |
213 | 0 | // Lie and provide the name as UUID |
214 | 0 | NS_ConvertUTF16toUTF8(devices[i]->Name()), |
215 | 0 | devices[i]->MaxChannels(), |
216 | 0 | mDelayAgnostic, |
217 | 0 | mExtendedFilter); |
218 | 0 | RefPtr<MediaDevice> device = MakeRefPtr<MediaDevice>( |
219 | 0 | source, |
220 | 0 | source->GetName(), |
221 | 0 | NS_ConvertUTF8toUTF16(source->GetUUID())); |
222 | 0 | if (devices[i]->Preferred()) { |
223 | | #ifdef DEBUG |
224 | | if (!foundPreferredDevice) { |
225 | | foundPreferredDevice = true; |
226 | | } else { |
227 | | MOZ_ASSERT(!foundPreferredDevice, |
228 | | "Found more than one preferred audio input device" |
229 | | "while enumerating"); |
230 | | } |
231 | | #endif |
232 | | aDevices->InsertElementAt(0, device); |
233 | 0 | } else { |
234 | 0 | aDevices->AppendElement(device); |
235 | 0 | } |
236 | 0 | } |
237 | 0 | } |
238 | 0 | } |
239 | | |
240 | | void |
241 | | MediaEngineWebRTC::EnumerateSpeakerDevices(uint64_t aWindowId, |
242 | | nsTArray<RefPtr<MediaDevice> >* aDevices) |
243 | 0 | { |
244 | 0 | nsTArray<RefPtr<AudioDeviceInfo>> devices; |
245 | 0 | CubebUtils::GetDeviceCollection(devices, CubebUtils::Output); |
246 | 0 | for (auto& device : devices) { |
247 | 0 | if (device->State() == CUBEB_DEVICE_STATE_ENABLED) { |
248 | 0 | MOZ_ASSERT(device->Type() == CUBEB_DEVICE_TYPE_OUTPUT); |
249 | 0 | nsString uuid(device->Name()); |
250 | 0 | // If, for example, input and output are in the same device, uuid |
251 | 0 | // would be the same for both which ends up to create the same |
252 | 0 | // deviceIDs (in JS). |
253 | 0 | uuid.Append(NS_LITERAL_STRING("_Speaker")); |
254 | 0 | aDevices->AppendElement(MakeRefPtr<MediaDevice>( |
255 | 0 | device->Name(), |
256 | 0 | dom::MediaDeviceKind::Audiooutput, |
257 | 0 | uuid)); |
258 | 0 | } |
259 | 0 | } |
260 | 0 | } |
261 | | |
262 | | |
263 | | void |
264 | | MediaEngineWebRTC::EnumerateDevices(uint64_t aWindowId, |
265 | | dom::MediaSourceEnum aMediaSource, |
266 | | MediaSinkEnum aMediaSink, |
267 | | nsTArray<RefPtr<MediaDevice>>* aDevices) |
268 | 0 | { |
269 | 0 | MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other || |
270 | 0 | aMediaSink != MediaSinkEnum::Other); |
271 | 0 | // We spawn threads to handle gUM runnables, so we must protect the member vars |
272 | 0 | MutexAutoLock lock(mMutex); |
273 | 0 | if (MediaEngineSource::IsVideo(aMediaSource)) { |
274 | 0 | EnumerateVideoDevices(aWindowId, aMediaSource, aDevices); |
275 | 0 | } else if (aMediaSource == dom::MediaSourceEnum::AudioCapture) { |
276 | 0 | RefPtr<MediaEngineWebRTCAudioCaptureSource> audioCaptureSource = |
277 | 0 | new MediaEngineWebRTCAudioCaptureSource(nullptr); |
278 | 0 | aDevices->AppendElement(MakeRefPtr<MediaDevice>( |
279 | 0 | audioCaptureSource, |
280 | 0 | audioCaptureSource->GetName(), |
281 | 0 | NS_ConvertUTF8toUTF16(audioCaptureSource->GetUUID()))); |
282 | 0 | } else if (aMediaSource == dom::MediaSourceEnum::Microphone) { |
283 | 0 | MOZ_ASSERT(aMediaSource == dom::MediaSourceEnum::Microphone); |
284 | 0 | EnumerateMicrophoneDevices(aWindowId, aDevices); |
285 | 0 | } |
286 | 0 |
|
287 | 0 | if (aMediaSink == MediaSinkEnum::Speaker) { |
288 | 0 | EnumerateSpeakerDevices(aWindowId, aDevices); |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | | void |
293 | | MediaEngineWebRTC::ReleaseResourcesForWindow(uint64_t aWindowId) |
294 | 0 | { |
295 | 0 | { |
296 | 0 | nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>* |
297 | 0 | audioDevicesForThisWindow = mAudioSources.Get(aWindowId); |
298 | 0 |
|
299 | 0 | if (audioDevicesForThisWindow) { |
300 | 0 | for (auto iter = audioDevicesForThisWindow->Iter(); !iter.Done(); |
301 | 0 | iter.Next()) { |
302 | 0 | iter.UserData()->Shutdown(); |
303 | 0 | } |
304 | 0 |
|
305 | 0 | // This makes audioDevicesForThisWindow invalid. |
306 | 0 | mAudioSources.Remove(aWindowId); |
307 | 0 | } |
308 | 0 | } |
309 | 0 |
|
310 | 0 | { |
311 | 0 | nsRefPtrHashtable<nsStringHashKey, MediaEngineSource>* |
312 | 0 | videoDevicesForThisWindow = mVideoSources.Get(aWindowId); |
313 | 0 | if (videoDevicesForThisWindow) { |
314 | 0 | for (auto iter = videoDevicesForThisWindow->Iter(); !iter.Done(); |
315 | 0 | iter.Next()) { |
316 | 0 | iter.UserData()->Shutdown(); |
317 | 0 | } |
318 | 0 |
|
319 | 0 | // This makes videoDevicesForThisWindow invalid. |
320 | 0 | mVideoSources.Remove(aWindowId); |
321 | 0 | } |
322 | 0 | } |
323 | 0 | } |
324 | | |
325 | | namespace { |
326 | | template<typename T> |
327 | | void ShutdownSources(T& aHashTable) |
328 | 0 | { |
329 | 0 | for (auto iter = aHashTable.Iter(); !iter.Done(); iter.Next()) { |
330 | 0 | for (auto iterInner = iter.UserData()->Iter(); !iterInner.Done(); |
331 | 0 | iterInner.Next()) { |
332 | 0 | MediaEngineSource* source = iterInner.UserData(); |
333 | 0 | source->Shutdown(); |
334 | 0 | } |
335 | 0 | } |
336 | 0 | } |
337 | | } |
338 | | |
339 | | void |
340 | | MediaEngineWebRTC::Shutdown() |
341 | 0 | { |
342 | 0 | // This is likely paranoia |
343 | 0 | MutexAutoLock lock(mMutex); |
344 | 0 |
|
345 | 0 | if (camera::GetCamerasChildIfExists()) { |
346 | 0 | camera::GetChildAndCall( |
347 | 0 | &camera::CamerasChild::RemoveDeviceChangeCallback, this); |
348 | 0 | } |
349 | 0 |
|
350 | 0 | LOG(("%s", __FUNCTION__)); |
351 | 0 | // Shutdown all the sources, since we may have dangling references to the |
352 | 0 | // sources in nsDOMUserMediaStreams waiting for GC/CC |
353 | 0 | ShutdownSources(mVideoSources); |
354 | 0 | ShutdownSources(mAudioSources); |
355 | 0 |
|
356 | 0 | mEnumerator = nullptr; |
357 | 0 |
|
358 | 0 | mozilla::camera::Shutdown(); |
359 | 0 | } |
360 | | |
361 | | CubebDeviceEnumerator::CubebDeviceEnumerator() |
362 | | : mMutex("CubebDeviceListMutex") |
363 | | , mManualInvalidation(false) |
364 | 0 | { |
365 | 0 | int rv = cubeb_register_device_collection_changed(GetCubebContext(), |
366 | 0 | CUBEB_DEVICE_TYPE_INPUT, |
367 | 0 | &mozilla::CubebDeviceEnumerator::AudioDeviceListChanged_s, |
368 | 0 | this); |
369 | 0 |
|
370 | 0 | if (rv != CUBEB_OK) { |
371 | 0 | NS_WARNING("Could not register the audio input" |
372 | 0 | " device collection changed callback."); |
373 | 0 | mManualInvalidation = true; |
374 | 0 | } |
375 | 0 | } |
376 | | |
377 | | CubebDeviceEnumerator::~CubebDeviceEnumerator() |
378 | 0 | { |
379 | 0 | int rv = cubeb_register_device_collection_changed(GetCubebContext(), |
380 | 0 | CUBEB_DEVICE_TYPE_INPUT, |
381 | 0 | nullptr, |
382 | 0 | this); |
383 | 0 | if (rv != CUBEB_OK) { |
384 | 0 | NS_WARNING("Could not unregister the audio input" |
385 | 0 | " device collection changed callback."); |
386 | 0 | } |
387 | 0 | } |
388 | | |
389 | | void |
390 | | CubebDeviceEnumerator::EnumerateAudioInputDevices(nsTArray<RefPtr<AudioDeviceInfo>>& aOutDevices) |
391 | 0 | { |
392 | 0 | aOutDevices.Clear(); |
393 | 0 |
|
394 | 0 | cubeb* context = GetCubebContext(); |
395 | 0 | if (!context) { |
396 | 0 | return; |
397 | 0 | } |
398 | 0 | |
399 | 0 | MutexAutoLock lock(mMutex); |
400 | 0 |
|
401 | | #ifdef ANDROID |
402 | | if (mDevices.IsEmpty()) { |
403 | | // Bug 1473346: enumerating devices is not supported on Android in cubeb, |
404 | | // simply state that there is a single mic, that it is the default, and has a |
405 | | // single channel. All the other values are made up and are not to be used. |
406 | | RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(nullptr, |
407 | | NS_ConvertUTF8toUTF16(""), |
408 | | NS_ConvertUTF8toUTF16(""), |
409 | | NS_ConvertUTF8toUTF16(""), |
410 | | CUBEB_DEVICE_TYPE_INPUT, |
411 | | CUBEB_DEVICE_STATE_ENABLED, |
412 | | CUBEB_DEVICE_PREF_ALL, |
413 | | CUBEB_DEVICE_FMT_ALL, |
414 | | CUBEB_DEVICE_FMT_S16NE, |
415 | | 1, |
416 | | 44100, |
417 | | 44100, |
418 | | 41000, |
419 | | 410, |
420 | | 128); |
421 | | mDevices.AppendElement(info); |
422 | | } |
423 | | #else |
424 | 0 | if (mDevices.IsEmpty() || mManualInvalidation) { |
425 | 0 | mDevices.Clear(); |
426 | 0 | CubebUtils::GetDeviceCollection(mDevices, CubebUtils::Input); |
427 | 0 | } |
428 | 0 | #endif |
429 | 0 |
|
430 | 0 | aOutDevices.AppendElements(mDevices); |
431 | 0 | } |
432 | | |
433 | | already_AddRefed<AudioDeviceInfo> |
434 | | CubebDeviceEnumerator::DeviceInfoFromID(CubebUtils::AudioDeviceID aID) |
435 | 0 | { |
436 | 0 | MutexAutoLock lock(mMutex); |
437 | 0 |
|
438 | 0 | for (uint32_t i = 0; i < mDevices.Length(); i++) { |
439 | 0 | if (mDevices[i]->DeviceID() == aID) { |
440 | 0 | RefPtr<AudioDeviceInfo> other = mDevices[i]; |
441 | 0 | return other.forget(); |
442 | 0 | } |
443 | 0 | } |
444 | 0 | return nullptr; |
445 | 0 | } |
446 | | |
447 | | void |
448 | | CubebDeviceEnumerator::AudioDeviceListChanged_s(cubeb* aContext, void* aUser) |
449 | 0 | { |
450 | 0 | CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser); |
451 | 0 | self->AudioDeviceListChanged(); |
452 | 0 | } |
453 | | |
454 | | void |
455 | | CubebDeviceEnumerator::AudioDeviceListChanged() |
456 | 0 | { |
457 | 0 | MutexAutoLock lock(mMutex); |
458 | 0 |
|
459 | 0 | mDevices.Clear(); |
460 | 0 | } |
461 | | |
462 | | } |