mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-26 20:25:34 +08:00
wgc dll test pass
This commit is contained in:
421
webrtc/wgc_capturer_win_unittest.cc
Normal file
421
webrtc/wgc_capturer_win_unittest.cc
Normal file
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#include "modules/desktop_capture/win/wgc_capturer_win.h"
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/win/test_support/test_window.h"
|
||||
#include "modules/desktop_capture/win/window_capture_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/thread.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "rtc_base/win/scoped_com_initializer.h"
|
||||
#include "rtc_base/win/windows_version.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
#include "test/gtest.h"
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
const char kWindowThreadName[] = "wgc_capturer_test_window_thread";
|
||||
const WCHAR kWindowTitle[] = L"WGC Capturer Test Window";
|
||||
const char kCapturerImplHistogram[] =
|
||||
"WebRTC.DesktopCapture.Win.DesktopCapturerImpl";
|
||||
const char kCapturerResultHistogram[] =
|
||||
"WebRTC.DesktopCapture.Win.WgcCapturerResult";
|
||||
const int kSuccess = 0;
|
||||
const int kSessionStartFailure = 4;
|
||||
const char kCaptureSessionResultHistogram[] =
|
||||
"WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult";
|
||||
const int kSourceClosed = 1;
|
||||
const char kCaptureTimeHistogram[] =
|
||||
"WebRTC.DesktopCapture.Win.WgcCapturerFrameTime";
|
||||
const int kSmallWindowWidth = 200;
|
||||
const int kSmallWindowHeight = 100;
|
||||
const int kMediumWindowWidth = 300;
|
||||
const int kMediumWindowHeight = 200;
|
||||
const int kLargeWindowWidth = 400;
|
||||
const int kLargeWindowHeight = 500;
|
||||
// The size of the image we capture is slightly smaller than the actual size of
|
||||
// the window.
|
||||
const int kWindowWidthSubtrahend = 14;
|
||||
const int kWindowHeightSubtrahend = 7;
|
||||
// Custom message constants so we can direct our thread to close windows
|
||||
// and quit running.
|
||||
const UINT kNoOp = WM_APP;
|
||||
const UINT kDestroyWindow = WM_APP + 1;
|
||||
const UINT kQuitRunning = WM_APP + 2;
|
||||
enum CaptureType { kWindowCapture = 0, kScreenCapture = 1 };
|
||||
} // namespace
|
||||
class WgcCapturerWinTest : public ::testing::TestWithParam<CaptureType>,
|
||||
public DesktopCapturer::Callback {
|
||||
public:
|
||||
void SetUp() override {
|
||||
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_RS5) {
|
||||
RTC_LOG(LS_INFO)
|
||||
<< "Skipping WgcCapturerWinTests on Windows versions < RS5.";
|
||||
GTEST_SKIP();
|
||||
}
|
||||
com_initializer_ =
|
||||
std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
|
||||
EXPECT_TRUE(com_initializer_->Succeeded());
|
||||
}
|
||||
void SetUpForWindowCapture(int window_width = kMediumWindowWidth,
|
||||
int window_height = kMediumWindowHeight) {
|
||||
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
|
||||
DesktopCaptureOptions::CreateDefault());
|
||||
CreateWindowOnSeparateThread(window_width, window_height);
|
||||
StartWindowThreadMessageLoop();
|
||||
source_id_ = GetTestWindowIdFromSourceList();
|
||||
}
|
||||
void SetUpForScreenCapture() {
|
||||
capturer_ = WgcCapturerWin::CreateRawScreenCapturer(
|
||||
DesktopCaptureOptions::CreateDefault());
|
||||
source_id_ = GetScreenIdFromSourceList();
|
||||
}
|
||||
void TearDown() override {
|
||||
if (window_open_) {
|
||||
CloseTestWindow();
|
||||
}
|
||||
}
|
||||
// The window must live on a separate thread so that we can run a message pump
|
||||
// without blocking the test thread. This is necessary if we are interested in
|
||||
// having GraphicsCaptureItem events (i.e. the Closed event) fire, and it more
|
||||
// closely resembles how capture works in the wild.
|
||||
void CreateWindowOnSeparateThread(int window_width, int window_height) {
|
||||
window_thread_ = rtc::Thread::Create();
|
||||
window_thread_->SetName(kWindowThreadName, nullptr);
|
||||
window_thread_->Start();
|
||||
window_thread_->Invoke<void>(RTC_FROM_HERE, [this, window_width,
|
||||
window_height]() {
|
||||
window_thread_id_ = GetCurrentThreadId();
|
||||
window_info_ =
|
||||
CreateTestWindow(kWindowTitle, window_height, window_width);
|
||||
window_open_ = true;
|
||||
while (!IsWindowResponding(window_info_.hwnd)) {
|
||||
RTC_LOG(LS_INFO) << "Waiting for test window to become responsive in "
|
||||
"WgcWindowCaptureTest.";
|
||||
}
|
||||
while (!IsWindowValidAndVisible(window_info_.hwnd)) {
|
||||
RTC_LOG(LS_INFO) << "Waiting for test window to be visible in "
|
||||
"WgcWindowCaptureTest.";
|
||||
}
|
||||
});
|
||||
ASSERT_TRUE(window_thread_->RunningForTest());
|
||||
ASSERT_FALSE(window_thread_->IsCurrent());
|
||||
}
|
||||
void StartWindowThreadMessageLoop() {
|
||||
window_thread_->PostTask(RTC_FROM_HERE, [this]() {
|
||||
MSG msg;
|
||||
BOOL gm;
|
||||
while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
|
||||
::DispatchMessage(&msg);
|
||||
if (msg.message == kDestroyWindow) {
|
||||
DestroyTestWindow(window_info_);
|
||||
}
|
||||
if (msg.message == kQuitRunning) {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
void CloseTestWindow() {
|
||||
::PostThreadMessage(window_thread_id_, kDestroyWindow, 0, 0);
|
||||
::PostThreadMessage(window_thread_id_, kQuitRunning, 0, 0);
|
||||
window_thread_->Stop();
|
||||
window_open_ = false;
|
||||
}
|
||||
DesktopCapturer::SourceId GetTestWindowIdFromSourceList() {
|
||||
// Frequently, the test window will not show up in GetSourceList because it
|
||||
// was created too recently. Since we are confident the window will be found
|
||||
// eventually we loop here until we find it.
|
||||
intptr_t src_id;
|
||||
do {
|
||||
DesktopCapturer::SourceList sources;
|
||||
EXPECT_TRUE(capturer_->GetSourceList(&sources));
|
||||
auto it = std::find_if(
|
||||
sources.begin(), sources.end(),
|
||||
[&](const DesktopCapturer::Source& src) {
|
||||
return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd);
|
||||
});
|
||||
src_id = it->id;
|
||||
} while (src_id != reinterpret_cast<intptr_t>(window_info_.hwnd));
|
||||
return src_id;
|
||||
}
|
||||
DesktopCapturer::SourceId GetScreenIdFromSourceList() {
|
||||
DesktopCapturer::SourceList sources;
|
||||
EXPECT_TRUE(capturer_->GetSourceList(&sources));
|
||||
EXPECT_GT(sources.size(), 0ULL);
|
||||
return sources[0].id;
|
||||
}
|
||||
void DoCapture() {
|
||||
// Sometimes the first few frames are empty becaues the capture engine is
|
||||
// still starting up. We also may drop a few frames when the window is
|
||||
// resized or un-minimized.
|
||||
do {
|
||||
capturer_->CaptureFrame();
|
||||
} while (result_ == DesktopCapturer::Result::ERROR_TEMPORARY);
|
||||
EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS);
|
||||
EXPECT_TRUE(frame_);
|
||||
EXPECT_GT(metrics::NumEvents(kCapturerResultHistogram, kSuccess),
|
||||
successful_captures_);
|
||||
++successful_captures_;
|
||||
}
|
||||
void ValidateFrame(int expected_width, int expected_height) {
|
||||
EXPECT_EQ(frame_->size().width(), expected_width - kWindowWidthSubtrahend);
|
||||
EXPECT_EQ(frame_->size().height(),
|
||||
expected_height - kWindowHeightSubtrahend);
|
||||
// Verify the buffer contains as much data as it should, and that the right
|
||||
// colors are found.
|
||||
int data_length = frame_->stride() * frame_->size().height();
|
||||
// The first and last pixel should have the same color because they will be
|
||||
// from the border of the window.
|
||||
// Pixels have 4 bytes of data so the whole pixel needs a uint32_t to fit.
|
||||
uint32_t first_pixel = static_cast<uint32_t>(*frame_->data());
|
||||
uint32_t last_pixel = static_cast<uint32_t>(
|
||||
*(frame_->data() + data_length - DesktopFrame::kBytesPerPixel));
|
||||
EXPECT_EQ(first_pixel, last_pixel);
|
||||
// Let's also check a pixel from the middle of the content area, which the
|
||||
// TestWindow will paint a consistent color for us to verify.
|
||||
uint8_t* middle_pixel = frame_->data() + (data_length / 2);
|
||||
int sub_pixel_offset = DesktopFrame::kBytesPerPixel / 4;
|
||||
EXPECT_EQ(*middle_pixel, kTestWindowBValue);
|
||||
middle_pixel += sub_pixel_offset;
|
||||
EXPECT_EQ(*middle_pixel, kTestWindowGValue);
|
||||
middle_pixel += sub_pixel_offset;
|
||||
EXPECT_EQ(*middle_pixel, kTestWindowRValue);
|
||||
middle_pixel += sub_pixel_offset;
|
||||
// The window is opaque so we expect 0xFF for the Alpha channel.
|
||||
EXPECT_EQ(*middle_pixel, 0xFF);
|
||||
}
|
||||
// DesktopCapturer::Callback interface
|
||||
// The capturer synchronously invokes this method before |CaptureFrame()|
|
||||
// returns.
|
||||
void OnCaptureResult(DesktopCapturer::Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) override {
|
||||
result_ = result;
|
||||
frame_ = std::move(frame);
|
||||
}
|
||||
protected:
|
||||
std::unique_ptr<ScopedCOMInitializer> com_initializer_;
|
||||
DWORD window_thread_id_;
|
||||
std::unique_ptr<rtc::Thread> window_thread_;
|
||||
WindowInfo window_info_;
|
||||
intptr_t source_id_;
|
||||
bool window_open_ = false;
|
||||
DesktopCapturer::Result result_;
|
||||
int successful_captures_ = 0;
|
||||
std::unique_ptr<DesktopFrame> frame_;
|
||||
std::unique_ptr<DesktopCapturer> capturer_;
|
||||
};
|
||||
TEST_P(WgcCapturerWinTest, SelectValidSource) {
|
||||
if (GetParam() == CaptureType::kWindowCapture) {
|
||||
SetUpForWindowCapture();
|
||||
} else {
|
||||
SetUpForScreenCapture();
|
||||
}
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
}
|
||||
TEST_P(WgcCapturerWinTest, SelectInvalidSource) {
|
||||
if (GetParam() == CaptureType::kWindowCapture) {
|
||||
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
|
||||
DesktopCaptureOptions::CreateDefault());
|
||||
source_id_ = kNullWindowId;
|
||||
} else {
|
||||
capturer_ = WgcCapturerWin::CreateRawScreenCapturer(
|
||||
DesktopCaptureOptions::CreateDefault());
|
||||
source_id_ = kInvalidScreenId;
|
||||
}
|
||||
EXPECT_FALSE(capturer_->SelectSource(source_id_));
|
||||
}
|
||||
TEST_P(WgcCapturerWinTest, Capture) {
|
||||
if (GetParam() == CaptureType::kWindowCapture) {
|
||||
SetUpForWindowCapture();
|
||||
} else {
|
||||
SetUpForScreenCapture();
|
||||
}
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
capturer_->Start(this);
|
||||
EXPECT_GE(metrics::NumEvents(kCapturerImplHistogram,
|
||||
DesktopCapturerId::kWgcCapturerWin),
|
||||
1);
|
||||
DoCapture();
|
||||
EXPECT_GT(frame_->size().width(), 0);
|
||||
EXPECT_GT(frame_->size().height(), 0);
|
||||
}
|
||||
TEST_P(WgcCapturerWinTest, CaptureTime) {
|
||||
if (GetParam() == CaptureType::kWindowCapture) {
|
||||
SetUpForWindowCapture();
|
||||
} else {
|
||||
SetUpForScreenCapture();
|
||||
}
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
capturer_->Start(this);
|
||||
int64_t start_time;
|
||||
do {
|
||||
start_time = rtc::TimeNanos();
|
||||
capturer_->CaptureFrame();
|
||||
} while (result_ == DesktopCapturer::Result::ERROR_TEMPORARY);
|
||||
int capture_time_ms =
|
||||
(rtc::TimeNanos() - start_time) / rtc::kNumNanosecsPerMillisec;
|
||||
EXPECT_TRUE(frame_);
|
||||
// The test may measure the time slightly differently than the capturer. So we
|
||||
// just check if it's within 5 ms.
|
||||
EXPECT_NEAR(frame_->capture_time_ms(), capture_time_ms, 5);
|
||||
EXPECT_GE(
|
||||
metrics::NumEvents(kCaptureTimeHistogram, frame_->capture_time_ms()), 1);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(SourceAgnostic,
|
||||
WgcCapturerWinTest,
|
||||
::testing::Values(CaptureType::kWindowCapture,
|
||||
CaptureType::kScreenCapture));
|
||||
// Monitor specific tests.
|
||||
TEST_F(WgcCapturerWinTest, FocusOnMonitor) {
|
||||
SetUpForScreenCapture();
|
||||
EXPECT_TRUE(capturer_->SelectSource(0));
|
||||
// You can't set focus on a monitor.
|
||||
EXPECT_FALSE(capturer_->FocusOnSelectedSource());
|
||||
}
|
||||
TEST_F(WgcCapturerWinTest, CaptureAllMonitors) {
|
||||
SetUpForScreenCapture();
|
||||
EXPECT_TRUE(capturer_->SelectSource(kFullDesktopScreenId));
|
||||
capturer_->Start(this);
|
||||
DoCapture();
|
||||
EXPECT_GT(frame_->size().width(), 0);
|
||||
EXPECT_GT(frame_->size().height(), 0);
|
||||
}
|
||||
// Window specific tests.
|
||||
TEST_F(WgcCapturerWinTest, FocusOnWindow) {
|
||||
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
|
||||
DesktopCaptureOptions::CreateDefault());
|
||||
window_info_ = CreateTestWindow(kWindowTitle);
|
||||
source_id_ = GetScreenIdFromSourceList();
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
EXPECT_TRUE(capturer_->FocusOnSelectedSource());
|
||||
HWND hwnd = reinterpret_cast<HWND>(source_id_);
|
||||
EXPECT_EQ(hwnd, ::GetActiveWindow());
|
||||
EXPECT_EQ(hwnd, ::GetForegroundWindow());
|
||||
EXPECT_EQ(hwnd, ::GetFocus());
|
||||
DestroyTestWindow(window_info_);
|
||||
}
|
||||
TEST_F(WgcCapturerWinTest, SelectMinimizedWindow) {
|
||||
SetUpForWindowCapture();
|
||||
MinimizeTestWindow(reinterpret_cast<HWND>(source_id_));
|
||||
EXPECT_FALSE(capturer_->SelectSource(source_id_));
|
||||
UnminimizeTestWindow(reinterpret_cast<HWND>(source_id_));
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
}
|
||||
TEST_F(WgcCapturerWinTest, SelectClosedWindow) {
|
||||
SetUpForWindowCapture();
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
CloseTestWindow();
|
||||
EXPECT_FALSE(capturer_->SelectSource(source_id_));
|
||||
}
|
||||
TEST_F(WgcCapturerWinTest, UnsupportedWindowStyle) {
|
||||
// Create a window with the WS_EX_TOOLWINDOW style, which WGC does not
|
||||
// support.
|
||||
window_info_ = CreateTestWindow(kWindowTitle, kMediumWindowWidth,
|
||||
kMediumWindowHeight, WS_EX_TOOLWINDOW);
|
||||
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
|
||||
DesktopCaptureOptions::CreateDefault());
|
||||
DesktopCapturer::SourceList sources;
|
||||
EXPECT_TRUE(capturer_->GetSourceList(&sources));
|
||||
auto it = std::find_if(
|
||||
sources.begin(), sources.end(), [&](const DesktopCapturer::Source& src) {
|
||||
return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd);
|
||||
});
|
||||
// We should not find the window, since we filter for unsupported styles.
|
||||
EXPECT_EQ(it, sources.end());
|
||||
DestroyTestWindow(window_info_);
|
||||
}
|
||||
TEST_F(WgcCapturerWinTest, IncreaseWindowSizeMidCapture) {
|
||||
SetUpForWindowCapture(kSmallWindowWidth, kSmallWindowHeight);
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
capturer_->Start(this);
|
||||
DoCapture();
|
||||
ValidateFrame(kSmallWindowWidth, kSmallWindowHeight);
|
||||
ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight);
|
||||
DoCapture();
|
||||
// We don't expect to see the new size until the next capture, as the frame
|
||||
// pool hadn't had a chance to resize yet to fit the new, larger image.
|
||||
DoCapture();
|
||||
ValidateFrame(kSmallWindowWidth, kMediumWindowHeight);
|
||||
ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight);
|
||||
DoCapture();
|
||||
DoCapture();
|
||||
ValidateFrame(kLargeWindowWidth, kMediumWindowHeight);
|
||||
}
|
||||
TEST_F(WgcCapturerWinTest, ReduceWindowSizeMidCapture) {
|
||||
SetUpForWindowCapture(kLargeWindowWidth, kLargeWindowHeight);
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
capturer_->Start(this);
|
||||
DoCapture();
|
||||
ValidateFrame(kLargeWindowWidth, kLargeWindowHeight);
|
||||
ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight);
|
||||
// We expect to see the new size immediately because the image data has shrunk
|
||||
// and will fit in the existing buffer.
|
||||
DoCapture();
|
||||
ValidateFrame(kLargeWindowWidth, kMediumWindowHeight);
|
||||
ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight);
|
||||
DoCapture();
|
||||
ValidateFrame(kSmallWindowWidth, kMediumWindowHeight);
|
||||
}
|
||||
TEST_F(WgcCapturerWinTest, MinimizeWindowMidCapture) {
|
||||
SetUpForWindowCapture();
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
capturer_->Start(this);
|
||||
// Minmize the window and capture should continue but return temporary errors.
|
||||
MinimizeTestWindow(window_info_.hwnd);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
capturer_->CaptureFrame();
|
||||
EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_TEMPORARY);
|
||||
}
|
||||
// Reopen the window and the capture should continue normally.
|
||||
UnminimizeTestWindow(window_info_.hwnd);
|
||||
DoCapture();
|
||||
// We can't verify the window size here because the test window does not
|
||||
// repaint itself after it is unminimized, but capturing successfully is still
|
||||
// a good test.
|
||||
}
|
||||
TEST_F(WgcCapturerWinTest, CloseWindowMidCapture) {
|
||||
SetUpForWindowCapture();
|
||||
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||
capturer_->Start(this);
|
||||
DoCapture();
|
||||
ValidateFrame(kMediumWindowWidth, kMediumWindowHeight);
|
||||
CloseTestWindow();
|
||||
// We need to call GetMessage to trigger the Closed event and the capturer's
|
||||
// event handler for it. If we are too early and the Closed event hasn't
|
||||
// arrived yet we should keep trying until the capturer receives it and stops.
|
||||
auto* wgc_capturer = static_cast<WgcCapturerWin*>(capturer_.get());
|
||||
while (wgc_capturer->IsSourceBeingCaptured(source_id_)) {
|
||||
// Since the capturer handles the Closed message, there will be no message
|
||||
// for us and GetMessage will hang, unless we send ourselves a message
|
||||
// first.
|
||||
::PostThreadMessage(GetCurrentThreadId(), kNoOp, 0, 0);
|
||||
MSG msg;
|
||||
::GetMessage(&msg, NULL, 0, 0);
|
||||
::DispatchMessage(&msg);
|
||||
}
|
||||
// Occasionally, one last frame will have made it into the frame pool before
|
||||
// the window closed. The first call will consume it, and in that case we need
|
||||
// to make one more call to CaptureFrame.
|
||||
capturer_->CaptureFrame();
|
||||
if (result_ == DesktopCapturer::Result::SUCCESS)
|
||||
capturer_->CaptureFrame();
|
||||
EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSessionStartFailure),
|
||||
1);
|
||||
EXPECT_GE(metrics::NumEvents(kCaptureSessionResultHistogram, kSourceClosed),
|
||||
1);
|
||||
EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_PERMANENT);
|
||||
}
|
||||
} // namespace webrtc
|
||||
Reference in New Issue
Block a user