diff --git a/modules/desktop_capture/win/wgc_capture_session.cc b/modules/desktop_capture/win/wgc_capture_session.cc index 580c41adf1..5f3db106cc 100644 --- a/modules/desktop_capture/win/wgc_capture_session.cc +++ b/modules/desktop_capture/win/wgc_capture_session.cc @@ -100,7 +100,7 @@ WgcCaptureSession::WgcCaptureSession(ComPtr d3d11_device, size_(size) {} WgcCaptureSession::~WgcCaptureSession() { - RemoveEventHandlers(); + RemoveEventHandler(); } HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) { @@ -166,17 +166,6 @@ HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) { return hr; } - // Because `WgcCapturerWin` created a `DispatcherQueue`, and we created - // `frame_pool_` via `Create`, the `FrameArrived` event will be delivered on - // the current thread. - frame_arrived_token_ = std::make_unique(); - auto frame_arrived_handler = - Microsoft::WRL::Callback>( - this, &WgcCaptureSession::OnFrameArrived); - hr = frame_pool_->add_FrameArrived(frame_arrived_handler.Get(), - frame_arrived_token_.get()); - hr = frame_pool_->CreateCaptureSession(item_.Get(), &session_); if (FAILED(hr)) { RecordStartCaptureResult(StartCaptureResult::kCreateCaptureSessionFailed); @@ -207,34 +196,53 @@ HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) { return hr; } -bool WgcCaptureSession::GetFrame(std::unique_ptr* output_frame) { - RTC_DCHECK_RUN_ON(&sequence_checker_); +void WgcCaptureSession::EnsureFrame() { + // Try to process the captured frame and copy it to the `queue_`. + HRESULT hr = ProcessFrame(); + if (SUCCEEDED(hr)) { + RTC_CHECK(queue_.current_frame()); + return; + } - // When GetFrame() asks for the first frame it can happen that no frame has - // arrived yet. We therefore try to get a new frame from the frame pool for a - // maximum of 10 times after sleeping for 20ms. We choose 20ms as it's just a - // bit longer than 17ms (for 60fps*) and hopefully avoids unlucky timing - // causing us to wait two frames when we mostly seem to only need to wait for - // one. This approach should ensure that GetFrame() always delivers a valid - // frame with a max latency of 200ms and often after sleeping only once. - // We also build up an `empty_frame_credit_count_` for each sleep call. As - // long as this credit is above zero, error logs for "empty frame" are - // avoided. The counter is reduced by one for each successful call to - // ProcessFrame() until the number of credits is zero. This counter is only - // expected to be above zero during a short startup phase. The scheme is - // heuristic and based on manual testing. + // We failed to process the frame, but we do have a frame so just return that. + if (queue_.current_frame()) { + RTC_LOG(LS_ERROR) << "ProcessFrame failed, using existing frame: " << hr; + return; + } + + // ProcessFrame failed and we don't have a current frame. This could indicate + // a startup path where we may need to try/wait a few times to ensure that we + // have a frame. We try to get a new frame from the frame pool for a maximum + // of 10 times after sleeping for 20ms. We choose 20ms as it's just a bit + // longer than 17ms (for 60fps*) and hopefully avoids unlucky timing causing + // us to wait two frames when we mostly seem to only need to wait for one. + // This approach should ensure that GetFrame() always delivers a valid frame + // with a max latency of 200ms and often after sleeping only once. + // The scheme is heuristic and based on manual testing. // (*) On a modern system, the FPS / monitor refresh rate is usually larger // than or equal to 60. + const int max_sleep_count = 10; const int sleep_time_ms = 20; int sleep_count = 0; while (!queue_.current_frame() && sleep_count < max_sleep_count) { sleep_count++; - empty_frame_credit_count_ = sleep_count + 1; webrtc::SleepMs(sleep_time_ms); - ProcessFrame(); + hr = ProcessFrame(); + if (FAILED(hr)) { + RTC_DLOG(LS_WARNING) << "ProcessFrame failed during startup: " << hr; + } } + RTC_LOG_IF(LS_ERROR, !is_frame_captured_) + << "Unable to process a valid frame even after trying 10 times."; +} + +bool WgcCaptureSession::GetFrame(std::unique_ptr* output_frame) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + EnsureFrame(); + RTC_DCHECK(is_frame_captured_); // 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 @@ -281,17 +289,6 @@ HRESULT WgcCaptureSession::CreateMappedTexture( return d3d11_device_->CreateTexture2D(&map_desc, nullptr, &mapped_texture_); } -HRESULT WgcCaptureSession::OnFrameArrived( - WGC::IDirect3D11CaptureFramePool* sender, - IInspectable* event_args) { - RTC_DCHECK_RUN_ON(&sequence_checker_); - HRESULT hr = ProcessFrame(); - if (FAILED(hr)) { - RTC_DLOG(LS_WARNING) << "ProcessFrame failed: " << hr; - } - return hr; -} - HRESULT WgcCaptureSession::ProcessFrame() { RTC_DCHECK_RUN_ON(&sequence_checker_); @@ -303,11 +300,6 @@ HRESULT WgcCaptureSession::ProcessFrame() { RTC_DCHECK(is_capture_started_); - queue_.MoveToNextFrame(); - if (queue_.current_frame() && queue_.current_frame()->IsShared()) { - RTC_DLOG(LS_VERBOSE) << "Overwriting frame that is still shared."; - } - ComPtr capture_frame; HRESULT hr = frame_pool_->TryGetNextFrame(&capture_frame); if (FAILED(hr)) { @@ -317,15 +309,21 @@ HRESULT WgcCaptureSession::ProcessFrame() { } if (!capture_frame) { - // Avoid logging errors while we still have credits (or allowance) to - // consider this condition as expected and not as an error. - if (empty_frame_credit_count_ == 0) { + // Avoid logging errors until at least one valid frame has been captured. + if (is_frame_captured_) { RTC_DLOG(LS_WARNING) << "Frame pool was empty => kFrameDropped."; RecordGetFrameResult(GetFrameResult::kFrameDropped); } return E_FAIL; } + is_frame_captured_ = true; + + queue_.MoveToNextFrame(); + if (queue_.current_frame() && queue_.current_frame()->IsShared()) { + RTC_DLOG(LS_VERBOSE) << "Overwriting frame that is still shared."; + } + // We need to get `capture_frame` as an `ID3D11Texture2D` so that we can get // the raw image data in the format required by the `DesktopFrame` interface. ComPtr @@ -435,12 +433,8 @@ HRESULT WgcCaptureSession::ProcessFrame() { // Make a copy of the data pointed to by `map_info.pData` to the preallocated // `current_frame` so we are free to unmap our texture. uint8_t* src_data = static_cast(map_info.pData); - uint8_t* dst_data = current_frame->data(); - for (int i = 0; i < image_height; i++) { - memcpy(dst_data, src_data, current_frame->stride()); - dst_data += current_frame->stride(); - src_data += map_info.RowPitch; - } + current_frame->CopyPixelsFrom(src_data, current_frame->stride(), + DesktopRect::MakeSize(current_frame->size())); d3d_context->Unmap(mapped_texture_.Get(), 0); @@ -470,8 +464,6 @@ HRESULT WgcCaptureSession::ProcessFrame() { } } - if (empty_frame_credit_count_ > 0) - --empty_frame_credit_count_; size_ = new_size; RecordGetFrameResult(GetFrameResult::kSuccess); return hr; @@ -484,7 +476,7 @@ HRESULT WgcCaptureSession::OnItemClosed(WGC::IGraphicsCaptureItem* sender, RTC_LOG(LS_INFO) << "Capture target has been closed."; item_closed_ = true; - RemoveEventHandlers(); + RemoveEventHandler(); // Do not attempt to free resources in the OnItemClosed handler, as this // causes a race where we try to delete the item that is calling us. Removing @@ -494,16 +486,8 @@ HRESULT WgcCaptureSession::OnItemClosed(WGC::IGraphicsCaptureItem* sender, return S_OK; } -void WgcCaptureSession::RemoveEventHandlers() { +void WgcCaptureSession::RemoveEventHandler() { HRESULT hr; - if (frame_pool_ && frame_arrived_token_) { - hr = frame_pool_->remove_FrameArrived(*frame_arrived_token_); - frame_arrived_token_.reset(); - if (FAILED(hr)) { - RTC_LOG(LS_WARNING) << "Failed to remove FrameArrived event handler: " - << hr; - } - } if (item_ && item_closed_token_) { hr = item_->remove_Closed(*item_closed_token_); item_closed_token_.reset(); diff --git a/modules/desktop_capture/win/wgc_capture_session.h b/modules/desktop_capture/win/wgc_capture_session.h index a2d7a87cf1..c25ef0f105 100644 --- a/modules/desktop_capture/win/wgc_capture_session.h +++ b/modules/desktop_capture/win/wgc_capture_session.h @@ -72,19 +72,17 @@ class WgcCaptureSession final { ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* sender, IInspectable* event_args); - // Event handler for `frame_pool_`'s FrameArrived event. - HRESULT OnFrameArrived( - ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool* sender, - IInspectable* event_args); + // Wraps calls to ProcessFrame and deals with the uniqe start-up phase + // ensuring that we always have one captured frame available. + void EnsureFrame(); // Process the captured frame and copy it to the `queue_`. HRESULT ProcessFrame(); - void RemoveEventHandlers(); + void RemoveEventHandler(); bool allow_zero_hertz() const { return allow_zero_hertz_; } - std::unique_ptr frame_arrived_token_; std::unique_ptr item_closed_token_; // A Direct3D11 Device provided by the caller. We use this to create an @@ -132,15 +130,8 @@ class WgcCaptureSession final { bool item_closed_ = false; bool is_capture_started_ = false; - // Counts number of "empty frame credits" which have built up in GetFrame() - // when a sequence of calls to ProcessFrame() was called with 20ms sleep calls - // between each. The counter is reduced by one (to a minimum of zero) when - // ProcessFrame() succeeds. As long as the counter is larger than zero, calls - // to RecordGetFrameResult(kTryGetNextFrameFailed) are disabled. - // The reason for adding this scheme is to prevent logs of - // kTryGetNextFrameFailed in the startup phase when some empty frames is - // expected and should not be seen as an error. - int empty_frame_credit_count_ = 0; + // Set to true when at least one frame has been captured. + bool is_frame_captured_ = false; // Caches the value of DesktopCaptureOptions.allow_wgc_zero_hertz() in // StartCapture(). Adds 0Hz detection in ProcessFrame() when enabled which