video_capture_factory.cc (9592B)
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 "video_capture_factory.h" 8 9 #include "VideoEngine.h" 10 #include "desktop_capture_impl.h" 11 #include "fake_video_capture/device_info_fake.h" 12 #include "fake_video_capture/video_capture_fake.h" 13 #include "mozilla/StaticPrefs_media.h" 14 15 #if defined(WEBRTC_USE_PIPEWIRE) 16 # include "video_engine/placeholder_device_info.h" 17 #endif 18 19 #if defined(WEBRTC_USE_PIPEWIRE) && defined(MOZ_ENABLE_DBUS) 20 # include "mozilla/widget/AsyncDBus.h" 21 #endif 22 23 #include <memory> 24 25 namespace mozilla { 26 27 VideoCaptureFactory::VideoCaptureFactory() { 28 #if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID) 29 mVideoCaptureOptions = std::make_unique<webrtc::VideoCaptureOptions>(); 30 // In case pipewire is enabled, this acts as a fallback and can be always 31 // enabled. 32 mVideoCaptureOptions->set_allow_v4l2(true); 33 bool allowPipeWire = false; 34 # if defined(WEBRTC_USE_PIPEWIRE) 35 allowPipeWire = 36 mozilla::StaticPrefs::media_webrtc_camera_allow_pipewire_AtStartup(); 37 mVideoCaptureOptions->set_allow_pipewire(allowPipeWire); 38 # endif 39 if (!allowPipeWire) { 40 // V4L2 backend can and should be initialized right away since there are no 41 // permissions involved 42 InitCameraBackend(); 43 } 44 #endif 45 } 46 47 std::shared_ptr<webrtc::VideoCaptureModule::DeviceInfo> 48 VideoCaptureFactory::CreateDeviceInfo( 49 int32_t aId, mozilla::camera::CaptureDeviceType aType) { 50 if (aType == mozilla::camera::CaptureDeviceType::Camera) { 51 std::shared_ptr<webrtc::VideoCaptureModule::DeviceInfo> deviceInfo; 52 mUseFakeCamera = mUseFakeCamera.orElse([] { 53 return Some(StaticPrefs::media_getusermedia_camera_fake_force()); 54 }); 55 if (*mUseFakeCamera) { 56 deviceInfo.reset(new webrtc::videocapturemodule::DeviceInfoFake()); 57 return deviceInfo; 58 } 59 #if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID) 60 # if defined(WEBRTC_USE_PIPEWIRE) 61 // Special case when PipeWire is not initialized yet and we need to insert 62 // a camera device placeholder based on camera device availability we get 63 // from the camera portal 64 if (!mCameraBackendInitialized && mVideoCaptureOptions->allow_pipewire()) { 65 MOZ_ASSERT(mCameraAvailability != Unknown); 66 deviceInfo.reset( 67 new PlaceholderDeviceInfo(mCameraAvailability == Available)); 68 return deviceInfo; 69 } 70 # endif 71 72 deviceInfo.reset(webrtc::VideoCaptureFactory::CreateDeviceInfo( 73 mVideoCaptureOptions.get())); 74 #else 75 deviceInfo.reset(webrtc::VideoCaptureFactory::CreateDeviceInfo()); 76 #endif 77 return deviceInfo; 78 } 79 80 #if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) 81 MOZ_ASSERT("CreateDeviceInfo NO DESKTOP CAPTURE IMPL ON ANDROID" == nullptr); 82 return nullptr; 83 #else 84 return webrtc::DesktopCaptureImpl::CreateDeviceInfo(aId, aType); 85 #endif 86 } 87 88 VideoCaptureFactory::CreateVideoCaptureResult 89 VideoCaptureFactory::CreateVideoCapture( 90 int32_t aModuleId, const char* aUniqueId, 91 mozilla::camera::CaptureDeviceType aType) { 92 CreateVideoCaptureResult result; 93 if (aType == mozilla::camera::CaptureDeviceType::Camera) { 94 if (mUseFakeCamera.valueOr(false)) { 95 nsCOMPtr<nsISerialEventTarget> target; 96 NS_CreateBackgroundTaskQueue("VideoCaptureFake::mTarget", 97 getter_AddRefs(target)); 98 result.mCapturer = 99 webrtc::videocapturemodule::VideoCaptureFake::Create(target); 100 return result; 101 } 102 #if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID) 103 result.mCapturer = webrtc::VideoCaptureFactory::Create( 104 mVideoCaptureOptions.get(), aUniqueId); 105 #else 106 result.mCapturer = webrtc::VideoCaptureFactory::Create(aUniqueId); 107 #endif 108 return result; 109 } 110 111 #if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) 112 MOZ_ASSERT("CreateVideoCapture NO DESKTOP CAPTURE IMPL ON ANDROID" == 113 nullptr); 114 #else 115 result.mDesktopImpl = 116 webrtc::DesktopCaptureImpl::Create(aModuleId, aUniqueId, aType); 117 result.mCapturer = 118 webrtc::scoped_refptr<webrtc::VideoCaptureModule>(result.mDesktopImpl); 119 #endif 120 121 return result; 122 } 123 124 auto VideoCaptureFactory::InitCameraBackend() 125 -> RefPtr<CameraBackendInitPromise> { 126 if (!mPromise) { 127 mPromise = mPromiseHolder.Ensure(__func__); 128 #if (defined(WEBRTC_LINUX) || defined(WEBRTC_BSD)) && !defined(WEBRTC_ANDROID) 129 MOZ_ASSERT(mVideoCaptureOptions); 130 mVideoCaptureOptions->Init(this); 131 # if defined(WEBRTC_USE_PIPEWIRE) 132 mPromise = mPromise->Then( 133 GetCurrentSerialEventTarget(), __func__, 134 [this, self = RefPtr(this)]( 135 const CameraBackendInitPromise::ResolveOrRejectValue& aValue) { 136 if (aValue.IsReject() && 137 aValue.RejectValue() != NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR) { 138 // Fallback to V4L2 in case of PipeWire or camera portal failure. 139 // There is nothing we need to do in order to initialize V4L2 so 140 // consider the backend initialized and ready to be used. 141 mVideoCaptureOptions->set_allow_pipewire(false); 142 mCameraBackendInitialized = true; 143 144 return CameraBackendInitPromise::CreateAndResolve( 145 NS_OK, 146 "VideoCaptureFactory::InitCameraBackend Resolve with " 147 "fallback to V4L2"); 148 } 149 150 return CameraBackendInitPromise::CreateAndResolveOrReject( 151 aValue, 152 "VideoCaptureFactory::InitCameraBackend Resolve or Reject"); 153 }); 154 # endif 155 #else 156 mCameraBackendInitialized = true; 157 mPromiseHolder.Resolve(NS_OK, 158 "VideoCaptureFactory::InitCameraBackend Resolve"); 159 #endif 160 } 161 162 return mPromise; 163 } 164 165 auto VideoCaptureFactory::HasCameraDevice() 166 -> RefPtr<VideoCaptureFactory::HasCameraDevicePromise> { 167 #if defined(WEBRTC_USE_PIPEWIRE) && defined(MOZ_ENABLE_DBUS) 168 if (mVideoCaptureOptions && mVideoCaptureOptions->allow_pipewire()) { 169 return widget::CreateDBusProxyForBus( 170 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, 171 /* aInterfaceInfo = */ nullptr, "org.freedesktop.portal.Desktop", 172 "/org/freedesktop/portal/desktop", 173 "org.freedesktop.portal.Camera") 174 ->Then( 175 GetCurrentSerialEventTarget(), __func__, 176 [](RefPtr<GDBusProxy>&& aProxy) { 177 GVariant* variant = 178 g_dbus_proxy_get_cached_property(aProxy, "IsCameraPresent"); 179 if (!variant) { 180 return HasCameraDevicePromise::CreateAndReject( 181 NS_ERROR_NO_INTERFACE, 182 "VideoCaptureFactory::HasCameraDevice Reject"); 183 } 184 185 if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) { 186 return HasCameraDevicePromise::CreateAndReject( 187 NS_ERROR_UNEXPECTED, 188 "VideoCaptureFactory::HasCameraDevice Reject"); 189 } 190 191 const bool hasCamera = g_variant_get_boolean(variant); 192 g_variant_unref(variant); 193 return HasCameraDevicePromise::CreateAndResolve( 194 hasCamera ? Available : NotAvailable, 195 "VideoCaptureFactory::HasCameraDevice Resolve"); 196 }, 197 [](GUniquePtr<GError>&& aError) { 198 return HasCameraDevicePromise::CreateAndReject( 199 NS_ERROR_NO_INTERFACE, 200 "VideoCaptureFactory::HasCameraDevice Reject"); 201 }); 202 } 203 #endif 204 return HasCameraDevicePromise::CreateAndReject( 205 NS_ERROR_NOT_IMPLEMENTED, "VideoCaptureFactory::HasCameraDevice Reject"); 206 } 207 208 auto VideoCaptureFactory::UpdateCameraAvailability() 209 -> RefPtr<UpdateCameraAvailabilityPromise> { 210 return VideoCaptureFactory::HasCameraDevice()->Then( 211 GetCurrentSerialEventTarget(), __func__, 212 [this, self = RefPtr(this)]( 213 const HasCameraDevicePromise::ResolveOrRejectValue& aValue) { 214 if (aValue.IsResolve()) { 215 mCameraAvailability = aValue.ResolveValue(); 216 217 return HasCameraDevicePromise::CreateAndResolve( 218 mCameraAvailability, 219 "VideoCaptureFactory::UpdateCameraAvailability Resolve"); 220 } 221 222 // We want to fallback to V4L2 at this point, therefore make sure a 223 // camera device is announced so we can proceed with a gUM request, 224 // where we can fallback to V4L2 backend. 225 mCameraAvailability = Available; 226 227 return HasCameraDevicePromise::CreateAndReject( 228 aValue.RejectValue(), 229 "VideoCaptureFactory::UpdateCameraAvailability Reject"); 230 }); 231 } 232 233 void VideoCaptureFactory::OnInitialized( 234 webrtc::VideoCaptureOptions::Status status) { 235 switch (status) { 236 case webrtc::VideoCaptureOptions::Status::SUCCESS: 237 mCameraBackendInitialized = true; 238 mPromiseHolder.Resolve(NS_OK, __func__); 239 return; 240 case webrtc::VideoCaptureOptions::Status::UNAVAILABLE: 241 mPromiseHolder.Reject(NS_ERROR_NOT_AVAILABLE, __func__); 242 return; 243 case webrtc::VideoCaptureOptions::Status::DENIED: 244 mPromiseHolder.Reject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, __func__); 245 return; 246 default: 247 mPromiseHolder.Reject(NS_ERROR_FAILURE, __func__); 248 return; 249 } 250 } 251 252 void VideoCaptureFactory::Invalidate() { mUseFakeCamera = Nothing(); } 253 254 } // namespace mozilla