Coverage Report

Created: 2018-09-25 14:53

/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
}