/* * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "video_engine/desktop_capture_impl.h" #include #include #include #include "CamerasTypes.h" #include "VideoEngine.h" #include "VideoUtils.h" #include "api/video/i420_buffer.h" #include "common_video/libyuv/include/webrtc_libyuv.h" #include "libyuv.h" // NOLINT #include "modules/include/module_common_types.h" #include "modules/video_capture/video_capture_config.h" #include "modules/video_capture/video_capture_impl.h" #include "system_wrappers/include/clock.h" #include "rtc_base/logging.h" #include "rtc_base/ref_counted_object.h" #include "rtc_base/time_utils.h" #include "rtc_base/trace_event.h" #include "modules/desktop_capture/desktop_and_cursor_composer.h" #include "modules/desktop_capture/desktop_frame.h" #include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capturer_differ_wrapper.h" #include "modules/video_capture/video_capture.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/SyncRunnable.h" #include "mozilla/TaskQueue.h" #include "mozilla/TimeStamp.h" #include "nsThreadUtils.h" #include "tab_capturer.h" using mozilla::NewRunnableMethod; using mozilla::TabCapturerWebrtc; using mozilla::TimeDuration; using mozilla::camera::CaptureDeviceType; using mozilla::camera::CaptureEngine; static void CaptureFrameOnThread(nsITimer* aTimer, void* aClosure) { static_cast(aClosure)->CaptureFrameOnThread(); } namespace webrtc { int32_t ScreenDeviceInfoImpl::Init() { mDesktopDeviceInfo = std::unique_ptr(DesktopDeviceInfo::Create()); return 0; } int32_t ScreenDeviceInfoImpl::Refresh() { mDesktopDeviceInfo->Refresh(); return 0; } uint32_t ScreenDeviceInfoImpl::NumberOfDevices() { return mDesktopDeviceInfo->getDisplayDeviceCount(); } int32_t ScreenDeviceInfoImpl::GetDeviceName( uint32_t aDeviceNumber, char* aDeviceNameUTF8, uint32_t aDeviceNameUTF8Size, char* aDeviceUniqueIdUTF8, uint32_t aDeviceUniqueIdUTF8Size, char* aProductUniqueIdUTF8, uint32_t aProductUniqueIdUTF8Size, pid_t* aPid) { DesktopDisplayDevice desktopDisplayDevice; // always initialize output if (aDeviceNameUTF8 && aDeviceNameUTF8Size > 0) { memset(aDeviceNameUTF8, 0, aDeviceNameUTF8Size); } if (aDeviceUniqueIdUTF8 && aDeviceUniqueIdUTF8Size > 0) { memset(aDeviceUniqueIdUTF8, 0, aDeviceUniqueIdUTF8Size); } if (aProductUniqueIdUTF8 && aProductUniqueIdUTF8Size > 0) { memset(aProductUniqueIdUTF8, 0, aProductUniqueIdUTF8Size); } if (mDesktopDeviceInfo->getDesktopDisplayDeviceInfo( aDeviceNumber, desktopDisplayDevice) == 0) { size_t len; const char* deviceName = desktopDisplayDevice.getDeviceName(); len = deviceName ? strlen(deviceName) : 0; if (len && aDeviceNameUTF8 && len < aDeviceNameUTF8Size) { memcpy(aDeviceNameUTF8, deviceName, len); } const char* deviceUniqueId = desktopDisplayDevice.getUniqueIdName(); len = deviceUniqueId ? strlen(deviceUniqueId) : 0; if (len && aDeviceUniqueIdUTF8 && len < aDeviceUniqueIdUTF8Size) { memcpy(aDeviceUniqueIdUTF8, deviceUniqueId, len); } } return 0; } int32_t ScreenDeviceInfoImpl::DisplayCaptureSettingsDialogBox( const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8, void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY) { // no device properties to change return 0; } int32_t ScreenDeviceInfoImpl::NumberOfCapabilities( const char* aDeviceUniqueIdUTF8) { return 0; } int32_t ScreenDeviceInfoImpl::GetCapability( const char* aDeviceUniqueIdUTF8, uint32_t aDeviceCapabilityNumber, VideoCaptureCapability& aCapability) { return 0; } int32_t ScreenDeviceInfoImpl::GetBestMatchedCapability( const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested, VideoCaptureCapability& aResulting) { return 0; } int32_t ScreenDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8, VideoRotation& aOrientation) { return 0; } VideoCaptureModule* DesktopCaptureImpl::Create(const int32_t aModuleId, const char* aUniqueId, const CaptureDeviceType aType) { return new rtc::RefCountedObject(aModuleId, aUniqueId, aType); } int32_t WindowDeviceInfoImpl::Init() { mDesktopDeviceInfo = std::unique_ptr(DesktopDeviceInfo::Create()); return 0; } int32_t WindowDeviceInfoImpl::Refresh() { mDesktopDeviceInfo->Refresh(); return 0; } uint32_t WindowDeviceInfoImpl::NumberOfDevices() { return mDesktopDeviceInfo->getWindowCount(); } int32_t WindowDeviceInfoImpl::GetDeviceName( uint32_t aDeviceNumber, char* aDeviceNameUTF8, uint32_t aDeviceNameUTF8Size, char* aDeviceUniqueIdUTF8, uint32_t aDeviceUniqueIdUTF8Size, char* aProductUniqueIdUTF8, uint32_t aProductUniqueIdUTF8Size, pid_t* aPid) { DesktopDisplayDevice desktopDisplayDevice; // always initialize output if (aDeviceNameUTF8 && aDeviceNameUTF8Size > 0) { memset(aDeviceNameUTF8, 0, aDeviceNameUTF8Size); } if (aDeviceUniqueIdUTF8 && aDeviceUniqueIdUTF8Size > 0) { memset(aDeviceUniqueIdUTF8, 0, aDeviceUniqueIdUTF8Size); } if (aProductUniqueIdUTF8 && aProductUniqueIdUTF8Size > 0) { memset(aProductUniqueIdUTF8, 0, aProductUniqueIdUTF8Size); } if (mDesktopDeviceInfo->getWindowInfo(aDeviceNumber, desktopDisplayDevice) == 0) { size_t len; const char* deviceName = desktopDisplayDevice.getDeviceName(); len = deviceName ? strlen(deviceName) : 0; if (len && aDeviceNameUTF8 && len < aDeviceNameUTF8Size) { memcpy(aDeviceNameUTF8, deviceName, len); } const char* deviceUniqueId = desktopDisplayDevice.getUniqueIdName(); len = deviceUniqueId ? strlen(deviceUniqueId) : 0; if (len && aDeviceUniqueIdUTF8 && len < aDeviceUniqueIdUTF8Size) { memcpy(aDeviceUniqueIdUTF8, deviceUniqueId, len); } if (aPid) { *aPid = desktopDisplayDevice.getPid(); } } return 0; } int32_t WindowDeviceInfoImpl::DisplayCaptureSettingsDialogBox( const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8, void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY) { // no device properties to change return 0; } int32_t WindowDeviceInfoImpl::NumberOfCapabilities( const char* aDeviceUniqueIdUTF8) { return 0; } int32_t WindowDeviceInfoImpl::GetCapability( const char* aDeviceUniqueIdUTF8, uint32_t aDeviceCapabilityNumber, VideoCaptureCapability& aCapability) { return 0; } int32_t WindowDeviceInfoImpl::GetBestMatchedCapability( const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested, VideoCaptureCapability& aResulting) { return 0; } int32_t WindowDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8, VideoRotation& aOrientation) { return 0; } int32_t BrowserDeviceInfoImpl::Init() { mDesktopDeviceInfo = std::unique_ptr(DesktopDeviceInfo::Create()); return 0; } int32_t BrowserDeviceInfoImpl::Refresh() { mDesktopDeviceInfo->Refresh(); return 0; } uint32_t BrowserDeviceInfoImpl::NumberOfDevices() { return mDesktopDeviceInfo->getTabCount(); } int32_t BrowserDeviceInfoImpl::GetDeviceName( uint32_t aDeviceNumber, char* aDeviceNameUTF8, uint32_t aDeviceNameUTF8Size, char* aDeviceUniqueIdUTF8, uint32_t aDeviceUniqueIdUTF8Size, char* aProductUniqueIdUTF8, uint32_t aProductUniqueIdUTF8Size, pid_t* aPid) { DesktopTab desktopTab; // always initialize output if (aDeviceNameUTF8 && aDeviceNameUTF8Size > 0) { memset(aDeviceNameUTF8, 0, aDeviceNameUTF8Size); } if (aDeviceUniqueIdUTF8 && aDeviceUniqueIdUTF8Size > 0) { memset(aDeviceUniqueIdUTF8, 0, aDeviceUniqueIdUTF8Size); } if (aProductUniqueIdUTF8 && aProductUniqueIdUTF8Size > 0) { memset(aProductUniqueIdUTF8, 0, aProductUniqueIdUTF8Size); } if (mDesktopDeviceInfo->getTabInfo(aDeviceNumber, desktopTab) == 0) { size_t len; const char* deviceName = desktopTab.getTabName(); len = deviceName ? strlen(deviceName) : 0; if (len && aDeviceNameUTF8 && len < aDeviceNameUTF8Size) { memcpy(aDeviceNameUTF8, deviceName, len); } const char* deviceUniqueId = desktopTab.getUniqueIdName(); len = deviceUniqueId ? strlen(deviceUniqueId) : 0; if (len && aDeviceUniqueIdUTF8 && len < aDeviceUniqueIdUTF8Size) { memcpy(aDeviceUniqueIdUTF8, deviceUniqueId, len); } } return 0; } int32_t BrowserDeviceInfoImpl::DisplayCaptureSettingsDialogBox( const char* aDeviceUniqueIdUTF8, const char* aDialogTitleUTF8, void* aParentWindow, uint32_t aPositionX, uint32_t aPositionY) { // no device properties to change return 0; } int32_t BrowserDeviceInfoImpl::NumberOfCapabilities( const char* aDeviceUniqueIdUTF8) { return 0; } int32_t BrowserDeviceInfoImpl::GetCapability( const char* aDeviceUniqueIdUTF8, uint32_t aDeviceCapabilityNumber, VideoCaptureCapability& aCapability) { return 0; } int32_t BrowserDeviceInfoImpl::GetBestMatchedCapability( const char* aDeviceUniqueIdUTF8, const VideoCaptureCapability& aRequested, VideoCaptureCapability& aResulting) { return 0; } int32_t BrowserDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8, VideoRotation& aOrientation) { return 0; } std::shared_ptr DesktopCaptureImpl::CreateDeviceInfo(const int32_t aId, const CaptureDeviceType aType) { if (aType == CaptureDeviceType::Screen) { auto screenInfo = std::make_shared(aId); if (!screenInfo || screenInfo->Init() != 0) { return nullptr; } return screenInfo; } if (aType == CaptureDeviceType::Window) { auto windowInfo = std::make_shared(aId); if (!windowInfo || windowInfo->Init() != 0) { return nullptr; } return windowInfo; } if (aType == CaptureDeviceType::Browser) { auto browserInfo = std::make_shared(aId); if (!browserInfo || browserInfo->Init() != 0) { return nullptr; } return browserInfo; } return nullptr; } const char* DesktopCaptureImpl::CurrentDeviceName() const { return mDeviceUniqueId.c_str(); } static DesktopCaptureOptions CreateDesktopCaptureOptions() { DesktopCaptureOptions options; // Help avoid an X11 deadlock, see bug 1456101. #ifdef MOZ_X11 MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread( mozilla::GetMainThreadSerialEventTarget(), NS_NewRunnableFunction(__func__, [&] { options = DesktopCaptureOptions::CreateDefault(); }))); #else options = DesktopCaptureOptions::CreateDefault(); #endif // Leave desktop effects enabled during WebRTC captures. options.set_disable_effects(false); #if defined(WEBRTC_WIN) if (mozilla::StaticPrefs::media_webrtc_capture_allow_directx()) { options.set_allow_directx_capturer(true); options.set_allow_use_magnification_api(false); } else { options.set_allow_use_magnification_api(true); } options.set_allow_cropping_window_capturer(true); # if defined(RTC_ENABLE_WIN_WGC) if (mozilla::StaticPrefs::media_webrtc_capture_allow_wgc()) { options.set_allow_wgc_capturer(true); } # endif #endif #if defined(WEBRTC_MAC) if (mozilla::StaticPrefs::media_webrtc_capture_allow_iosurface()) { options.set_allow_iosurface(true); } #endif #if defined(WEBRTC_USE_PIPEWIRE) if (mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire()) { options.set_allow_pipewire(true); } #endif return options; } static std::unique_ptr CreateTabCapturer( const DesktopCaptureOptions& options, DesktopCapturer::SourceId aSourceId, nsCOMPtr aCaptureThread) { std::unique_ptr capturer = TabCapturerWebrtc::Create(aSourceId, std::move(aCaptureThread)); if (capturer && options.detect_updated_region()) { capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer))); } return capturer; } static bool UsePipewire() { #if defined(WEBRTC_USE_PIPEWIRE) return mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire() && webrtc::DesktopCapturer::IsRunningUnderWayland(); #else return false; #endif } static std::unique_ptr CreateDesktopCapturerAndThread( CaptureDeviceType aDeviceType, DesktopCapturer::SourceId aSourceId, nsIThread** aOutThread) { DesktopCaptureOptions options = CreateDesktopCaptureOptions(); std::unique_ptr capturer; auto ensureThread = [&]() { if (*aOutThread) { return *aOutThread; } nsIThreadManager::ThreadCreationOptions threadOptions; #if defined(XP_WIN) || defined(XP_MACOSX) // Windows desktop capture needs a UI thread. // Mac screen capture needs a thread with a CFRunLoop. threadOptions.isUiThread = true; #endif NS_NewNamedThread("DesktopCapture", aOutThread, nullptr, threadOptions); return *aOutThread; }; if ((aDeviceType == CaptureDeviceType::Screen || aDeviceType == CaptureDeviceType::Window) && UsePipewire()) { capturer = DesktopCapturer::CreateGenericCapturer(options); if (!capturer) { return capturer; } capturer = std::make_unique(std::move(capturer), options); } else if (aDeviceType == CaptureDeviceType::Screen) { capturer = DesktopCapturer::CreateScreenCapturer(options); if (!capturer) { return capturer; } capturer->SelectSource(aSourceId); capturer = std::make_unique(std::move(capturer), options); } else if (aDeviceType == CaptureDeviceType::Window) { #if defined(RTC_ENABLE_WIN_WGC) options.set_allow_wgc_capturer_fallback(true); #endif capturer = DesktopCapturer::CreateWindowCapturer(options); if (!capturer) { return capturer; } capturer->SelectSource(aSourceId); capturer = std::make_unique(std::move(capturer), options); } else if (aDeviceType == CaptureDeviceType::Browser) { // XXX We don't capture cursors, so avoid the extra indirection layer. We // could also pass null for the pMouseCursorMonitor. capturer = CreateTabCapturer(options, aSourceId, ensureThread()); } else { MOZ_ASSERT(!capturer); return capturer; } MOZ_ASSERT(capturer); ensureThread(); return capturer; } DesktopCaptureImpl::DesktopCaptureImpl(const int32_t aId, const char* aUniqueId, const CaptureDeviceType aType) : mModuleId(aId), mTrackingId(mozilla::TrackingId(CaptureEngineToTrackingSourceStr([&] { switch (aType) { case CaptureDeviceType::Screen: return CaptureEngine::ScreenEngine; case CaptureDeviceType::Window: return CaptureEngine::WinEngine; case CaptureDeviceType::Browser: return CaptureEngine::BrowserEngine; default: return CaptureEngine::InvalidEngine; } }()), aId)), mDeviceUniqueId(aUniqueId), mDeviceType(aType), mControlThread(mozilla::GetCurrentSerialEventTarget()), mNextFrameMinimumTime(Timestamp::Zero()), mCallbacks("DesktopCaptureImpl::mCallbacks") {} DesktopCaptureImpl::~DesktopCaptureImpl() { MOZ_ASSERT(!mCaptureThread); MOZ_ASSERT(!mRequestedCapability); } void DesktopCaptureImpl::RegisterCaptureDataCallback( rtc::VideoSinkInterface* aDataCallback) { auto callbacks = mCallbacks.Lock(); callbacks->insert(aDataCallback); } void DesktopCaptureImpl::DeRegisterCaptureDataCallback( rtc::VideoSinkInterface* aDataCallback) { auto callbacks = mCallbacks.Lock(); auto it = callbacks->find(aDataCallback); if (it != callbacks->end()) { callbacks->erase(it); } } int32_t DesktopCaptureImpl::StopCaptureIfAllClientsClose() { { auto callbacks = mCallbacks.Lock(); if (!callbacks->empty()) { return 0; } } return StopCapture(); } int32_t DesktopCaptureImpl::SetCaptureRotation(VideoRotation aRotation) { MOZ_ASSERT_UNREACHABLE("Unused"); return -1; } bool DesktopCaptureImpl::SetApplyRotation(bool aEnable) { return true; } int32_t DesktopCaptureImpl::StartCapture( const VideoCaptureCapability& aCapability) { RTC_DCHECK_RUN_ON(&mControlThreadChecker); if (mRequestedCapability) { // Already initialized MOZ_ASSERT(*mRequestedCapability == aCapability); return 0; } MOZ_ASSERT(!mCaptureThread); DesktopCapturer::SourceId sourceId = std::stoi(mDeviceUniqueId); std::unique_ptr capturer = CreateDesktopCapturerAndThread( mDeviceType, sourceId, getter_AddRefs(mCaptureThread)); MOZ_ASSERT(!capturer == !mCaptureThread); if (!capturer) { return -1; } mRequestedCapability = mozilla::Some(aCapability); mCaptureThreadChecker.Detach(); MOZ_ALWAYS_SUCCEEDS(mCaptureThread->Dispatch(NS_NewRunnableFunction( "DesktopCaptureImpl::InitOnThread", [this, self = RefPtr(this), capturer = std::move(capturer), maxFps = std::max(aCapability.maxFPS, 1)]() mutable { InitOnThread(std::move(capturer), maxFps); }))); return 0; } bool DesktopCaptureImpl::FocusOnSelectedSource() { RTC_DCHECK_RUN_ON(&mControlThreadChecker); if (!mCaptureThread) { MOZ_ASSERT_UNREACHABLE( "FocusOnSelectedSource must be called after StartCapture"); return false; } bool success = false; MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread( mCaptureThread, NS_NewRunnableFunction(__func__, [&] { RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); MOZ_ASSERT(mCapturer); success = mCapturer && mCapturer->FocusOnSelectedSource(); }))); return success; } int32_t DesktopCaptureImpl::StopCapture() { RTC_DCHECK_RUN_ON(&mControlThreadChecker); if (mRequestedCapability) { // Sync-cancel the capture timer so no CaptureFrame calls will come in after // we return. MOZ_ALWAYS_SUCCEEDS(mozilla::SyncRunnable::DispatchToThread( mCaptureThread, NewRunnableMethod(__func__, this, &DesktopCaptureImpl::ShutdownOnThread))); mRequestedCapability = mozilla::Nothing(); } if (mCaptureThread) { // CaptureThread shutdown. mCaptureThread->AsyncShutdown(); mCaptureThread = nullptr; } return 0; } bool DesktopCaptureImpl::CaptureStarted() { MOZ_ASSERT_UNREACHABLE("Unused"); return true; } int32_t DesktopCaptureImpl::CaptureSettings(VideoCaptureCapability& aSettings) { MOZ_ASSERT_UNREACHABLE("Unused"); return -1; } void DesktopCaptureImpl::OnCaptureResult(DesktopCapturer::Result aResult, std::unique_ptr aFrame) { RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); if (!aFrame) { return; } const auto startProcessTime = Timestamp::Micros(rtc::TimeMicros()); auto frameTime = startProcessTime; if (auto diff = startProcessTime - mNextFrameMinimumTime; diff < TimeDelta::Zero()) { if (diff > TimeDelta::Millis(-1)) { // Two consecutive frames within a millisecond is OK. It could happen due // to timing. frameTime = mNextFrameMinimumTime; } else { // Three consecutive frames within two milliseconds seems too much, drop // one. MOZ_ASSERT(diff >= TimeDelta::Millis(-2)); RTC_LOG(LS_WARNING) << "DesktopCapture render time is getting too far " "ahead. Framerate is unexpectedly high."; return; } } uint8_t* videoFrame = aFrame->data(); VideoCaptureCapability frameInfo; frameInfo.width = aFrame->size().width(); frameInfo.height = aFrame->size().height(); frameInfo.videoType = VideoType::kARGB; size_t videoFrameLength = frameInfo.width * frameInfo.height * DesktopFrame::kBytesPerPixel; const int32_t width = frameInfo.width; const int32_t height = frameInfo.height; // Not encoded, convert to I420. if (frameInfo.videoType != VideoType::kMJPEG && CalcBufferSize(frameInfo.videoType, width, abs(height)) != videoFrameLength) { RTC_LOG(LS_ERROR) << "Wrong incoming frame length."; return; } int stride_y = width; int stride_uv = (width + 1) / 2; // Setting absolute height (in case it was negative). // In Windows, the image starts bottom left, instead of top left. // Setting a negative source height, inverts the image (within LibYuv). mozilla::PerformanceRecorder rec( "DesktopCaptureImpl::ConvertToI420"_ns, mTrackingId, width, abs(height)); // TODO(nisse): Use a pool? rtc::scoped_refptr buffer = I420Buffer::Create(width, abs(height), stride_y, stride_uv, stride_uv); const int conversionResult = libyuv::ConvertToI420( videoFrame, videoFrameLength, buffer->MutableDataY(), buffer->StrideY(), buffer->MutableDataU(), buffer->StrideU(), buffer->MutableDataV(), buffer->StrideV(), 0, 0, // No Cropping aFrame->stride() / DesktopFrame::kBytesPerPixel, height, width, height, libyuv::kRotate0, ConvertVideoType(frameInfo.videoType)); if (conversionResult != 0) { RTC_LOG(LS_ERROR) << "Failed to convert capture frame from type " << static_cast(frameInfo.videoType) << "to I420."; return; } rec.Record(); NotifyOnFrame(VideoFrame::Builder() .set_video_frame_buffer(buffer) .set_timestamp_us(frameTime.us()) .build()); const TimeDelta processTime = Timestamp::Micros(rtc::TimeMicros()) - startProcessTime; if (processTime > TimeDelta::Millis(10)) { RTC_LOG(LS_WARNING) << "Too long processing time of incoming frame with dimensions " << width << "x" << height << ": " << processTime.ms() << " ms"; } } void DesktopCaptureImpl::NotifyOnFrame(const VideoFrame& aFrame) { RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); MOZ_ASSERT(Timestamp::Millis(aFrame.render_time_ms()) > mNextFrameMinimumTime); // Set the next frame's minimum time to ensure two consecutive frames don't // have an identical render time (which is in milliseconds). mNextFrameMinimumTime = Timestamp::Millis(aFrame.render_time_ms()) + TimeDelta::Millis(1); auto callbacks = mCallbacks.Lock(); for (auto* cb : *callbacks) { cb->OnFrame(aFrame); } } void DesktopCaptureImpl::InitOnThread( std::unique_ptr aCapturer, int aFramerate) { RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); mCapturer = std::move(aCapturer); // We need to call Start on the same thread we call CaptureFrame on. mCapturer->Start(this); mCaptureTimer = NS_NewTimer(); mRequestedCaptureInterval = mozilla::Some( TimeDuration::FromSeconds(1. / static_cast(aFramerate))); CaptureFrameOnThread(); } void DesktopCaptureImpl::ShutdownOnThread() { RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); if (mCaptureTimer) { mCaptureTimer->Cancel(); mCaptureTimer = nullptr; } // DesktopCapturer dtor blocks until fully shut down. TabCapturerWebrtc needs // the capture thread to be alive. mCapturer = nullptr; mRequestedCaptureInterval = mozilla::Nothing(); } void DesktopCaptureImpl::CaptureFrameOnThread() { RTC_DCHECK_RUN_ON(&mCaptureThreadChecker); auto start = mozilla::TimeStamp::Now(); mCapturer->CaptureFrame(); auto end = mozilla::TimeStamp::Now(); // Calculate next capture time. const auto duration = end - start; const auto timeUntilRequestedCapture = *mRequestedCaptureInterval - duration; // Use at most x% CPU or limit framerate constexpr float sleepTimeFactor = (100.0f / kMaxDesktopCaptureCpuUsage) - 1.0f; static_assert(sleepTimeFactor >= 0.0); static_assert(sleepTimeFactor < 100.0); const auto sleepTime = duration.MultDouble(sleepTimeFactor); mCaptureTimer->InitHighResolutionWithNamedFuncCallback( &::CaptureFrameOnThread, this, std::max(timeUntilRequestedCapture, sleepTime), nsITimer::TYPE_ONE_SHOT, "DesktopCaptureImpl::mCaptureTimer"); } } // namespace webrtc