DXGI capturer hangs when changing resolution in detached sessions

This CL addresses an issue where the desktop appears to freeze after
resizing the desktop in a curtained CRD session when using the DXGI
capturer. This problem does not reproduce when using the GDI capturer
nor does it reproduce when the Windows session is attached to the local
console.

After digging in, it appears that the DXGI DuplicateOutput API stops
providing updated frame data. No errors are returned but yet no data is
produced. The problem is that when in this condition, there isn't a
good way to discern between this problem and a case where the desktop
is actually static.

The DxgiDuplicatorController already contains logic to attempt to
capture a frame prior to returning success after reinitialization.  This
logic works fine in the console case and occasionally works in the
detached session case. What I noticed in my reproductions was that DXGI
would produce a few frames before hanging (usually 1-2 but sometimes 3
or 4).  My solution is to check the session state and adjust the number
of frames we attempt to capture (I also simplified the wait logic as
there was a bug in the time calc and it seemed more complicated than it
needed to be).

One option considered would be to introduced a new differ class higher
up in the stack which would run the GDI and DXGI capturers in parallel
(instead of in the fallback configuration as they are today) however
that seemed like overkill for this specific issue.

Bug: chromium:1307357
Change-Id: Idba4bb9b2aa7692040344d480be3f0d09b9ce9e9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/256214
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Reviewed-by: Jamie Walch <jamiewalch@google.com>
Commit-Queue: Joe Downing <joedow@google.com>
Cr-Commit-Position: refs/heads/main@{#36286}
This commit is contained in:
Joe Downing 2022-03-21 15:52:35 -07:00 committed by WebRTC LUCI CQ
parent 608f9a5489
commit 44ff88dd04

View File

@ -25,6 +25,26 @@
namespace webrtc {
namespace {
constexpr DWORD kInvalidSessionId = 0xFFFFFFFF;
DWORD GetCurrentSessionId() {
DWORD session_id = kInvalidSessionId;
if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) {
RTC_LOG(LS_WARNING)
<< "Failed to retrieve current session Id, current binary "
"may not have required priviledge.";
}
return session_id;
}
bool IsConsoleSession() {
return WTSGetActiveConsoleSessionId() == GetCurrentSessionId();
}
} // namespace
// static
std::string DxgiDuplicatorController::ResultName(
DxgiDuplicatorController::Result result) {
@ -57,14 +77,8 @@ DxgiDuplicatorController::Instance() {
// static
bool DxgiDuplicatorController::IsCurrentSessionSupported() {
DWORD session_id = 0;
if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) {
RTC_LOG(LS_WARNING)
<< "Failed to retrieve current session Id, current binary "
"may not have required priviledge.";
return false;
}
return session_id != 0;
DWORD current_session_id = GetCurrentSessionId();
return current_session_id != kInvalidSessionId && current_session_id != 0;
}
DxgiDuplicatorController::DxgiDuplicatorController() : refcount_(0) {}
@ -423,15 +437,28 @@ bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context,
// On a modern system, the FPS / monitor refresh rate is usually larger than
// or equal to 60. So 17 milliseconds is enough to capture at least one frame.
const int64_t ms_per_frame = 17;
// Skips the first frame to ensure a full frame refresh has happened before
// this function returns.
const int64_t frames_to_skip = 1;
// Skip frames to ensure a full frame refresh has occurred and the DXGI
// machinery is producing frames before this function returns.
int64_t frames_to_skip = 1;
// The total time out milliseconds for this function. If we cannot get enough
// frames during this time interval, this function returns false, and cause
// the DXGI components to be reinitialized. This usually should not happen
// unless the system is switching display mode when this function is being
// called. 500 milliseconds should be enough for ~30 frames.
const int64_t timeout_ms = 500;
if (GetNumFramesCaptured() == 0 && !IsConsoleSession()) {
// When capturing a console session, waiting for a single frame is
// sufficient to ensure that DXGI output duplication is working. When the
// session is not attached to the console, it has been observed that DXGI
// may produce up to 4 frames (typically 1-2 though) before stopping. When
// this condition occurs, no errors are returned from the output duplication
// API, it simply appears that nothing is changing on the screen. Thus for
// detached sessions, we need to capture a few extra frames before we can be
// confident that output duplication was initialized properly.
frames_to_skip = 5;
}
if (GetNumFramesCaptured() >= frames_to_skip) {
return true;
}
@ -450,17 +477,16 @@ bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context,
}
const int64_t start_ms = rtc::TimeMillis();
int64_t last_frame_start_ms = 0;
while (GetNumFramesCaptured() < frames_to_skip) {
if (GetNumFramesCaptured() > 0) {
// Sleep `ms_per_frame` before capturing next frame to ensure the screen
// has been updated by the video adapter.
webrtc::SleepMs(ms_per_frame - (rtc::TimeMillis() - last_frame_start_ms));
}
last_frame_start_ms = rtc::TimeMillis();
if (!DoDuplicateAll(context, shared_frame)) {
return false;
}
// Calling DoDuplicateAll() may change the number of frames captured.
if (GetNumFramesCaptured() >= frames_to_skip) {
break;
}
if (rtc::TimeMillis() - start_ms > timeout_ms) {
RTC_LOG(LS_ERROR) << "Failed to capture " << frames_to_skip
<< " frames "
@ -468,6 +494,10 @@ bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context,
<< timeout_ms << " milliseconds.";
return false;
}
// Sleep `ms_per_frame` before attempting to capture the next frame to
// ensure the video adapter has time to update the screen.
webrtc::SleepMs(ms_per_frame);
}
return true;
}