blob: 96fe4baad9dd113b615648528c3a9cc04a1cd3e7 [file] [log] [blame]
// Copyright (c) 2012 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/web_contents_video_capture_device.h"
#include "base/bind_helpers.h"
#include "base/debug/debugger.h"
#include "base/run_loop.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/media/capture/video_capture_oracle.h"
#include "content/browser/media/capture/web_contents_capture_util.h"
#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
#include "content/browser/renderer_host/render_view_host_factory.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_widget_host_view_frame_subscriber.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/base/yuv_convert.h"
#include "media/video/capture/video_capture_types.h"
#include "skia/ext/platform_canvas.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/display.h"
#include "ui/gfx/screen.h"
namespace content {
namespace {
const int kTestWidth = 320;
const int kTestHeight = 240;
const int kTestFramesPerSecond = 20;
const float kTestDeviceScaleFactor = 2.0f;
const SkColor kNothingYet = 0xdeadbeef;
const SkColor kNotInterested = ~kNothingYet;
void DeadlineExceeded(base::Closure quit_closure) {
if (!base::debug::BeingDebugged()) {
quit_closure.Run();
FAIL() << "Deadline exceeded while waiting, quitting";
} else {
LOG(WARNING) << "Deadline exceeded; test would fail if debugger weren't "
<< "attached.";
}
}
void RunCurrentLoopWithDeadline() {
base::Timer deadline(false, false);
deadline.Start(FROM_HERE, TestTimeouts::action_max_timeout(), base::Bind(
&DeadlineExceeded, base::MessageLoop::current()->QuitClosure()));
base::MessageLoop::current()->Run();
deadline.Stop();
}
SkColor ConvertRgbToYuv(SkColor rgb) {
uint8 yuv[3];
media::ConvertRGB32ToYUV(reinterpret_cast<uint8*>(&rgb),
yuv, yuv + 1, yuv + 2, 1, 1, 1, 1, 1);
return SkColorSetRGB(yuv[0], yuv[1], yuv[2]);
}
// Thread-safe class that controls the source pattern to be captured by the
// system under test. The lifetime of this class is greater than the lifetime
// of all objects that reference it, so it does not need to be reference
// counted.
class CaptureTestSourceController {
public:
CaptureTestSourceController()
: color_(SK_ColorMAGENTA),
copy_result_size_(kTestWidth, kTestHeight),
can_copy_to_video_frame_(false),
use_frame_subscriber_(false) {}
void SetSolidColor(SkColor color) {
base::AutoLock guard(lock_);
color_ = color;
}
SkColor GetSolidColor() {
base::AutoLock guard(lock_);
return color_;
}
void SetCopyResultSize(int width, int height) {
base::AutoLock guard(lock_);
copy_result_size_ = gfx::Size(width, height);
}
gfx::Size GetCopyResultSize() {
base::AutoLock guard(lock_);
return copy_result_size_;
}
void SignalCopy() {
// TODO(nick): This actually should always be happening on the UI thread.
base::AutoLock guard(lock_);
if (!copy_done_.is_null()) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, copy_done_);
copy_done_.Reset();
}
}
void SetCanCopyToVideoFrame(bool value) {
base::AutoLock guard(lock_);
can_copy_to_video_frame_ = value;
}
bool CanCopyToVideoFrame() {
base::AutoLock guard(lock_);
return can_copy_to_video_frame_;
}
void SetUseFrameSubscriber(bool value) {
base::AutoLock guard(lock_);
use_frame_subscriber_ = value;
}
bool CanUseFrameSubscriber() {
base::AutoLock guard(lock_);
return use_frame_subscriber_;
}
void WaitForNextCopy() {
{
base::AutoLock guard(lock_);
copy_done_ = base::MessageLoop::current()->QuitClosure();
}
RunCurrentLoopWithDeadline();
}
private:
base::Lock lock_; // Guards changes to all members.
SkColor color_;
gfx::Size copy_result_size_;
bool can_copy_to_video_frame_;
bool use_frame_subscriber_;
base::Closure copy_done_;
DISALLOW_COPY_AND_ASSIGN(CaptureTestSourceController);
};
// A stub implementation which returns solid-color bitmaps in calls to
// CopyFromCompositingSurfaceToVideoFrame(), and which allows the video-frame
// readback path to be switched on and off. The behavior is controlled by a
// CaptureTestSourceController.
class CaptureTestView : public TestRenderWidgetHostView {
public:
explicit CaptureTestView(RenderWidgetHostImpl* rwh,
CaptureTestSourceController* controller)
: TestRenderWidgetHostView(rwh),
controller_(controller) {}
~CaptureTestView() override {}
// TestRenderWidgetHostView overrides.
gfx::Rect GetViewBounds() const override {
return gfx::Rect(100, 100, 100 + kTestWidth, 100 + kTestHeight);
}
bool CanCopyToVideoFrame() const override {
return controller_->CanCopyToVideoFrame();
}
void CopyFromCompositingSurfaceToVideoFrame(
const gfx::Rect& src_subrect,
const scoped_refptr<media::VideoFrame>& target,
const base::Callback<void(bool)>& callback) override {
SkColor c = ConvertRgbToYuv(controller_->GetSolidColor());
media::FillYUV(
target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
callback.Run(true);
controller_->SignalCopy();
}
void BeginFrameSubscription(
scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) override {
subscriber_.reset(subscriber.release());
}
void EndFrameSubscription() override { subscriber_.reset(); }
// Simulate a compositor paint event for our subscriber.
void SimulateUpdate() {
const base::TimeTicks present_time = base::TimeTicks::Now();
RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
scoped_refptr<media::VideoFrame> target;
if (subscriber_ && subscriber_->ShouldCaptureFrame(
gfx::Rect(), present_time, &target, &callback)) {
SkColor c = ConvertRgbToYuv(controller_->GetSolidColor());
media::FillYUV(
target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(callback, present_time, true));
controller_->SignalCopy();
}
}
private:
scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber_;
CaptureTestSourceController* const controller_;
DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestView);
};
#if defined(COMPILER_MSVC)
// MSVC warns on diamond inheritance. See comment for same warning on
// RenderViewHostImpl.
#pragma warning(push)
#pragma warning(disable: 4250)
#endif
// A stub implementation which returns solid-color bitmaps in calls to
// CopyFromBackingStore(). The behavior is controlled by a
// CaptureTestSourceController.
class CaptureTestRenderViewHost : public TestRenderViewHost {
public:
CaptureTestRenderViewHost(SiteInstance* instance,
RenderViewHostDelegate* delegate,
RenderWidgetHostDelegate* widget_delegate,
int routing_id,
int main_frame_routing_id,
bool swapped_out,
CaptureTestSourceController* controller)
: TestRenderViewHost(instance, delegate, widget_delegate, routing_id,
main_frame_routing_id, swapped_out),
controller_(controller) {
// Override the default view installed by TestRenderViewHost; we need
// our special subclass which has mocked-out tab capture support.
RenderWidgetHostView* old_view = GetView();
SetView(new CaptureTestView(this, controller));
delete old_view;
}
// TestRenderViewHost overrides.
void CopyFromBackingStore(
const gfx::Rect& src_rect,
const gfx::Size& accelerated_dst_size,
const base::Callback<void(bool, const SkBitmap&)>& callback,
const SkColorType color_type) override {
gfx::Size size = controller_->GetCopyResultSize();
SkColor color = controller_->GetSolidColor();
// Although it's not necessary, use a PlatformBitmap here (instead of a
// regular SkBitmap) to exercise possible threading issues.
skia::PlatformBitmap output;
EXPECT_TRUE(output.Allocate(size.width(), size.height(), false));
{
SkAutoLockPixels locker(output.GetBitmap());
output.GetBitmap().eraseColor(color);
}
callback.Run(true, output.GetBitmap());
controller_->SignalCopy();
}
private:
CaptureTestSourceController* controller_;
DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHost);
};
#if defined(COMPILER_MSVC)
// Re-enable warning 4250
#pragma warning(pop)
#endif
class CaptureTestRenderViewHostFactory : public RenderViewHostFactory {
public:
explicit CaptureTestRenderViewHostFactory(
CaptureTestSourceController* controller) : controller_(controller) {
RegisterFactory(this);
}
~CaptureTestRenderViewHostFactory() override { UnregisterFactory(); }
// RenderViewHostFactory implementation.
RenderViewHost* CreateRenderViewHost(
SiteInstance* instance,
RenderViewHostDelegate* delegate,
RenderWidgetHostDelegate* widget_delegate,
int routing_id,
int main_frame_routing_id,
bool swapped_out) override {
return new CaptureTestRenderViewHost(instance, delegate, widget_delegate,
routing_id, main_frame_routing_id,
swapped_out, controller_);
}
private:
CaptureTestSourceController* controller_;
DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHostFactory);
};
// A stub consumer of captured video frames, which checks the output of
// WebContentsVideoCaptureDevice.
class StubClient : public media::VideoCaptureDevice::Client {
public:
StubClient(const base::Callback<void(SkColor)>& color_callback,
const base::Closure& error_callback)
: color_callback_(color_callback),
error_callback_(error_callback) {
buffer_pool_ = new VideoCaptureBufferPool(2);
}
~StubClient() override {}
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> ReserveOutputBuffer(
media::VideoFrame::Format format,
const gfx::Size& dimensions) override {
CHECK_EQ(format, media::VideoFrame::I420);
const size_t frame_bytes =
media::VideoFrame::AllocationSize(media::VideoFrame::I420, dimensions);
int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; // Ignored.
int buffer_id =
buffer_pool_->ReserveForProducer(frame_bytes, &buffer_id_to_drop);
if (buffer_id == VideoCaptureBufferPool::kInvalidId)
return NULL;
void* data;
size_t size;
buffer_pool_->GetBufferInfo(buffer_id, &data, &size);
return scoped_refptr<media::VideoCaptureDevice::Client::Buffer>(
new PoolBuffer(buffer_pool_, buffer_id, data, size));
}
void OnIncomingCapturedData(const uint8* data,
int length,
const media::VideoCaptureFormat& frame_format,
int rotation,
base::TimeTicks timestamp) override {
FAIL();
}
void OnIncomingCapturedVideoFrame(
const scoped_refptr<Buffer>& buffer,
const media::VideoCaptureFormat& buffer_format,
const scoped_refptr<media::VideoFrame>& frame,
base::TimeTicks timestamp) override {
EXPECT_EQ(gfx::Size(kTestWidth, kTestHeight), buffer_format.frame_size);
EXPECT_EQ(media::PIXEL_FORMAT_I420, buffer_format.pixel_format);
EXPECT_EQ(media::VideoFrame::I420, frame->format());
uint8 yuv[3];
for (int plane = 0; plane < 3; ++plane)
yuv[plane] = frame->data(plane)[0];
// TODO(nick): We just look at the first pixel presently, because if
// the analysis is too slow, the backlog of frames will grow without bound
// and trouble erupts. http://crbug.com/174519
color_callback_.Run((SkColorSetRGB(yuv[0], yuv[1], yuv[2])));
}
void OnError(const std::string& reason) override { error_callback_.Run(); }
private:
class PoolBuffer : public media::VideoCaptureDevice::Client::Buffer {
public:
PoolBuffer(const scoped_refptr<VideoCaptureBufferPool>& pool,
int buffer_id,
void* data,
size_t size)
: Buffer(buffer_id, data, size), pool_(pool) {}
private:
~PoolBuffer() override { pool_->RelinquishProducerReservation(id()); }
const scoped_refptr<VideoCaptureBufferPool> pool_;
};
scoped_refptr<VideoCaptureBufferPool> buffer_pool_;
base::Callback<void(SkColor)> color_callback_;
base::Closure error_callback_;
DISALLOW_COPY_AND_ASSIGN(StubClient);
};
class StubClientObserver {
public:
StubClientObserver()
: error_encountered_(false),
wait_color_yuv_(0xcafe1950) {
client_.reset(new StubClient(
base::Bind(&StubClientObserver::OnColor, base::Unretained(this)),
base::Bind(&StubClientObserver::OnError, base::Unretained(this))));
}
virtual ~StubClientObserver() {}
scoped_ptr<media::VideoCaptureDevice::Client> PassClient() {
return client_.Pass();
}
void QuitIfConditionMet(SkColor color) {
base::AutoLock guard(lock_);
if (wait_color_yuv_ == color || error_encountered_)
base::MessageLoop::current()->Quit();
}
void WaitForNextColor(SkColor expected_color) {
{
base::AutoLock guard(lock_);
wait_color_yuv_ = ConvertRgbToYuv(expected_color);
error_encountered_ = false;
}
RunCurrentLoopWithDeadline();
{
base::AutoLock guard(lock_);
ASSERT_FALSE(error_encountered_);
}
}
void WaitForError() {
{
base::AutoLock guard(lock_);
wait_color_yuv_ = kNotInterested;
error_encountered_ = false;
}
RunCurrentLoopWithDeadline();
{
base::AutoLock guard(lock_);
ASSERT_TRUE(error_encountered_);
}
}
bool HasError() {
base::AutoLock guard(lock_);
return error_encountered_;
}
void OnError() {
{
base::AutoLock guard(lock_);
error_encountered_ = true;
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
&StubClientObserver::QuitIfConditionMet,
base::Unretained(this),
kNothingYet));
}
void OnColor(SkColor color) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
&StubClientObserver::QuitIfConditionMet,
base::Unretained(this),
color));
}
private:
base::Lock lock_;
bool error_encountered_;
SkColor wait_color_yuv_;
scoped_ptr<StubClient> client_;
DISALLOW_COPY_AND_ASSIGN(StubClientObserver);
};
// A dummy implementation of gfx::Screen, since WebContentsVideoCaptureDevice
// needs access to a gfx::Display's device scale factor.
class FakeScreen : public gfx::Screen {
public:
FakeScreen() : the_one_display_(0x1337, gfx::Rect(0, 0, 2560, 1440)) {
the_one_display_.set_device_scale_factor(kTestDeviceScaleFactor);
}
~FakeScreen() override {}
// gfx::Screen implementation (only what's needed for testing).
gfx::Point GetCursorScreenPoint() override { return gfx::Point(); }
gfx::NativeWindow GetWindowUnderCursor() override { return NULL; }
gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override {
return NULL;
}
int GetNumDisplays() const override { return 1; }
std::vector<gfx::Display> GetAllDisplays() const override {
return std::vector<gfx::Display>(1, the_one_display_);
}
gfx::Display GetDisplayNearestWindow(gfx::NativeView view) const override {
return the_one_display_;
}
gfx::Display GetDisplayNearestPoint(const gfx::Point& point) const override {
return the_one_display_;
}
gfx::Display GetDisplayMatching(const gfx::Rect& match_rect) const override {
return the_one_display_;
}
gfx::Display GetPrimaryDisplay() const override { return the_one_display_; }
void AddObserver(gfx::DisplayObserver* observer) override {}
void RemoveObserver(gfx::DisplayObserver* observer) override {}
private:
gfx::Display the_one_display_;
DISALLOW_COPY_AND_ASSIGN(FakeScreen);
};
// Test harness that sets up a minimal environment with necessary stubs.
class WebContentsVideoCaptureDeviceTest : public testing::Test {
public:
// This is public because C++ method pointer scoping rules are silly and make
// this hard to use with Bind().
void ResetWebContents() {
web_contents_.reset();
}
protected:
virtual void SetUp() {
gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, &fake_screen_);
ASSERT_EQ(&fake_screen_, gfx::Screen::GetNativeScreen());
// TODO(nick): Sadness and woe! Much "mock-the-world" boilerplate could be
// eliminated here, if only we could use RenderViewHostTestHarness. The
// catch is that we need our TestRenderViewHost to support a
// CopyFromBackingStore operation that we control. To accomplish that,
// either RenderViewHostTestHarness would have to support installing a
// custom RenderViewHostFactory, or else we implant some kind of delegated
// CopyFromBackingStore functionality into TestRenderViewHost itself.
render_process_host_factory_.reset(new MockRenderProcessHostFactory());
// Create our (self-registering) RVH factory, so that when we create a
// WebContents, it in turn creates CaptureTestRenderViewHosts.
render_view_host_factory_.reset(
new CaptureTestRenderViewHostFactory(&controller_));
browser_context_.reset(new TestBrowserContext());
scoped_refptr<SiteInstance> site_instance =
SiteInstance::Create(browser_context_.get());
SiteInstanceImpl::set_render_process_host_factory(
render_process_host_factory_.get());
web_contents_.reset(
TestWebContents::Create(browser_context_.get(), site_instance.get()));
RenderFrameHost* const main_frame = web_contents_->GetMainFrame();
device_.reset(WebContentsVideoCaptureDevice::Create(
base::StringPrintf("web-contents-media-stream://%d:%d",
main_frame->GetProcess()->GetID(),
main_frame->GetRoutingID())));
base::RunLoop().RunUntilIdle();
}
virtual void TearDown() {
// Tear down in opposite order of set-up.
// The device is destroyed asynchronously, and will notify the
// CaptureTestSourceController when it finishes destruction.
// Trigger this, and wait.
if (device_) {
device_->StopAndDeAllocate();
device_.reset();
}
base::RunLoop().RunUntilIdle();
// Destroy the browser objects.
web_contents_.reset();
browser_context_.reset();
base::RunLoop().RunUntilIdle();
SiteInstanceImpl::set_render_process_host_factory(NULL);
render_view_host_factory_.reset();
render_process_host_factory_.reset();
gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, NULL);
}
// Accessors.
CaptureTestSourceController* source() { return &controller_; }
WebContents* web_contents() const { return web_contents_.get(); }
media::VideoCaptureDevice* device() { return device_.get(); }
void SimulateDrawEvent() {
if (source()->CanUseFrameSubscriber()) {
// Print
CaptureTestView* test_view = static_cast<CaptureTestView*>(
web_contents_->GetRenderViewHost()->GetView());
test_view->SimulateUpdate();
} else {
// Simulate a non-accelerated paint.
NotificationService::current()->Notify(
NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
Source<RenderWidgetHost>(web_contents_->GetRenderViewHost()),
NotificationService::NoDetails());
}
}
void DestroyVideoCaptureDevice() { device_.reset(); }
StubClientObserver* client_observer() {
return &client_observer_;
}
private:
FakeScreen fake_screen_;
StubClientObserver client_observer_;
// The controller controls which pixel patterns to produce.
CaptureTestSourceController controller_;
// Self-registering RenderProcessHostFactory.
scoped_ptr<MockRenderProcessHostFactory> render_process_host_factory_;
// Creates capture-capable RenderViewHosts whose pixel content production is
// under the control of |controller_|.
scoped_ptr<CaptureTestRenderViewHostFactory> render_view_host_factory_;
// A mocked-out browser and tab.
scoped_ptr<TestBrowserContext> browser_context_;
scoped_ptr<WebContents> web_contents_;
// Finally, the WebContentsVideoCaptureDevice under test.
scoped_ptr<media::VideoCaptureDevice> device_;
TestBrowserThreadBundle thread_bundle_;
};
TEST_F(WebContentsVideoCaptureDeviceTest, InvalidInitialWebContentsError) {
// Before the installs itself on the UI thread up to start capturing, we'll
// delete the web contents. This should trigger an error which can happen in
// practice; we should be able to recover gracefully.
ResetWebContents();
media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
capture_params.requested_format.frame_rate = kTestFramesPerSecond;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
device()->AllocateAndStart(capture_params, client_observer()->PassClient());
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError());
device()->StopAndDeAllocate();
}
TEST_F(WebContentsVideoCaptureDeviceTest, WebContentsDestroyed) {
const gfx::Size capture_preferred_size(
static_cast<int>(kTestWidth / kTestDeviceScaleFactor),
static_cast<int>(kTestHeight / kTestDeviceScaleFactor));
ASSERT_NE(capture_preferred_size, web_contents()->GetPreferredSize());
// We'll simulate the tab being closed after the capture pipeline is up and
// running.
media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
capture_params.requested_format.frame_rate = kTestFramesPerSecond;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
device()->AllocateAndStart(capture_params, client_observer()->PassClient());
// Do one capture to prove
source()->SetSolidColor(SK_ColorRED);
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
base::RunLoop().RunUntilIdle();
// Check that the preferred size of the WebContents matches the one provided
// by WebContentsVideoCaptureDevice.
EXPECT_EQ(capture_preferred_size, web_contents()->GetPreferredSize());
// Post a task to close the tab. We should see an error reported to the
// consumer.
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&WebContentsVideoCaptureDeviceTest::ResetWebContents,
base::Unretained(this)));
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError());
device()->StopAndDeAllocate();
}
TEST_F(WebContentsVideoCaptureDeviceTest,
StopDeviceBeforeCaptureMachineCreation) {
media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
capture_params.requested_format.frame_rate = kTestFramesPerSecond;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
device()->AllocateAndStart(capture_params, client_observer()->PassClient());
// Make a point of not running the UI messageloop here.
device()->StopAndDeAllocate();
DestroyVideoCaptureDevice();
// Currently, there should be CreateCaptureMachineOnUIThread() and
// DestroyCaptureMachineOnUIThread() tasks pending on the current (UI) message
// loop. These should both succeed without crashing, and the machine should
// wind up in the idle state.
base::RunLoop().RunUntilIdle();
}
TEST_F(WebContentsVideoCaptureDeviceTest, StopWithRendererWorkToDo) {
// Set up the test to use RGB copies and an normal
source()->SetCanCopyToVideoFrame(false);
source()->SetUseFrameSubscriber(false);
media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
capture_params.requested_format.frame_rate = kTestFramesPerSecond;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
device()->AllocateAndStart(capture_params, client_observer()->PassClient());
base::RunLoop().RunUntilIdle();
for (int i = 0; i < 10; ++i)
SimulateDrawEvent();
ASSERT_FALSE(client_observer()->HasError());
device()->StopAndDeAllocate();
ASSERT_FALSE(client_observer()->HasError());
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(client_observer()->HasError());
}
TEST_F(WebContentsVideoCaptureDeviceTest, DeviceRestart) {
media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
capture_params.requested_format.frame_rate = kTestFramesPerSecond;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
device()->AllocateAndStart(capture_params, client_observer()->PassClient());
base::RunLoop().RunUntilIdle();
source()->SetSolidColor(SK_ColorRED);
SimulateDrawEvent();
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
SimulateDrawEvent();
SimulateDrawEvent();
source()->SetSolidColor(SK_ColorGREEN);
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
device()->StopAndDeAllocate();
// Device is stopped, but content can still be animating.
SimulateDrawEvent();
SimulateDrawEvent();
base::RunLoop().RunUntilIdle();
StubClientObserver observer2;
device()->AllocateAndStart(capture_params, observer2.PassClient());
source()->SetSolidColor(SK_ColorBLUE);
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(observer2.WaitForNextColor(SK_ColorBLUE));
source()->SetSolidColor(SK_ColorYELLOW);
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(observer2.WaitForNextColor(SK_ColorYELLOW));
device()->StopAndDeAllocate();
}
// The "happy case" test. No scaling is needed, so we should be able to change
// the picture emitted from the source and expect to see each delivered to the
// consumer. The test will alternate between the three capture paths, simulating
// falling in and out of accelerated compositing.
TEST_F(WebContentsVideoCaptureDeviceTest, GoesThroughAllTheMotions) {
media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
capture_params.requested_format.frame_rate = kTestFramesPerSecond;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
device()->AllocateAndStart(capture_params, client_observer()->PassClient());
for (int i = 0; i < 6; i++) {
const char* name = NULL;
switch (i % 3) {
case 0:
source()->SetCanCopyToVideoFrame(true);
source()->SetUseFrameSubscriber(false);
name = "VideoFrame";
break;
case 1:
source()->SetCanCopyToVideoFrame(false);
source()->SetUseFrameSubscriber(true);
name = "Subscriber";
break;
case 2:
source()->SetCanCopyToVideoFrame(false);
source()->SetUseFrameSubscriber(false);
name = "SkBitmap";
break;
default:
FAIL();
}
SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i));
source()->SetSolidColor(SK_ColorRED);
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
source()->SetSolidColor(SK_ColorGREEN);
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
source()->SetSolidColor(SK_ColorBLUE);
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorBLUE));
source()->SetSolidColor(SK_ColorBLACK);
SimulateDrawEvent();
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorBLACK));
}
device()->StopAndDeAllocate();
}
TEST_F(WebContentsVideoCaptureDeviceTest, RejectsInvalidAllocateParams) {
media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(1280, 720);
capture_params.requested_format.frame_rate = -2;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&media::VideoCaptureDevice::AllocateAndStart,
base::Unretained(device()),
capture_params,
base::Passed(client_observer()->PassClient())));
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError());
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&media::VideoCaptureDevice::StopAndDeAllocate,
base::Unretained(device())));
base::RunLoop().RunUntilIdle();
}
TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) {
media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
capture_params.requested_format.frame_rate = kTestFramesPerSecond;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
// 1x1 is too small to process; we intend for this to result in an error.
source()->SetCopyResultSize(1, 1);
source()->SetSolidColor(SK_ColorRED);
device()->AllocateAndStart(capture_params, client_observer()->PassClient());
// These frames ought to be dropped during the Render stage. Let
// several captures to happen.
ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
// Now push some good frames through; they should be processed normally.
source()->SetCopyResultSize(kTestWidth, kTestHeight);
source()->SetSolidColor(SK_ColorGREEN);
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
source()->SetSolidColor(SK_ColorRED);
ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
device()->StopAndDeAllocate();
}
} // namespace
} // namespace content