From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../video_engine/desktop_capture_impl.cc | 776 +++++++++++++++++++++ 1 file changed, 776 insertions(+) create mode 100644 dom/media/systemservices/video_engine/desktop_capture_impl.cc (limited to 'dom/media/systemservices/video_engine/desktop_capture_impl.cc') diff --git a/dom/media/systemservices/video_engine/desktop_capture_impl.cc b/dom/media/systemservices/video_engine/desktop_capture_impl.cc new file mode 100644 index 0000000000..2274a21e8a --- /dev/null +++ b/dom/media/systemservices/video_engine/desktop_capture_impl.cc @@ -0,0 +1,776 @@ +/* + * 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 -- cgit v1.2.3