wgc_capturer_win_unittest.cc (27394B)
1 /* 2 * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include "modules/desktop_capture/win/wgc_capturer_win.h" 12 13 #include <algorithm> 14 #include <cstddef> 15 #include <cstdint> 16 #include <memory> 17 #include <utility> 18 #include <vector> 19 20 #include "modules/desktop_capture/desktop_capture_options.h" 21 #include "modules/desktop_capture/desktop_capture_types.h" 22 #include "modules/desktop_capture/desktop_capturer.h" 23 #include "modules/desktop_capture/win/screen_capture_utils.h" 24 #include "modules/desktop_capture/win/test_support/test_window.h" 25 #include "modules/desktop_capture/win/wgc_capture_session.h" 26 #include "modules/desktop_capture/win/window_capture_utils.h" 27 #include "rtc_base/logging.h" 28 #include "rtc_base/task_queue_for_test.h" 29 #include "rtc_base/thread.h" 30 #include "rtc_base/time_utils.h" 31 #include "rtc_base/win/scoped_com_initializer.h" 32 #include "rtc_base/win/windows_version.h" 33 #include "system_wrappers/include/metrics.h" 34 #include "test/gtest.h" 35 36 namespace webrtc { 37 namespace { 38 39 constexpr char kWindowThreadName[] = "wgc_capturer_test_window_thread"; 40 constexpr WCHAR kWindowTitle[] = L"WGC Capturer Test Window"; 41 42 constexpr char kCapturerImplHistogram[] = 43 "WebRTC.DesktopCapture.Win.DesktopCapturerImpl"; 44 45 constexpr char kCapturerResultHistogram[] = 46 "WebRTC.DesktopCapture.Win.WgcCapturerResult"; 47 constexpr int kSuccess = 0; 48 constexpr int kSessionStartFailure = 4; 49 50 constexpr char kCaptureSessionResultHistogram[] = 51 "WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult"; 52 constexpr int kSourceClosed = 1; 53 54 constexpr char kCaptureTimeHistogram[] = 55 "WebRTC.DesktopCapture.Win.WgcCapturerFrameTime"; 56 57 constexpr char kFullScreenDetectorResult[] = 58 "WebRTC.Screenshare.FullScreenDetectorResult"; 59 constexpr int detector_result_success = 1; 60 constexpr int detector_result_failure_same_title_windows = 2; 61 constexpr int detector_result_failure_slide_show_not_chosen = 3; 62 63 constexpr char kCaptureFullscreenDetectorHistogram[] = 64 "WebRTC.Screenshare.DesktopCapturerFullscreenDetector"; 65 66 // The capturer keeps `kNumBuffers` in its frame pool, so we need to request 67 // that many frames to clear those out. The next frame will have the new size 68 // (if the size has changed) so we will resize the frame pool at this point. 69 // Then, we need to clear any frames that may have delivered to the frame pool 70 // before the resize. Finally, the next frame will be guaranteed to be the new 71 // size. 72 constexpr int kNumCapturesToFlushBuffers = 73 WgcCaptureSession::kNumBuffers * 2 + 1; 74 75 constexpr int kSmallWindowWidth = 200; 76 constexpr int kSmallWindowHeight = 100; 77 constexpr int kMediumWindowWidth = 300; 78 constexpr int kMediumWindowHeight = 200; 79 constexpr int kLargeWindowWidth = 400; 80 constexpr int kLargeWindowHeight = 500; 81 82 // The size of the image we capture is slightly smaller than the actual size of 83 // the window. 84 constexpr int kWindowWidthSubtrahend = 14; 85 constexpr int kWindowHeightSubtrahend = 7; 86 87 // Custom message constants so we can direct our thread to close windows and 88 // quit running. 89 constexpr UINT kDestroyWindow = WM_APP; 90 constexpr UINT kQuitRunning = WM_APP + 1; 91 92 // When testing changes to real windows, sometimes the effects (close or resize) 93 // don't happen immediately, we want to keep trying until we see the effect but 94 // only for a reasonable amount of time. 95 constexpr int kMaxTries = 50; 96 97 } // namespace 98 99 class WgcCapturerWinTest : public ::testing::TestWithParam<CaptureType>, 100 public DesktopCapturer::Callback { 101 public: 102 void SetUp() override { 103 com_initializer_ = 104 std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA); 105 EXPECT_TRUE(com_initializer_->Succeeded()); 106 107 if (!IsWgcSupported(GetParam())) { 108 RTC_LOG(LS_INFO) 109 << "Skipping WgcCapturerWinTests on unsupported platforms."; 110 GTEST_SKIP(); 111 } 112 } 113 114 void SetUpForWindowCapture(int window_width = kMediumWindowWidth, 115 int window_height = kMediumWindowHeight) { 116 capturer_ = WgcCapturerWin::CreateRawWindowCapturer( 117 DesktopCaptureOptions::CreateDefault()); 118 CreateWindowOnSeparateThread(window_width, window_height); 119 StartWindowThreadMessageLoop(); 120 source_id_ = GetTestWindowIdFromSourceList(); 121 } 122 123 void SetUpForScreenCapture() { 124 capturer_ = WgcCapturerWin::CreateRawScreenCapturer( 125 DesktopCaptureOptions::CreateDefault()); 126 source_id_ = GetScreenIdFromSourceList(); 127 } 128 129 void TearDown() override { 130 if (window_open_) { 131 CloseTestWindow(); 132 } 133 } 134 135 // The window must live on a separate thread so that we can run a message pump 136 // without blocking the test thread. This is necessary if we are interested in 137 // having GraphicsCaptureItem events (i.e. the Closed event) fire, and it more 138 // closely resembles how capture works in the wild. 139 void CreateWindowOnSeparateThread(int window_width, int window_height) { 140 window_thread_ = Thread::Create(); 141 window_thread_->SetName(kWindowThreadName, nullptr); 142 window_thread_->Start(); 143 SendTask(window_thread_.get(), [this, window_width, window_height]() { 144 window_thread_id_ = GetCurrentThreadId(); 145 window_info_ = 146 CreateTestWindow(kWindowTitle, window_height, window_width); 147 window_open_ = true; 148 149 while (!IsWindowResponding(window_info_.hwnd)) { 150 RTC_LOG(LS_INFO) << "Waiting for test window to become responsive in " 151 "WgcWindowCaptureTest."; 152 } 153 154 while (!IsWindowValidAndVisible(window_info_.hwnd)) { 155 RTC_LOG(LS_INFO) << "Waiting for test window to be visible in " 156 "WgcWindowCaptureTest."; 157 } 158 }); 159 160 ASSERT_TRUE(window_thread_->RunningForTest()); 161 ASSERT_FALSE(window_thread_->IsCurrent()); 162 } 163 164 void StartWindowThreadMessageLoop() { 165 window_thread_->PostTask([this]() { 166 MSG msg; 167 BOOL gm; 168 while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) { 169 ::DispatchMessage(&msg); 170 if (msg.message == kDestroyWindow) { 171 DestroyTestWindow(window_info_); 172 } 173 if (msg.message == kQuitRunning) { 174 PostQuitMessage(0); 175 } 176 } 177 }); 178 } 179 180 void CloseTestWindow() { 181 ::PostThreadMessage(window_thread_id_, kDestroyWindow, 0, 0); 182 ::PostThreadMessage(window_thread_id_, kQuitRunning, 0, 0); 183 window_thread_->Stop(); 184 window_open_ = false; 185 } 186 187 DesktopCapturer::SourceId GetTestWindowIdFromSourceList() { 188 // Frequently, the test window will not show up in GetSourceList because it 189 // was created too recently. Since we are confident the window will be found 190 // eventually we loop here until we find it. 191 intptr_t src_id = 0; 192 do { 193 DesktopCapturer::SourceList sources; 194 EXPECT_TRUE(capturer_->GetSourceList(&sources)); 195 auto it = std::find_if( 196 sources.begin(), sources.end(), 197 [&](const DesktopCapturer::Source& src) { 198 return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd); 199 }); 200 201 if (it != sources.end()) 202 src_id = it->id; 203 } while (src_id != reinterpret_cast<intptr_t>(window_info_.hwnd)); 204 205 return src_id; 206 } 207 208 DesktopCapturer::SourceId GetScreenIdFromSourceList() { 209 DesktopCapturer::SourceList sources; 210 EXPECT_TRUE(capturer_->GetSourceList(&sources)); 211 EXPECT_GT(sources.size(), 0ULL); 212 return sources[0].id; 213 } 214 215 void DoCapture(int num_captures = 1) { 216 // Capture the requested number of frames. We expect the first capture to 217 // always succeed. If we're asked for multiple frames, we do expect to see a 218 // a couple dropped frames due to resizing the window. 219 const int max_tries = num_captures == 1 ? 1 : kMaxTries; 220 int success_count = 0; 221 for (int i = 0; success_count < num_captures && i < max_tries; i++) { 222 capturer_->CaptureFrame(); 223 if (result_ == DesktopCapturer::Result::ERROR_PERMANENT) 224 break; 225 if (result_ == DesktopCapturer::Result::SUCCESS) 226 success_count++; 227 } 228 229 total_successful_captures_ += success_count; 230 EXPECT_EQ(success_count, num_captures); 231 EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS); 232 EXPECT_TRUE(frame_); 233 EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSuccess), 234 total_successful_captures_); 235 } 236 237 void ValidateFrame(int expected_width, int expected_height) { 238 EXPECT_EQ(frame_->size().width(), expected_width - kWindowWidthSubtrahend); 239 EXPECT_EQ(frame_->size().height(), 240 expected_height - kWindowHeightSubtrahend); 241 242 // Verify the buffer contains as much data as it should. 243 int data_length = frame_->stride() * frame_->size().height(); 244 245 // The first and last pixel should have the same color because they will be 246 // from the border of the window. 247 // Pixels have 4 bytes of data so the whole pixel needs a uint32_t to fit. 248 uint32_t first_pixel = static_cast<uint32_t>(*frame_->data()); 249 uint32_t last_pixel = static_cast<uint32_t>( 250 *(frame_->data() + data_length - DesktopFrame::kBytesPerPixel)); 251 EXPECT_EQ(first_pixel, last_pixel); 252 253 // Let's also check a pixel from the middle of the content area, which the 254 // test window will paint a consistent color for us to verify. 255 uint8_t* middle_pixel = frame_->data() + (data_length / 2); 256 257 int sub_pixel_offset = DesktopFrame::kBytesPerPixel / 4; 258 EXPECT_EQ(*middle_pixel, kTestWindowBValue); 259 middle_pixel += sub_pixel_offset; 260 EXPECT_EQ(*middle_pixel, kTestWindowGValue); 261 middle_pixel += sub_pixel_offset; 262 EXPECT_EQ(*middle_pixel, kTestWindowRValue); 263 middle_pixel += sub_pixel_offset; 264 265 // The window is opaque so we expect 0xFF for the Alpha channel. 266 EXPECT_EQ(*middle_pixel, 0xFF); 267 } 268 269 // DesktopCapturer::Callback interface 270 // The capturer synchronously invokes this method before `CaptureFrame()` 271 // returns. 272 void OnCaptureResult(DesktopCapturer::Result result, 273 std::unique_ptr<DesktopFrame> frame) override { 274 result_ = result; 275 frame_ = std::move(frame); 276 } 277 278 protected: 279 std::unique_ptr<ScopedCOMInitializer> com_initializer_; 280 DWORD window_thread_id_; 281 std::unique_ptr<Thread> window_thread_; 282 WindowInfo window_info_; 283 intptr_t source_id_; 284 bool window_open_ = false; 285 DesktopCapturer::Result result_; 286 int total_successful_captures_ = 0; 287 std::unique_ptr<DesktopFrame> frame_; 288 std::unique_ptr<DesktopCapturer> capturer_; 289 }; 290 291 TEST_P(WgcCapturerWinTest, SelectValidSource) { 292 if (GetParam() == CaptureType::kWindow) { 293 SetUpForWindowCapture(); 294 } else { 295 SetUpForScreenCapture(); 296 } 297 298 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 299 } 300 301 TEST_P(WgcCapturerWinTest, SelectInvalidSource) { 302 if (GetParam() == CaptureType::kWindow) { 303 capturer_ = WgcCapturerWin::CreateRawWindowCapturer( 304 DesktopCaptureOptions::CreateDefault()); 305 source_id_ = kNullWindowId; 306 } else { 307 capturer_ = WgcCapturerWin::CreateRawScreenCapturer( 308 DesktopCaptureOptions::CreateDefault()); 309 source_id_ = kInvalidScreenId; 310 } 311 312 EXPECT_FALSE(capturer_->SelectSource(source_id_)); 313 } 314 315 TEST_P(WgcCapturerWinTest, Capture) { 316 if (GetParam() == CaptureType::kWindow) { 317 SetUpForWindowCapture(); 318 } else { 319 SetUpForScreenCapture(); 320 } 321 322 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 323 324 capturer_->Start(this); 325 EXPECT_GE(metrics::NumEvents(kCapturerImplHistogram, 326 DesktopCapturerId::kWgcCapturerWin), 327 1); 328 329 DoCapture(); 330 EXPECT_GT(frame_->size().width(), 0); 331 EXPECT_GT(frame_->size().height(), 0); 332 } 333 334 TEST_P(WgcCapturerWinTest, CaptureTime) { 335 if (GetParam() == CaptureType::kWindow) { 336 SetUpForWindowCapture(); 337 } else { 338 SetUpForScreenCapture(); 339 } 340 341 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 342 capturer_->Start(this); 343 344 int64_t start_time; 345 start_time = TimeNanos(); 346 capturer_->CaptureFrame(); 347 348 int capture_time_ms = (TimeNanos() - start_time) / kNumNanosecsPerMillisec; 349 EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS); 350 EXPECT_TRUE(frame_); 351 352 // The test may measure the time slightly differently than the capturer. So we 353 // just check if it's within 5 ms. 354 EXPECT_NEAR(frame_->capture_time_ms(), capture_time_ms, 5); 355 EXPECT_GE( 356 metrics::NumEvents(kCaptureTimeHistogram, frame_->capture_time_ms()), 1); 357 } 358 359 INSTANTIATE_TEST_SUITE_P(SourceAgnostic, 360 WgcCapturerWinTest, 361 ::testing::Values(CaptureType::kWindow, 362 CaptureType::kScreen)); 363 364 TEST(WgcCapturerNoMonitorTest, NoMonitors) { 365 ScopedCOMInitializer com_initializer(ScopedCOMInitializer::kMTA); 366 EXPECT_TRUE(com_initializer.Succeeded()); 367 if (HasActiveDisplay()) { 368 RTC_LOG(LS_INFO) << "Skip WgcCapturerWinTest designed specifically for " 369 "systems with no monitors"; 370 GTEST_SKIP(); 371 } 372 373 // A bug in `CreateForMonitor` prevents screen capture when no displays are 374 // attached. 375 EXPECT_FALSE(IsWgcSupported(CaptureType::kScreen)); 376 377 // A bug in the DWM (Desktop Window Manager) prevents it from providing image 378 // data if there are no displays attached. This was fixed in Windows 11. 379 if (rtc_win::GetVersion() < rtc_win::Version::VERSION_WIN11) 380 EXPECT_FALSE(IsWgcSupported(CaptureType::kWindow)); 381 else 382 EXPECT_TRUE(IsWgcSupported(CaptureType::kWindow)); 383 } 384 385 class WgcCapturerMonitorTest : public WgcCapturerWinTest { 386 public: 387 void SetUp() { 388 com_initializer_ = 389 std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA); 390 EXPECT_TRUE(com_initializer_->Succeeded()); 391 392 if (!IsWgcSupported(CaptureType::kScreen)) { 393 RTC_LOG(LS_INFO) 394 << "Skipping WgcCapturerWinTests on unsupported platforms."; 395 GTEST_SKIP(); 396 } 397 } 398 }; 399 400 TEST_F(WgcCapturerMonitorTest, FocusOnMonitor) { 401 SetUpForScreenCapture(); 402 EXPECT_TRUE(capturer_->SelectSource(0)); 403 404 // You can't set focus on a monitor. 405 EXPECT_FALSE(capturer_->FocusOnSelectedSource()); 406 } 407 408 TEST_F(WgcCapturerMonitorTest, CaptureAllMonitors) { 409 SetUpForScreenCapture(); 410 EXPECT_TRUE(capturer_->SelectSource(kFullDesktopScreenId)); 411 412 capturer_->Start(this); 413 DoCapture(); 414 EXPECT_GT(frame_->size().width(), 0); 415 EXPECT_GT(frame_->size().height(), 0); 416 } 417 418 class WgcCapturerWindowTest : public WgcCapturerWinTest { 419 public: 420 void SetUp() { 421 com_initializer_ = 422 std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA); 423 EXPECT_TRUE(com_initializer_->Succeeded()); 424 425 if (!IsWgcSupported(CaptureType::kWindow)) { 426 RTC_LOG(LS_INFO) 427 << "Skipping WgcCapturerWinTests on unsupported platforms."; 428 GTEST_SKIP(); 429 } 430 } 431 }; 432 433 TEST_F(WgcCapturerWindowTest, FocusOnWindow) { 434 capturer_ = WgcCapturerWin::CreateRawWindowCapturer( 435 DesktopCaptureOptions::CreateDefault()); 436 window_info_ = CreateTestWindow(kWindowTitle); 437 source_id_ = GetScreenIdFromSourceList(); 438 439 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 440 EXPECT_TRUE(capturer_->FocusOnSelectedSource()); 441 442 HWND hwnd = reinterpret_cast<HWND>(source_id_); 443 EXPECT_EQ(hwnd, ::GetActiveWindow()); 444 EXPECT_EQ(hwnd, ::GetForegroundWindow()); 445 EXPECT_EQ(hwnd, ::GetFocus()); 446 DestroyTestWindow(window_info_); 447 } 448 449 TEST_F(WgcCapturerWindowTest, SelectMinimizedWindow) { 450 SetUpForWindowCapture(); 451 MinimizeTestWindow(reinterpret_cast<HWND>(source_id_)); 452 EXPECT_FALSE(capturer_->SelectSource(source_id_)); 453 454 UnminimizeTestWindow(reinterpret_cast<HWND>(source_id_)); 455 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 456 } 457 458 TEST_F(WgcCapturerWindowTest, SelectClosedWindow) { 459 SetUpForWindowCapture(); 460 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 461 462 CloseTestWindow(); 463 EXPECT_FALSE(capturer_->SelectSource(source_id_)); 464 } 465 466 TEST_F(WgcCapturerWindowTest, UnsupportedWindowStyle) { 467 // Create a window with the WS_EX_TOOLWINDOW style, which WGC does not 468 // support. 469 window_info_ = CreateTestWindow(kWindowTitle, kMediumWindowWidth, 470 kMediumWindowHeight, WS_EX_TOOLWINDOW); 471 capturer_ = WgcCapturerWin::CreateRawWindowCapturer( 472 DesktopCaptureOptions::CreateDefault()); 473 DesktopCapturer::SourceList sources; 474 EXPECT_TRUE(capturer_->GetSourceList(&sources)); 475 auto it = std::find_if( 476 sources.begin(), sources.end(), [&](const DesktopCapturer::Source& src) { 477 return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd); 478 }); 479 480 // We should not find the window, since we filter for unsupported styles. 481 EXPECT_EQ(it, sources.end()); 482 DestroyTestWindow(window_info_); 483 } 484 485 TEST_F(WgcCapturerWindowTest, IncreaseWindowSizeMidCapture) { 486 SetUpForWindowCapture(kSmallWindowWidth, kSmallWindowHeight); 487 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 488 489 capturer_->Start(this); 490 DoCapture(); 491 ValidateFrame(kSmallWindowWidth, kSmallWindowHeight); 492 493 ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight); 494 DoCapture(kNumCapturesToFlushBuffers); 495 ValidateFrame(kSmallWindowWidth, kMediumWindowHeight); 496 497 ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight); 498 DoCapture(kNumCapturesToFlushBuffers); 499 ValidateFrame(kLargeWindowWidth, kMediumWindowHeight); 500 } 501 502 TEST_F(WgcCapturerWindowTest, ReduceWindowSizeMidCapture) { 503 SetUpForWindowCapture(kLargeWindowWidth, kLargeWindowHeight); 504 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 505 506 capturer_->Start(this); 507 DoCapture(); 508 ValidateFrame(kLargeWindowWidth, kLargeWindowHeight); 509 510 ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight); 511 DoCapture(kNumCapturesToFlushBuffers); 512 ValidateFrame(kLargeWindowWidth, kMediumWindowHeight); 513 514 ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight); 515 DoCapture(kNumCapturesToFlushBuffers); 516 ValidateFrame(kSmallWindowWidth, kMediumWindowHeight); 517 } 518 519 TEST_F(WgcCapturerWindowTest, MinimizeWindowMidCapture) { 520 SetUpForWindowCapture(); 521 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 522 523 capturer_->Start(this); 524 525 // Minmize the window and capture should continue but return temporary errors. 526 MinimizeTestWindow(window_info_.hwnd); 527 for (int i = 0; i < 5; ++i) { 528 capturer_->CaptureFrame(); 529 EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_TEMPORARY); 530 } 531 532 // Reopen the window and the capture should continue normally. 533 UnminimizeTestWindow(window_info_.hwnd); 534 DoCapture(); 535 // We can't verify the window size here because the test window does not 536 // repaint itself after it is unminimized, but capturing successfully is still 537 // a good test. 538 } 539 540 TEST_F(WgcCapturerWindowTest, CloseWindowMidCapture) { 541 SetUpForWindowCapture(); 542 EXPECT_TRUE(capturer_->SelectSource(source_id_)); 543 544 capturer_->Start(this); 545 DoCapture(); 546 ValidateFrame(kMediumWindowWidth, kMediumWindowHeight); 547 548 CloseTestWindow(); 549 550 // We need to pump our message queue so the Closed event will be delivered to 551 // the capturer's event handler. If we are too early and the Closed event 552 // hasn't arrived yet we should keep trying until the capturer receives it and 553 // stops. 554 auto* wgc_capturer = static_cast<WgcCapturerWin*>(capturer_.get()); 555 MSG msg; 556 for (int i = 0; 557 wgc_capturer->IsSourceBeingCaptured(source_id_) && i < kMaxTries; ++i) { 558 // Unlike GetMessage, PeekMessage will not hang if there are no messages in 559 // the queue. 560 PeekMessage(&msg, 0, 0, 0, PM_REMOVE); 561 Thread::SleepMs(1); 562 } 563 564 EXPECT_FALSE(wgc_capturer->IsSourceBeingCaptured(source_id_)); 565 566 // The frame pool can buffer `kNumBuffers` frames. We must consume these 567 // and then make one more call to CaptureFrame before we expect to see the 568 // failure. 569 int num_tries = 0; 570 do { 571 capturer_->CaptureFrame(); 572 } while (result_ == DesktopCapturer::Result::SUCCESS && 573 ++num_tries <= WgcCaptureSession::kNumBuffers); 574 575 EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSessionStartFailure), 576 1); 577 EXPECT_GE(metrics::NumEvents(kCaptureSessionResultHistogram, kSourceClosed), 578 1); 579 EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_PERMANENT); 580 } 581 582 class WgcCapturerFullScreenDetectorTest : public WgcCapturerWindowTest { 583 public: 584 void SetUp() override { 585 capturer_ = WgcCapturerWin::CreateRawWindowCapturer( 586 DesktopCaptureOptions::CreateDefault()); 587 wgc_capturer_ = static_cast<WgcCapturerWin*>(capturer_.get()); 588 589 editor_window_ = CreateEditorWindow(); 590 CreateSlideShowWindow(); 591 WgcCapturerWindowTest::SetUp(); 592 } 593 594 WindowInfo CreateEditorWindow() { 595 return CreateTestWindow( 596 L"My - Title - PowerPoint", kMediumWindowHeight, kMediumWindowWidth, 597 /*extended_styles=*/0, /*window_class=*/L"PPTFrameClass"); 598 } 599 600 void CreateSlideShowWindow() { 601 slide_show_window_ = 602 CreateTestWindow(L"PowerPoint Slide Show - [My - Title]", 603 kLargeWindowHeight, kLargeWindowWidth, 604 /*extended_styles=*/0, 605 /*window_class=*/L"screenClass"); 606 ResizeTestWindowToFullScreen(slide_show_window_.hwnd); 607 } 608 609 WgcCapturerWin* wgc_capturer_; 610 WindowInfo editor_window_; 611 WindowInfo slide_show_window_; 612 }; 613 614 TEST_F(WgcCapturerFullScreenDetectorTest, SlideShowNotFoundByDefaultConfig) { 615 // The default behavior on WGC capturer of `use_heuristic` is false. 616 wgc_capturer_->SetUpFullScreenDetectorForTest( 617 /*use_heuristic=*/false, 618 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd)); 619 620 EXPECT_TRUE(wgc_capturer_->SelectSource( 621 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 622 wgc_capturer_->Start(this); 623 DoCapture(); 624 625 EXPECT_TRUE(wgc_capturer_->IsSourceBeingCaptured( 626 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 627 EXPECT_FALSE(wgc_capturer_->IsSourceBeingCaptured( 628 reinterpret_cast<DesktopCapturer::SourceId>(slide_show_window_.hwnd))); 629 EXPECT_EQ(metrics::NumEvents(kCaptureFullscreenDetectorHistogram, true), 0); 630 } 631 632 TEST_F(WgcCapturerFullScreenDetectorTest, 633 CorrectSlideShowFoundForEditorWhenSlideShowCreatedAfter) { 634 wgc_capturer_->SetUpFullScreenDetectorForTest( 635 /*use_heuristic=*/true, 636 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd)); 637 638 EXPECT_TRUE(wgc_capturer_->SelectSource( 639 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 640 wgc_capturer_->Start(this); 641 DoCapture(); 642 643 EXPECT_FALSE(wgc_capturer_->IsSourceBeingCaptured( 644 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 645 EXPECT_TRUE(wgc_capturer_->IsSourceBeingCaptured( 646 reinterpret_cast<DesktopCapturer::SourceId>(slide_show_window_.hwnd))); 647 648 EXPECT_EQ(metrics::NumEvents(kCaptureFullscreenDetectorHistogram, true), 1); 649 EXPECT_EQ( 650 metrics::NumEvents(kFullScreenDetectorResult, detector_result_success), 651 1); 652 EXPECT_EQ(metrics::NumEvents(kFullScreenDetectorResult, 653 detector_result_failure_slide_show_not_chosen), 654 0); 655 } 656 657 TEST_F(WgcCapturerFullScreenDetectorTest, 658 SlideShowNotFoundForEditorWhenSlideShowCreatedBefore) { 659 wgc_capturer_->SetUpFullScreenDetectorForTest( 660 /*use_heuristic=*/true, 661 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd), 662 /*fullscreen_slide_show_started_after_capture_start=*/false); 663 664 EXPECT_TRUE(wgc_capturer_->SelectSource( 665 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 666 wgc_capturer_->Start(this); 667 DoCapture(); 668 669 EXPECT_TRUE(wgc_capturer_->IsSourceBeingCaptured( 670 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 671 EXPECT_FALSE(wgc_capturer_->IsSourceBeingCaptured( 672 reinterpret_cast<DesktopCapturer::SourceId>(slide_show_window_.hwnd))); 673 674 EXPECT_EQ(metrics::NumEvents(kCaptureFullscreenDetectorHistogram, true), 0); 675 EXPECT_EQ( 676 metrics::NumEvents(kFullScreenDetectorResult, detector_result_success), 677 0); 678 EXPECT_EQ(metrics::NumEvents(kFullScreenDetectorResult, 679 detector_result_failure_slide_show_not_chosen), 680 1); 681 } 682 683 TEST_F(WgcCapturerFullScreenDetectorTest, LoggedOnlyOnce) { 684 wgc_capturer_->SetUpFullScreenDetectorForTest( 685 /*use_heuristic=*/true, 686 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd)); 687 688 EXPECT_TRUE(wgc_capturer_->SelectSource( 689 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 690 wgc_capturer_->Start(this); 691 DoCapture(); 692 DoCapture(); 693 694 EXPECT_TRUE(wgc_capturer_->IsSourceBeingCaptured( 695 reinterpret_cast<DesktopCapturer::SourceId>(slide_show_window_.hwnd))); 696 EXPECT_EQ(metrics::NumEvents(kCaptureFullscreenDetectorHistogram, true), 1); 697 EXPECT_EQ( 698 metrics::NumEvents(kFullScreenDetectorResult, detector_result_success), 699 1); 700 } 701 702 TEST_F(WgcCapturerFullScreenDetectorTest, 703 SlideShowNotFoundWithMultipleSameTitleEditors) { 704 WindowInfo same_title_editor_window = CreateEditorWindow(); 705 EXPECT_NE(editor_window_.hwnd, same_title_editor_window.hwnd); 706 wgc_capturer_->SetUpFullScreenDetectorForTest( 707 /*use_heuristic=*/true, 708 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd)); 709 710 EXPECT_TRUE(wgc_capturer_->SelectSource( 711 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 712 wgc_capturer_->Start(this); 713 DoCapture(); 714 715 EXPECT_TRUE(wgc_capturer_->IsSourceBeingCaptured( 716 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 717 EXPECT_FALSE(wgc_capturer_->IsSourceBeingCaptured( 718 reinterpret_cast<DesktopCapturer::SourceId>(slide_show_window_.hwnd))); 719 EXPECT_EQ(metrics::NumEvents(kCaptureFullscreenDetectorHistogram, true), 0); 720 EXPECT_EQ( 721 metrics::NumEvents(kFullScreenDetectorResult, detector_result_success), 722 0); 723 EXPECT_EQ(metrics::NumEvents(kFullScreenDetectorResult, 724 detector_result_failure_same_title_windows), 725 1); 726 } 727 728 TEST_F(WgcCapturerFullScreenDetectorTest, 729 CaptureTiedToSlideShowIfSlideShowIsShared) { 730 wgc_capturer_->SetUpFullScreenDetectorForTest( 731 /*use_heuristic=*/true, 732 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd)); 733 wgc_capturer_->SetUpFullScreenDetectorForTest( 734 /*use_heuristic=*/true, 735 reinterpret_cast<DesktopCapturer::SourceId>(slide_show_window_.hwnd)); 736 737 EXPECT_TRUE(wgc_capturer_->SelectSource( 738 reinterpret_cast<DesktopCapturer::SourceId>(slide_show_window_.hwnd))); 739 wgc_capturer_->Start(this); 740 DoCapture(); 741 742 EXPECT_FALSE(wgc_capturer_->IsSourceBeingCaptured( 743 reinterpret_cast<DesktopCapturer::SourceId>(editor_window_.hwnd))); 744 EXPECT_TRUE(wgc_capturer_->IsSourceBeingCaptured( 745 reinterpret_cast<DesktopCapturer::SourceId>(slide_show_window_.hwnd))); 746 EXPECT_EQ(metrics::NumEvents(kCaptureFullscreenDetectorHistogram, true), 0); 747 EXPECT_EQ( 748 metrics::NumEvents(kFullScreenDetectorResult, detector_result_success), 749 0); 750 EXPECT_EQ(metrics::NumEvents(kFullScreenDetectorResult, 751 detector_result_failure_slide_show_not_chosen), 752 0); 753 } 754 755 } // namespace webrtc