Add long running gmail memory benchmark for background tab.
[chromium-blink-merge.git] / content / browser / media / capture / web_contents_video_capture_device_unittest.cc
blob80a95f929b63ff4fac8a68e66af09dd5fef29add
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/media/capture/web_contents_video_capture_device.h"
7 #include "base/bind_helpers.h"
8 #include "base/debug/debugger.h"
9 #include "base/run_loop.h"
10 #include "base/test/test_timeouts.h"
11 #include "base/time/time.h"
12 #include "base/timer/timer.h"
13 #include "content/browser/browser_thread_impl.h"
14 #include "content/browser/frame_host/render_frame_host_impl.h"
15 #include "content/browser/media/capture/web_contents_capture_util.h"
16 #include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
17 #include "content/browser/renderer_host/render_view_host_factory.h"
18 #include "content/browser/renderer_host/render_widget_host_impl.h"
19 #include "content/browser/web_contents/web_contents_impl.h"
20 #include "content/public/browser/notification_service.h"
21 #include "content/public/browser/notification_types.h"
22 #include "content/public/browser/render_widget_host_view_frame_subscriber.h"
23 #include "content/public/test/mock_render_process_host.h"
24 #include "content/public/test/test_browser_context.h"
25 #include "content/public/test/test_browser_thread_bundle.h"
26 #include "content/public/test/test_utils.h"
27 #include "content/test/test_render_view_host.h"
28 #include "content/test/test_web_contents.h"
29 #include "media/base/video_capture_types.h"
30 #include "media/base/video_frame.h"
31 #include "media/base/video_util.h"
32 #include "media/base/yuv_convert.h"
33 #include "skia/ext/platform_canvas.h"
34 #include "testing/gmock/include/gmock/gmock.h"
35 #include "testing/gtest/include/gtest/gtest.h"
36 #include "third_party/skia/include/core/SkColor.h"
37 #include "ui/base/layout.h"
38 #include "ui/gfx/display.h"
39 #include "ui/gfx/geometry/dip_util.h"
40 #include "ui/gfx/geometry/size_conversions.h"
41 #include "ui/gfx/screen.h"
42 #include "ui/gfx/test/test_screen.h"
44 namespace content {
45 namespace {
47 const int kTestWidth = 320;
48 const int kTestHeight = 240;
49 const int kTestFramesPerSecond = 20;
50 const float kTestDeviceScaleFactor = 2.0f;
51 const SkColor kNothingYet = 0xdeadbeef;
52 const SkColor kNotInterested = ~kNothingYet;
54 void DeadlineExceeded(base::Closure quit_closure) {
55 if (!base::debug::BeingDebugged()) {
56 quit_closure.Run();
57 FAIL() << "Deadline exceeded while waiting, quitting";
58 } else {
59 LOG(WARNING) << "Deadline exceeded; test would fail if debugger weren't "
60 << "attached.";
64 void RunCurrentLoopWithDeadline() {
65 base::Timer deadline(false, false);
66 deadline.Start(FROM_HERE, TestTimeouts::action_max_timeout(), base::Bind(
67 &DeadlineExceeded, base::MessageLoop::current()->QuitClosure()));
68 base::MessageLoop::current()->Run();
69 deadline.Stop();
72 SkColor ConvertRgbToYuv(SkColor rgb) {
73 uint8 yuv[3];
74 media::ConvertRGB32ToYUV(reinterpret_cast<uint8*>(&rgb),
75 yuv, yuv + 1, yuv + 2, 1, 1, 1, 1, 1);
76 return SkColorSetRGB(yuv[0], yuv[1], yuv[2]);
79 // Thread-safe class that controls the source pattern to be captured by the
80 // system under test. The lifetime of this class is greater than the lifetime
81 // of all objects that reference it, so it does not need to be reference
82 // counted.
83 class CaptureTestSourceController {
84 public:
85 CaptureTestSourceController()
86 : color_(SK_ColorMAGENTA),
87 copy_result_size_(kTestWidth, kTestHeight),
88 can_copy_to_video_frame_(false),
89 use_frame_subscriber_(false) {}
91 void SetSolidColor(SkColor color) {
92 base::AutoLock guard(lock_);
93 color_ = color;
96 SkColor GetSolidColor() {
97 base::AutoLock guard(lock_);
98 return color_;
101 void SetCopyResultSize(int width, int height) {
102 base::AutoLock guard(lock_);
103 copy_result_size_ = gfx::Size(width, height);
106 gfx::Size GetCopyResultSize() {
107 base::AutoLock guard(lock_);
108 return copy_result_size_;
111 void SignalCopy() {
112 // TODO(nick): This actually should always be happening on the UI thread.
113 base::AutoLock guard(lock_);
114 if (!copy_done_.is_null()) {
115 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, copy_done_);
116 copy_done_.Reset();
120 void SetCanCopyToVideoFrame(bool value) {
121 base::AutoLock guard(lock_);
122 can_copy_to_video_frame_ = value;
125 bool CanCopyToVideoFrame() {
126 base::AutoLock guard(lock_);
127 return can_copy_to_video_frame_;
130 void SetUseFrameSubscriber(bool value) {
131 base::AutoLock guard(lock_);
132 use_frame_subscriber_ = value;
135 bool CanUseFrameSubscriber() {
136 base::AutoLock guard(lock_);
137 return use_frame_subscriber_;
140 void WaitForNextCopy() {
142 base::AutoLock guard(lock_);
143 copy_done_ = base::MessageLoop::current()->QuitClosure();
146 RunCurrentLoopWithDeadline();
149 private:
150 base::Lock lock_; // Guards changes to all members.
151 SkColor color_;
152 gfx::Size copy_result_size_;
153 bool can_copy_to_video_frame_;
154 bool use_frame_subscriber_;
155 base::Closure copy_done_;
157 DISALLOW_COPY_AND_ASSIGN(CaptureTestSourceController);
160 // A stub implementation which returns solid-color bitmaps in calls to
161 // CopyFromCompositingSurfaceToVideoFrame(), and which allows the video-frame
162 // readback path to be switched on and off. The behavior is controlled by a
163 // CaptureTestSourceController.
164 class CaptureTestView : public TestRenderWidgetHostView {
165 public:
166 explicit CaptureTestView(RenderWidgetHostImpl* rwh,
167 CaptureTestSourceController* controller)
168 : TestRenderWidgetHostView(rwh),
169 controller_(controller),
170 fake_bounds_(100, 100, 100 + kTestWidth, 100 + kTestHeight) {}
172 ~CaptureTestView() override {}
174 // TestRenderWidgetHostView overrides.
175 gfx::Rect GetViewBounds() const override {
176 return fake_bounds_;
179 void SetSize(const gfx::Size& size) override {
180 SetBounds(gfx::Rect(fake_bounds_.origin(), size));
183 void SetBounds(const gfx::Rect& rect) override {
184 fake_bounds_ = rect;
187 bool CanCopyToVideoFrame() const override {
188 return controller_->CanCopyToVideoFrame();
191 void CopyFromCompositingSurfaceToVideoFrame(
192 const gfx::Rect& src_subrect,
193 const scoped_refptr<media::VideoFrame>& target,
194 const base::Callback<void(bool)>& callback) override {
195 SkColor c = ConvertRgbToYuv(controller_->GetSolidColor());
196 media::FillYUV(
197 target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
198 callback.Run(true);
199 controller_->SignalCopy();
202 void BeginFrameSubscription(
203 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) override {
204 subscriber_.reset(subscriber.release());
207 void EndFrameSubscription() override { subscriber_.reset(); }
209 // Simulate a compositor paint event for our subscriber.
210 void SimulateUpdate() {
211 const base::TimeTicks present_time = base::TimeTicks::Now();
212 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
213 scoped_refptr<media::VideoFrame> target;
214 if (subscriber_ && subscriber_->ShouldCaptureFrame(
215 gfx::Rect(), present_time, &target, &callback)) {
216 SkColor c = ConvertRgbToYuv(controller_->GetSolidColor());
217 media::FillYUV(
218 target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
219 BrowserThread::PostTask(BrowserThread::UI,
220 FROM_HERE,
221 base::Bind(callback, present_time, true));
222 controller_->SignalCopy();
226 private:
227 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber_;
228 CaptureTestSourceController* const controller_;
229 gfx::Rect fake_bounds_;
231 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestView);
234 #if defined(COMPILER_MSVC)
235 // MSVC warns on diamond inheritance. See comment for same warning on
236 // RenderViewHostImpl.
237 #pragma warning(push)
238 #pragma warning(disable: 4250)
239 #endif
241 // A stub implementation which returns solid-color bitmaps in calls to
242 // CopyFromBackingStore(). The behavior is controlled by a
243 // CaptureTestSourceController.
244 class CaptureTestRenderViewHost : public TestRenderViewHost {
245 public:
246 CaptureTestRenderViewHost(SiteInstance* instance,
247 RenderViewHostDelegate* delegate,
248 RenderWidgetHostDelegate* widget_delegate,
249 int routing_id,
250 int main_frame_routing_id,
251 bool swapped_out,
252 CaptureTestSourceController* controller)
253 : TestRenderViewHost(instance, delegate, widget_delegate, routing_id,
254 main_frame_routing_id, swapped_out),
255 controller_(controller) {
256 // Override the default view installed by TestRenderViewHost; we need
257 // our special subclass which has mocked-out tab capture support.
258 RenderWidgetHostView* old_view = GetView();
259 SetView(new CaptureTestView(this, controller));
260 delete old_view;
263 // TestRenderViewHost overrides.
264 void CopyFromBackingStore(const gfx::Rect& src_rect,
265 const gfx::Size& accelerated_dst_size,
266 ReadbackRequestCallback& callback,
267 const SkColorType color_type) override {
268 gfx::Size size = controller_->GetCopyResultSize();
269 SkColor color = controller_->GetSolidColor();
271 // Although it's not necessary, use a PlatformBitmap here (instead of a
272 // regular SkBitmap) to exercise possible threading issues.
273 skia::PlatformBitmap output;
274 EXPECT_TRUE(output.Allocate(size.width(), size.height(), false));
276 SkAutoLockPixels locker(output.GetBitmap());
277 output.GetBitmap().eraseColor(color);
279 callback.Run(output.GetBitmap(), content::READBACK_SUCCESS);
280 controller_->SignalCopy();
283 private:
284 CaptureTestSourceController* controller_;
286 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHost);
289 #if defined(COMPILER_MSVC)
290 // Re-enable warning 4250
291 #pragma warning(pop)
292 #endif
294 class CaptureTestRenderViewHostFactory : public RenderViewHostFactory {
295 public:
296 explicit CaptureTestRenderViewHostFactory(
297 CaptureTestSourceController* controller) : controller_(controller) {
298 RegisterFactory(this);
301 ~CaptureTestRenderViewHostFactory() override { UnregisterFactory(); }
303 // RenderViewHostFactory implementation.
304 RenderViewHost* CreateRenderViewHost(
305 SiteInstance* instance,
306 RenderViewHostDelegate* delegate,
307 RenderWidgetHostDelegate* widget_delegate,
308 int routing_id,
309 int main_frame_routing_id,
310 bool swapped_out) override {
311 return new CaptureTestRenderViewHost(instance, delegate, widget_delegate,
312 routing_id, main_frame_routing_id,
313 swapped_out, controller_);
315 private:
316 CaptureTestSourceController* controller_;
318 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHostFactory);
321 // A stub consumer of captured video frames, which checks the output of
322 // WebContentsVideoCaptureDevice.
323 class StubClient : public media::VideoCaptureDevice::Client {
324 public:
325 StubClient(
326 const base::Callback<void(SkColor, const gfx::Size&)>& report_callback,
327 const base::Closure& error_callback)
328 : report_callback_(report_callback),
329 error_callback_(error_callback) {
330 buffer_pool_ = new VideoCaptureBufferPool(2);
332 ~StubClient() override {}
334 MOCK_METHOD5(OnIncomingCapturedData,
335 void(const uint8* data,
336 int length,
337 const media::VideoCaptureFormat& frame_format,
338 int rotation,
339 const base::TimeTicks& timestamp));
340 MOCK_METHOD9(OnIncomingCapturedYuvData,
341 void (const uint8* y_data,
342 const uint8* u_data,
343 const uint8* v_data,
344 size_t y_stride,
345 size_t u_stride,
346 size_t v_stride,
347 const media::VideoCaptureFormat& frame_format,
348 int clockwise_rotation,
349 const base::TimeTicks& timestamp));
351 MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void));
353 scoped_ptr<media::VideoCaptureDevice::Client::Buffer> ReserveOutputBuffer(
354 const gfx::Size& dimensions,
355 media::VideoCapturePixelFormat format,
356 media::VideoPixelStorage storage) override {
357 CHECK_EQ(format, media::VIDEO_CAPTURE_PIXEL_FORMAT_I420);
358 int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; // Ignored.
359 const int buffer_id = buffer_pool_->ReserveForProducer(
360 format, storage, dimensions, &buffer_id_to_drop);
361 if (buffer_id == VideoCaptureBufferPool::kInvalidId)
362 return NULL;
364 return scoped_ptr<media::VideoCaptureDevice::Client::Buffer>(
365 new AutoReleaseBuffer(
366 buffer_pool_, buffer_pool_->GetBufferHandle(buffer_id), buffer_id));
368 // Trampoline method to workaround GMOCK problems with scoped_ptr<>.
369 void OnIncomingCapturedBuffer(scoped_ptr<Buffer> buffer,
370 const media::VideoCaptureFormat& frame_format,
371 const base::TimeTicks& timestamp) override {
372 DoOnIncomingCapturedBuffer();
375 void OnIncomingCapturedVideoFrame(
376 scoped_ptr<Buffer> buffer,
377 const scoped_refptr<media::VideoFrame>& frame,
378 const base::TimeTicks& timestamp) override {
379 EXPECT_FALSE(frame->visible_rect().IsEmpty());
380 EXPECT_EQ(media::PIXEL_FORMAT_I420, frame->format());
381 double frame_rate = 0;
382 EXPECT_TRUE(
383 frame->metadata()->GetDouble(media::VideoFrameMetadata::FRAME_RATE,
384 &frame_rate));
385 EXPECT_EQ(kTestFramesPerSecond, frame_rate);
387 // TODO(miu): We just look at the center pixel presently, because if the
388 // analysis is too slow, the backlog of frames will grow without bound and
389 // trouble erupts. http://crbug.com/174519
390 using media::VideoFrame;
391 const gfx::Point center = frame->visible_rect().CenterPoint();
392 const int center_offset_y =
393 (frame->stride(VideoFrame::kYPlane) * center.y()) + center.x();
394 const int center_offset_uv =
395 (frame->stride(VideoFrame::kUPlane) * (center.y() / 2)) +
396 (center.x() / 2);
397 report_callback_.Run(
398 SkColorSetRGB(frame->data(VideoFrame::kYPlane)[center_offset_y],
399 frame->data(VideoFrame::kUPlane)[center_offset_uv],
400 frame->data(VideoFrame::kVPlane)[center_offset_uv]),
401 frame->visible_rect().size());
404 void OnError(const std::string& reason) override { error_callback_.Run(); }
406 double GetBufferPoolUtilization() const override { return 0.0; }
408 private:
409 class AutoReleaseBuffer : public media::VideoCaptureDevice::Client::Buffer {
410 public:
411 AutoReleaseBuffer(
412 const scoped_refptr<VideoCaptureBufferPool>& pool,
413 scoped_ptr<VideoCaptureBufferPool::BufferHandle> buffer_handle,
414 int buffer_id)
415 : id_(buffer_id),
416 pool_(pool),
417 buffer_handle_(buffer_handle.Pass()) {
418 DCHECK(pool_.get());
420 int id() const override { return id_; }
421 size_t size() const override { return buffer_handle_->size(); }
422 void* data() override { return buffer_handle_->data(); }
423 ClientBuffer AsClientBuffer() override { return nullptr; }
424 #if defined(OS_POSIX)
425 base::FileDescriptor AsPlatformFile() override {
426 return base::FileDescriptor();
428 #endif
430 private:
431 ~AutoReleaseBuffer() override { pool_->RelinquishProducerReservation(id_); }
433 const int id_;
434 const scoped_refptr<VideoCaptureBufferPool> pool_;
435 const scoped_ptr<VideoCaptureBufferPool::BufferHandle> buffer_handle_;
438 scoped_refptr<VideoCaptureBufferPool> buffer_pool_;
439 base::Callback<void(SkColor, const gfx::Size&)> report_callback_;
440 base::Closure error_callback_;
442 DISALLOW_COPY_AND_ASSIGN(StubClient);
445 class StubClientObserver {
446 public:
447 StubClientObserver()
448 : error_encountered_(false),
449 wait_color_yuv_(0xcafe1950),
450 wait_size_(kTestWidth, kTestHeight) {
451 client_.reset(new StubClient(
452 base::Bind(&StubClientObserver::DidDeliverFrame,
453 base::Unretained(this)),
454 base::Bind(&StubClientObserver::OnError, base::Unretained(this))));
457 virtual ~StubClientObserver() {}
459 scoped_ptr<media::VideoCaptureDevice::Client> PassClient() {
460 return client_.Pass();
463 void QuitIfConditionsMet(SkColor color, const gfx::Size& size) {
464 base::AutoLock guard(lock_);
465 if (error_encountered_)
466 base::MessageLoop::current()->Quit();
467 else if (wait_color_yuv_ == color && wait_size_.IsEmpty())
468 base::MessageLoop::current()->Quit();
469 else if (wait_color_yuv_ == color && wait_size_ == size)
470 base::MessageLoop::current()->Quit();
473 // Run the current loop until a frame is delivered with the |expected_color|
474 // and any non-empty frame size.
475 void WaitForNextColor(SkColor expected_color) {
476 WaitForNextColorAndFrameSize(expected_color, gfx::Size());
479 // Run the current loop until a frame is delivered with the |expected_color|
480 // and is of the |expected_size|.
481 void WaitForNextColorAndFrameSize(SkColor expected_color,
482 const gfx::Size& expected_size) {
484 base::AutoLock guard(lock_);
485 wait_color_yuv_ = ConvertRgbToYuv(expected_color);
486 wait_size_ = expected_size;
487 error_encountered_ = false;
489 RunCurrentLoopWithDeadline();
491 base::AutoLock guard(lock_);
492 ASSERT_FALSE(error_encountered_);
496 void WaitForError() {
498 base::AutoLock guard(lock_);
499 wait_color_yuv_ = kNotInterested;
500 wait_size_ = gfx::Size();
501 error_encountered_ = false;
503 RunCurrentLoopWithDeadline();
505 base::AutoLock guard(lock_);
506 ASSERT_TRUE(error_encountered_);
510 bool HasError() {
511 base::AutoLock guard(lock_);
512 return error_encountered_;
515 void OnError() {
517 base::AutoLock guard(lock_);
518 error_encountered_ = true;
520 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
521 &StubClientObserver::QuitIfConditionsMet,
522 base::Unretained(this),
523 kNothingYet,
524 gfx::Size()));
527 void DidDeliverFrame(SkColor color, const gfx::Size& size) {
528 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
529 &StubClientObserver::QuitIfConditionsMet,
530 base::Unretained(this),
531 color,
532 size));
535 private:
536 base::Lock lock_;
537 bool error_encountered_;
538 SkColor wait_color_yuv_;
539 gfx::Size wait_size_;
540 scoped_ptr<StubClient> client_;
542 DISALLOW_COPY_AND_ASSIGN(StubClientObserver);
545 // Test harness that sets up a minimal environment with necessary stubs.
546 class WebContentsVideoCaptureDeviceTest : public testing::Test {
547 public:
548 // This is public because C++ method pointer scoping rules are silly and make
549 // this hard to use with Bind().
550 void ResetWebContents() {
551 web_contents_.reset();
554 protected:
555 void SetUp() override {
556 test_screen_.display()->set_id(0x1337);
557 test_screen_.display()->set_bounds(gfx::Rect(0, 0, 2560, 1440));
558 test_screen_.display()->set_device_scale_factor(kTestDeviceScaleFactor);
560 gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, &test_screen_);
561 ASSERT_EQ(&test_screen_, gfx::Screen::GetNativeScreen());
563 // TODO(nick): Sadness and woe! Much "mock-the-world" boilerplate could be
564 // eliminated here, if only we could use RenderViewHostTestHarness. The
565 // catch is that we need our TestRenderViewHost to support a
566 // CopyFromBackingStore operation that we control. To accomplish that,
567 // either RenderViewHostTestHarness would have to support installing a
568 // custom RenderViewHostFactory, or else we implant some kind of delegated
569 // CopyFromBackingStore functionality into TestRenderViewHost itself.
571 render_process_host_factory_.reset(new MockRenderProcessHostFactory());
572 // Create our (self-registering) RVH factory, so that when we create a
573 // WebContents, it in turn creates CaptureTestRenderViewHosts.
574 render_view_host_factory_.reset(
575 new CaptureTestRenderViewHostFactory(&controller_));
577 browser_context_.reset(new TestBrowserContext());
579 scoped_refptr<SiteInstance> site_instance =
580 SiteInstance::Create(browser_context_.get());
581 SiteInstanceImpl::set_render_process_host_factory(
582 render_process_host_factory_.get());
583 web_contents_.reset(
584 TestWebContents::Create(browser_context_.get(), site_instance.get()));
585 RenderFrameHost* const main_frame = web_contents_->GetMainFrame();
586 device_.reset(WebContentsVideoCaptureDevice::Create(
587 base::StringPrintf("web-contents-media-stream://%d:%d",
588 main_frame->GetProcess()->GetID(),
589 main_frame->GetRoutingID())));
591 base::RunLoop().RunUntilIdle();
594 void TearDown() override {
595 // Tear down in opposite order of set-up.
597 // The device is destroyed asynchronously, and will notify the
598 // CaptureTestSourceController when it finishes destruction.
599 // Trigger this, and wait.
600 if (device_) {
601 device_->StopAndDeAllocate();
602 device_.reset();
605 base::RunLoop().RunUntilIdle();
607 // Destroy the browser objects.
608 web_contents_.reset();
609 browser_context_.reset();
611 base::RunLoop().RunUntilIdle();
613 SiteInstanceImpl::set_render_process_host_factory(NULL);
614 render_view_host_factory_.reset();
615 render_process_host_factory_.reset();
617 gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, NULL);
620 // Accessors.
621 CaptureTestSourceController* source() { return &controller_; }
622 WebContents* web_contents() const { return web_contents_.get(); }
623 media::VideoCaptureDevice* device() { return device_.get(); }
625 // Returns the device scale factor of the capture target's native view. This
626 // is necessary because, architecturally, the TestScreen implementation is
627 // ignored on Mac platforms (when determining the device scale factor for a
628 // particular window).
629 float GetDeviceScaleFactor() const {
630 RenderWidgetHostView* const view =
631 web_contents_->GetRenderViewHost()->GetView();
632 CHECK(view);
633 return ui::GetScaleFactorForNativeView(view->GetNativeView());
636 void SimulateDrawEvent() {
637 if (source()->CanUseFrameSubscriber()) {
638 // Print
639 CaptureTestView* test_view = static_cast<CaptureTestView*>(
640 web_contents_->GetRenderViewHost()->GetView());
641 test_view->SimulateUpdate();
642 } else {
643 // Simulate a non-accelerated paint.
644 NotificationService::current()->Notify(
645 NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
646 Source<RenderWidgetHost>(web_contents_->GetRenderViewHost()),
647 NotificationService::NoDetails());
651 void SimulateSourceSizeChange(const gfx::Size& size) {
652 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
653 CaptureTestView* test_view = static_cast<CaptureTestView*>(
654 web_contents_->GetRenderViewHost()->GetView());
655 test_view->SetSize(size);
656 // Normally, RenderWidgetHostImpl would notify WebContentsImpl that the size
657 // has changed. However, in this test setup where there is no render
658 // process, we must notify WebContentsImpl directly.
659 WebContentsImpl* const as_web_contents_impl =
660 static_cast<WebContentsImpl*>(web_contents_.get());
661 RenderWidgetHostDelegate* const as_rwh_delegate =
662 static_cast<RenderWidgetHostDelegate*>(as_web_contents_impl);
663 as_rwh_delegate->RenderWidgetWasResized(
664 as_web_contents_impl->GetMainFrame()->GetRenderWidgetHost(), true);
667 void DestroyVideoCaptureDevice() { device_.reset(); }
669 StubClientObserver* client_observer() {
670 return &client_observer_;
673 private:
674 gfx::test::TestScreen test_screen_;
676 StubClientObserver client_observer_;
678 // The controller controls which pixel patterns to produce.
679 CaptureTestSourceController controller_;
681 // Self-registering RenderProcessHostFactory.
682 scoped_ptr<MockRenderProcessHostFactory> render_process_host_factory_;
684 // Creates capture-capable RenderViewHosts whose pixel content production is
685 // under the control of |controller_|.
686 scoped_ptr<CaptureTestRenderViewHostFactory> render_view_host_factory_;
688 // A mocked-out browser and tab.
689 scoped_ptr<TestBrowserContext> browser_context_;
690 scoped_ptr<WebContents> web_contents_;
692 // Finally, the WebContentsVideoCaptureDevice under test.
693 scoped_ptr<media::VideoCaptureDevice> device_;
695 TestBrowserThreadBundle thread_bundle_;
698 TEST_F(WebContentsVideoCaptureDeviceTest, InvalidInitialWebContentsError) {
699 // Before the installs itself on the UI thread up to start capturing, we'll
700 // delete the web contents. This should trigger an error which can happen in
701 // practice; we should be able to recover gracefully.
702 ResetWebContents();
704 media::VideoCaptureParams capture_params;
705 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
706 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
707 capture_params.requested_format.pixel_format =
708 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
709 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
710 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError());
711 device()->StopAndDeAllocate();
714 TEST_F(WebContentsVideoCaptureDeviceTest, WebContentsDestroyed) {
715 const float device_scale_factor = GetDeviceScaleFactor();
716 const gfx::Size capture_preferred_size(
717 static_cast<int>(kTestWidth / device_scale_factor),
718 static_cast<int>(kTestHeight / device_scale_factor));
719 ASSERT_NE(capture_preferred_size, web_contents()->GetPreferredSize());
721 // We'll simulate the tab being closed after the capture pipeline is up and
722 // running.
723 media::VideoCaptureParams capture_params;
724 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
725 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
726 capture_params.requested_format.pixel_format =
727 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
728 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
729 // Do one capture to prove
730 source()->SetSolidColor(SK_ColorRED);
731 SimulateDrawEvent();
732 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
734 base::RunLoop().RunUntilIdle();
736 // Check that the preferred size of the WebContents matches the one provided
737 // by WebContentsVideoCaptureDevice.
738 EXPECT_EQ(capture_preferred_size, web_contents()->GetPreferredSize());
740 // Post a task to close the tab. We should see an error reported to the
741 // consumer.
742 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
743 base::Bind(&WebContentsVideoCaptureDeviceTest::ResetWebContents,
744 base::Unretained(this)));
745 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError());
746 device()->StopAndDeAllocate();
749 TEST_F(WebContentsVideoCaptureDeviceTest,
750 StopDeviceBeforeCaptureMachineCreation) {
751 media::VideoCaptureParams capture_params;
752 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
753 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
754 capture_params.requested_format.pixel_format =
755 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
756 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
758 // Make a point of not running the UI messageloop here.
759 device()->StopAndDeAllocate();
760 DestroyVideoCaptureDevice();
762 // Currently, there should be CreateCaptureMachineOnUIThread() and
763 // DestroyCaptureMachineOnUIThread() tasks pending on the current (UI) message
764 // loop. These should both succeed without crashing, and the machine should
765 // wind up in the idle state.
766 base::RunLoop().RunUntilIdle();
769 TEST_F(WebContentsVideoCaptureDeviceTest, StopWithRendererWorkToDo) {
770 // Set up the test to use RGB copies and an normal
771 source()->SetCanCopyToVideoFrame(false);
772 source()->SetUseFrameSubscriber(false);
773 media::VideoCaptureParams capture_params;
774 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
775 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
776 capture_params.requested_format.pixel_format =
777 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
778 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
780 base::RunLoop().RunUntilIdle();
782 for (int i = 0; i < 10; ++i)
783 SimulateDrawEvent();
785 ASSERT_FALSE(client_observer()->HasError());
786 device()->StopAndDeAllocate();
787 ASSERT_FALSE(client_observer()->HasError());
788 base::RunLoop().RunUntilIdle();
789 ASSERT_FALSE(client_observer()->HasError());
792 TEST_F(WebContentsVideoCaptureDeviceTest, DeviceRestart) {
793 media::VideoCaptureParams capture_params;
794 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
795 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
796 capture_params.requested_format.pixel_format =
797 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
798 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
799 base::RunLoop().RunUntilIdle();
800 source()->SetSolidColor(SK_ColorRED);
801 SimulateDrawEvent();
802 SimulateDrawEvent();
803 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
804 SimulateDrawEvent();
805 SimulateDrawEvent();
806 source()->SetSolidColor(SK_ColorGREEN);
807 SimulateDrawEvent();
808 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
809 device()->StopAndDeAllocate();
811 // Device is stopped, but content can still be animating.
812 SimulateDrawEvent();
813 SimulateDrawEvent();
814 base::RunLoop().RunUntilIdle();
816 StubClientObserver observer2;
817 device()->AllocateAndStart(capture_params, observer2.PassClient());
818 source()->SetSolidColor(SK_ColorBLUE);
819 SimulateDrawEvent();
820 ASSERT_NO_FATAL_FAILURE(observer2.WaitForNextColor(SK_ColorBLUE));
821 source()->SetSolidColor(SK_ColorYELLOW);
822 SimulateDrawEvent();
823 ASSERT_NO_FATAL_FAILURE(observer2.WaitForNextColor(SK_ColorYELLOW));
824 device()->StopAndDeAllocate();
827 // The "happy case" test. No scaling is needed, so we should be able to change
828 // the picture emitted from the source and expect to see each delivered to the
829 // consumer. The test will alternate between the three capture paths, simulating
830 // falling in and out of accelerated compositing.
831 TEST_F(WebContentsVideoCaptureDeviceTest, GoesThroughAllTheMotions) {
832 media::VideoCaptureParams capture_params;
833 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
834 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
835 capture_params.requested_format.pixel_format =
836 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
837 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
839 for (int i = 0; i < 6; i++) {
840 const char* name = NULL;
841 switch (i % 3) {
842 case 0:
843 source()->SetCanCopyToVideoFrame(true);
844 source()->SetUseFrameSubscriber(false);
845 name = "VideoFrame";
846 break;
847 case 1:
848 source()->SetCanCopyToVideoFrame(false);
849 source()->SetUseFrameSubscriber(true);
850 name = "Subscriber";
851 break;
852 case 2:
853 source()->SetCanCopyToVideoFrame(false);
854 source()->SetUseFrameSubscriber(false);
855 name = "SkBitmap";
856 break;
857 default:
858 FAIL();
861 SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i));
863 source()->SetSolidColor(SK_ColorRED);
864 SimulateDrawEvent();
865 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
867 source()->SetSolidColor(SK_ColorGREEN);
868 SimulateDrawEvent();
869 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
871 source()->SetSolidColor(SK_ColorBLUE);
872 SimulateDrawEvent();
873 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorBLUE));
875 source()->SetSolidColor(SK_ColorBLACK);
876 SimulateDrawEvent();
877 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorBLACK));
879 device()->StopAndDeAllocate();
882 TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) {
883 media::VideoCaptureParams capture_params;
884 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
885 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
886 capture_params.requested_format.pixel_format =
887 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
888 // 1x1 is too small to process; we intend for this to result in an error.
889 source()->SetCopyResultSize(1, 1);
890 source()->SetSolidColor(SK_ColorRED);
891 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
893 // These frames ought to be dropped during the Render stage. Let
894 // several captures to happen.
895 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
896 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
897 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
898 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
899 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
901 // Now push some good frames through; they should be processed normally.
902 source()->SetCopyResultSize(kTestWidth, kTestHeight);
903 source()->SetSolidColor(SK_ColorGREEN);
904 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
905 source()->SetSolidColor(SK_ColorRED);
906 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
908 device()->StopAndDeAllocate();
911 // Tests that, when configured with the FIXED_ASPECT_RATIO resolution change
912 // policy, the source size changes result in video frames of possibly varying
913 // resolutions, but all with the same aspect ratio.
914 TEST_F(WebContentsVideoCaptureDeviceTest, VariableResolution_FixedAspectRatio) {
915 media::VideoCaptureParams capture_params;
916 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
917 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
918 capture_params.requested_format.pixel_format =
919 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
920 capture_params.resolution_change_policy =
921 media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO;
923 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
925 source()->SetUseFrameSubscriber(true);
927 // Source size equals maximum size. Expect delivered frames to be
928 // kTestWidth by kTestHeight.
929 source()->SetSolidColor(SK_ColorRED);
930 const float device_scale_factor = GetDeviceScaleFactor();
931 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
932 device_scale_factor, gfx::Size(kTestWidth, kTestHeight)));
933 SimulateDrawEvent();
934 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
935 SK_ColorRED, gfx::Size(kTestWidth, kTestHeight)));
937 // Source size is half in both dimensions. Expect delivered frames to be of
938 // the same aspect ratio as kTestWidth by kTestHeight, but larger than the
939 // half size because the minimum height is 180 lines.
940 source()->SetSolidColor(SK_ColorGREEN);
941 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
942 device_scale_factor, gfx::Size(kTestWidth / 2, kTestHeight / 2)));
943 SimulateDrawEvent();
944 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
945 SK_ColorGREEN, gfx::Size(180 * kTestWidth / kTestHeight, 180)));
947 // Source size changes aspect ratio. Expect delivered frames to be padded
948 // in the horizontal dimension to preserve aspect ratio.
949 source()->SetSolidColor(SK_ColorBLUE);
950 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
951 device_scale_factor, gfx::Size(kTestWidth / 2, kTestHeight)));
952 SimulateDrawEvent();
953 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
954 SK_ColorBLUE, gfx::Size(kTestWidth, kTestHeight)));
956 // Source size changes aspect ratio again. Expect delivered frames to be
957 // padded in the vertical dimension to preserve aspect ratio.
958 source()->SetSolidColor(SK_ColorBLACK);
959 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
960 device_scale_factor, gfx::Size(kTestWidth, kTestHeight / 2)));
961 SimulateDrawEvent();
962 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
963 SK_ColorBLACK, gfx::Size(kTestWidth, kTestHeight)));
965 device()->StopAndDeAllocate();
968 // Tests that, when configured with the ANY_WITHIN_LIMIT resolution change
969 // policy, the source size changes result in video frames of possibly varying
970 // resolutions.
971 TEST_F(WebContentsVideoCaptureDeviceTest, VariableResolution_AnyWithinLimits) {
972 media::VideoCaptureParams capture_params;
973 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
974 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
975 capture_params.requested_format.pixel_format =
976 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
977 capture_params.resolution_change_policy =
978 media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT;
980 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
982 source()->SetUseFrameSubscriber(true);
984 // Source size equals maximum size. Expect delivered frames to be
985 // kTestWidth by kTestHeight.
986 source()->SetSolidColor(SK_ColorRED);
987 const float device_scale_factor = GetDeviceScaleFactor();
988 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
989 device_scale_factor, gfx::Size(kTestWidth, kTestHeight)));
990 SimulateDrawEvent();
991 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
992 SK_ColorRED, gfx::Size(kTestWidth, kTestHeight)));
994 // Source size is half in both dimensions. Expect delivered frames to also
995 // be half in both dimensions.
996 source()->SetSolidColor(SK_ColorGREEN);
997 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
998 device_scale_factor, gfx::Size(kTestWidth / 2, kTestHeight / 2)));
999 SimulateDrawEvent();
1000 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
1001 SK_ColorGREEN, gfx::Size(kTestWidth / 2, kTestHeight / 2)));
1003 // Source size changes to something arbitrary. Since the source size is
1004 // less than the maximum size, expect delivered frames to be the same size
1005 // as the source size.
1006 source()->SetSolidColor(SK_ColorBLUE);
1007 gfx::Size arbitrary_source_size(kTestWidth / 2 + 42, kTestHeight - 10);
1008 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(device_scale_factor,
1009 arbitrary_source_size));
1010 SimulateDrawEvent();
1011 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
1012 SK_ColorBLUE, arbitrary_source_size));
1014 // Source size changes to something arbitrary that exceeds the maximum frame
1015 // size. Since the source size exceeds the maximum size, expect delivered
1016 // frames to be downscaled.
1017 source()->SetSolidColor(SK_ColorBLACK);
1018 arbitrary_source_size = gfx::Size(kTestWidth * 2, kTestHeight / 2);
1019 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(device_scale_factor,
1020 arbitrary_source_size));
1021 SimulateDrawEvent();
1022 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
1023 SK_ColorBLACK, gfx::Size(kTestWidth,
1024 kTestWidth * arbitrary_source_size.height() /
1025 arbitrary_source_size.width())));
1027 device()->StopAndDeAllocate();
1030 TEST_F(WebContentsVideoCaptureDeviceTest,
1031 ComputesStandardResolutionsForPreferredSize) {
1032 // Helper function to run the same testing procedure for multiple combinations
1033 // of |policy|, |standard_size| and |oddball_size|.
1034 const auto RunTestForPreferredSize =
1035 [=](media::ResolutionChangePolicy policy,
1036 const gfx::Size& oddball_size,
1037 const gfx::Size& standard_size) {
1038 SCOPED_TRACE(::testing::Message()
1039 << "policy=" << policy
1040 << ", oddball_size=" << oddball_size.ToString()
1041 << ", standard_size=" << standard_size.ToString());
1043 // Compute the expected preferred size. For the fixed-resolution use case,
1044 // the |oddball_size| is always the expected size; whereas for the
1045 // variable-resolution cases, the |standard_size| is the expected size.
1046 // Also, adjust to account for the device scale factor.
1047 gfx::Size capture_preferred_size = gfx::ToFlooredSize(gfx::ScaleSize(
1048 policy == media::RESOLUTION_POLICY_FIXED_RESOLUTION ?
1049 oddball_size : standard_size,
1050 1.0f / GetDeviceScaleFactor()));
1051 ASSERT_NE(capture_preferred_size, web_contents()->GetPreferredSize());
1053 // Start the WebContentsVideoCaptureDevice.
1054 media::VideoCaptureParams capture_params;
1055 capture_params.requested_format.frame_size = oddball_size;
1056 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
1057 capture_params.requested_format.pixel_format =
1058 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
1059 capture_params.resolution_change_policy = policy;
1060 StubClientObserver unused_observer;
1061 device()->AllocateAndStart(capture_params, unused_observer.PassClient());
1062 base::RunLoop().RunUntilIdle();
1064 // Check that the preferred size of the WebContents matches the one provided
1065 // by WebContentsVideoCaptureDevice.
1066 EXPECT_EQ(capture_preferred_size, web_contents()->GetPreferredSize());
1068 // Stop the WebContentsVideoCaptureDevice.
1069 device()->StopAndDeAllocate();
1070 base::RunLoop().RunUntilIdle();
1073 const media::ResolutionChangePolicy policies[3] = {
1074 media::RESOLUTION_POLICY_FIXED_RESOLUTION,
1075 media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO,
1076 media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT,
1079 for (size_t i = 0; i < arraysize(policies); ++i) {
1080 // A 16:9 standard resolution should be set as the preferred size when the
1081 // source size is almost or exactly 16:9.
1082 for (int delta_w = 0; delta_w <= +5; ++delta_w) {
1083 for (int delta_h = 0; delta_h <= +5; ++delta_h) {
1084 RunTestForPreferredSize(policies[i],
1085 gfx::Size(1280 + delta_w, 720 + delta_h),
1086 gfx::Size(1280, 720));
1089 for (int delta_w = -5; delta_w <= +5; ++delta_w) {
1090 for (int delta_h = -5; delta_h <= +5; ++delta_h) {
1091 RunTestForPreferredSize(policies[i],
1092 gfx::Size(1365 + delta_w, 768 + delta_h),
1093 gfx::Size(1280, 720));
1097 // A 4:3 standard resolution should be set as the preferred size when the
1098 // source size is almost or exactly 4:3.
1099 for (int delta_w = 0; delta_w <= +5; ++delta_w) {
1100 for (int delta_h = 0; delta_h <= +5; ++delta_h) {
1101 RunTestForPreferredSize(policies[i],
1102 gfx::Size(640 + delta_w, 480 + delta_h),
1103 gfx::Size(640, 480));
1106 for (int delta_w = -5; delta_w <= +5; ++delta_w) {
1107 for (int delta_h = -5; delta_h <= +5; ++delta_h) {
1108 RunTestForPreferredSize(policies[i],
1109 gfx::Size(800 + delta_w, 600 + delta_h),
1110 gfx::Size(768, 576));
1114 // When the source size is not a common video aspect ratio, there is no
1115 // adjustment made.
1116 RunTestForPreferredSize(
1117 policies[i], gfx::Size(1000, 1000), gfx::Size(1000, 1000));
1118 RunTestForPreferredSize(
1119 policies[i], gfx::Size(1600, 1000), gfx::Size(1600, 1000));
1120 RunTestForPreferredSize(
1121 policies[i], gfx::Size(837, 999), gfx::Size(837, 999));
1125 } // namespace
1126 } // namespace content