desktop_capture_impl.cc (21226B)
1 /* 2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include "desktop_capture_impl.h" 12 13 #include <charconv> 14 #include <cstdlib> 15 #include <memory> 16 #include <string> 17 18 #include "CamerasTypes.h" 19 #include "VideoEngine.h" 20 #include "api/video/i420_buffer.h" 21 #include "common_video/libyuv/include/webrtc_libyuv.h" 22 #include "desktop_device_info.h" 23 #include "libyuv/convert.h" 24 #include "modules/desktop_capture/desktop_and_cursor_composer.h" 25 #include "modules/desktop_capture/desktop_capture_options.h" 26 #include "modules/desktop_capture/desktop_capturer_differ_wrapper.h" 27 #include "modules/desktop_capture/desktop_frame.h" 28 #include "modules/video_capture/video_capture.h" 29 #include "mozilla/StaticPrefs_media.h" 30 #include "mozilla/SyncRunnable.h" 31 #include "mozilla/TimeStamp.h" 32 #include "nsThreadUtils.h" 33 #include "rtc_base/logging.h" 34 #include "rtc_base/time_utils.h" 35 #include "rtc_base/trace_event.h" 36 #include "tab_capturer.h" 37 38 #ifdef XP_MACOSX 39 # include "modules/desktop_capture/mac/screen_capturer_sck.h" 40 #endif 41 42 using mozilla::NewRunnableMethod; 43 using mozilla::TabCapturerWebrtc; 44 using mozilla::TimeDuration; 45 using mozilla::camera::CaptureDeviceType; 46 using mozilla::camera::CaptureEngine; 47 48 static void CaptureFrameOnThread(nsITimer* aTimer, void* aClosure) { 49 static_cast<webrtc::DesktopCaptureImpl*>(aClosure)->CaptureFrameOnThread(); 50 } 51 52 namespace webrtc { 53 54 DesktopCaptureImpl* DesktopCaptureImpl::Create(const int32_t aModuleId, 55 const char* aUniqueId, 56 const CaptureDeviceType aType) { 57 return new webrtc::RefCountedObject<DesktopCaptureImpl>(aModuleId, aUniqueId, 58 aType); 59 } 60 61 static DesktopCaptureOptions CreateDesktopCaptureOptions() { 62 DesktopCaptureOptions options; 63 // Help avoid an X11 deadlock, see bug 1456101. 64 #ifdef MOZ_X11 65 MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread( 66 mozilla::GetMainThreadSerialEventTarget(), 67 NS_NewRunnableFunction(__func__, [&] { 68 options = DesktopCaptureOptions::CreateDefault(); 69 }))); 70 #else 71 options = DesktopCaptureOptions::CreateDefault(); 72 #endif 73 74 // Leave desktop effects enabled during WebRTC captures. 75 options.set_disable_effects(false); 76 77 #if defined(WEBRTC_WIN) 78 options.set_allow_directx_capturer( 79 mozilla::StaticPrefs::media_webrtc_capture_allow_directx()); 80 options.set_allow_cropping_window_capturer(true); 81 # if defined(RTC_ENABLE_WIN_WGC) 82 if (mozilla::StaticPrefs::media_webrtc_capture_screen_allow_wgc()) { 83 options.set_allow_wgc_screen_capturer(true); 84 options.set_allow_wgc_zero_hertz( 85 mozilla::StaticPrefs::media_webrtc_capture_wgc_allow_zero_hertz()); 86 } 87 if (mozilla::StaticPrefs::media_webrtc_capture_window_allow_wgc()) { 88 options.set_allow_wgc_window_capturer(true); 89 options.set_allow_wgc_zero_hertz( 90 mozilla::StaticPrefs::media_webrtc_capture_wgc_allow_zero_hertz()); 91 } 92 # endif 93 #endif 94 95 #if defined(WEBRTC_MAC) 96 options.set_prefer_cursor_embedded(true); 97 options.set_allow_sck_capturer( 98 mozilla::StaticPrefs:: 99 media_getdisplaymedia_screencapturekit_enabled_AtStartup()); 100 options.set_allow_sck_system_picker( 101 GenericCapturerSckWithPickerAvailable() && 102 mozilla::StaticPrefs:: 103 media_getdisplaymedia_screencapturekit_picker_enabled_AtStartup()); 104 options.set_allow_iosurface( 105 mozilla::StaticPrefs::media_webrtc_capture_allow_iosurface()); 106 #endif 107 108 #if defined(WEBRTC_USE_PIPEWIRE) 109 options.set_allow_pipewire( 110 mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire() && 111 webrtc::DesktopCapturer::IsRunningUnderWayland()); 112 #endif 113 114 return options; 115 } 116 117 std::shared_ptr<VideoCaptureModule::DeviceInfo> 118 DesktopCaptureImpl::CreateDeviceInfo(const int32_t aId, 119 const CaptureDeviceType aType) { 120 if (aType == CaptureDeviceType::Screen) { 121 auto options = CreateDesktopCaptureOptions(); 122 #ifdef XP_MACOSX 123 if (!options.allow_sck_system_picker() && 124 !mozilla::StaticPrefs:: 125 media_getdisplaymedia_screencapturekit_enumeration_enabled_AtStartup()) { 126 options.set_allow_sck_capturer(false); 127 } 128 #endif 129 return CreateDesktopDeviceInfo(aId, CreateScreenCaptureInfo(options)); 130 } 131 if (aType == CaptureDeviceType::Window) { 132 return CreateDesktopDeviceInfo( 133 aId, CreateWindowCaptureInfo(CreateDesktopCaptureOptions())); 134 } 135 if (aType == CaptureDeviceType::Browser) { 136 return CreateTabDeviceInfo(aId, CreateTabCaptureInfo()); 137 } 138 return nullptr; 139 } 140 141 const char* DesktopCaptureImpl::CurrentDeviceName() const { 142 return mDeviceUniqueId.c_str(); 143 } 144 145 static std::unique_ptr<DesktopCapturer> CreateTabCapturer( 146 const DesktopCaptureOptions& options, DesktopCapturer::SourceId aSourceId, 147 nsCOMPtr<nsISerialEventTarget> aCaptureThread) { 148 std::unique_ptr<DesktopCapturer> capturer = 149 TabCapturerWebrtc::Create(aSourceId, std::move(aCaptureThread)); 150 if (capturer && options.detect_updated_region()) { 151 capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer))); 152 } 153 154 return capturer; 155 } 156 157 static std::unique_ptr<DesktopCapturer> CreateDesktopCapturerAndThread( 158 CaptureDeviceType aDeviceType, DesktopCapturer::SourceId aSourceId, 159 nsIThread** aOutThread) { 160 DesktopCaptureOptions options = CreateDesktopCaptureOptions(); 161 auto ensureThread = [&]() { 162 if (*aOutThread) { 163 return *aOutThread; 164 } 165 166 nsIThreadManager::ThreadCreationOptions threadOptions; 167 #if defined(XP_WIN) || defined(XP_MACOSX) 168 // Windows desktop capture needs a UI thread. 169 // Mac screen capture needs a thread with a CFRunLoop. 170 threadOptions.isUiThread = true; 171 #endif 172 NS_NewNamedThread("DesktopCapture", aOutThread, nullptr, threadOptions); 173 return *aOutThread; 174 }; 175 176 auto createCapturer = [&]() -> std::unique_ptr<DesktopCapturer> { 177 if (aDeviceType == CaptureDeviceType::Screen || 178 aDeviceType == CaptureDeviceType::Window) { 179 auto capturer = DesktopCapturer::CreateGenericCapturer(options); 180 if (capturer) { 181 #if defined(XP_MACOSX) 182 // See comment for same conditional below. 183 if (options.prefer_cursor_embedded() && options.allow_sck_capturer() && 184 ScreenCapturerSckAvailable()) { 185 return capturer; 186 } 187 #endif 188 return std::make_unique<DesktopAndCursorComposer>(std::move(capturer), 189 options); 190 } 191 } 192 193 if (aDeviceType == CaptureDeviceType::Screen) { 194 auto capturer = DesktopCapturer::CreateScreenCapturer(options); 195 if (!capturer) { 196 return capturer; 197 } 198 199 capturer->SelectSource(aSourceId); 200 201 #if defined(XP_MACOSX) 202 // The MouseCursorMonitor on macOS is rather expensive, as for every 203 // pulled frame it compares all pixels of the cursors used for the current 204 // and last frames. Getting to the pixels may also incur a conversion. 205 // 206 // Note that this comparison happens even if the backend reports it had 207 // embedded the cursor already, as the embedding only affects composing 208 // the monitored cursor into a captured frame. 209 // 210 // Avoid the composer (and monitor) if we can. 211 if (options.prefer_cursor_embedded() && options.allow_sck_capturer() && 212 ScreenCapturerSckAvailable()) { 213 return capturer; 214 } 215 #endif 216 217 return std::make_unique<DesktopAndCursorComposer>(std::move(capturer), 218 options); 219 } 220 221 if (aDeviceType == CaptureDeviceType::Window) { 222 #if defined(RTC_ENABLE_WIN_WGC) 223 options.set_allow_wgc_capturer_fallback(true); 224 #endif 225 auto capturer = DesktopCapturer::CreateWindowCapturer(options); 226 if (!capturer) { 227 return capturer; 228 } 229 230 capturer->SelectSource(aSourceId); 231 232 return std::make_unique<DesktopAndCursorComposer>(std::move(capturer), 233 options); 234 } 235 236 if (aDeviceType == CaptureDeviceType::Browser) { 237 // XXX We don't capture cursors, so avoid the extra indirection layer. We 238 // could also pass null for the pMouseCursorMonitor. 239 return CreateTabCapturer(options, aSourceId, ensureThread()); 240 } 241 242 return nullptr; 243 }; 244 245 std::unique_ptr<DesktopCapturer> capturer = createCapturer(); 246 if (!capturer) { 247 return capturer; 248 } 249 250 MOZ_ASSERT(capturer); 251 ensureThread(); 252 253 return capturer; 254 } 255 256 DesktopCaptureImpl::DesktopCaptureImpl(const int32_t aId, const char* aUniqueId, 257 const CaptureDeviceType aType) 258 : mModuleId(aId), 259 mTrackingId(mozilla::TrackingId(CaptureEngineToTrackingSourceStr([&] { 260 switch (aType) { 261 case CaptureDeviceType::Screen: 262 return CaptureEngine::ScreenEngine; 263 case CaptureDeviceType::Window: 264 return CaptureEngine::WinEngine; 265 case CaptureDeviceType::Browser: 266 return CaptureEngine::BrowserEngine; 267 default: 268 return CaptureEngine::InvalidEngine; 269 } 270 }()), 271 aId)), 272 mDeviceUniqueId(aUniqueId), 273 mDeviceType(aType), 274 mControlThread(mozilla::GetCurrentSerialEventTarget()), 275 mNextFrameMinimumTime(Timestamp::Zero()), 276 mCallbacks("DesktopCaptureImpl::mCallbacks"), 277 mBufferPool(false, 2) {} 278 279 DesktopCaptureImpl::~DesktopCaptureImpl() { 280 MOZ_ASSERT(!mCaptureThread); 281 MOZ_ASSERT(!mRequestedCapability); 282 } 283 284 void DesktopCaptureImpl::RegisterCaptureDataCallback( 285 webrtc::VideoSinkInterface<VideoFrame>* aDataCallback) { 286 auto callbacks = mCallbacks.Lock(); 287 callbacks->insert(aDataCallback); 288 } 289 290 void DesktopCaptureImpl::DeRegisterCaptureDataCallback( 291 webrtc::VideoSinkInterface<VideoFrame>* aDataCallback) { 292 auto callbacks = mCallbacks.Lock(); 293 auto it = callbacks->find(aDataCallback); 294 if (it != callbacks->end()) { 295 callbacks->erase(it); 296 } 297 } 298 299 int32_t DesktopCaptureImpl::StopCaptureIfAllClientsClose() { 300 { 301 auto callbacks = mCallbacks.Lock(); 302 if (!callbacks->empty()) { 303 return 0; 304 } 305 } 306 return StopCapture(); 307 } 308 309 int32_t DesktopCaptureImpl::SetCaptureRotation(VideoRotation aRotation) { 310 MOZ_ASSERT_UNREACHABLE("Unused"); 311 return -1; 312 } 313 314 bool DesktopCaptureImpl::SetApplyRotation(bool aEnable) { return true; } 315 316 int32_t DesktopCaptureImpl::StartCapture( 317 const VideoCaptureCapability& aCapability) { 318 RTC_DCHECK_RUN_ON(&mControlThreadChecker); 319 320 const int maxFps = std::max(aCapability.maxFPS, 1); 321 if (mRequestedCapability) { 322 MOZ_DIAGNOSTIC_ASSERT(mCaptureThread); 323 if (std::max(mRequestedCapability->maxFPS, 1) == maxFps) { 324 // No change in effective requested capability (only knob is fps). 325 return 0; 326 } 327 mRequestedCapability = mozilla::Some(aCapability); 328 MOZ_ALWAYS_SUCCEEDS(mCaptureThread->Dispatch( 329 NS_NewRunnableFunction("DesktopCaptureImpl::UpdateOnThread", 330 [this, self = RefPtr(this), maxFps]() mutable { 331 UpdateOnThread(maxFps); 332 }))); 333 return 0; 334 } 335 DesktopCapturer::SourceId sourceId{}; 336 auto [firstNoMatch, error] = std::from_chars( 337 mDeviceUniqueId.data(), mDeviceUniqueId.data() + mDeviceUniqueId.size(), 338 sourceId); 339 if (error != std::errc() || 340 firstNoMatch != (mDeviceUniqueId.data() + mDeviceUniqueId.size())) { 341 std::string errorMsg = 342 error == std::errc::invalid_argument 343 ? "Invalid value of mDeviceUniqueId." 344 : error == std::errc::result_out_of_range 345 ? "mDeviceUniqueIds value is out of range to cast." 346 : "An unknown error has occurred."; 347 MOZ_ASSERT_UNREACHABLE("Error casting mDeviceUniqueId to SourceId."); 348 RTC_LOG(LS_ERROR) 349 << "Attempting to cast mDeviceUniqueId to SourceId returned an error: " 350 << errorMsg; 351 return -1; 352 } 353 std::unique_ptr capturer = CreateDesktopCapturerAndThread( 354 mDeviceType, sourceId, getter_AddRefs(mCaptureThread)); 355 356 MOZ_ASSERT(!capturer == !mCaptureThread); 357 if (!capturer) { 358 return -1; 359 } 360 361 mRequestedCapability = mozilla::Some(aCapability); 362 mCaptureThreadChecker.Detach(); 363 364 MOZ_ALWAYS_SUCCEEDS(mCaptureThread->Dispatch(NS_NewRunnableFunction( 365 "DesktopCaptureImpl::InitOnThread", 366 [this, self = RefPtr(this), capturer = std::move(capturer), 367 maxFps]() mutable { InitOnThread(std::move(capturer), maxFps); }))); 368 369 return 0; 370 } 371 372 bool DesktopCaptureImpl::FocusOnSelectedSource() { 373 RTC_DCHECK_RUN_ON(&mControlThreadChecker); 374 if (!mCaptureThread) { 375 MOZ_ASSERT_UNREACHABLE( 376 "FocusOnSelectedSource must be called after StartCapture"); 377 return false; 378 } 379 380 bool success = false; 381 MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread( 382 mCaptureThread, NS_NewRunnableFunction(__func__, [&] { 383 RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); 384 MOZ_ASSERT(mCapturer); 385 success = mCapturer && mCapturer->FocusOnSelectedSource(); 386 }))); 387 return success; 388 } 389 390 int32_t DesktopCaptureImpl::StopCapture() { 391 RTC_DCHECK_RUN_ON(&mControlThreadChecker); 392 if (mRequestedCapability) { 393 // Sync-cancel the capture timer so no CaptureFrame calls will come in after 394 // we return. 395 MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread( 396 mCaptureThread, 397 NewRunnableMethod(__func__, this, 398 &DesktopCaptureImpl::ShutdownOnThread))); 399 400 mRequestedCapability = mozilla::Nothing(); 401 } 402 403 mBufferPool.Release(); 404 405 if (mCaptureThread) { 406 // CaptureThread shutdown. 407 mCaptureThread->AsyncShutdown(); 408 mCaptureThread = nullptr; 409 } 410 411 return 0; 412 } 413 414 bool DesktopCaptureImpl::CaptureStarted() { 415 MOZ_ASSERT_UNREACHABLE("Unused"); 416 return true; 417 } 418 419 int32_t DesktopCaptureImpl::CaptureSettings(VideoCaptureCapability& aSettings) { 420 MOZ_ASSERT_UNREACHABLE("Unused"); 421 return -1; 422 } 423 424 void DesktopCaptureImpl::OnCaptureResult(DesktopCapturer::Result aResult, 425 std::unique_ptr<DesktopFrame> aFrame) { 426 RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); 427 428 if (aResult == DesktopCapturer::Result::ERROR_PERMANENT) { 429 // This is non-recoverable error, therefore stop asking for frames 430 mCaptureTimer->Cancel(); 431 mCaptureTimer = nullptr; 432 mCaptureEndedEvent.Notify(); 433 return; 434 } 435 436 if (!aFrame) { 437 return; 438 } 439 440 const auto startProcessTime = Timestamp::Micros(webrtc::TimeMicros()); 441 auto frameTime = startProcessTime; 442 if (auto diff = startProcessTime - mNextFrameMinimumTime; 443 diff < TimeDelta::Zero()) { 444 if (diff > TimeDelta::Millis(-1)) { 445 // Two consecutive frames within a millisecond is OK. It could happen due 446 // to timing. 447 frameTime = mNextFrameMinimumTime; 448 } else { 449 // Three consecutive frames within two milliseconds seems too much, drop 450 // one. 451 MOZ_ASSERT(diff >= TimeDelta::Millis(-2)); 452 RTC_LOG(LS_WARNING) << "DesktopCapture render time is getting too far " 453 "ahead. Framerate is unexpectedly high."; 454 return; 455 } 456 } 457 458 uint8_t* videoFrame = aFrame->data(); 459 VideoCaptureCapability frameInfo; 460 frameInfo.width = aFrame->size().width(); 461 frameInfo.height = aFrame->size().height(); 462 frameInfo.videoType = VideoType::kARGB; 463 464 size_t videoFrameLength = 465 frameInfo.width * frameInfo.height * DesktopFrame::kBytesPerPixel; 466 467 const int32_t width = frameInfo.width; 468 const int32_t height = frameInfo.height; 469 470 // Not encoded, convert to I420. 471 if (frameInfo.videoType != VideoType::kMJPEG && 472 CalcBufferSize(frameInfo.videoType, width, abs(height)) != 473 videoFrameLength) { 474 RTC_LOG(LS_ERROR) << "Wrong incoming frame length."; 475 return; 476 } 477 478 // Setting absolute height (in case it was negative). 479 // In Windows, the image starts bottom left, instead of top left. 480 // Setting a negative source height, inverts the image (within LibYuv). 481 482 mozilla::PerformanceRecorder<mozilla::CopyVideoStage> rec( 483 "DesktopCaptureImpl::ConvertToI420"_ns, mTrackingId, width, abs(height)); 484 485 webrtc::scoped_refptr<webrtc::I420Buffer> buffer = 486 mBufferPool.CreateI420Buffer(width, abs(height)); 487 if (!buffer) { 488 RTC_LOG(LS_ERROR) << "Failed to allocate I420Buffer from pool."; 489 MOZ_ASSERT_UNREACHABLE( 490 "We might fail to allocate a buffer, but with this " 491 "being a recycling pool that shouldn't happen"); 492 return; 493 } 494 495 const int conversionResult = libyuv::ConvertToI420( 496 videoFrame, videoFrameLength, buffer->MutableDataY(), buffer->StrideY(), 497 buffer->MutableDataU(), buffer->StrideU(), buffer->MutableDataV(), 498 buffer->StrideV(), 0, 0, // No Cropping 499 aFrame->stride() / DesktopFrame::kBytesPerPixel, height, width, height, 500 libyuv::kRotate0, ConvertVideoType(frameInfo.videoType)); 501 if (conversionResult != 0) { 502 RTC_LOG(LS_ERROR) << "Failed to convert capture frame from type " 503 << static_cast<int>(frameInfo.videoType) << "to I420."; 504 return; 505 } 506 rec.Record(); 507 508 NotifyOnFrame(VideoFrame::Builder() 509 .set_video_frame_buffer(buffer) 510 .set_timestamp_us(frameTime.us()) 511 .build()); 512 513 const TimeDelta processTime = 514 Timestamp::Micros(webrtc::TimeMicros()) - startProcessTime; 515 516 if (processTime > TimeDelta::Millis(10)) { 517 RTC_LOG(LS_WARNING) 518 << "Too long processing time of incoming frame with dimensions " 519 << width << "x" << height << ": " << processTime.ms() << " ms"; 520 } 521 } 522 523 void DesktopCaptureImpl::NotifyOnFrame(const VideoFrame& aFrame) { 524 RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); 525 // Set the next frame's minimum time to ensure two consecutive frames don't 526 // have an identical render time (which is in milliseconds). 527 Timestamp nextFrameMinimumTime = 528 Timestamp::Millis(aFrame.render_time_ms()) + TimeDelta::Millis(1); 529 530 MOZ_ASSERT(nextFrameMinimumTime >= mNextFrameMinimumTime); 531 532 mNextFrameMinimumTime = nextFrameMinimumTime; 533 auto callbacks = mCallbacks.Lock(); 534 for (auto* cb : *callbacks) { 535 cb->OnFrame(aFrame); 536 } 537 } 538 539 void DesktopCaptureImpl::InitOnThread( 540 std::unique_ptr<DesktopCapturer> aCapturer, int aFramerate) { 541 RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); 542 543 mCapturer = std::move(aCapturer); 544 545 // We need to call Start on the same thread we call CaptureFrame on. 546 mCapturer->Start(this); 547 548 mCaptureTimer = NS_NewTimer(); 549 mRequestedCaptureInterval = mozilla::Some( 550 TimeDuration::FromSeconds(1. / static_cast<double>(aFramerate))); 551 552 CaptureFrameOnThread(); 553 } 554 555 void DesktopCaptureImpl::UpdateOnThread(int aFramerate) { 556 RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); 557 MOZ_DIAGNOSTIC_ASSERT(mCapturer); 558 MOZ_DIAGNOSTIC_ASSERT(mCaptureTimer); 559 560 mRequestedCaptureInterval = mozilla::Some( 561 TimeDuration::FromSeconds(1. / static_cast<double>(aFramerate))); 562 563 CaptureFrameOnThread(); 564 } 565 566 void DesktopCaptureImpl::ShutdownOnThread() { 567 RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); 568 if (mCaptureTimer) { 569 mCaptureTimer->Cancel(); 570 mCaptureTimer = nullptr; 571 } 572 573 // DesktopCapturer dtor blocks until fully shut down. TabCapturerWebrtc needs 574 // the capture thread to be alive. 575 mCapturer = nullptr; 576 577 mRequestedCaptureInterval = mozilla::Nothing(); 578 } 579 580 void DesktopCaptureImpl::CaptureFrameOnThread() { 581 RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); 582 583 auto start = mozilla::TimeStamp::Now(); 584 mCapturer->CaptureFrame(); 585 586 // Sync result callback may have canceled the timer in CaptureFrame because of 587 // a permanent error and there is no point to continue. 588 if (!mCaptureTimer) { 589 return; 590 } 591 592 auto end = mozilla::TimeStamp::Now(); 593 594 // Calculate next capture time. 595 const auto duration = end - start; 596 const auto timeUntilRequestedCapture = *mRequestedCaptureInterval - duration; 597 598 // Use at most x% CPU or limit framerate 599 constexpr float sleepTimeFactor = 600 (100.0f / kMaxDesktopCaptureCpuUsage) - 1.0f; 601 static_assert(sleepTimeFactor >= 0.0); 602 static_assert(sleepTimeFactor < 100.0); 603 const auto sleepTime = duration.MultDouble(sleepTimeFactor); 604 605 mCaptureTimer->InitHighResolutionWithNamedFuncCallback( 606 &::CaptureFrameOnThread, this, 607 std::max(timeUntilRequestedCapture, sleepTime), nsITimer::TYPE_ONE_SHOT, 608 "DesktopCaptureImpl::mCaptureTimer"_ns); 609 } 610 611 mozilla::MediaEventSource<void>* DesktopCaptureImpl::CaptureEndedEvent() { 612 return &mCaptureEndedEvent; 613 } 614 615 } // namespace webrtc