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