/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et ft=cpp : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaEngineWebRTC.h" #include "AllocationHandle.h" #include "CamerasChild.h" #include "CSFLog.h" #include "MediaEngineTabVideoSource.h" #include "MediaEngineRemoteVideoSource.h" #include "MediaEngineWebRTCAudio.h" #include "MediaTrackConstraints.h" #include "mozilla/dom/MediaDeviceInfo.h" #include "mozilla/Logging.h" #include "nsIComponentRegistrar.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsITabSource.h" #include "prenv.h" static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia"); #undef LOG #define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args) namespace mozilla { MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs& aPrefs) : mMutex("mozilla::MediaEngineWebRTC"), mDelayAgnostic(aPrefs.mDelayAgnostic), mExtendedFilter(aPrefs.mExtendedFilter), mHasTabVideoSource(false) { nsCOMPtr compMgr; NS_GetComponentRegistrar(getter_AddRefs(compMgr)); if (compMgr) { compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource); } camera::GetChildAndCall(&camera::CamerasChild::AddDeviceChangeCallback, this); } void MediaEngineWebRTC::SetFakeDeviceChangeEvents() { camera::GetChildAndCall(&camera::CamerasChild::SetFakeDeviceChangeEvents); } void MediaEngineWebRTC::EnumerateVideoDevices( uint64_t aWindowId, camera::CaptureEngine aCapEngine, nsTArray>* aDevices) { mMutex.AssertCurrentThreadOwns(); // flag sources with cross-origin exploit potential bool scaryKind = (aCapEngine == camera::ScreenEngine || aCapEngine == camera::BrowserEngine); /* * We still enumerate every time, in case a new device was plugged in since * the last call. TODO: Verify that WebRTC actually does deal with hotplugging * new devices (with or without new engine creation) and accordingly adjust. * Enumeration is not neccessary if GIPS reports the same set of devices * for a given instance of the engine. Likewise, if a device was plugged out, * mVideoSources must be updated. */ int num; #if defined(_ARM64_) && defined(XP_WIN) // There are problems with using DirectShow on versions of Windows before // 19H1 on arm64. This disables the camera on older versions of Windows. if (aCapEngine == camera::CameraEngine) { typedef ULONG (*RtlGetVersionFn)(LPOSVERSIONINFOEXW); RtlGetVersionFn RtlGetVersion; RtlGetVersion = (RtlGetVersionFn)GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion"); if (RtlGetVersion) { OSVERSIONINFOEXW info; info.dwOSVersionInfoSize = sizeof(info); RtlGetVersion(&info); // 19H1 is 18346 if (info.dwBuildNumber < 18346) { return; } } } #endif num = mozilla::camera::GetChildAndCall( &mozilla::camera::CamerasChild::NumberOfCaptureDevices, aCapEngine); for (int i = 0; i < num; i++) { char deviceName[MediaEngineSource::kMaxDeviceNameLength]; char uniqueId[MediaEngineSource::kMaxUniqueIdLength]; bool scarySource = false; // paranoia deviceName[0] = '\0'; uniqueId[0] = '\0'; int error; error = mozilla::camera::GetChildAndCall( &mozilla::camera::CamerasChild::GetCaptureDevice, aCapEngine, i, deviceName, sizeof(deviceName), uniqueId, sizeof(uniqueId), &scarySource); if (error) { LOG(("camera:GetCaptureDevice: Failed %d", error)); continue; } #ifdef DEBUG LOG((" Capture Device Index %d, Name %s", i, deviceName)); webrtc::CaptureCapability cap; int numCaps = mozilla::camera::GetChildAndCall( &mozilla::camera::CamerasChild::NumberOfCapabilities, aCapEngine, uniqueId); LOG(("Number of Capabilities %d", numCaps)); for (int j = 0; j < numCaps; j++) { if (mozilla::camera::GetChildAndCall( &mozilla::camera::CamerasChild::GetCaptureCapability, aCapEngine, uniqueId, j, cap) != 0) { break; } LOG(("type=%d width=%d height=%d maxFPS=%d", static_cast(cap.videoType), cap.width, cap.height, cap.maxFPS)); } #endif if (uniqueId[0] == '\0') { // In case a device doesn't set uniqueId! strncpy(uniqueId, deviceName, sizeof(uniqueId)); uniqueId[sizeof(uniqueId) - 1] = '\0'; // strncpy isn't safe } NS_ConvertUTF8toUTF16 uuid(uniqueId); RefPtr vSource; nsRefPtrHashtable* devicesForThisWindow = mVideoSources.LookupOrAdd(aWindowId); if (devicesForThisWindow->Get(uuid, getter_AddRefs(vSource)) && vSource->RequiresSharing()) { // We've already seen this shared device, just refresh and append. static_cast(vSource.get())->Refresh(i); } else { vSource = new MediaEngineRemoteVideoSource(i, aCapEngine, scaryKind || scarySource); devicesForThisWindow->Put(uuid, vSource); } aDevices->AppendElement(MakeRefPtr( vSource, vSource->GetName(), NS_ConvertUTF8toUTF16(vSource->GetUUID()), NS_LITERAL_STRING(""))); } if (mHasTabVideoSource || aCapEngine == camera::BrowserEngine) { RefPtr tabVideoSource = new MediaEngineTabVideoSource(); aDevices->AppendElement(MakeRefPtr( tabVideoSource, tabVideoSource->GetName(), NS_ConvertUTF8toUTF16(tabVideoSource->GetUUID()), NS_LITERAL_STRING(""))); } } void MediaEngineWebRTC::EnumerateMicrophoneDevices( uint64_t aWindowId, nsTArray>* aDevices) { mMutex.AssertCurrentThreadOwns(); mEnumerator = CubebDeviceEnumerator::GetInstance(); nsTArray> devices; mEnumerator->EnumerateAudioInputDevices(devices); DebugOnly foundPreferredDevice = false; for (uint32_t i = 0; i < devices.Length(); i++) { #ifndef ANDROID MOZ_ASSERT(devices[i]->DeviceID()); #endif LOG(("Cubeb device %u: type 0x%x, state 0x%x, name %s, id %p", i, devices[i]->Type(), devices[i]->State(), NS_ConvertUTF16toUTF8(devices[i]->Name()).get(), devices[i]->DeviceID())); if (devices[i]->State() == CUBEB_DEVICE_STATE_ENABLED) { MOZ_ASSERT(devices[i]->Type() == CUBEB_DEVICE_TYPE_INPUT); RefPtr source = new MediaEngineWebRTCMicrophoneSource( devices[i], devices[i]->Name(), // Lie and provide the name as UUID NS_ConvertUTF16toUTF8(devices[i]->Name()), devices[i]->MaxChannels(), mDelayAgnostic, mExtendedFilter); RefPtr device = MakeRefPtr( source, source->GetName(), NS_ConvertUTF8toUTF16(source->GetUUID()), NS_LITERAL_STRING("")); if (devices[i]->Preferred()) { #ifdef DEBUG if (!foundPreferredDevice) { foundPreferredDevice = true; } else { MOZ_ASSERT(!foundPreferredDevice, "Found more than one preferred audio input device" "while enumerating"); } #endif aDevices->InsertElementAt(0, device); } else { aDevices->AppendElement(device); } } } } void MediaEngineWebRTC::EnumerateSpeakerDevices( uint64_t aWindowId, nsTArray>* aDevices) { if (!mEnumerator) { mEnumerator = CubebDeviceEnumerator::GetInstance(); } nsTArray> devices; mEnumerator->EnumerateAudioOutputDevices(devices); for (auto& device : devices) { if (device->State() == CUBEB_DEVICE_STATE_ENABLED) { MOZ_ASSERT(device->Type() == CUBEB_DEVICE_TYPE_OUTPUT); nsString uuid(device->Name()); // If, for example, input and output are in the same device, uuid // would be the same for both which ends up to create the same // deviceIDs (in JS). uuid.Append(NS_LITERAL_STRING("_Speaker")); aDevices->AppendElement(MakeRefPtr(device, uuid)); } } } void MediaEngineWebRTC::EnumerateDevices( uint64_t aWindowId, dom::MediaSourceEnum aMediaSource, MediaSinkEnum aMediaSink, nsTArray>* aDevices) { MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other || aMediaSink != MediaSinkEnum::Other); MutexAutoLock lock(mMutex); if (MediaEngineSource::IsVideo(aMediaSource)) { switch (aMediaSource) { case dom::MediaSourceEnum::Window: // Since the mediaSource constraint is deprecated, treat the Window // value as a request for getDisplayMedia-equivalent sharing: Combine // window and fullscreen into a single list of choices. The other values // are still useful for testing. EnumerateVideoDevices(aWindowId, camera::WinEngine, aDevices); EnumerateVideoDevices(aWindowId, camera::ScreenEngine, aDevices); break; case dom::MediaSourceEnum::Application: EnumerateVideoDevices(aWindowId, camera::AppEngine, aDevices); break; case dom::MediaSourceEnum::Screen: EnumerateVideoDevices(aWindowId, camera::ScreenEngine, aDevices); break; case dom::MediaSourceEnum::Browser: EnumerateVideoDevices(aWindowId, camera::BrowserEngine, aDevices); break; case dom::MediaSourceEnum::Camera: EnumerateVideoDevices(aWindowId, camera::CameraEngine, aDevices); break; default: MOZ_CRASH("No valid video source"); break; } } else if (aMediaSource == dom::MediaSourceEnum::AudioCapture) { RefPtr audioCaptureSource = new MediaEngineWebRTCAudioCaptureSource(nullptr); aDevices->AppendElement(MakeRefPtr( audioCaptureSource, audioCaptureSource->GetName(), NS_ConvertUTF8toUTF16(audioCaptureSource->GetUUID()), NS_LITERAL_STRING(""))); } else if (aMediaSource == dom::MediaSourceEnum::Microphone) { MOZ_ASSERT(aMediaSource == dom::MediaSourceEnum::Microphone); EnumerateMicrophoneDevices(aWindowId, aDevices); } if (aMediaSink == MediaSinkEnum::Speaker) { EnumerateSpeakerDevices(aWindowId, aDevices); } } void MediaEngineWebRTC::ReleaseResourcesForWindow(uint64_t aWindowId) { { nsRefPtrHashtable* audioDevicesForThisWindow = mAudioSources.Get(aWindowId); if (audioDevicesForThisWindow) { for (auto iter = audioDevicesForThisWindow->Iter(); !iter.Done(); iter.Next()) { iter.UserData()->Shutdown(); } // This makes audioDevicesForThisWindow invalid. mAudioSources.Remove(aWindowId); } } { nsRefPtrHashtable* videoDevicesForThisWindow = mVideoSources.Get(aWindowId); if (videoDevicesForThisWindow) { for (auto iter = videoDevicesForThisWindow->Iter(); !iter.Done(); iter.Next()) { iter.UserData()->Shutdown(); } // This makes videoDevicesForThisWindow invalid. mVideoSources.Remove(aWindowId); } } } namespace { template void ShutdownSources(T& aHashTable) { for (auto iter = aHashTable.Iter(); !iter.Done(); iter.Next()) { for (auto iterInner = iter.UserData()->Iter(); !iterInner.Done(); iterInner.Next()) { MediaEngineSource* source = iterInner.UserData(); source->Shutdown(); } } } } // namespace void MediaEngineWebRTC::Shutdown() { // This is likely paranoia MutexAutoLock lock(mMutex); if (camera::GetCamerasChildIfExists()) { camera::GetChildAndCall(&camera::CamerasChild::RemoveDeviceChangeCallback, this); } LOG(("%s", __FUNCTION__)); // Shutdown all the sources, since we may have dangling references to the // sources in nsDOMUserMediaStreams waiting for GC/CC ShutdownSources(mVideoSources); ShutdownSources(mAudioSources); mEnumerator = nullptr; mozilla::camera::Shutdown(); } } // namespace mozilla