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