Removes usage of FrameArrived event for WGC

* Removes a ~60Hz thread-wakeup signal caused by the FrameArrived event
* Initial power measurements shows a reduced power consumption
* No increase in time to first captured packet found

Bug: chromium:1428592
Change-Id: Ia23b5db0c87e70e5b0d6919394494a24d8944493
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/301200
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Commit-Queue: Henrik Andreassson <henrika@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39861}
This commit is contained in:
henrika 2023-04-14 12:21:25 +02:00 committed by WebRTC LUCI CQ
parent 6b4702355b
commit 58e8cb0553
2 changed files with 56 additions and 81 deletions

View File

@ -100,7 +100,7 @@ WgcCaptureSession::WgcCaptureSession(ComPtr<ID3D11Device> 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<EventRegistrationToken>();
auto frame_arrived_handler =
Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler<
WGC::Direct3D11CaptureFramePool*, IInspectable*>>(
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<DesktopFrame>* 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<DesktopFrame>* 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<WGC::IDirect3D11CaptureFrame> 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<ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface>
@ -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<uint8_t*>(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();

View File

@ -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<EventRegistrationToken> frame_arrived_token_;
std::unique_ptr<EventRegistrationToken> 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