blob: 8350ff669be162cc4601ab6ea5d38d63e8efb42b [file] [log] [blame]
/*
* Copyright (c) 2016 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 <string.h>
#include <algorithm>
#include <initializer_list>
#include <iostream> // TODO(zijiehe): Remove once flaky has been resolved.
#include <memory>
#include <utility>
// TODO(zijiehe): Remove once flaky has been resolved.
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/desktop_region.h"
#include "modules/desktop_capture/mock_desktop_capturer_callback.h"
#include "modules/desktop_capture/rgba_color.h"
#include "modules/desktop_capture/screen_drawer.h"
#include "rtc_base/checks.h"
#include "rtc_base/constructor_magic.h"
#include "rtc_base/logging.h"
#include "rtc_base/third_party/base64/base64.h"
#include "test/gmock.h"
#include "test/gtest.h"
#if defined(WEBRTC_WIN)
#include "modules/desktop_capture/win/screen_capturer_win_directx.h"
#include "rtc_base/win/windows_version.h"
#endif // defined(WEBRTC_WIN)
using ::testing::_;
namespace webrtc {
namespace {
ACTION_P2(SaveCaptureResult, result, dest) {
*result = arg0;
*dest = std::move(*arg1);
}
// Returns true if color in `rect` of `frame` is `color`.
bool ArePixelsColoredBy(const DesktopFrame& frame,
DesktopRect rect,
RgbaColor color,
bool may_partially_draw) {
if (!may_partially_draw) {
// updated_region() should cover the painted area.
DesktopRegion updated_region(frame.updated_region());
updated_region.IntersectWith(rect);
if (!updated_region.Equals(DesktopRegion(rect))) {
return false;
}
}
// Color in the `rect` should be `color`.
uint8_t* row = frame.GetFrameDataAtPos(rect.top_left());
for (int i = 0; i < rect.height(); i++) {
uint8_t* column = row;
for (int j = 0; j < rect.width(); j++) {
if (color != RgbaColor(column)) {
return false;
}
column += DesktopFrame::kBytesPerPixel;
}
row += frame.stride();
}
return true;
}
} // namespace
class ScreenCapturerIntegrationTest : public ::testing::Test {
public:
void SetUp() override {
capturer_ = DesktopCapturer::CreateScreenCapturer(
DesktopCaptureOptions::CreateDefault());
}
protected:
void TestCaptureUpdatedRegion(
std::initializer_list<DesktopCapturer*> capturers) {
RTC_DCHECK(capturers.size() > 0);
// A large enough area for the tests, which should be able to be fulfilled
// by most systems.
#if defined(WEBRTC_WIN)
// On Windows, an interesting warning window may pop up randomly. The root
// cause is still under investigation, so reduce the test area to work
// around. Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6666.
const int kTestArea = 416;
#else
const int kTestArea = 512;
#endif
const int kRectSize = 32;
std::unique_ptr<ScreenDrawer> drawer = ScreenDrawer::Create();
if (!drawer || drawer->DrawableRegion().is_empty()) {
RTC_LOG(LS_WARNING)
<< "No ScreenDrawer implementation for current platform.";
return;
}
if (drawer->DrawableRegion().width() < kTestArea ||
drawer->DrawableRegion().height() < kTestArea) {
RTC_LOG(LS_WARNING)
<< "ScreenDrawer::DrawableRegion() is too small for the "
"CaptureUpdatedRegion tests.";
return;
}
for (DesktopCapturer* capturer : capturers) {
capturer->Start(&callback_);
}
// Draw a set of `kRectSize` by `kRectSize` rectangles at (`i`, `i`), or
// `i` by `i` rectangles at (`kRectSize`, `kRectSize`). One of (controlled
// by `c`) its primary colors is `i`, and the other two are 0x7f. So we
// won't draw a black or white rectangle.
for (int c = 0; c < 3; c++) {
// A fixed size rectangle.
for (int i = 0; i < kTestArea - kRectSize; i += 16) {
DesktopRect rect = DesktopRect::MakeXYWH(i, i, kRectSize, kRectSize);
rect.Translate(drawer->DrawableRegion().top_left());
RgbaColor color((c == 0 ? (i & 0xff) : 0x7f),
(c == 1 ? (i & 0xff) : 0x7f),
(c == 2 ? (i & 0xff) : 0x7f));
// Fail fast.
ASSERT_NO_FATAL_FAILURE(
TestCaptureOneFrame(capturers, drawer.get(), rect, color));
}
// A variable-size rectangle.
for (int i = 0; i < kTestArea - kRectSize; i += 16) {
DesktopRect rect = DesktopRect::MakeXYWH(kRectSize, kRectSize, i, i);
rect.Translate(drawer->DrawableRegion().top_left());
RgbaColor color((c == 0 ? (i & 0xff) : 0x7f),
(c == 1 ? (i & 0xff) : 0x7f),
(c == 2 ? (i & 0xff) : 0x7f));
// Fail fast.
ASSERT_NO_FATAL_FAILURE(
TestCaptureOneFrame(capturers, drawer.get(), rect, color));
}
}
}
void TestCaptureUpdatedRegion() {
TestCaptureUpdatedRegion({capturer_.get()});
}
#if defined(WEBRTC_WIN)
// Enable allow_directx_capturer in DesktopCaptureOptions, but let
// DesktopCapturer::CreateScreenCapturer() to decide whether a DirectX
// capturer should be used.
void MaybeCreateDirectxCapturer() {
DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault());
options.set_allow_directx_capturer(true);
capturer_ = DesktopCapturer::CreateScreenCapturer(options);
}
bool CreateDirectxCapturer() {
if (!ScreenCapturerWinDirectx::IsSupported()) {
RTC_LOG(LS_WARNING) << "Directx capturer is not supported";
return false;
}
MaybeCreateDirectxCapturer();
return true;
}
void CreateMagnifierCapturer() {
DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault());
options.set_allow_use_magnification_api(true);
capturer_ = DesktopCapturer::CreateScreenCapturer(options);
}
#endif // defined(WEBRTC_WIN)
std::unique_ptr<DesktopCapturer> capturer_;
MockDesktopCapturerCallback callback_;
private:
// Repeats capturing the frame by using `capturers` one-by-one for 600 times,
// typically 30 seconds, until they succeeded captured a `color` rectangle at
// `rect`. This function uses `drawer`->WaitForPendingDraws() between two
// attempts to wait for the screen to update.
void TestCaptureOneFrame(std::vector<DesktopCapturer*> capturers,
ScreenDrawer* drawer,
DesktopRect rect,
RgbaColor color) {
const int wait_capture_round = 600;
drawer->Clear();
size_t succeeded_capturers = 0;
for (int i = 0; i < wait_capture_round; i++) {
drawer->DrawRectangle(rect, color);
drawer->WaitForPendingDraws();
for (size_t j = 0; j < capturers.size(); j++) {
if (capturers[j] == nullptr) {
// DesktopCapturer should return an empty updated_region() if no
// update detected. So we won't test it again if it has captured the
// rectangle we drew.
continue;
}
std::unique_ptr<DesktopFrame> frame = CaptureFrame(capturers[j]);
if (!frame) {
// CaptureFrame() has triggered an assertion failure already, we only
// need to return here.
return;
}
if (ArePixelsColoredBy(*frame, rect, color,
drawer->MayDrawIncompleteShapes())) {
capturers[j] = nullptr;
succeeded_capturers++;
}
// The following else if statement is for debugging purpose only, which
// should be removed after flaky of ScreenCapturerIntegrationTest has
// been resolved.
else if (i == wait_capture_round - 1) {
std::string result;
rtc::Base64::EncodeFromArray(
frame->data(), frame->size().height() * frame->stride(), &result);
std::cout << frame->size().width() << " x " << frame->size().height()
<< std::endl;
// Split the entire string (can be over 4M) into several lines to
// avoid browser from sticking.
static const size_t kLineLength = 32768;
const char* result_end = result.c_str() + result.length();
for (const char* it = result.c_str(); it < result_end;
it += kLineLength) {
const size_t max_length = result_end - it;
std::cout << std::string(it, std::min(kLineLength, max_length))
<< std::endl;
}
std::cout << "Failed to capture rectangle " << rect.left() << " x "
<< rect.top() << " - " << rect.right() << " x "
<< rect.bottom() << " with color ("
<< static_cast<int>(color.red) << ", "
<< static_cast<int>(color.green) << ", "
<< static_cast<int>(color.blue) << ", "
<< static_cast<int>(color.alpha) << ")" << std::endl;
ASSERT_TRUE(false) << "ScreenCapturerIntegrationTest may be flaky. "
"Please kindly FYI the broken link to "
"zijiehe@chromium.org for investigation. If "
"the failure continually happens, but I have "
"not responded as quick as expected, disable "
"*all* tests in "
"screen_capturer_integration_test.cc to "
"unblock other developers.";
}
}
if (succeeded_capturers == capturers.size()) {
break;
}
}
ASSERT_EQ(succeeded_capturers, capturers.size());
}
// Expects `capturer` to successfully capture a frame, and returns it.
std::unique_ptr<DesktopFrame> CaptureFrame(DesktopCapturer* capturer) {
for (int i = 0; i < 10; i++) {
std::unique_ptr<DesktopFrame> frame;
DesktopCapturer::Result result;
EXPECT_CALL(callback_, OnCaptureResultPtr(_, _))
.WillOnce(SaveCaptureResult(&result, &frame));
capturer->CaptureFrame();
::testing::Mock::VerifyAndClearExpectations(&callback_);
if (result == DesktopCapturer::Result::SUCCESS) {
EXPECT_TRUE(frame);
return frame;
} else {
EXPECT_FALSE(frame);
}
}
EXPECT_TRUE(false);
return nullptr;
}
};
#if defined(WEBRTC_WIN)
// ScreenCapturerWinGdi randomly returns blank screen, the root cause is still
// unknown. Bug, https://bugs.chromium.org/p/webrtc/issues/detail?id=6843.
#define MAYBE_CaptureUpdatedRegion DISABLED_CaptureUpdatedRegion
#else
#define MAYBE_CaptureUpdatedRegion CaptureUpdatedRegion
#endif
TEST_F(ScreenCapturerIntegrationTest, MAYBE_CaptureUpdatedRegion) {
TestCaptureUpdatedRegion();
}
#if defined(WEBRTC_WIN)
// ScreenCapturerWinGdi randomly returns blank screen, the root cause is still
// unknown. Bug, https://bugs.chromium.org/p/webrtc/issues/detail?id=6843.
#define MAYBE_TwoCapturers DISABLED_TwoCapturers
#else
#define MAYBE_TwoCapturers TwoCapturers
#endif
TEST_F(ScreenCapturerIntegrationTest, MAYBE_TwoCapturers) {
std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_);
SetUp();
TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()});
}
#if defined(WEBRTC_WIN)
// Windows cannot capture contents on VMs hosted in GCE. See bug
// https://bugs.chromium.org/p/webrtc/issues/detail?id=8153.
TEST_F(ScreenCapturerIntegrationTest,
DISABLED_CaptureUpdatedRegionWithDirectxCapturer) {
if (!CreateDirectxCapturer()) {
return;
}
TestCaptureUpdatedRegion();
}
TEST_F(ScreenCapturerIntegrationTest, DISABLED_TwoDirectxCapturers) {
if (!CreateDirectxCapturer()) {
return;
}
std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_);
RTC_CHECK(CreateDirectxCapturer());
TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()});
}
TEST_F(ScreenCapturerIntegrationTest,
DISABLED_CaptureUpdatedRegionWithMagnifierCapturer) {
// On Windows 8 or later, magnifier APIs return a frame with a border on test
// environment, so disable these tests.
// Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6844
// TODO(zijiehe): Find the root cause of the border and failure, which cannot
// reproduce on my dev machine.
if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) {
return;
}
CreateMagnifierCapturer();
TestCaptureUpdatedRegion();
}
TEST_F(ScreenCapturerIntegrationTest, DISABLED_TwoMagnifierCapturers) {
// On Windows 8 or later, magnifier APIs return a frame with a border on test
// environment, so disable these tests.
// Bug https://bugs.chromium.org/p/webrtc/issues/detail?id=6844
// TODO(zijiehe): Find the root cause of the border and failure, which cannot
// reproduce on my dev machine.
if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::Version::VERSION_WIN8) {
return;
}
CreateMagnifierCapturer();
std::unique_ptr<DesktopCapturer> capturer2 = std::move(capturer_);
CreateMagnifierCapturer();
TestCaptureUpdatedRegion({capturer_.get(), capturer2.get()});
}
TEST_F(ScreenCapturerIntegrationTest,
DISABLED_MaybeCaptureUpdatedRegionWithDirectxCapturer) {
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN8) {
// ScreenCapturerWinGdi randomly returns blank screen, the root cause is
// still unknown. Bug,
// https://bugs.chromium.org/p/webrtc/issues/detail?id=6843.
// On Windows 7 or early version, MaybeCreateDirectxCapturer() always
// creates GDI capturer.
return;
}
// Even DirectX capturer is not supported in current system, we should be able
// to select a usable capturer.
MaybeCreateDirectxCapturer();
TestCaptureUpdatedRegion();
}
#endif // defined(WEBRTC_WIN)
} // namespace webrtc