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