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:
parent
6b4702355b
commit
58e8cb0553
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user