tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaEngineWebRTC.cpp (12367B)


      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 "CamerasChild.h"
     10 #include "MediaEngineRemoteVideoSource.h"
     11 #include "MediaEngineWebRTCAudio.h"
     12 #include "MediaManager.h"
     13 #include "mozilla/Logging.h"
     14 
     15 // Pipewire detection support
     16 #if defined(WEBRTC_USE_PIPEWIRE)
     17 #  include "modules/desktop_capture/desktop_capturer.h"
     18 #  include "mozilla/StaticPrefs_media.h"
     19 #endif
     20 
     21 #if defined(WEBRTC_MAC)
     22 #  include "modules/desktop_capture/mac/screen_capturer_sck.h"
     23 #  include "mozilla/StaticPrefs_media.h"
     24 #endif
     25 
     26 #define FAKE_ONDEVICECHANGE_EVENT_PERIOD_IN_MS 500
     27 
     28 static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia");
     29 #define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args)
     30 
     31 namespace mozilla {
     32 
     33 using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet;
     34 using camera::CamerasChild;
     35 using camera::GetChildAndCall;
     36 using dom::MediaSourceEnum;
     37 
     38 CubebDeviceEnumerator* GetEnumerator() {
     39  return CubebDeviceEnumerator::GetInstance();
     40 }
     41 
     42 MediaEngineWebRTC::MediaEngineWebRTC() {
     43  AssertIsOnOwningThread();
     44 
     45  GetChildAndCall(
     46      &CamerasChild::ConnectDeviceListChangeListener<MediaEngineWebRTC>,
     47      &mCameraListChangeListener, AbstractThread::MainThread(), this,
     48      &MediaEngineWebRTC::DeviceListChanged);
     49  mMicrophoneListChangeListener =
     50      GetEnumerator()->OnAudioInputDeviceListChange().Connect(
     51          AbstractThread::MainThread(), this,
     52          &MediaEngineWebRTC::DeviceListChanged);
     53  mSpeakerListChangeListener =
     54      GetEnumerator()->OnAudioOutputDeviceListChange().Connect(
     55          AbstractThread::MainThread(), this,
     56          &MediaEngineWebRTC::DeviceListChanged);
     57 }
     58 
     59 void MediaEngineWebRTC::EnumerateVideoDevices(
     60    MediaSourceEnum aMediaSource, nsTArray<RefPtr<MediaDevice>>* aDevices) {
     61  AssertIsOnOwningThread();
     62  // flag sources with cross-origin exploit potential
     63  bool scaryKind = (aMediaSource == MediaSourceEnum::Screen ||
     64                    aMediaSource == MediaSourceEnum::Browser);
     65  bool desktopKind = aMediaSource == MediaSourceEnum::Application ||
     66                     aMediaSource == MediaSourceEnum::Screen ||
     67                     aMediaSource == MediaSourceEnum::Window;
     68  (void)desktopKind;  // Suppress "unused variable" on Windows and Android.
     69 #if defined(WEBRTC_USE_PIPEWIRE)
     70  bool canRequestOsLevelPrompt =
     71      mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire() &&
     72      webrtc::DesktopCapturer::IsRunningUnderWayland() && desktopKind;
     73 #elif defined(WEBRTC_MAC)
     74  bool canRequestOsLevelPrompt =
     75      mozilla::StaticPrefs::
     76          media_getdisplaymedia_screencapturekit_enabled_AtStartup() &&
     77      mozilla::StaticPrefs::
     78          media_getdisplaymedia_screencapturekit_picker_enabled_AtStartup() &&
     79      webrtc::GenericCapturerSckWithPickerAvailable() && desktopKind;
     80 #else
     81  bool canRequestOsLevelPrompt = false;
     82 #endif
     83  /*
     84   * We still enumerate every time, in case a new device was plugged in since
     85   * the last call. TODO: Verify that WebRTC actually does deal with hotplugging
     86   * new devices (with or without new engine creation) and accordingly adjust.
     87   * Enumeration is not neccessary if GIPS reports the same set of devices
     88   * for a given instance of the engine.
     89   */
     90  int num;
     91 #if defined(_ARM64_) && defined(XP_WIN)
     92  // There are problems with using DirectShow on versions of Windows before
     93  // 19H1 on arm64. This disables the camera on older versions of Windows.
     94  if (aMediaSource == MediaSourceEnum::Camera) {
     95    typedef ULONG (*RtlGetVersionFn)(LPOSVERSIONINFOEXW);
     96    RtlGetVersionFn RtlGetVersion;
     97    RtlGetVersion = (RtlGetVersionFn)GetProcAddress(GetModuleHandleA("ntdll"),
     98                                                    "RtlGetVersion");
     99    if (RtlGetVersion) {
    100      OSVERSIONINFOEXW info;
    101      info.dwOSVersionInfoSize = sizeof(info);
    102      RtlGetVersion(&info);
    103      // 19H1 is 18346
    104      if (info.dwBuildNumber < 18346) {
    105        return;
    106      }
    107    }
    108  }
    109 #endif
    110  camera::CaptureEngine capEngine =
    111      MediaEngineRemoteVideoSource::CaptureEngine(aMediaSource);
    112  num = GetChildAndCall(&CamerasChild::NumberOfCaptureDevices, capEngine);
    113 
    114  for (int i = 0; i < num; i++) {
    115    char deviceName[MediaEngineSource::kMaxDeviceNameLength];
    116    char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
    117    bool scarySource = false;
    118 
    119    // paranoia
    120    deviceName[0] = '\0';
    121    uniqueId[0] = '\0';
    122    int error;
    123 
    124    error = GetChildAndCall(&CamerasChild::GetCaptureDevice, capEngine, i,
    125                            deviceName, sizeof(deviceName), uniqueId,
    126                            sizeof(uniqueId), &scarySource);
    127    if (error) {
    128      LOG(("camera:GetCaptureDevice: Failed %d", error));
    129      continue;
    130    }
    131 #ifdef DEBUG
    132    LOG(("  Capture Device Index %d, Name %s", i, deviceName));
    133 
    134    webrtc::CaptureCapability cap;
    135    int numCaps = GetChildAndCall(&CamerasChild::NumberOfCapabilities,
    136                                  capEngine, uniqueId);
    137    LOG(("Number of Capabilities %d", numCaps));
    138    for (int j = 0; j < numCaps; j++) {
    139      if (GetChildAndCall(&CamerasChild::GetCaptureCapability, capEngine,
    140                          uniqueId, j, &cap) != 0) {
    141        break;
    142      }
    143      LOG(("type=%d width=%d height=%d maxFPS=%d",
    144           static_cast<int>(cap.videoType), cap.width, cap.height, cap.maxFPS));
    145    }
    146 #endif
    147 
    148    NS_ConvertUTF8toUTF16 name(deviceName);
    149    NS_ConvertUTF8toUTF16 uuid(uniqueId);
    150    // The remote video backend doesn't implement group id. We return the
    151    // device name and higher layers will correlate this with the name of
    152    // audio devices.
    153 
    154    aDevices->EmplaceBack(new MediaDevice(
    155        this, aMediaSource, name, uuid, uuid,
    156        MediaDevice::IsScary(scaryKind || scarySource),
    157        canRequestOsLevelPrompt ? MediaDevice::OsPromptable::Yes
    158                                : MediaDevice::OsPromptable::No));
    159  }
    160 }
    161 
    162 void MediaEngineWebRTC::EnumerateMicrophoneDevices(
    163    nsTArray<RefPtr<MediaDevice>>* aDevices) {
    164  AssertIsOnOwningThread();
    165 
    166  RefPtr<const AudioDeviceSet> devices =
    167      GetEnumerator()->EnumerateAudioInputDevices();
    168 
    169  DebugOnly<bool> foundPreferredDevice = false;
    170 
    171  for (const auto& deviceInfo : *devices) {
    172 #ifndef ANDROID
    173    MOZ_ASSERT(deviceInfo->DeviceID());
    174 #endif
    175    LOG(("Cubeb device: type 0x%x, state 0x%x, name %s, id %p",
    176         deviceInfo->Type(), deviceInfo->State(),
    177         NS_ConvertUTF16toUTF8(deviceInfo->Name()).get(),
    178         deviceInfo->DeviceID()));
    179 
    180    if (deviceInfo->State() == CUBEB_DEVICE_STATE_ENABLED) {
    181      MOZ_ASSERT(deviceInfo->Type() == CUBEB_DEVICE_TYPE_INPUT);
    182      // Lie and provide the name as UUID
    183      RefPtr device = new MediaDevice(this, deviceInfo, deviceInfo->Name());
    184      if (deviceInfo->Preferred()) {
    185 #ifdef DEBUG
    186        if (!foundPreferredDevice) {
    187          foundPreferredDevice = true;
    188        } else {
    189          // This is possible on windows, there is a default communication
    190          // device, and a default device:
    191          // See https://bugzilla.mozilla.org/show_bug.cgi?id=1542739
    192 #  ifndef XP_WIN
    193          MOZ_ASSERT(!foundPreferredDevice,
    194                     "Found more than one preferred audio input device"
    195                     "while enumerating");
    196 #  endif
    197        }
    198 #endif
    199        aDevices->InsertElementAt(0, std::move(device));
    200      } else {
    201        aDevices->AppendElement(std::move(device));
    202      }
    203    }
    204  }
    205 }
    206 
    207 void MediaEngineWebRTC::EnumerateSpeakerDevices(
    208    nsTArray<RefPtr<MediaDevice>>* aDevices) {
    209  AssertIsOnOwningThread();
    210 
    211  RefPtr<const AudioDeviceSet> devices =
    212      GetEnumerator()->EnumerateAudioOutputDevices();
    213 
    214 #ifndef XP_WIN
    215  DebugOnly<bool> preferredDeviceFound = false;
    216 #endif
    217  for (const auto& deviceInfo : *devices) {
    218    LOG(("Cubeb device: type 0x%x, state 0x%x, name %s, id %p",
    219         deviceInfo->Type(), deviceInfo->State(),
    220         NS_ConvertUTF16toUTF8(deviceInfo->Name()).get(),
    221         deviceInfo->DeviceID()));
    222    if (deviceInfo->State() == CUBEB_DEVICE_STATE_ENABLED) {
    223      MOZ_ASSERT(deviceInfo->Type() == CUBEB_DEVICE_TYPE_OUTPUT);
    224      nsString uuid(deviceInfo->Name());
    225      // If, for example, input and output are in the same device, uuid
    226      // would be the same for both which ends up to create the same
    227      // deviceIDs (in JS).
    228      uuid.Append(u"_Speaker"_ns);
    229      RefPtr device = new MediaDevice(this, deviceInfo, uuid);
    230      if (deviceInfo->Preferred()) {
    231        // In windows is possible to have more than one preferred device
    232 #if defined(DEBUG) && !defined(XP_WIN)
    233        MOZ_ASSERT(!preferredDeviceFound, "More than one preferred device");
    234        preferredDeviceFound = true;
    235 #endif
    236        aDevices->InsertElementAt(0, std::move(device));
    237      } else {
    238        aDevices->AppendElement(std::move(device));
    239      }
    240    }
    241  }
    242 }
    243 
    244 void MediaEngineWebRTC::EnumerateDevices(
    245    MediaSourceEnum aMediaSource, MediaSinkEnum aMediaSink,
    246    nsTArray<RefPtr<MediaDevice>>* aDevices) {
    247  AssertIsOnOwningThread();
    248  MOZ_ASSERT(aMediaSource != MediaSourceEnum::Other ||
    249             aMediaSink != MediaSinkEnum::Other);
    250  if (MediaEngineSource::IsVideo(aMediaSource)) {
    251    switch (aMediaSource) {
    252      case MediaSourceEnum::Window:
    253        // Since the mediaSource constraint is deprecated, treat the Window
    254        // value as a request for getDisplayMedia-equivalent sharing: Combine
    255        // window and fullscreen into a single list of choices. The other values
    256        // are still useful for testing.
    257        EnumerateVideoDevices(MediaSourceEnum::Window, aDevices);
    258        EnumerateVideoDevices(MediaSourceEnum::Browser, aDevices);
    259        EnumerateVideoDevices(MediaSourceEnum::Screen, aDevices);
    260        break;
    261      case MediaSourceEnum::Screen:
    262      case MediaSourceEnum::Browser:
    263      case MediaSourceEnum::Camera:
    264        EnumerateVideoDevices(aMediaSource, aDevices);
    265        break;
    266      default:
    267        MOZ_CRASH("No valid video source");
    268        break;
    269    }
    270  } else if (aMediaSource == MediaSourceEnum::AudioCapture) {
    271    aDevices->EmplaceBack(new MediaDevice(
    272        this, aMediaSource, u"AudioCapture"_ns,
    273        MediaEngineWebRTCAudioCaptureSource::GetUUID(),
    274        MediaEngineWebRTCAudioCaptureSource::GetGroupId(),
    275        MediaDevice::IsScary::No, MediaDevice::OsPromptable::No));
    276  } else if (aMediaSource == MediaSourceEnum::Microphone) {
    277    EnumerateMicrophoneDevices(aDevices);
    278  }
    279 
    280  if (aMediaSink == MediaSinkEnum::Speaker) {
    281    EnumerateSpeakerDevices(aDevices);
    282  }
    283 }
    284 
    285 RefPtr<MediaEngineSource> MediaEngineWebRTC::CreateSource(
    286    const MediaDevice* aMediaDevice) {
    287  MOZ_ASSERT(aMediaDevice->mEngine == this);
    288  if (MediaEngineSource::IsVideo(aMediaDevice->mMediaSource)) {
    289    return new MediaEngineRemoteVideoSource(aMediaDevice);
    290  }
    291  switch (aMediaDevice->mMediaSource) {
    292    case MediaSourceEnum::AudioCapture:
    293      return new MediaEngineWebRTCAudioCaptureSource(aMediaDevice);
    294    case MediaSourceEnum::Microphone:
    295      return new MediaEngineWebRTCMicrophoneSource(aMediaDevice);
    296    default:
    297      MOZ_CRASH("Unsupported source type");
    298      return nullptr;
    299  }
    300 }
    301 
    302 RefPtr<MediaEngineSource> MediaEngineWebRTC::CreateSourceFrom(
    303    const MediaEngineSource* aSource, const MediaDevice* aMediaDevice) {
    304  MOZ_ASSERT(aMediaDevice->mEngine == this);
    305  if (MediaEngineSource::IsVideo(aMediaDevice->mMediaSource)) {
    306    return MediaEngineRemoteVideoSource::CreateFrom(
    307        static_cast<const MediaEngineRemoteVideoSource*>(aSource),
    308        aMediaDevice);
    309  }
    310  switch (aMediaDevice->mMediaSource) {
    311    case MediaSourceEnum::Microphone:
    312      return MediaEngineWebRTCMicrophoneSource::CreateFrom(
    313          static_cast<const MediaEngineWebRTCMicrophoneSource*>(aSource),
    314          aMediaDevice);
    315    default:
    316      MOZ_CRASH("Unsupported source type");
    317      return nullptr;
    318  }
    319 }
    320 
    321 void MediaEngineWebRTC::Shutdown() {
    322  AssertIsOnOwningThread();
    323  mCameraListChangeListener.DisconnectIfExists();
    324  mMicrophoneListChangeListener.DisconnectIfExists();
    325  mSpeakerListChangeListener.DisconnectIfExists();
    326 
    327  LOG(("%s", __FUNCTION__));
    328  mozilla::camera::Shutdown();
    329 }
    330 
    331 }  // namespace mozilla
    332 
    333 #undef LOG