diff --git a/modules/desktop_capture/desktop_capture_options.h b/modules/desktop_capture/desktop_capture_options.h index 67dffee08a..88990cd0a9 100644 --- a/modules/desktop_capture/desktop_capture_options.h +++ b/modules/desktop_capture/desktop_capture_options.h @@ -185,6 +185,11 @@ class RTC_EXPORT DesktopCaptureOptions { void set_allow_wgc_capturer_fallback(bool allow) { allow_wgc_capturer_fallback_ = allow; } + + // This flag enables 0Hz mode in combination with the WGC capturer. + // The flag has no effect if the allow_wgc_capturer flag is false. + bool allow_wgc_zero_hertz() const { return allow_wgc_zero_hertz_; } + void set_allow_wgc_zero_hertz(bool allow) { allow_wgc_zero_hertz_ = allow; } #endif // defined(RTC_ENABLE_WIN_WGC) #endif // defined(WEBRTC_WIN) @@ -239,6 +244,7 @@ class RTC_EXPORT DesktopCaptureOptions { #if defined(RTC_ENABLE_WIN_WGC) bool allow_wgc_capturer_ = false; bool allow_wgc_capturer_fallback_ = false; + bool allow_wgc_zero_hertz_ = false; #endif #endif #if defined(WEBRTC_USE_X11) diff --git a/modules/desktop_capture/win/wgc_capture_session.cc b/modules/desktop_capture/win/wgc_capture_session.cc index fa6cf26675..580c41adf1 100644 --- a/modules/desktop_capture/win/wgc_capture_session.cc +++ b/modules/desktop_capture/win/wgc_capture_session.cc @@ -192,6 +192,8 @@ HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) { } } + allow_zero_hertz_ = options.allow_wgc_zero_hertz(); + hr = session_->StartCapture(); if (FAILED(hr)) { RTC_LOG(LS_ERROR) << "Failed to start CaptureSession: " << hr; @@ -234,13 +236,22 @@ bool WgcCaptureSession::GetFrame(std::unique_ptr* output_frame) { ProcessFrame(); } - // Return false if we still don't have a valid frame leading to a - // DesktopCapturer::Result::ERROR_PERMANENT posted by the WGC capturer. - if (!queue_.current_frame()) { + // Return a NULL frame and false as `result` if we still don't have a valid + // frame. This will lead to a DesktopCapturer::Result::ERROR_PERMANENT being + // posted by the WGC capturer. + DesktopFrame* current_frame = queue_.current_frame(); + if (!current_frame) { RTC_LOG(LS_ERROR) << "GetFrame failed."; return false; } + // Swap in the DesktopRegion in `damage_region_` which is updated in + // ProcessFrame(). The updated region is either empty or the full rect being + // captured where an empty damage region corresponds to "no change in content + // since last frame". + current_frame->mutable_updated_region()->Swap(&damage_region_); + damage_region_.Clear(); + // Emit the current frame. std::unique_ptr new_frame = queue_.current_frame()->Share(); *output_frame = std::move(new_frame); @@ -294,7 +305,7 @@ HRESULT WgcCaptureSession::ProcessFrame() { queue_.MoveToNextFrame(); if (queue_.current_frame() && queue_.current_frame()->IsShared()) { - RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared."; + RTC_DLOG(LS_VERBOSE) << "Overwriting frame that is still shared."; } ComPtr capture_frame; @@ -410,7 +421,7 @@ HRESULT WgcCaptureSession::ProcessFrame() { // Allocate the current frame buffer only if it is not already allocated or // if the size has changed. Note that we can't reallocate other buffers at // this point, since the caller may still be reading from them. The queue can - // hold up tp two frames. + // hold up to two frames. DesktopSize image_size(image_width, image_height); if (!queue_.current_frame() || !queue_.current_frame()->size().equals(image_size)) { @@ -433,6 +444,32 @@ HRESULT WgcCaptureSession::ProcessFrame() { d3d_context->Unmap(mapped_texture_.Get(), 0); + if (allow_zero_hertz()) { + DesktopFrame* previous_frame = queue_.previous_frame(); + if (previous_frame) { + const int previous_frame_size = + previous_frame->stride() * previous_frame->size().height(); + const int current_frame_size = + current_frame->stride() * current_frame->size().height(); + + // Compare the latest frame with the previous and check if the frames are + // equal (both contain the exact same pixel values). + if (current_frame_size == previous_frame_size) { + const bool frames_are_equal = !memcmp( + current_frame->data(), previous_frame->data(), current_frame_size); + if (!frames_are_equal) { + // TODO(https://crbug.com/1421242): If we had an API to report proper + // damage regions we should be doing AddRect() with a SetRect() call + // on a resize. + damage_region_.SetRect(DesktopRect::MakeSize(current_frame->size())); + } + } else { + // Mark resized frames as damaged. + damage_region_.SetRect(DesktopRect::MakeSize(current_frame->size())); + } + } + } + if (empty_frame_credit_count_ > 0) --empty_frame_credit_count_; size_ = new_size; diff --git a/modules/desktop_capture/win/wgc_capture_session.h b/modules/desktop_capture/win/wgc_capture_session.h index a5c294453c..a2d7a87cf1 100644 --- a/modules/desktop_capture/win/wgc_capture_session.h +++ b/modules/desktop_capture/win/wgc_capture_session.h @@ -82,6 +82,8 @@ class WgcCaptureSession final { void RemoveEventHandlers(); + bool allow_zero_hertz() const { return allow_zero_hertz_; } + std::unique_ptr frame_arrived_token_; std::unique_ptr item_closed_token_; @@ -140,6 +142,17 @@ class WgcCaptureSession final { // expected and should not be seen as an error. int empty_frame_credit_count_ = 0; + // Caches the value of DesktopCaptureOptions.allow_wgc_zero_hertz() in + // StartCapture(). Adds 0Hz detection in ProcessFrame() when enabled which + // adds complexity since memcmp() is performed on two successive frames. + bool allow_zero_hertz_ = false; + + // Tracks damage region updates that were reported since the last time a frame + // was captured. Currently only supports either the complete rect being + // captured or an empty region. Will always be empty if `allow_zero_hertz_` is + // false. + DesktopRegion damage_region_; + SequenceChecker sequence_checker_; };