xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/screen_capturer_integration_test.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2016 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 <string.h>
12 
13 #include <algorithm>
14 #include <initializer_list>
15 #include <iostream>  // TODO(zijiehe): Remove once flaky has been resolved.
16 #include <memory>
17 #include <utility>
18 
19 // TODO(zijiehe): Remove once flaky has been resolved.
20 #include "modules/desktop_capture/desktop_capture_options.h"
21 #include "modules/desktop_capture/desktop_capturer.h"
22 #include "modules/desktop_capture/desktop_frame.h"
23 #include "modules/desktop_capture/desktop_region.h"
24 #include "modules/desktop_capture/mock_desktop_capturer_callback.h"
25 #include "modules/desktop_capture/rgba_color.h"
26 #include "modules/desktop_capture/screen_drawer.h"
27 #include "rtc_base/checks.h"
28 #include "rtc_base/logging.h"
29 #include "rtc_base/third_party/base64/base64.h"
30 #include "test/gmock.h"
31 #include "test/gtest.h"
32 
33 #if defined(WEBRTC_WIN)
34 #include "modules/desktop_capture/win/screen_capturer_win_directx.h"
35 #include "rtc_base/win/windows_version.h"
36 #endif  // defined(WEBRTC_WIN)
37 
38 using ::testing::_;
39 
40 namespace webrtc {
41 
42 namespace {
43 
ACTION_P2(SaveCaptureResult,result,dest)44 ACTION_P2(SaveCaptureResult, result, dest) {
45   *result = arg0;
46   *dest = std::move(*arg1);
47 }
48 
49 // Returns true if color in `rect` of `frame` is `color`.
ArePixelsColoredBy(const DesktopFrame & frame,DesktopRect rect,RgbaColor color,bool may_partially_draw)50 bool ArePixelsColoredBy(const DesktopFrame& frame,
51                         DesktopRect rect,
52                         RgbaColor color,
53                         bool may_partially_draw) {
54   if (!may_partially_draw) {
55     // updated_region() should cover the painted area.
56     DesktopRegion updated_region(frame.updated_region());
57     updated_region.IntersectWith(rect);
58     if (!updated_region.Equals(DesktopRegion(rect))) {
59       return false;
60     }
61   }
62 
63   // Color in the `rect` should be `color`.
64   uint8_t* row = frame.GetFrameDataAtPos(rect.top_left());
65   for (int i = 0; i < rect.height(); i++) {
66     uint8_t* column = row;
67     for (int j = 0; j < rect.width(); j++) {
68       if (color != RgbaColor(column)) {
69         return false;
70       }
71       column += DesktopFrame::kBytesPerPixel;
72     }
73     row += frame.stride();
74   }
75   return true;
76 }
77 
78 }  // namespace
79 
80 class ScreenCapturerIntegrationTest : public ::testing::Test {
81  public:
SetUp()82   void SetUp() override {
83     capturer_ = DesktopCapturer::CreateScreenCapturer(
84         DesktopCaptureOptions::CreateDefault());
85   }
86 
87  protected:
TestCaptureUpdatedRegion(std::initializer_list<DesktopCapturer * > capturers)88   void TestCaptureUpdatedRegion(
89       std::initializer_list<DesktopCapturer*> capturers) {
90     RTC_DCHECK(capturers.size() > 0);
91 // A large enough area for the tests, which should be able to be fulfilled
92 // by most systems.
93 #if defined(WEBRTC_WIN)
94     // On Windows, an interesting warning window may pop up randomly. The root
95     // cause is still under investigation, so reduce the test area to work
96     // around. Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6666.
97     const int kTestArea = 416;
98 #else
99     const int kTestArea = 512;
100 #endif
101     const int kRectSize = 32;
102     std::unique_ptr<ScreenDrawer> drawer = ScreenDrawer::Create();
103     if (!drawer || drawer->DrawableRegion().is_empty()) {
104       RTC_LOG(LS_WARNING)
105           << "No ScreenDrawer implementation for current platform.";
106       return;
107     }
108     if (drawer->DrawableRegion().width() < kTestArea ||
109         drawer->DrawableRegion().height() < kTestArea) {
110       RTC_LOG(LS_WARNING)
111           << "ScreenDrawer::DrawableRegion() is too small for the "
112              "CaptureUpdatedRegion tests.";
113       return;
114     }
115 
116     for (DesktopCapturer* capturer : capturers) {
117       capturer->Start(&callback_);
118     }
119 
120     // Draw a set of `kRectSize` by `kRectSize` rectangles at (`i`, `i`), or
121     // `i` by `i` rectangles at (`kRectSize`, `kRectSize`). One of (controlled
122     // by `c`) its primary colors is `i`, and the other two are 0x7f. So we
123     // won't draw a black or white rectangle.
124     for (int c = 0; c < 3; c++) {
125       // A fixed size rectangle.
126       for (int i = 0; i < kTestArea - kRectSize; i += 16) {
127         DesktopRect rect = DesktopRect::MakeXYWH(i, i, kRectSize, kRectSize);
128         rect.Translate(drawer->DrawableRegion().top_left());
129         RgbaColor color((c == 0 ? (i & 0xff) : 0x7f),
130                         (c == 1 ? (i & 0xff) : 0x7f),
131                         (c == 2 ? (i & 0xff) : 0x7f));
132         // Fail fast.
133         ASSERT_NO_FATAL_FAILURE(
134             TestCaptureOneFrame(capturers, drawer.get(), rect, color));
135       }
136 
137       // A variable-size rectangle.
138       for (int i = 0; i < kTestArea - kRectSize; i += 16) {
139         DesktopRect rect = DesktopRect::MakeXYWH(kRectSize, kRectSize, i, i);
140         rect.Translate(drawer->DrawableRegion().top_left());
141         RgbaColor color((c == 0 ? (i & 0xff) : 0x7f),
142                         (c == 1 ? (i & 0xff) : 0x7f),
143                         (c == 2 ? (i & 0xff) : 0x7f));
144         // Fail fast.
145         ASSERT_NO_FATAL_FAILURE(
146             TestCaptureOneFrame(capturers, drawer.get(), rect, color));
147       }
148     }
149   }
150 
TestCaptureUpdatedRegion()151   void TestCaptureUpdatedRegion() {
152     TestCaptureUpdatedRegion({capturer_.get()});
153   }
154 
155 #if defined(WEBRTC_WIN)
156   // Enable allow_directx_capturer in DesktopCaptureOptions, but let
157   // DesktopCapturer::CreateScreenCapturer() to decide whether a DirectX
158   // capturer should be used.
MaybeCreateDirectxCapturer()159   void MaybeCreateDirectxCapturer() {
160     DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault());
161     options.set_allow_directx_capturer(true);
162     capturer_ = DesktopCapturer::CreateScreenCapturer(options);
163   }
164 
CreateDirectxCapturer()165   bool CreateDirectxCapturer() {
166     if (!ScreenCapturerWinDirectx::IsSupported()) {
167       RTC_LOG(LS_WARNING) << "Directx capturer is not supported";
168       return false;
169     }
170 
171     MaybeCreateDirectxCapturer();
172     return true;
173   }
174 
CreateMagnifierCapturer()175   void CreateMagnifierCapturer() {
176     DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault());
177     options.set_allow_use_magnification_api(true);
178     capturer_ = DesktopCapturer::CreateScreenCapturer(options);
179   }
180 #endif  // defined(WEBRTC_WIN)
181 
182   std::unique_ptr<DesktopCapturer> capturer_;
183   MockDesktopCapturerCallback callback_;
184 
185  private:
186   // Repeats capturing the frame by using `capturers` one-by-one for 600 times,
187   // typically 30 seconds, until they succeeded captured a `color` rectangle at
188   // `rect`. This function uses `drawer`->WaitForPendingDraws() between two
189   // attempts to wait for the screen to update.
TestCaptureOneFrame(std::vector<DesktopCapturer * > capturers,ScreenDrawer * drawer,DesktopRect rect,RgbaColor color)190   void TestCaptureOneFrame(std::vector<DesktopCapturer*> capturers,
191                            ScreenDrawer* drawer,
192                            DesktopRect rect,
193                            RgbaColor color) {
194     const int wait_capture_round = 600;
195     drawer->Clear();
196     size_t succeeded_capturers = 0;
197     for (int i = 0; i < wait_capture_round; i++) {
198       drawer->DrawRectangle(rect, color);
199       drawer->WaitForPendingDraws();
200       for (size_t j = 0; j < capturers.size(); j++) {
201         if (capturers[j] == nullptr) {
202           // DesktopCapturer should return an empty updated_region() if no
203           // update detected. So we won't test it again if it has captured the
204           // rectangle we drew.
205           continue;
206         }
207         std::unique_ptr<DesktopFrame> frame = CaptureFrame(capturers[j]);
208         if (!frame) {
209           // CaptureFrame() has triggered an assertion failure already, we only
210           // need to return here.
211           return;
212         }
213 
214         if (ArePixelsColoredBy(*frame, rect, color,
215                                drawer->MayDrawIncompleteShapes())) {
216           capturers[j] = nullptr;
217           succeeded_capturers++;
218         }
219         // The following else if statement is for debugging purpose only, which
220         // should be removed after flaky of ScreenCapturerIntegrationTest has
221         // been resolved.
222         else if (i == wait_capture_round - 1) {
223           std::string result;
224           rtc::Base64::EncodeFromArray(
225               frame->data(), frame->size().height() * frame->stride(), &result);
226           std::cout << frame->size().width() << " x " << frame->size().height()
227                     << std::endl;
228           // Split the entire string (can be over 4M) into several lines to
229           // avoid browser from sticking.
230           static const size_t kLineLength = 32768;
231           const char* result_end = result.c_str() + result.length();
232           for (const char* it = result.c_str(); it < result_end;
233                it += kLineLength) {
234             const size_t max_length = result_end - it;
235             std::cout << std::string(it, std::min(kLineLength, max_length))
236                       << std::endl;
237           }
238           std::cout << "Failed to capture rectangle " << rect.left() << " x "
239                     << rect.top() << " - " << rect.right() << " x "
240                     << rect.bottom() << " with color ("
241                     << static_cast<int>(color.red) << ", "
242                     << static_cast<int>(color.green) << ", "
243                     << static_cast<int>(color.blue) << ", "
244                     << static_cast<int>(color.alpha) << ")" << std::endl;
245           ASSERT_TRUE(false) << "ScreenCapturerIntegrationTest may be flaky. "
246                                 "Please kindly FYI the broken link to "
247                                 "[email protected] for investigation. If "
248                                 "the failure continually happens, but I have "
249                                 "not responded as quick as expected, disable "
250                                 "*all* tests in "
251                                 "screen_capturer_integration_test.cc to "
252                                 "unblock other developers.";
253         }
254       }
255 
256       if (succeeded_capturers == capturers.size()) {
257         break;
258       }
259     }
260 
261     ASSERT_EQ(succeeded_capturers, capturers.size());
262   }
263 
264   // Expects `capturer` to successfully capture a frame, and returns it.
CaptureFrame(DesktopCapturer * capturer)265   std::unique_ptr<DesktopFrame> CaptureFrame(DesktopCapturer* capturer) {
266     for (int i = 0; i < 10; i++) {
267       std::unique_ptr<DesktopFrame> frame;
268       DesktopCapturer::Result result;
269       EXPECT_CALL(callback_, OnCaptureResultPtr(_, _))
270           .WillOnce(SaveCaptureResult(&result, &frame));
271       capturer->CaptureFrame();
272       ::testing::Mock::VerifyAndClearExpectations(&callback_);
273       if (result == DesktopCapturer::Result::SUCCESS) {
274         EXPECT_TRUE(frame);
275         return frame;
276       } else {
277         EXPECT_FALSE(frame);
278       }
279     }
280 
281     EXPECT_TRUE(false);
282     return nullptr;
283   }
284 };
285 
286 #if defined(WEBRTC_WIN)
287 // ScreenCapturerWinGdi randomly returns blank screen, the root cause is still
288 // unknown. Bug, https://bugs.chromium.org/p/webrtc/issues/detail?id=6843.
289 #define MAYBE_CaptureUpdatedRegion DISABLED_CaptureUpdatedRegion
290 #else
291 #define MAYBE_CaptureUpdatedRegion CaptureUpdatedRegion
292 #endif
TEST_F(ScreenCapturerIntegrationTest,MAYBE_CaptureUpdatedRegion)293 TEST_F(ScreenCapturerIntegrationTest, MAYBE_CaptureUpdatedRegion) {
294   TestCaptureUpdatedRegion();
295 }
296 
297 #if defined(WEBRTC_WIN)
298 // ScreenCapturerWinGdi randomly returns blank screen, the root cause is still
299 // unknown. Bug, https://bugs.chromium.org/p/webrtc/issues/detail?id=6843.
300 #define MAYBE_TwoCapturers DISABLED_TwoCapturers
301 #else
302 #define MAYBE_TwoCapturers TwoCapturers
303 #endif
TEST_F(ScreenCapturerIntegrationTest,MAYBE_TwoCapturers)304 TEST_F(ScreenCapturerIntegrationTest, MAYBE_TwoCapturers) {
305   std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_);
306   SetUp();
307   TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()});
308 }
309 
310 #if defined(WEBRTC_WIN)
311 
312 // Windows cannot capture contents on VMs hosted in GCE. See bug
313 // https://bugs.chromium.org/p/webrtc/issues/detail?id=8153.
TEST_F(ScreenCapturerIntegrationTest,DISABLED_CaptureUpdatedRegionWithDirectxCapturer)314 TEST_F(ScreenCapturerIntegrationTest,
315        DISABLED_CaptureUpdatedRegionWithDirectxCapturer) {
316   if (!CreateDirectxCapturer()) {
317     return;
318   }
319 
320   TestCaptureUpdatedRegion();
321 }
322 
TEST_F(ScreenCapturerIntegrationTest,DISABLED_TwoDirectxCapturers)323 TEST_F(ScreenCapturerIntegrationTest, DISABLED_TwoDirectxCapturers) {
324   if (!CreateDirectxCapturer()) {
325     return;
326   }
327 
328   std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_);
329   RTC_CHECK(CreateDirectxCapturer());
330   TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()});
331 }
332 
TEST_F(ScreenCapturerIntegrationTest,DISABLED_CaptureUpdatedRegionWithMagnifierCapturer)333 TEST_F(ScreenCapturerIntegrationTest,
334        DISABLED_CaptureUpdatedRegionWithMagnifierCapturer) {
335   // On Windows 8 or later, magnifier APIs return a frame with a border on test
336   // environment, so disable these tests.
337   // Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6844
338   // TODO(zijiehe): Find the root cause of the border and failure, which cannot
339   // reproduce on my dev machine.
340   if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) {
341     return;
342   }
343   CreateMagnifierCapturer();
344   TestCaptureUpdatedRegion();
345 }
346 
TEST_F(ScreenCapturerIntegrationTest,DISABLED_TwoMagnifierCapturers)347 TEST_F(ScreenCapturerIntegrationTest, DISABLED_TwoMagnifierCapturers) {
348   // On Windows 8 or later, magnifier APIs return a frame with a border on test
349   // environment, so disable these tests.
350   // Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6844
351   // TODO(zijiehe): Find the root cause of the border and failure, which cannot
352   // reproduce on my dev machine.
353   if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) {
354     return;
355   }
356   CreateMagnifierCapturer();
357   std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_);
358   CreateMagnifierCapturer();
359   TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()});
360 }
361 
TEST_F(ScreenCapturerIntegrationTest,DISABLED_MaybeCaptureUpdatedRegionWithDirectxCapturer)362 TEST_F(ScreenCapturerIntegrationTest,
363        DISABLED_MaybeCaptureUpdatedRegionWithDirectxCapturer) {
364   if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN8) {
365     // ScreenCapturerWinGdi randomly returns blank screen, the root cause is
366     // still unknown. Bug,
367     // https://bugs.chromium.org/p/webrtc/issues/detail?id=6843.
368     // On Windows 7 or early version, MaybeCreateDirectxCapturer() always
369     // creates GDI capturer.
370     return;
371   }
372   // Even DirectX capturer is not supported in current system, we should be able
373   // to select a usable capturer.
374   MaybeCreateDirectxCapturer();
375   TestCaptureUpdatedRegion();
376 }
377 
378 #endif  // defined(WEBRTC_WIN)
379 
380 }  // namespace webrtc
381