Commit 89733b60 authored by Yuri Wiitala's avatar Yuri Wiitala Committed by Commit Bot

Refactor many components of WCVCDBrowserTest for re-use.

Moves/Refactors a lot of test infrastructure that can be re-used in an
upcoming change to also test the new browser window capture impl:

ContentCaptureDeviceBrowserTestBase: A common base class that sets up a
content shell navigated to a test page, whose content can also be
changed as test procedures require.

FakeVideoCaptureStack: A simple representation of the entire downstream
video capture stack that just takes the screen-captured video frames an
stores them in a queue for later examination by the tests.

FrameTestUtil: Math/Color utilities for analyzing the content in the
captured video frames.

Bug: 806366
Change-Id: I899db11043944ea5a1206a58b2593a317222fdab
Reviewed-on: https://chromium-review.googlesource.com/1006072
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Reviewed-by: default avatarXiangjun Zhang <xjz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#558034}
parent b137b49a
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/capture/content_capture_device_browsertest_base.h"
#include <cmath>
#include <utility>
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "content/browser/media/capture/frame_sink_video_capture_device.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using net::test_server::BasicHttpResponse;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
namespace content {
ContentCaptureDeviceBrowserTestBase::ContentCaptureDeviceBrowserTestBase() =
default;
ContentCaptureDeviceBrowserTestBase::~ContentCaptureDeviceBrowserTestBase() =
default;
void ContentCaptureDeviceBrowserTestBase::ChangePageContentColor(
std::string css_color_hex) {
// See the HandleRequest() method for the original documents being modified
// here.
std::string script;
if (IsCrossSiteCaptureTest()) {
const GURL& inner_frame_url =
embedded_test_server()->GetURL(kInnerFrameHostname, kInnerFramePath);
script = base::StringPrintf(
"document.getElementsByTagName('iframe')[0].src = '%s?color=123456';",
inner_frame_url.spec().c_str());
} else {
script = "document.body.style.backgroundColor = '#123456';";
}
script.replace(script.find("123456"), 6, css_color_hex);
CHECK(ExecuteScript(shell()->web_contents(), script));
}
gfx::Size ContentCaptureDeviceBrowserTestBase::GetExpectedSourceSize() {
const gfx::Size source_size = GetCapturedSourceSize();
if (expected_source_size_) {
EXPECT_EQ(*expected_source_size_, source_size)
<< "Sanity-check failed: Source size changed during this test.";
} else {
expected_source_size_.emplace(source_size);
VLOG(1) << "Captured source size is " << expected_source_size_->ToString();
}
return source_size;
}
media::VideoCaptureParams
ContentCaptureDeviceBrowserTestBase::SnapshotCaptureParams() {
constexpr gfx::Size kMaxCaptureSize = gfx::Size(320, 320);
constexpr int kMaxFramesPerSecond = 60;
gfx::Size capture_size = kMaxCaptureSize;
if (IsFixedAspectRatioTest()) {
// Half either the width or height, depending on the source size. The goal
// is to force obvious letterboxing (or pillarboxing), regardless of how the
// source is currently sized/oriented.
const gfx::Size source_size = GetExpectedSourceSize();
if (source_size.width() < source_size.height()) {
capture_size.set_height(capture_size.height() / 2);
} else {
capture_size.set_width(capture_size.width() / 2);
}
}
media::VideoCaptureParams params;
params.requested_format = media::VideoCaptureFormat(
capture_size, kMaxFramesPerSecond, media::PIXEL_FORMAT_I420);
params.resolution_change_policy =
IsFixedAspectRatioTest()
? media::ResolutionChangePolicy::FIXED_ASPECT_RATIO
: media::ResolutionChangePolicy::ANY_WITHIN_LIMIT;
return params;
}
base::TimeDelta ContentCaptureDeviceBrowserTestBase::GetMinCapturePeriod() {
return base::TimeDelta::FromMicroseconds(
base::Time::kMicrosecondsPerSecond /
device_->capture_params().requested_format.frame_rate);
}
void ContentCaptureDeviceBrowserTestBase::NavigateToInitialDocument() {
// If doing a cross-site capture test, navigate to the more-complex document
// that also contains an iframe (rendered in a separate process). Otherwise,
// navigate to the simpler document.
if (IsCrossSiteCaptureTest()) {
ASSERT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL(kOuterFrameHostname, kOuterFramePath)));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
// Confirm the iframe is a cross-process child render frame.
auto* const child_frame =
ChildFrameAt(shell()->web_contents()->GetMainFrame(), 0);
ASSERT_TRUE(child_frame);
ASSERT_TRUE(child_frame->IsCrossProcessSubframe());
} else {
ASSERT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL(
kSingleFrameHostname, kSingleFramePath)));
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
}
}
void ContentCaptureDeviceBrowserTestBase::
AllocateAndStartAndWaitForFirstFrame() {
capture_stack()->Reset();
device_ = CreateDevice();
device_->AllocateAndStartWithReceiver(SnapshotCaptureParams(),
capture_stack()->CreateFrameReceiver());
RunUntilIdle();
EXPECT_TRUE(capture_stack()->started());
EXPECT_FALSE(capture_stack()->error_occurred());
capture_stack()->ExpectNoLogMessages();
WaitForFirstFrame();
}
void ContentCaptureDeviceBrowserTestBase::StopAndDeAllocate() {
device_->StopAndDeAllocate();
RunUntilIdle();
device_.reset();
}
void ContentCaptureDeviceBrowserTestBase::RunUntilIdle() {
base::RunLoop().RunUntilIdle();
}
bool ContentCaptureDeviceBrowserTestBase::IsSoftwareCompositingTest() const {
return false;
}
bool ContentCaptureDeviceBrowserTestBase::IsFixedAspectRatioTest() const {
return false;
}
bool ContentCaptureDeviceBrowserTestBase::IsCrossSiteCaptureTest() const {
return false;
}
void ContentCaptureDeviceBrowserTestBase::SetUp() {
// IMPORTANT: Do not add the switches::kUseGpuInTests command line flag: It
// causes the tests to take 12+ seconds just to spin up a render process on
// debug builds. It can also cause test failures in MSAN builds, or exacerbate
// OOM situations on highly-loaded machines.
// Screen capture requires readback from compositor output.
EnablePixelOutput();
// Conditionally force software compositing instead of GPU-accelerated
// compositing.
if (IsSoftwareCompositingTest()) {
UseSoftwareCompositing();
}
ContentBrowserTest::SetUp();
}
void ContentCaptureDeviceBrowserTestBase::SetUpCommandLine(
base::CommandLine* command_line) {
IsolateAllSitesForTesting(command_line);
}
void ContentCaptureDeviceBrowserTestBase::SetUpOnMainThread() {
ContentBrowserTest::SetUpOnMainThread();
// Set-up and start the embedded test HTTP server.
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(&ContentCaptureDeviceBrowserTestBase::HandleRequest,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
}
void ContentCaptureDeviceBrowserTestBase::TearDownOnMainThread() {
ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
ClearCapturedFramesQueue();
// Run any left-over tasks (usually these are delete-soon's and orphaned
// tasks).
RunUntilIdle();
ContentBrowserTest::TearDownOnMainThread();
}
std::unique_ptr<HttpResponse>
ContentCaptureDeviceBrowserTestBase::HandleRequest(const HttpRequest& request) {
auto response = std::make_unique<BasicHttpResponse>();
response->set_content_type("text/html");
const GURL& url = request.GetURL();
if (url.path() == kOuterFramePath) {
// A page with a solid white fill color, but containing an iframe in its
// upper-left quadrant.
const GURL& inner_frame_url =
embedded_test_server()->GetURL(kInnerFrameHostname, kInnerFramePath);
response->set_content(base::StringPrintf(
"<!doctype html>"
"<body style='background-color: #ffffff;'>"
"<iframe src='%s' style='position:absolute; "
"top:0px; left:0px; margin:none; padding:none; border:none;'>"
"</iframe>"
"<script>"
"window.addEventListener('load', () => {"
" const iframe = document.getElementsByTagName('iframe')[0];"
" iframe.width = document.documentElement.clientWidth / 2;"
" iframe.height = document.documentElement.clientHeight / 2;"
"});"
"</script>"
"</body>",
inner_frame_url.spec().c_str()));
} else {
// A page whose solid fill color is based on a query parameter, or
// defaults to black.
const std::string& query = url.query();
std::string color = "#000000";
const auto pos = query.find("color=");
if (pos != std::string::npos) {
color = "#" + query.substr(pos + 6, 6);
}
response->set_content(
base::StringPrintf("<!doctype html>"
"<body style='background-color: %s;'></body>",
color.c_str()));
}
return std::move(response);
}
// static
constexpr char ContentCaptureDeviceBrowserTestBase::kInnerFrameHostname[];
// static
constexpr char ContentCaptureDeviceBrowserTestBase::kInnerFramePath[];
// static
constexpr char ContentCaptureDeviceBrowserTestBase::kOuterFrameHostname[];
// static
constexpr char ContentCaptureDeviceBrowserTestBase::kOuterFramePath[];
// static
constexpr char ContentCaptureDeviceBrowserTestBase::kSingleFrameHostname[];
// static
constexpr char ContentCaptureDeviceBrowserTestBase::kSingleFramePath[];
} // namespace content
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_MEDIA_CAPTURE_CONTENT_CAPTURE_DEVICE_BROWSERTEST_BASE_H_
#define CONTENT_BROWSER_MEDIA_CAPTURE_CONTENT_CAPTURE_DEVICE_BROWSERTEST_BASE_H_
#include <memory>
#include <string>
#include "base/macros.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/media/capture/fake_video_capture_stack.h"
#include "content/public/test/content_browser_test.h"
#include "media/capture/video_capture_types.h"
#include "ui/gfx/geometry/size.h"
namespace net {
namespace test_server {
struct HttpRequest;
class HttpResponse;
} // namespace test_server
} // namespace net
namespace content {
class FrameSinkVideoCaptureDevice;
// Common base class for screen capture browser tests. Since this is a
// ContentBrowserTest, it assumes the test environment consists of a content
// shell and a single WebContents.
class ContentCaptureDeviceBrowserTestBase : public ContentBrowserTest {
public:
ContentCaptureDeviceBrowserTestBase();
~ContentCaptureDeviceBrowserTestBase() override;
FakeVideoCaptureStack* capture_stack() { return &capture_stack_; }
FrameSinkVideoCaptureDevice* device() const { return device_.get(); }
// Alters the solid fill color making up the page content. This will trigger a
// compositor update, which will trigger a frame capture.
void ChangePageContentColor(std::string css_color_hex);
// Returns the captured source size, but also sanity-checks that it is not
// changing during the test. Prefer to use this method instead of
// GetCapturedSourceSize() to improve test stability.
gfx::Size GetExpectedSourceSize();
// Returns capture parameters based on the captured source size.
media::VideoCaptureParams SnapshotCaptureParams();
// Returns the actual minimum capture period the device is using. This should
// not be called until after AllocateAndStartAndWaitForFirstFrame().
base::TimeDelta GetMinCapturePeriod();
// Navigates to the initial document, according to the current test
// parameters, and waits for page load completion. All test fixtures should
// call this before any of the other methods.
void NavigateToInitialDocument();
// Creates and starts the device for frame capture, and checks that the
// initial refresh frame is delivered.
void AllocateAndStartAndWaitForFirstFrame();
// Stops and destroys the device.
void StopAndDeAllocate();
// Runs the message loop until idle.
void RunUntilIdle();
void ClearCapturedFramesQueue() { capture_stack_.ClearCapturedFramesQueue(); }
bool HasCapturedFramesInQueue() const {
return capture_stack_.has_captured_frames();
}
protected:
// These all return false, but can be overridden for parameterized tests to
// change the behavior of this base class.
virtual bool IsSoftwareCompositingTest() const;
virtual bool IsFixedAspectRatioTest() const;
virtual bool IsCrossSiteCaptureTest() const;
// Returns the size of the original content (i.e., not including any
// stretching/scaling being done to fit it within a video frame).
virtual gfx::Size GetCapturedSourceSize() const = 0;
// Returns a new FrameSinkVideoCaptureDevice instance.
virtual std::unique_ptr<FrameSinkVideoCaptureDevice> CreateDevice() = 0;
// Called to wait for the first frame with expected content.
virtual void WaitForFirstFrame() = 0;
// ContentBrowserTest overrides to enable pixel output and set-up/tear-down
// the embedded HTTP server that provides test content.
void SetUp() override;
void SetUpCommandLine(base::CommandLine* command_line) override;
void SetUpOnMainThread() override;
void TearDownOnMainThread() override;
private:
// Called by the embedded test HTTP server to provide the document resources.
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request);
FakeVideoCaptureStack capture_stack_;
base::Optional<gfx::Size> expected_source_size_;
std::unique_ptr<FrameSinkVideoCaptureDevice> device_;
// Arbitrary string constants used to refer to each document by
// host+path. Note that the "inner frame" and "outer frame" must have
// different hostnames to engage the cross-site process isolation logic in the
// browser.
static constexpr char kInnerFrameHostname[] = "innerframe.com";
static constexpr char kInnerFramePath[] = "/inner.html";
static constexpr char kOuterFrameHostname[] = "outerframe.com";
static constexpr char kOuterFramePath[] = "/outer.html";
static constexpr char kSingleFrameHostname[] = "singleframe.com";
static constexpr char kSingleFramePath[] = "/single.html";
DISALLOW_COPY_AND_ASSIGN(ContentCaptureDeviceBrowserTestBase);
};
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_CAPTURE_CONTENT_CAPTURE_DEVICE_BROWSERTEST_BASE_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/capture/fake_video_capture_stack.h"
#include <stdint.h>
#include <utility>
#include "base/bind_helpers.h"
#include "media/base/video_frame.h"
#include "media/capture/video/video_frame_receiver.h"
#include "media/capture/video_capture_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/geometry/rect.h"
namespace content {
FakeVideoCaptureStack::FakeVideoCaptureStack() = default;
FakeVideoCaptureStack::~FakeVideoCaptureStack() = default;
void FakeVideoCaptureStack::Reset() {
frames_.clear();
last_frame_timestamp_ = base::TimeDelta::Min();
}
class FakeVideoCaptureStack::Receiver : public media::VideoFrameReceiver {
public:
explicit Receiver(FakeVideoCaptureStack* capture_stack)
: capture_stack_(capture_stack) {}
~Receiver() final = default;
private:
using Buffer = media::VideoCaptureDevice::Client::Buffer;
void OnNewBuffer(int buffer_id,
media::mojom::VideoBufferHandlePtr buffer_handle) final {
buffers_[buffer_id] = std::move(buffer_handle);
}
void OnFrameReadyInBuffer(
int buffer_id,
int frame_feedback_id,
std::unique_ptr<Buffer::ScopedAccessPermission> access,
media::mojom::VideoFrameInfoPtr frame_info) final {
const auto it = buffers_.find(buffer_id);
CHECK(it != buffers_.end());
CHECK(it->second->is_shared_buffer_handle());
mojo::ScopedSharedBufferHandle& buffer =
it->second->get_shared_buffer_handle();
const size_t mapped_size =
media::VideoCaptureFormat(frame_info->coded_size, 0.0f,
frame_info->pixel_format)
.ImageAllocationSize();
mojo::ScopedSharedBufferMapping mapping = buffer->Map(mapped_size);
CHECK(mapping.get());
auto frame = media::VideoFrame::WrapExternalData(
frame_info->pixel_format, frame_info->coded_size,
frame_info->visible_rect, frame_info->visible_rect.size(),
reinterpret_cast<uint8_t*>(mapping.get()), mapped_size,
frame_info->timestamp);
CHECK(frame);
frame->metadata()->MergeInternalValuesFrom(frame_info->metadata);
// This destruction observer will unmap the shared memory when the
// VideoFrame goes out-of-scope.
frame->AddDestructionObserver(
base::BindOnce(base::DoNothing::Once<mojo::ScopedSharedBufferMapping>(),
std::move(mapping)));
// This destruction observer will notify the video capture device once all
// downstream code is done using the VideoFrame.
frame->AddDestructionObserver(base::BindOnce(
[](std::unique_ptr<Buffer::ScopedAccessPermission> access) {},
std::move(access)));
capture_stack_->OnReceivedFrame(std::move(frame));
}
void OnBufferRetired(int buffer_id) final {
const auto it = buffers_.find(buffer_id);
CHECK(it != buffers_.end());
buffers_.erase(it);
}
void OnError() final { capture_stack_->error_occurred_ = true; }
void OnLog(const std::string& message) final {
capture_stack_->log_messages_.push_back(message);
}
void OnStarted() final { capture_stack_->started_ = true; }
void OnStartedUsingGpuDecode() final { NOTREACHED(); }
FakeVideoCaptureStack* const capture_stack_;
base::flat_map<int, media::mojom::VideoBufferHandlePtr> buffers_;
DISALLOW_COPY_AND_ASSIGN(Receiver);
};
std::unique_ptr<media::VideoFrameReceiver>
FakeVideoCaptureStack::CreateFrameReceiver() {
return std::make_unique<Receiver>(this);
}
SkBitmap FakeVideoCaptureStack::NextCapturedFrame() {
CHECK(!frames_.empty());
media::VideoFrame& frame = *(frames_.front());
SkBitmap bitmap;
bitmap.allocN32Pixels(frame.visible_rect().width(),
frame.visible_rect().height());
// TODO(crbug/810131): This is not Rec.709 colorspace conversion, and so will
// introduce inaccuracies.
libyuv::I420ToARGB(frame.visible_data(media::VideoFrame::kYPlane),
frame.stride(media::VideoFrame::kYPlane),
frame.visible_data(media::VideoFrame::kUPlane),
frame.stride(media::VideoFrame::kUPlane),
frame.visible_data(media::VideoFrame::kVPlane),
frame.stride(media::VideoFrame::kVPlane),
reinterpret_cast<uint8_t*>(bitmap.getPixels()),
static_cast<int>(bitmap.rowBytes()), bitmap.width(),
bitmap.height());
frames_.pop_front();
return bitmap;
}
void FakeVideoCaptureStack::ClearCapturedFramesQueue() {
frames_.clear();
}
void FakeVideoCaptureStack::ExpectHasLogMessages() {
EXPECT_FALSE(log_messages_.empty());
while (!log_messages_.empty()) {
VLOG(1) << "Next log message: " << log_messages_.front();
log_messages_.pop_front();
}
}
void FakeVideoCaptureStack::ExpectNoLogMessages() {
while (!log_messages_.empty()) {
ADD_FAILURE() << "Unexpected log message: " << log_messages_.front();
log_messages_.pop_front();
}
}
void FakeVideoCaptureStack::OnReceivedFrame(
scoped_refptr<media::VideoFrame> frame) {
// Frame timestamps should be monotionically increasing.
EXPECT_LT(last_frame_timestamp_, frame->timestamp());
last_frame_timestamp_ = frame->timestamp();
frames_.emplace_back(std::move(frame));
}
} // namespace content
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_MEDIA_CAPTURE_FAKE_VIDEO_CAPTURE_STACK_H_
#define CONTENT_BROWSER_MEDIA_CAPTURE_FAKE_VIDEO_CAPTURE_STACK_H_
#include <memory>
#include <string>
#include "base/containers/circular_deque.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace media {
class VideoFrame;
class VideoFrameReceiver;
}; // namespace media
namespace content {
// Provides a fake representation of the entire video capture stack. It creates
// a VideoFrameReceiver that a device can deliver video frames to, and adapts
// that to a simple collector of video frames, represented as SkBitmaps, for
// further examination by the browser tests.
class FakeVideoCaptureStack {
public:
FakeVideoCaptureStack();
~FakeVideoCaptureStack();
// Reset the capture stack to a state where it contains no frames and is
// expecting a first frame.
void Reset();
// Returns a VideoFrameReceiver that the implementation under test delivers
// frames to.
std::unique_ptr<media::VideoFrameReceiver> CreateFrameReceiver();
// Returns true if the device called VideoFrameReceiver::OnStarted().
bool started() const { return started_; }
// Returns true if the device called VideoFrameReceiver::OnError().
bool error_occurred() const { return error_occurred_; }
// Accessors to capture frame queue.
bool has_captured_frames() const { return !frames_.empty(); }
SkBitmap NextCapturedFrame();
void ClearCapturedFramesQueue();
// Called when tests expect there to be one or more log messages sent to the
// video capture stack. Turn on verbose logging for a dump of the actual log
// messages. This method clears the queue of log messages.
void ExpectHasLogMessages();
// Called when tests expect there to be no log messages sent to the video
// capture stack.
void ExpectNoLogMessages();
private:
// A minimal implementation of VideoFrameReceiver that wraps buffers into
// VideoFrame instances and forwards all relevant callbacks and data to the
// parent FakeVideoCaptureStack.
class Receiver;
// Checks that the frame timestamp is monotonically increasing and then
// stashes it in the |frames_| queue for later examination by the tests.
void OnReceivedFrame(scoped_refptr<media::VideoFrame> frame);
bool started_ = false;
bool error_occurred_ = false;
base::circular_deque<std::string> log_messages_;
base::circular_deque<scoped_refptr<media::VideoFrame>> frames_;
base::TimeDelta last_frame_timestamp_ = base::TimeDelta::Min();
};
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_CAPTURE_FAKE_VIDEO_CAPTURE_STACK_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/capture/frame_test_util.h"
#include <stdint.h>
#include <cmath>
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/transform.h"
namespace content {
// static
FrameTestUtil::RGB FrameTestUtil::ComputeAverageColor(
SkBitmap frame,
const gfx::Rect& raw_include_rect,
const gfx::Rect& raw_exclude_rect) {
// Clip the rects to the valid region within |frame|. Also, only the subregion
// of |exclude_rect| within |include_rect| is relevant.
gfx::Rect include_rect = raw_include_rect;
include_rect.Intersect(gfx::Rect(0, 0, frame.width(), frame.height()));
gfx::Rect exclude_rect = raw_exclude_rect;
exclude_rect.Intersect(include_rect);
// Sum up the color values in each color channel for all pixels in
// |include_rect| not contained by |exclude_rect|.
int64_t include_sums[3] = {0};
for (int y = include_rect.y(), bottom = include_rect.bottom(); y < bottom;
++y) {
for (int x = include_rect.x(), right = include_rect.right(); x < right;
++x) {
const SkColor color = frame.getColor(x, y);
if (exclude_rect.Contains(x, y)) {
continue;
}
include_sums[0] += SkColorGetR(color);
include_sums[1] += SkColorGetG(color);
include_sums[2] += SkColorGetB(color);
}
}
// Divide the sums by the area to compute the average color.
const int include_area =
include_rect.size().GetArea() - exclude_rect.size().GetArea();
if (include_area <= 0) {
return RGB{NAN, NAN, NAN};
} else {
const auto include_area_f = static_cast<double>(include_area);
return RGB{include_sums[0] / include_area_f,
include_sums[1] / include_area_f,
include_sums[2] / include_area_f};
}
}
// static
bool FrameTestUtil::IsApproximatelySameColor(SkColor color,
const RGB& rgb,
int max_diff) {
const double r_diff = std::abs(SkColorGetR(color) - rgb.r);
const double g_diff = std::abs(SkColorGetG(color) - rgb.g);
const double b_diff = std::abs(SkColorGetB(color) - rgb.b);
return r_diff < max_diff && g_diff < max_diff && b_diff < max_diff;
}
// static
gfx::RectF FrameTestUtil::TransformSimilarly(const gfx::Rect& original,
const gfx::RectF& transformed,
const gfx::Rect& rect) {
if (original.IsEmpty()) {
return gfx::RectF(transformed.x() - original.x(),
transformed.y() - original.y(), 0.0f, 0.0f);
}
// The following is the scale-then-translate 2D matrix.
const gfx::Transform transform(transformed.width() / original.width(), 0.0f,
0.0f, transformed.height() / original.height(),
transformed.x() - original.x(),
transformed.y() - original.y());
gfx::RectF result(rect);
transform.TransformRect(&result);
return result;
}
std::ostream& operator<<(std::ostream& out, const FrameTestUtil::RGB& rgb) {
return (out << "{r=" << rgb.r << ",g=" << rgb.g << ",b=" << rgb.b << '}');
}
} // namespace content
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_MEDIA_CAPTURE_FRAME_TEST_UTIL_H_
#define CONTENT_BROWSER_MEDIA_CAPTURE_FRAME_TEST_UTIL_H_
#include <ostream>
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
namespace gfx {
class Rect;
class RectF;
} // namespace gfx
namespace content {
class FrameTestUtil {
public:
struct RGB {
double r;
double g;
double b;
};
// Returns the average RGB color in |include_rect| except for pixels also in
// |exclude_rect|.
static RGB ComputeAverageColor(SkBitmap frame,
const gfx::Rect& include_rect,
const gfx::Rect& exclude_rect);
// Returns true if the red, green, and blue components are all within
// |max_diff| of each other.
static bool IsApproximatelySameColor(SkColor color,
const RGB& rgb,
int max_diff = kMaxColorDifference);
// Determines how |original| has been scaled and translated to become
// |transformed|, and then applies the same transform on |rect| and returns
// the result.
static gfx::RectF TransformSimilarly(const gfx::Rect& original,
const gfx::RectF& transformed,
const gfx::Rect& rect);
// The default maximum color value difference, assuming there will be a little
// error due to pixel boundaries being rounded after coordinate system
// transforms.
static constexpr int kMaxColorDifference = 16;
// A much more-relaxed maximum color value difference, assuming errors caused
// by indifference towards color space concerns (and also "studio" versus
// "jpeg" YUV ranges).
// TODO(crbug/810131): Once color space issues are fixed, remove this.
static constexpr int kMaxInaccurateColorDifference = 48;
};
// A convenience for logging and gtest expectations output.
std::ostream& operator<<(std::ostream& out, const FrameTestUtil::RGB& rgb);
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_CAPTURE_FRAME_TEST_UTIL_H_
...@@ -995,7 +995,16 @@ test("content_browsertests") { ...@@ -995,7 +995,16 @@ test("content_browsertests") {
} }
if (is_linux || is_mac || is_win) { if (is_linux || is_mac || is_win) {
sources += [ "../browser/media/capture/web_contents_video_capture_device_browsertest.cc" ] sources += [
"../browser/media/capture/content_capture_device_browsertest_base.cc",
"../browser/media/capture/content_capture_device_browsertest_base.h",
"../browser/media/capture/fake_video_capture_stack.cc",
"../browser/media/capture/fake_video_capture_stack.h",
"../browser/media/capture/frame_test_util.cc",
"../browser/media/capture/frame_test_util.h",
"../browser/media/capture/web_contents_video_capture_device_browsertest.cc",
]
deps += [ "//third_party/libyuv" ] deps += [ "//third_party/libyuv" ]
data += [ data += [
"//net/tools/testserver/", "//net/tools/testserver/",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment