diff options
Diffstat (limited to 'dom/media/systemservices/video_engine/desktop_capture_impl.cc')
-rw-r--r-- | dom/media/systemservices/video_engine/desktop_capture_impl.cc | 760 |
1 files changed, 760 insertions, 0 deletions
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..fd88433cfa --- /dev/null +++ b/dom/media/systemservices/video_engine/desktop_capture_impl.cc @@ -0,0 +1,760 @@ +/* + * 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 "modules/video_capture/video_capture_impl.h" + +#include <stdlib.h> +#include <memory> +#include <string> + +#include "CamerasTypes.h" +#include "PerformanceRecorder.h" + +#include "api/video/i420_buffer.h" +#include "base/scoped_nsautorelease_pool.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 "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 "video_engine/desktop_capture_impl.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/video_capture/video_capture.h" +#include "mozilla/Maybe.h" +#include "mozilla/StaticPrefs_media.h" + +#include "PerformanceRecorder.h" + +#if defined(_WIN32) +# include "platform_uithread.h" +#else +# include "rtc_base/platform_thread.h" +#endif + +namespace webrtc { + +ScreenDeviceInfoImpl::ScreenDeviceInfoImpl(const int32_t id) : _id(id) {} + +ScreenDeviceInfoImpl::~ScreenDeviceInfoImpl(void) {} + +int32_t ScreenDeviceInfoImpl::Init() { + desktop_device_info_ = + std::unique_ptr<DesktopDeviceInfo>(DesktopDeviceInfoImpl::Create()); + return 0; +} + +int32_t ScreenDeviceInfoImpl::Refresh() { + desktop_device_info_->Refresh(); + return 0; +} + +uint32_t ScreenDeviceInfoImpl::NumberOfDevices() { + return desktop_device_info_->getDisplayDeviceCount(); +} + +int32_t ScreenDeviceInfoImpl::GetDeviceName( + uint32_t deviceNumber, char* deviceNameUTF8, uint32_t deviceNameUTF8Size, + char* deviceUniqueIdUTF8, uint32_t deviceUniqueIdUTF8Size, + char* productUniqueIdUTF8, uint32_t productUniqueIdUTF8Size, pid_t* pid) { + DesktopDisplayDevice desktopDisplayDevice; + + // always initialize output + if (deviceNameUTF8 && deviceNameUTF8Size > 0) { + memset(deviceNameUTF8, 0, deviceNameUTF8Size); + } + + if (deviceUniqueIdUTF8 && deviceUniqueIdUTF8Size > 0) { + memset(deviceUniqueIdUTF8, 0, deviceUniqueIdUTF8Size); + } + if (productUniqueIdUTF8 && productUniqueIdUTF8Size > 0) { + memset(productUniqueIdUTF8, 0, productUniqueIdUTF8Size); + } + + if (desktop_device_info_->getDesktopDisplayDeviceInfo( + deviceNumber, desktopDisplayDevice) == 0) { + size_t len; + + const char* deviceName = desktopDisplayDevice.getDeviceName(); + len = deviceName ? strlen(deviceName) : 0; + if (len && deviceNameUTF8 && len < deviceNameUTF8Size) { + memcpy(deviceNameUTF8, deviceName, len); + } + + const char* deviceUniqueId = desktopDisplayDevice.getUniqueIdName(); + len = deviceUniqueId ? strlen(deviceUniqueId) : 0; + if (len && deviceUniqueIdUTF8 && len < deviceUniqueIdUTF8Size) { + memcpy(deviceUniqueIdUTF8, deviceUniqueId, len); + } + } + + return 0; +} + +int32_t ScreenDeviceInfoImpl::DisplayCaptureSettingsDialogBox( + const char* deviceUniqueIdUTF8, const char* dialogTitleUTF8, + void* parentWindow, uint32_t positionX, uint32_t positionY) { + // no device properties to change + return 0; +} + +int32_t ScreenDeviceInfoImpl::NumberOfCapabilities( + const char* deviceUniqueIdUTF8) { + return 0; +} + +int32_t ScreenDeviceInfoImpl::GetCapability( + const char* deviceUniqueIdUTF8, const uint32_t deviceCapabilityNumber, + VideoCaptureCapability& capability) { + return 0; +} + +int32_t ScreenDeviceInfoImpl::GetBestMatchedCapability( + const char* deviceUniqueIdUTF8, const VideoCaptureCapability& requested, + VideoCaptureCapability& resulting) { + return 0; +} + +int32_t ScreenDeviceInfoImpl::GetOrientation(const char* deviceUniqueIdUTF8, + VideoRotation& orientation) { + return 0; +} + +VideoCaptureModule* DesktopCaptureImpl::Create(const int32_t id, + const char* uniqueId, + const CaptureDeviceType type) { + return new rtc::RefCountedObject<DesktopCaptureImpl>(id, uniqueId, type); +} + +int32_t WindowDeviceInfoImpl::Init() { + desktop_device_info_ = + std::unique_ptr<DesktopDeviceInfo>(DesktopDeviceInfoImpl::Create()); + return 0; +} + +int32_t WindowDeviceInfoImpl::Refresh() { + desktop_device_info_->Refresh(); + return 0; +} + +uint32_t WindowDeviceInfoImpl::NumberOfDevices() { + return desktop_device_info_->getWindowCount(); +} + +int32_t WindowDeviceInfoImpl::GetDeviceName( + uint32_t deviceNumber, char* deviceNameUTF8, uint32_t deviceNameUTF8Size, + char* deviceUniqueIdUTF8, uint32_t deviceUniqueIdUTF8Size, + char* productUniqueIdUTF8, uint32_t productUniqueIdUTF8Size, pid_t* pid) { + DesktopDisplayDevice desktopDisplayDevice; + + // always initialize output + if (deviceNameUTF8 && deviceNameUTF8Size > 0) { + memset(deviceNameUTF8, 0, deviceNameUTF8Size); + } + if (deviceUniqueIdUTF8 && deviceUniqueIdUTF8Size > 0) { + memset(deviceUniqueIdUTF8, 0, deviceUniqueIdUTF8Size); + } + if (productUniqueIdUTF8 && productUniqueIdUTF8Size > 0) { + memset(productUniqueIdUTF8, 0, productUniqueIdUTF8Size); + } + + if (desktop_device_info_->getWindowInfo(deviceNumber, desktopDisplayDevice) == + 0) { + size_t len; + + const char* deviceName = desktopDisplayDevice.getDeviceName(); + len = deviceName ? strlen(deviceName) : 0; + if (len && deviceNameUTF8 && len < deviceNameUTF8Size) { + memcpy(deviceNameUTF8, deviceName, len); + } + + const char* deviceUniqueId = desktopDisplayDevice.getUniqueIdName(); + len = deviceUniqueId ? strlen(deviceUniqueId) : 0; + if (len && deviceUniqueIdUTF8 && len < deviceUniqueIdUTF8Size) { + memcpy(deviceUniqueIdUTF8, deviceUniqueId, len); + } + if (pid) { + *pid = desktopDisplayDevice.getPid(); + } + } + + return 0; +} + +int32_t WindowDeviceInfoImpl::DisplayCaptureSettingsDialogBox( + const char* deviceUniqueIdUTF8, const char* dialogTitleUTF8, + void* parentWindow, uint32_t positionX, uint32_t positionY) { + // no device properties to change + return 0; +} + +int32_t WindowDeviceInfoImpl::NumberOfCapabilities( + const char* deviceUniqueIdUTF8) { + return 0; +} + +int32_t WindowDeviceInfoImpl::GetCapability( + const char* deviceUniqueIdUTF8, const uint32_t deviceCapabilityNumber, + VideoCaptureCapability& capability) { + return 0; +} + +int32_t WindowDeviceInfoImpl::GetBestMatchedCapability( + const char* deviceUniqueIdUTF8, const VideoCaptureCapability& requested, + VideoCaptureCapability& resulting) { + return 0; +} + +int32_t WindowDeviceInfoImpl::GetOrientation(const char* deviceUniqueIdUTF8, + VideoRotation& orientation) { + return 0; +} + +int32_t BrowserDeviceInfoImpl::Init() { + desktop_device_info_ = + std::unique_ptr<DesktopDeviceInfo>(DesktopDeviceInfoImpl::Create()); + return 0; +} + +int32_t BrowserDeviceInfoImpl::Refresh() { + desktop_device_info_->Refresh(); + return 0; +} + +uint32_t BrowserDeviceInfoImpl::NumberOfDevices() { + return desktop_device_info_->getTabCount(); +} + +int32_t BrowserDeviceInfoImpl::GetDeviceName( + uint32_t deviceNumber, char* deviceNameUTF8, uint32_t deviceNameUTF8Size, + char* deviceUniqueIdUTF8, uint32_t deviceUniqueIdUTF8Size, + char* productUniqueIdUTF8, uint32_t productUniqueIdUTF8Size, pid_t* pid) { + DesktopTab desktopTab; + + // always initialize output + if (deviceNameUTF8 && deviceNameUTF8Size > 0) { + memset(deviceNameUTF8, 0, deviceNameUTF8Size); + } + if (deviceUniqueIdUTF8 && deviceUniqueIdUTF8Size > 0) { + memset(deviceUniqueIdUTF8, 0, deviceUniqueIdUTF8Size); + } + if (productUniqueIdUTF8 && productUniqueIdUTF8Size > 0) { + memset(productUniqueIdUTF8, 0, productUniqueIdUTF8Size); + } + + if (desktop_device_info_->getTabInfo(deviceNumber, desktopTab) == 0) { + size_t len; + + const char* deviceName = desktopTab.getTabName(); + len = deviceName ? strlen(deviceName) : 0; + if (len && deviceNameUTF8 && len < deviceNameUTF8Size) { + memcpy(deviceNameUTF8, deviceName, len); + } + + const char* deviceUniqueId = desktopTab.getUniqueIdName(); + len = deviceUniqueId ? strlen(deviceUniqueId) : 0; + if (len && deviceUniqueIdUTF8 && len < deviceUniqueIdUTF8Size) { + memcpy(deviceUniqueIdUTF8, deviceUniqueId, len); + } + } + + return 0; +} + +int32_t BrowserDeviceInfoImpl::DisplayCaptureSettingsDialogBox( + const char* deviceUniqueIdUTF8, const char* dialogTitleUTF8, + void* parentWindow, uint32_t positionX, uint32_t positionY) { + // no device properties to change + return 0; +} + +int32_t BrowserDeviceInfoImpl::NumberOfCapabilities( + const char* deviceUniqueIdUTF8) { + return 0; +} + +int32_t BrowserDeviceInfoImpl::GetCapability( + const char* deviceUniqueIdUTF8, const uint32_t deviceCapabilityNumber, + VideoCaptureCapability& capability) { + return 0; +} + +int32_t BrowserDeviceInfoImpl::GetBestMatchedCapability( + const char* deviceUniqueIdUTF8, const VideoCaptureCapability& requested, + VideoCaptureCapability& resulting) { + return 0; +} + +int32_t BrowserDeviceInfoImpl::GetOrientation(const char* deviceUniqueIdUTF8, + VideoRotation& orientation) { + return 0; +} + +VideoCaptureModule::DeviceInfo* DesktopCaptureImpl::CreateDeviceInfo( + const int32_t id, const CaptureDeviceType type) { + if (type == CaptureDeviceType::Screen) { + ScreenDeviceInfoImpl* pScreenDeviceInfoImpl = new ScreenDeviceInfoImpl(id); + if (!pScreenDeviceInfoImpl || pScreenDeviceInfoImpl->Init()) { + delete pScreenDeviceInfoImpl; + pScreenDeviceInfoImpl = NULL; + } + return pScreenDeviceInfoImpl; + } else if (type == CaptureDeviceType::Window) { + WindowDeviceInfoImpl* pWindowDeviceInfoImpl = new WindowDeviceInfoImpl(id); + if (!pWindowDeviceInfoImpl || pWindowDeviceInfoImpl->Init()) { + delete pWindowDeviceInfoImpl; + pWindowDeviceInfoImpl = NULL; + } + return pWindowDeviceInfoImpl; + } else if (type == CaptureDeviceType::Browser) { + BrowserDeviceInfoImpl* pBrowserDeviceInfoImpl = + new BrowserDeviceInfoImpl(id); + if (!pBrowserDeviceInfoImpl || pBrowserDeviceInfoImpl->Init()) { + delete pBrowserDeviceInfoImpl; + pBrowserDeviceInfoImpl = NULL; + } + return pBrowserDeviceInfoImpl; + } + return NULL; +} + +const char* DesktopCaptureImpl::CurrentDeviceName() const { + return _deviceUniqueId.c_str(); +} + +static DesktopCaptureOptions CreateDesktopCaptureOptions() { + DesktopCaptureOptions options = DesktopCaptureOptions::CreateDefault(); + // 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; +} + +int32_t DesktopCaptureImpl::LazyInitDesktopCapturer() { + // Already initialized + if (desktop_capturer_cursor_composer_) { + return 0; + } + + DesktopCaptureOptions options = CreateDesktopCaptureOptions(); + + if (_deviceType == CaptureDeviceType::Screen) { + std::unique_ptr<DesktopCapturer> pScreenCapturer = + DesktopCapturer::CreateScreenCapturer(options); + if (!pScreenCapturer.get()) { + return -1; + } + + DesktopCapturer::SourceId sourceId = atoi(_deviceUniqueId.c_str()); + pScreenCapturer->SelectSource(sourceId); + + desktop_capturer_cursor_composer_ = + std::unique_ptr<DesktopAndCursorComposer>( + new DesktopAndCursorComposer(std::move(pScreenCapturer), options)); + } else if (_deviceType == CaptureDeviceType::Window) { +#if defined(RTC_ENABLE_WIN_WGC) + options.set_allow_wgc_capturer_fallback(true); +#endif + std::unique_ptr<DesktopCapturer> pWindowCapturer = + DesktopCapturer::CreateWindowCapturer(options); + if (!pWindowCapturer.get()) { + return -1; + } + + DesktopCapturer::SourceId sourceId = atoi(_deviceUniqueId.c_str()); + pWindowCapturer->SelectSource(sourceId); + + desktop_capturer_cursor_composer_ = + std::unique_ptr<DesktopAndCursorComposer>( + new DesktopAndCursorComposer(std::move(pWindowCapturer), options)); + } else if (_deviceType == CaptureDeviceType::Browser) { + // XXX We don't capture cursors, so avoid the extra indirection layer. We + // could also pass null for the pMouseCursorMonitor. + desktop_capturer_cursor_composer_ = + DesktopCapturer::CreateTabCapturer(options); + if (!desktop_capturer_cursor_composer_) { + return -1; + } + + DesktopCapturer::SourceId sourceId = atoi(_deviceUniqueId.c_str()); + desktop_capturer_cursor_composer_->SelectSource(sourceId); + } + return 0; +} + +DesktopCaptureImpl::DesktopCaptureImpl(const int32_t id, const char* uniqueId, + const CaptureDeviceType type) + : _id(id), + _tracking_id( + mozilla::TrackingId(CaptureEngineToTrackingSourceStr([&] { + switch (type) { + case CaptureDeviceType::Screen: + return CaptureEngine::ScreenEngine; + case CaptureDeviceType::Window: + return CaptureEngine::WinEngine; + case CaptureDeviceType::Browser: + return CaptureEngine::BrowserEngine; + default: + return CaptureEngine::InvalidEngine; + } + }()), + id)), + _deviceUniqueId(uniqueId), + _deviceType(type), + _requestedCapability(), + _rotateFrame(kVideoRotation_0), + last_capture_time_ms_(rtc::TimeMillis()), + time_event_(EventWrapper::Create()), + capturer_thread_(nullptr), + started_(false) { + _requestedCapability.width = kDefaultWidth; + _requestedCapability.height = kDefaultHeight; + _requestedCapability.maxFPS = 30; + _requestedCapability.videoType = VideoType::kI420; + _maxFPSNeeded = 1000 / _requestedCapability.maxFPS; + memset(_incomingFrameTimesNanos, 0, sizeof(_incomingFrameTimesNanos)); +} + +DesktopCaptureImpl::~DesktopCaptureImpl() { + time_event_->Set(); + if (capturer_thread_) { +#if defined(_WIN32) + capturer_thread_->Stop(); +#else + capturer_thread_->Finalize(); +#endif + } +} + +void DesktopCaptureImpl::RegisterCaptureDataCallback( + rtc::VideoSinkInterface<VideoFrame>* dataCallback) { + rtc::CritScope lock(&_apiCs); + _dataCallBacks.insert(dataCallback); +} + +void DesktopCaptureImpl::DeRegisterCaptureDataCallback( + rtc::VideoSinkInterface<VideoFrame>* dataCallback) { + rtc::CritScope lock(&_apiCs); + auto it = _dataCallBacks.find(dataCallback); + if (it != _dataCallBacks.end()) { + _dataCallBacks.erase(it); + } +} + +int32_t DesktopCaptureImpl::StopCaptureIfAllClientsClose() { + if (_dataCallBacks.empty()) { + return StopCapture(); + } else { + return 0; + } +} + +int32_t DesktopCaptureImpl::DeliverCapturedFrame( + webrtc::VideoFrame& captureFrame) { + UpdateFrameCount(); // frame count used for local frame rate callBack. + + // Set the capture time + captureFrame.set_timestamp_us(rtc::TimeMicros()); + + if (captureFrame.render_time_ms() == last_capture_time_ms_) { + // We don't allow the same capture time for two frames, drop this one. + return -1; + } + last_capture_time_ms_ = captureFrame.render_time_ms(); + + for (auto dataCallBack : _dataCallBacks) { + dataCallBack->OnFrame(captureFrame); + } + + return 0; +} + +// Originally copied from VideoCaptureImpl::IncomingFrame, but has diverged +// somewhat. See Bug 1038324 and bug 1738946. +int32_t DesktopCaptureImpl::IncomingFrame( + uint8_t* videoFrame, size_t videoFrameLength, size_t widthWithPadding, + const VideoCaptureCapability& frameInfo) { + int64_t startProcessTime = rtc::TimeNanos(); + rtc::CritScope cs(&_apiCs); + + 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 -1; + } + + 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<mozilla::CopyVideoStage> rec( + "DesktopCaptureImpl::ConvertToI420"_ns, _tracking_id, width, abs(height)); + // TODO(nisse): Use a pool? + rtc::scoped_refptr<I420Buffer> buffer = + I420Buffer::Create(width, abs(height), stride_y, stride_uv, stride_uv); + + const int conversionResult = libyuv::ConvertToI420( + videoFrame, videoFrameLength, buffer.get()->MutableDataY(), + buffer.get()->StrideY(), buffer.get()->MutableDataU(), + buffer.get()->StrideU(), buffer.get()->MutableDataV(), + buffer.get()->StrideV(), 0, 0, // No Cropping + static_cast<int>(widthWithPadding), height, width, height, + libyuv::kRotate0, ConvertVideoType(frameInfo.videoType)); + if (conversionResult != 0) { + RTC_LOG(LS_ERROR) << "Failed to convert capture frame from type " + << static_cast<int>(frameInfo.videoType) << "to I420."; + return -1; + } + rec.Record(); + + VideoFrame captureFrame(buffer, 0, rtc::TimeMillis(), kVideoRotation_0); + + DeliverCapturedFrame(captureFrame); + + const int64_t processTime = + (rtc::TimeNanos() - startProcessTime) / rtc::kNumNanosecsPerMillisec; + + if (processTime > 10) { + RTC_LOG(LS_WARNING) << "Too long processing time of incoming frame: " + << processTime << " ms"; + } + + return 0; +} + +int32_t DesktopCaptureImpl::SetCaptureRotation(VideoRotation rotation) { + rtc::CritScope lock(&_apiCs); + _rotateFrame = rotation; + return 0; +} + +bool DesktopCaptureImpl::SetApplyRotation(bool enable) { return true; } + +void DesktopCaptureImpl::UpdateFrameCount() { + if (_incomingFrameTimesNanos[0] == 0) { + // first no shift + } else { + // shift + for (int i = (kFrameRateCountHistorySize - 2); i >= 0; i--) { + _incomingFrameTimesNanos[i + 1] = _incomingFrameTimesNanos[i]; + } + } + _incomingFrameTimesNanos[0] = rtc::TimeNanos(); +} + +uint32_t DesktopCaptureImpl::CalculateFrameRate(int64_t now_ns) { + int32_t num = 0; + int32_t nrOfFrames = 0; + for (num = 1; num < (kFrameRateCountHistorySize - 1); num++) { + if (_incomingFrameTimesNanos[num] <= 0 || + (now_ns - _incomingFrameTimesNanos[num]) / + rtc::kNumNanosecsPerMillisec > + kFrameRateHistoryWindowMs) // don't use data older than 2sec + { + break; + } else { + nrOfFrames++; + } + } + if (num > 1) { + int64_t diff = (now_ns - _incomingFrameTimesNanos[num - 1]) / + rtc::kNumNanosecsPerMillisec; + if (diff > 0) { + return uint32_t((nrOfFrames * 1000.0f / diff) + 0.5f); + } + } + + return nrOfFrames; +} + +void DesktopCaptureImpl::LazyInitCaptureThread() { + MOZ_ASSERT(desktop_capturer_cursor_composer_, + "DesktopCapturer must be initialized before the capture thread"); + if (capturer_thread_) { + return; + } +#if defined(_WIN32) + capturer_thread_ = std::make_unique<rtc::PlatformUIThread>( + std::function([self = (void*)this]() { Run(self); }), + "ScreenCaptureThread", rtc::ThreadAttributes{}); + capturer_thread_->RequestCallbackTimer(_maxFPSNeeded); +#else + auto self = rtc::scoped_refptr<DesktopCaptureImpl>(this); + capturer_thread_ = + std::make_unique<rtc::PlatformThread>(rtc::PlatformThread::SpawnJoinable( + [self] { self->process(); }, "ScreenCaptureThread")); +#endif + started_ = true; +} + +int32_t DesktopCaptureImpl::StartCapture( + const VideoCaptureCapability& capability) { + rtc::CritScope lock(&_apiCs); + // See Bug 1780884 for followup on understanding why multiple calls happen. + // MOZ_ASSERT(!started_, "Capture must be stopped before Start() can be + // called"); + if (started_) { + return 0; + } + + if (uint32_t err = LazyInitDesktopCapturer(); err) { + return err; + } + started_ = true; + _requestedCapability = capability; + _maxFPSNeeded = _requestedCapability.maxFPS > 0 + ? 1000 / _requestedCapability.maxFPS + : 1000; + LazyInitCaptureThread(); + + return 0; +} + +bool DesktopCaptureImpl::FocusOnSelectedSource() { + if (uint32_t err = LazyInitDesktopCapturer(); err) { + return false; + } + + return desktop_capturer_cursor_composer_->FocusOnSelectedSource(); +} + +int32_t DesktopCaptureImpl::StopCapture() { + if (started_) { + started_ = false; + MOZ_ASSERT(capturer_thread_, "Capturer thread should be initialized."); + +#if defined(_WIN32) + capturer_thread_ + ->Stop(); // thread is guaranteed stopped before this returns +#else + capturer_thread_ + ->Finalize(); // thread is guaranteed stopped before this returns +#endif + desktop_capturer_cursor_composer_.reset(); + cursor_composer_started_ = false; + capturer_thread_.reset(); + return 0; + } + return -1; +} + +bool DesktopCaptureImpl::CaptureStarted() { return started_; } + +int32_t DesktopCaptureImpl::CaptureSettings(VideoCaptureCapability& settings) { + return -1; +} + +void DesktopCaptureImpl::OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr<DesktopFrame> frame) { + if (frame.get() == nullptr) return; + uint8_t* videoFrame = frame->data(); + VideoCaptureCapability frameInfo; + frameInfo.width = frame->size().width(); + frameInfo.height = frame->size().height(); + frameInfo.videoType = VideoType::kARGB; + + size_t videoFrameLength = + frameInfo.width * frameInfo.height * DesktopFrame::kBytesPerPixel; + IncomingFrame(videoFrame, videoFrameLength, + frame->stride() / DesktopFrame::kBytesPerPixel, frameInfo); +} + +void DesktopCaptureImpl::process() { + // We need to call Start on the same thread we call CaptureFrame on. + if (!cursor_composer_started_) { + desktop_capturer_cursor_composer_->Start(this); + cursor_composer_started_ = true; + } +#if defined(WEBRTC_WIN) + ProcessIter(); +#else + do { + ProcessIter(); + } while (started_); +#endif +} + +void DesktopCaptureImpl::ProcessIter() { +// We should deliver at least one frame before stopping +#if !defined(_WIN32) + int64_t startProcessTime = rtc::TimeNanos(); +#endif + + // Don't leak while we're looping + base::ScopedNSAutoreleasePool autoreleasepool; + +#if defined(WEBRTC_MAC) + // Give cycles to the RunLoop so frame callbacks can happen + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, true); +#endif + + desktop_capturer_cursor_composer_->CaptureFrame(); + +#if !defined(_WIN32) + const uint32_t processTime = + ((uint32_t)(rtc::TimeNanos() - startProcessTime)) / + rtc::kNumNanosecsPerMillisec; + // Use at most x% CPU or limit framerate + const float sleepTimeFactor = (100.0f / kMaxDesktopCaptureCpuUsage) - 1.0f; + const uint32_t sleepTime = sleepTimeFactor * processTime; + time_event_->Wait(std::max<uint32_t>(_maxFPSNeeded, sleepTime)); +#endif + +#if defined(WEBRTC_WIN) +// Let the timer events in PlatformUIThread drive process, +// don't sleep. +#elif defined(WEBRTC_MAC) + sched_yield(); +#else + static const struct timespec ts_null = {0}; + nanosleep(&ts_null, nullptr); +#endif +} + +} // namespace webrtc |