Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / content / browser / media / capture / web_contents_video_capture_device_unittest.cc
blob417d98c669048723139feedfe47de791ffc1cf14
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/screen.h"
41 #include "ui/gfx/test/test_screen.h"
43 namespace content {
44 namespace {
46 const int kTestWidth = 320;
47 const int kTestHeight = 240;
48 const int kTestFramesPerSecond = 20;
49 const float kTestDeviceScaleFactor = 2.0f;
50 const SkColor kNothingYet = 0xdeadbeef;
51 const SkColor kNotInterested = ~kNothingYet;
53 void DeadlineExceeded(base::Closure quit_closure) {
54 if (!base::debug::BeingDebugged()) {
55 quit_closure.Run();
56 FAIL() << "Deadline exceeded while waiting, quitting";
57 } else {
58 LOG(WARNING) << "Deadline exceeded; test would fail if debugger weren't "
59 << "attached.";
63 void RunCurrentLoopWithDeadline() {
64 base::Timer deadline(false, false);
65 deadline.Start(FROM_HERE, TestTimeouts::action_max_timeout(), base::Bind(
66 &DeadlineExceeded, base::MessageLoop::current()->QuitClosure()));
67 base::MessageLoop::current()->Run();
68 deadline.Stop();
71 SkColor ConvertRgbToYuv(SkColor rgb) {
72 uint8 yuv[3];
73 media::ConvertRGB32ToYUV(reinterpret_cast<uint8*>(&rgb),
74 yuv, yuv + 1, yuv + 2, 1, 1, 1, 1, 1);
75 return SkColorSetRGB(yuv[0], yuv[1], yuv[2]);
78 // Thread-safe class that controls the source pattern to be captured by the
79 // system under test. The lifetime of this class is greater than the lifetime
80 // of all objects that reference it, so it does not need to be reference
81 // counted.
82 class CaptureTestSourceController {
83 public:
84 CaptureTestSourceController()
85 : color_(SK_ColorMAGENTA),
86 copy_result_size_(kTestWidth, kTestHeight),
87 can_copy_to_video_frame_(false),
88 use_frame_subscriber_(false) {}
90 void SetSolidColor(SkColor color) {
91 base::AutoLock guard(lock_);
92 color_ = color;
95 SkColor GetSolidColor() {
96 base::AutoLock guard(lock_);
97 return color_;
100 void SetCopyResultSize(int width, int height) {
101 base::AutoLock guard(lock_);
102 copy_result_size_ = gfx::Size(width, height);
105 gfx::Size GetCopyResultSize() {
106 base::AutoLock guard(lock_);
107 return copy_result_size_;
110 void SignalCopy() {
111 // TODO(nick): This actually should always be happening on the UI thread.
112 base::AutoLock guard(lock_);
113 if (!copy_done_.is_null()) {
114 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, copy_done_);
115 copy_done_.Reset();
119 void SetCanCopyToVideoFrame(bool value) {
120 base::AutoLock guard(lock_);
121 can_copy_to_video_frame_ = value;
124 bool CanCopyToVideoFrame() {
125 base::AutoLock guard(lock_);
126 return can_copy_to_video_frame_;
129 void SetUseFrameSubscriber(bool value) {
130 base::AutoLock guard(lock_);
131 use_frame_subscriber_ = value;
134 bool CanUseFrameSubscriber() {
135 base::AutoLock guard(lock_);
136 return use_frame_subscriber_;
139 void WaitForNextCopy() {
141 base::AutoLock guard(lock_);
142 copy_done_ = base::MessageLoop::current()->QuitClosure();
145 RunCurrentLoopWithDeadline();
148 private:
149 base::Lock lock_; // Guards changes to all members.
150 SkColor color_;
151 gfx::Size copy_result_size_;
152 bool can_copy_to_video_frame_;
153 bool use_frame_subscriber_;
154 base::Closure copy_done_;
156 DISALLOW_COPY_AND_ASSIGN(CaptureTestSourceController);
159 // A stub implementation which returns solid-color bitmaps in calls to
160 // CopyFromCompositingSurfaceToVideoFrame(), and which allows the video-frame
161 // readback path to be switched on and off. The behavior is controlled by a
162 // CaptureTestSourceController.
163 class CaptureTestView : public TestRenderWidgetHostView {
164 public:
165 explicit CaptureTestView(RenderWidgetHostImpl* rwh,
166 CaptureTestSourceController* controller)
167 : TestRenderWidgetHostView(rwh),
168 controller_(controller),
169 fake_bounds_(100, 100, 100 + kTestWidth, 100 + kTestHeight) {}
171 ~CaptureTestView() override {}
173 // TestRenderWidgetHostView overrides.
174 gfx::Rect GetViewBounds() const override {
175 return fake_bounds_;
178 void SetSize(const gfx::Size& size) override {
179 SetBounds(gfx::Rect(fake_bounds_.origin(), size));
182 void SetBounds(const gfx::Rect& rect) override {
183 fake_bounds_ = rect;
186 bool CanCopyToVideoFrame() const override {
187 return controller_->CanCopyToVideoFrame();
190 void CopyFromCompositingSurfaceToVideoFrame(
191 const gfx::Rect& src_subrect,
192 const scoped_refptr<media::VideoFrame>& target,
193 const base::Callback<void(bool)>& callback) override {
194 SkColor c = ConvertRgbToYuv(controller_->GetSolidColor());
195 media::FillYUV(
196 target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
197 callback.Run(true);
198 controller_->SignalCopy();
201 void BeginFrameSubscription(
202 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) override {
203 subscriber_.reset(subscriber.release());
206 void EndFrameSubscription() override { subscriber_.reset(); }
208 // Simulate a compositor paint event for our subscriber.
209 void SimulateUpdate() {
210 const base::TimeTicks present_time = base::TimeTicks::Now();
211 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback;
212 scoped_refptr<media::VideoFrame> target;
213 if (subscriber_ && subscriber_->ShouldCaptureFrame(
214 gfx::Rect(), present_time, &target, &callback)) {
215 SkColor c = ConvertRgbToYuv(controller_->GetSolidColor());
216 media::FillYUV(
217 target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c));
218 BrowserThread::PostTask(BrowserThread::UI,
219 FROM_HERE,
220 base::Bind(callback, present_time, true));
221 controller_->SignalCopy();
225 private:
226 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber_;
227 CaptureTestSourceController* const controller_;
228 gfx::Rect fake_bounds_;
230 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestView);
233 #if defined(COMPILER_MSVC)
234 // MSVC warns on diamond inheritance. See comment for same warning on
235 // RenderViewHostImpl.
236 #pragma warning(push)
237 #pragma warning(disable: 4250)
238 #endif
240 // A stub implementation which returns solid-color bitmaps in calls to
241 // CopyFromBackingStore(). The behavior is controlled by a
242 // CaptureTestSourceController.
243 class CaptureTestRenderViewHost : public TestRenderViewHost {
244 public:
245 CaptureTestRenderViewHost(SiteInstance* instance,
246 RenderViewHostDelegate* delegate,
247 RenderWidgetHostDelegate* widget_delegate,
248 int routing_id,
249 int main_frame_routing_id,
250 bool swapped_out,
251 CaptureTestSourceController* controller)
252 : TestRenderViewHost(instance, delegate, widget_delegate, routing_id,
253 main_frame_routing_id, swapped_out),
254 controller_(controller) {
255 // Override the default view installed by TestRenderViewHost; we need
256 // our special subclass which has mocked-out tab capture support.
257 RenderWidgetHostView* old_view = GetView();
258 SetView(new CaptureTestView(this, controller));
259 delete old_view;
262 // TestRenderViewHost overrides.
263 void CopyFromBackingStore(const gfx::Rect& src_rect,
264 const gfx::Size& accelerated_dst_size,
265 ReadbackRequestCallback& callback,
266 const SkColorType color_type) override {
267 gfx::Size size = controller_->GetCopyResultSize();
268 SkColor color = controller_->GetSolidColor();
270 // Although it's not necessary, use a PlatformBitmap here (instead of a
271 // regular SkBitmap) to exercise possible threading issues.
272 skia::PlatformBitmap output;
273 EXPECT_TRUE(output.Allocate(size.width(), size.height(), false));
275 SkAutoLockPixels locker(output.GetBitmap());
276 output.GetBitmap().eraseColor(color);
278 callback.Run(output.GetBitmap(), content::READBACK_SUCCESS);
279 controller_->SignalCopy();
282 private:
283 CaptureTestSourceController* controller_;
285 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHost);
288 #if defined(COMPILER_MSVC)
289 // Re-enable warning 4250
290 #pragma warning(pop)
291 #endif
293 class CaptureTestRenderViewHostFactory : public RenderViewHostFactory {
294 public:
295 explicit CaptureTestRenderViewHostFactory(
296 CaptureTestSourceController* controller) : controller_(controller) {
297 RegisterFactory(this);
300 ~CaptureTestRenderViewHostFactory() override { UnregisterFactory(); }
302 // RenderViewHostFactory implementation.
303 RenderViewHost* CreateRenderViewHost(
304 SiteInstance* instance,
305 RenderViewHostDelegate* delegate,
306 RenderWidgetHostDelegate* widget_delegate,
307 int routing_id,
308 int main_frame_routing_id,
309 bool swapped_out) override {
310 return new CaptureTestRenderViewHost(instance, delegate, widget_delegate,
311 routing_id, main_frame_routing_id,
312 swapped_out, controller_);
314 private:
315 CaptureTestSourceController* controller_;
317 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHostFactory);
320 // A stub consumer of captured video frames, which checks the output of
321 // WebContentsVideoCaptureDevice.
322 class StubClient : public media::VideoCaptureDevice::Client {
323 public:
324 StubClient(
325 const base::Callback<void(SkColor, const gfx::Size&)>& report_callback,
326 const base::Closure& error_callback)
327 : report_callback_(report_callback),
328 error_callback_(error_callback) {
329 buffer_pool_ = new VideoCaptureBufferPool(2);
331 ~StubClient() override {}
333 MOCK_METHOD5(OnIncomingCapturedData,
334 void(const uint8* data,
335 int length,
336 const media::VideoCaptureFormat& frame_format,
337 int rotation,
338 const base::TimeTicks& timestamp));
339 MOCK_METHOD9(OnIncomingCapturedYuvData,
340 void (const uint8* y_data,
341 const uint8* u_data,
342 const uint8* v_data,
343 size_t y_stride,
344 size_t u_stride,
345 size_t v_stride,
346 const media::VideoCaptureFormat& frame_format,
347 int clockwise_rotation,
348 const base::TimeTicks& timestamp));
350 MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void));
352 scoped_ptr<media::VideoCaptureDevice::Client::Buffer> ReserveOutputBuffer(
353 const gfx::Size& dimensions,
354 media::VideoCapturePixelFormat format,
355 media::VideoPixelStorage storage) override {
356 CHECK_EQ(format, media::VIDEO_CAPTURE_PIXEL_FORMAT_I420);
357 int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; // Ignored.
358 const int buffer_id = buffer_pool_->ReserveForProducer(
359 format, storage, dimensions, &buffer_id_to_drop);
360 if (buffer_id == VideoCaptureBufferPool::kInvalidId)
361 return NULL;
363 return scoped_ptr<media::VideoCaptureDevice::Client::Buffer>(
364 new AutoReleaseBuffer(
365 buffer_pool_, buffer_pool_->GetBufferHandle(buffer_id), buffer_id));
367 // Trampoline method to workaround GMOCK problems with scoped_ptr<>.
368 void OnIncomingCapturedBuffer(scoped_ptr<Buffer> buffer,
369 const media::VideoCaptureFormat& frame_format,
370 const base::TimeTicks& timestamp) override {
371 DoOnIncomingCapturedBuffer();
374 void OnIncomingCapturedVideoFrame(
375 scoped_ptr<Buffer> buffer,
376 const scoped_refptr<media::VideoFrame>& frame,
377 const base::TimeTicks& timestamp) override {
378 EXPECT_FALSE(frame->visible_rect().IsEmpty());
379 EXPECT_EQ(media::PIXEL_FORMAT_I420, frame->format());
380 double frame_rate = 0;
381 EXPECT_TRUE(
382 frame->metadata()->GetDouble(media::VideoFrameMetadata::FRAME_RATE,
383 &frame_rate));
384 EXPECT_EQ(kTestFramesPerSecond, frame_rate);
386 // TODO(miu): We just look at the center pixel presently, because if the
387 // analysis is too slow, the backlog of frames will grow without bound and
388 // trouble erupts. http://crbug.com/174519
389 using media::VideoFrame;
390 const gfx::Point center = frame->visible_rect().CenterPoint();
391 const int center_offset_y =
392 (frame->stride(VideoFrame::kYPlane) * center.y()) + center.x();
393 const int center_offset_uv =
394 (frame->stride(VideoFrame::kUPlane) * (center.y() / 2)) +
395 (center.x() / 2);
396 report_callback_.Run(
397 SkColorSetRGB(frame->data(VideoFrame::kYPlane)[center_offset_y],
398 frame->data(VideoFrame::kUPlane)[center_offset_uv],
399 frame->data(VideoFrame::kVPlane)[center_offset_uv]),
400 frame->visible_rect().size());
403 void OnError(const std::string& reason) override { error_callback_.Run(); }
405 double GetBufferPoolUtilization() const override { return 0.0; }
407 private:
408 class AutoReleaseBuffer : public media::VideoCaptureDevice::Client::Buffer {
409 public:
410 AutoReleaseBuffer(
411 const scoped_refptr<VideoCaptureBufferPool>& pool,
412 scoped_ptr<VideoCaptureBufferPool::BufferHandle> buffer_handle,
413 int buffer_id)
414 : id_(buffer_id),
415 pool_(pool),
416 buffer_handle_(buffer_handle.Pass()) {
417 DCHECK(pool_.get());
419 int id() const override { return id_; }
420 size_t size() const override { return buffer_handle_->size(); }
421 void* data() override { return buffer_handle_->data(); }
422 ClientBuffer AsClientBuffer() override { return nullptr; }
423 #if defined(OS_POSIX)
424 base::FileDescriptor AsPlatformFile() override {
425 return base::FileDescriptor();
427 #endif
429 private:
430 ~AutoReleaseBuffer() override { pool_->RelinquishProducerReservation(id_); }
432 const int id_;
433 const scoped_refptr<VideoCaptureBufferPool> pool_;
434 const scoped_ptr<VideoCaptureBufferPool::BufferHandle> buffer_handle_;
437 scoped_refptr<VideoCaptureBufferPool> buffer_pool_;
438 base::Callback<void(SkColor, const gfx::Size&)> report_callback_;
439 base::Closure error_callback_;
441 DISALLOW_COPY_AND_ASSIGN(StubClient);
444 class StubClientObserver {
445 public:
446 StubClientObserver()
447 : error_encountered_(false),
448 wait_color_yuv_(0xcafe1950),
449 wait_size_(kTestWidth, kTestHeight) {
450 client_.reset(new StubClient(
451 base::Bind(&StubClientObserver::DidDeliverFrame,
452 base::Unretained(this)),
453 base::Bind(&StubClientObserver::OnError, base::Unretained(this))));
456 virtual ~StubClientObserver() {}
458 scoped_ptr<media::VideoCaptureDevice::Client> PassClient() {
459 return client_.Pass();
462 void QuitIfConditionsMet(SkColor color, const gfx::Size& size) {
463 base::AutoLock guard(lock_);
464 if (error_encountered_)
465 base::MessageLoop::current()->Quit();
466 else if (wait_color_yuv_ == color && wait_size_.IsEmpty())
467 base::MessageLoop::current()->Quit();
468 else if (wait_color_yuv_ == color && wait_size_ == size)
469 base::MessageLoop::current()->Quit();
472 // Run the current loop until a frame is delivered with the |expected_color|
473 // and any non-empty frame size.
474 void WaitForNextColor(SkColor expected_color) {
475 WaitForNextColorAndFrameSize(expected_color, gfx::Size());
478 // Run the current loop until a frame is delivered with the |expected_color|
479 // and is of the |expected_size|.
480 void WaitForNextColorAndFrameSize(SkColor expected_color,
481 const gfx::Size& expected_size) {
483 base::AutoLock guard(lock_);
484 wait_color_yuv_ = ConvertRgbToYuv(expected_color);
485 wait_size_ = expected_size;
486 error_encountered_ = false;
488 RunCurrentLoopWithDeadline();
490 base::AutoLock guard(lock_);
491 ASSERT_FALSE(error_encountered_);
495 void WaitForError() {
497 base::AutoLock guard(lock_);
498 wait_color_yuv_ = kNotInterested;
499 wait_size_ = gfx::Size();
500 error_encountered_ = false;
502 RunCurrentLoopWithDeadline();
504 base::AutoLock guard(lock_);
505 ASSERT_TRUE(error_encountered_);
509 bool HasError() {
510 base::AutoLock guard(lock_);
511 return error_encountered_;
514 void OnError() {
516 base::AutoLock guard(lock_);
517 error_encountered_ = true;
519 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
520 &StubClientObserver::QuitIfConditionsMet,
521 base::Unretained(this),
522 kNothingYet,
523 gfx::Size()));
526 void DidDeliverFrame(SkColor color, const gfx::Size& size) {
527 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
528 &StubClientObserver::QuitIfConditionsMet,
529 base::Unretained(this),
530 color,
531 size));
534 private:
535 base::Lock lock_;
536 bool error_encountered_;
537 SkColor wait_color_yuv_;
538 gfx::Size wait_size_;
539 scoped_ptr<StubClient> client_;
541 DISALLOW_COPY_AND_ASSIGN(StubClientObserver);
544 // Test harness that sets up a minimal environment with necessary stubs.
545 class WebContentsVideoCaptureDeviceTest : public testing::Test {
546 public:
547 // This is public because C++ method pointer scoping rules are silly and make
548 // this hard to use with Bind().
549 void ResetWebContents() {
550 web_contents_.reset();
553 protected:
554 void SetUp() override {
555 test_screen_.display()->set_id(0x1337);
556 test_screen_.display()->set_bounds(gfx::Rect(0, 0, 2560, 1440));
557 test_screen_.display()->set_device_scale_factor(kTestDeviceScaleFactor);
559 gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, &test_screen_);
560 ASSERT_EQ(&test_screen_, gfx::Screen::GetNativeScreen());
562 // TODO(nick): Sadness and woe! Much "mock-the-world" boilerplate could be
563 // eliminated here, if only we could use RenderViewHostTestHarness. The
564 // catch is that we need our TestRenderViewHost to support a
565 // CopyFromBackingStore operation that we control. To accomplish that,
566 // either RenderViewHostTestHarness would have to support installing a
567 // custom RenderViewHostFactory, or else we implant some kind of delegated
568 // CopyFromBackingStore functionality into TestRenderViewHost itself.
570 render_process_host_factory_.reset(new MockRenderProcessHostFactory());
571 // Create our (self-registering) RVH factory, so that when we create a
572 // WebContents, it in turn creates CaptureTestRenderViewHosts.
573 render_view_host_factory_.reset(
574 new CaptureTestRenderViewHostFactory(&controller_));
576 browser_context_.reset(new TestBrowserContext());
578 scoped_refptr<SiteInstance> site_instance =
579 SiteInstance::Create(browser_context_.get());
580 SiteInstanceImpl::set_render_process_host_factory(
581 render_process_host_factory_.get());
582 web_contents_.reset(
583 TestWebContents::Create(browser_context_.get(), site_instance.get()));
584 RenderFrameHost* const main_frame = web_contents_->GetMainFrame();
585 device_.reset(WebContentsVideoCaptureDevice::Create(
586 base::StringPrintf("web-contents-media-stream://%d:%d",
587 main_frame->GetProcess()->GetID(),
588 main_frame->GetRoutingID())));
590 base::RunLoop().RunUntilIdle();
593 void TearDown() override {
594 // Tear down in opposite order of set-up.
596 // The device is destroyed asynchronously, and will notify the
597 // CaptureTestSourceController when it finishes destruction.
598 // Trigger this, and wait.
599 if (device_) {
600 device_->StopAndDeAllocate();
601 device_.reset();
604 base::RunLoop().RunUntilIdle();
606 // Destroy the browser objects.
607 web_contents_.reset();
608 browser_context_.reset();
610 base::RunLoop().RunUntilIdle();
612 SiteInstanceImpl::set_render_process_host_factory(NULL);
613 render_view_host_factory_.reset();
614 render_process_host_factory_.reset();
616 gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, NULL);
619 // Accessors.
620 CaptureTestSourceController* source() { return &controller_; }
621 WebContents* web_contents() const { return web_contents_.get(); }
622 media::VideoCaptureDevice* device() { return device_.get(); }
624 // Returns the device scale factor of the capture target's native view. This
625 // is necessary because, architecturally, the TestScreen implementation is
626 // ignored on Mac platforms (when determining the device scale factor for a
627 // particular window).
628 float GetDeviceScaleFactor() const {
629 RenderWidgetHostView* const view =
630 web_contents_->GetRenderViewHost()->GetView();
631 CHECK(view);
632 return ui::GetScaleFactorForNativeView(view->GetNativeView());
635 void SimulateDrawEvent() {
636 if (source()->CanUseFrameSubscriber()) {
637 // Print
638 CaptureTestView* test_view = static_cast<CaptureTestView*>(
639 web_contents_->GetRenderViewHost()->GetView());
640 test_view->SimulateUpdate();
641 } else {
642 // Simulate a non-accelerated paint.
643 NotificationService::current()->Notify(
644 NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
645 Source<RenderWidgetHost>(web_contents_->GetRenderViewHost()),
646 NotificationService::NoDetails());
650 void SimulateSourceSizeChange(const gfx::Size& size) {
651 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
652 CaptureTestView* test_view = static_cast<CaptureTestView*>(
653 web_contents_->GetRenderViewHost()->GetView());
654 test_view->SetSize(size);
655 // Normally, RenderWidgetHostImpl would notify WebContentsImpl that the size
656 // has changed. However, in this test setup where there is no render
657 // process, we must notify WebContentsImpl directly.
658 WebContentsImpl* const as_web_contents_impl =
659 static_cast<WebContentsImpl*>(web_contents_.get());
660 RenderWidgetHostDelegate* const as_rwh_delegate =
661 static_cast<RenderWidgetHostDelegate*>(as_web_contents_impl);
662 as_rwh_delegate->RenderWidgetWasResized(
663 as_web_contents_impl->GetMainFrame()->GetRenderWidgetHost(), true);
666 void DestroyVideoCaptureDevice() { device_.reset(); }
668 StubClientObserver* client_observer() {
669 return &client_observer_;
672 private:
673 gfx::test::TestScreen test_screen_;
675 StubClientObserver client_observer_;
677 // The controller controls which pixel patterns to produce.
678 CaptureTestSourceController controller_;
680 // Self-registering RenderProcessHostFactory.
681 scoped_ptr<MockRenderProcessHostFactory> render_process_host_factory_;
683 // Creates capture-capable RenderViewHosts whose pixel content production is
684 // under the control of |controller_|.
685 scoped_ptr<CaptureTestRenderViewHostFactory> render_view_host_factory_;
687 // A mocked-out browser and tab.
688 scoped_ptr<TestBrowserContext> browser_context_;
689 scoped_ptr<WebContents> web_contents_;
691 // Finally, the WebContentsVideoCaptureDevice under test.
692 scoped_ptr<media::VideoCaptureDevice> device_;
694 TestBrowserThreadBundle thread_bundle_;
697 TEST_F(WebContentsVideoCaptureDeviceTest, InvalidInitialWebContentsError) {
698 // Before the installs itself on the UI thread up to start capturing, we'll
699 // delete the web contents. This should trigger an error which can happen in
700 // practice; we should be able to recover gracefully.
701 ResetWebContents();
703 media::VideoCaptureParams capture_params;
704 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
705 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
706 capture_params.requested_format.pixel_format =
707 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
708 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
709 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError());
710 device()->StopAndDeAllocate();
713 TEST_F(WebContentsVideoCaptureDeviceTest, WebContentsDestroyed) {
714 const float device_scale_factor = GetDeviceScaleFactor();
715 const gfx::Size capture_preferred_size(
716 static_cast<int>(kTestWidth / device_scale_factor),
717 static_cast<int>(kTestHeight / device_scale_factor));
718 ASSERT_NE(capture_preferred_size, web_contents()->GetPreferredSize());
720 // We'll simulate the tab being closed after the capture pipeline is up and
721 // running.
722 media::VideoCaptureParams capture_params;
723 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
724 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
725 capture_params.requested_format.pixel_format =
726 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
727 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
728 // Do one capture to prove
729 source()->SetSolidColor(SK_ColorRED);
730 SimulateDrawEvent();
731 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
733 base::RunLoop().RunUntilIdle();
735 // Check that the preferred size of the WebContents matches the one provided
736 // by WebContentsVideoCaptureDevice.
737 EXPECT_EQ(capture_preferred_size, web_contents()->GetPreferredSize());
739 // Post a task to close the tab. We should see an error reported to the
740 // consumer.
741 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
742 base::Bind(&WebContentsVideoCaptureDeviceTest::ResetWebContents,
743 base::Unretained(this)));
744 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError());
745 device()->StopAndDeAllocate();
748 TEST_F(WebContentsVideoCaptureDeviceTest,
749 StopDeviceBeforeCaptureMachineCreation) {
750 media::VideoCaptureParams capture_params;
751 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
752 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
753 capture_params.requested_format.pixel_format =
754 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
755 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
757 // Make a point of not running the UI messageloop here.
758 device()->StopAndDeAllocate();
759 DestroyVideoCaptureDevice();
761 // Currently, there should be CreateCaptureMachineOnUIThread() and
762 // DestroyCaptureMachineOnUIThread() tasks pending on the current (UI) message
763 // loop. These should both succeed without crashing, and the machine should
764 // wind up in the idle state.
765 base::RunLoop().RunUntilIdle();
768 TEST_F(WebContentsVideoCaptureDeviceTest, StopWithRendererWorkToDo) {
769 // Set up the test to use RGB copies and an normal
770 source()->SetCanCopyToVideoFrame(false);
771 source()->SetUseFrameSubscriber(false);
772 media::VideoCaptureParams capture_params;
773 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
774 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
775 capture_params.requested_format.pixel_format =
776 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
777 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
779 base::RunLoop().RunUntilIdle();
781 for (int i = 0; i < 10; ++i)
782 SimulateDrawEvent();
784 ASSERT_FALSE(client_observer()->HasError());
785 device()->StopAndDeAllocate();
786 ASSERT_FALSE(client_observer()->HasError());
787 base::RunLoop().RunUntilIdle();
788 ASSERT_FALSE(client_observer()->HasError());
791 TEST_F(WebContentsVideoCaptureDeviceTest, DeviceRestart) {
792 media::VideoCaptureParams capture_params;
793 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
794 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
795 capture_params.requested_format.pixel_format =
796 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
797 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
798 base::RunLoop().RunUntilIdle();
799 source()->SetSolidColor(SK_ColorRED);
800 SimulateDrawEvent();
801 SimulateDrawEvent();
802 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
803 SimulateDrawEvent();
804 SimulateDrawEvent();
805 source()->SetSolidColor(SK_ColorGREEN);
806 SimulateDrawEvent();
807 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
808 device()->StopAndDeAllocate();
810 // Device is stopped, but content can still be animating.
811 SimulateDrawEvent();
812 SimulateDrawEvent();
813 base::RunLoop().RunUntilIdle();
815 StubClientObserver observer2;
816 device()->AllocateAndStart(capture_params, observer2.PassClient());
817 source()->SetSolidColor(SK_ColorBLUE);
818 SimulateDrawEvent();
819 ASSERT_NO_FATAL_FAILURE(observer2.WaitForNextColor(SK_ColorBLUE));
820 source()->SetSolidColor(SK_ColorYELLOW);
821 SimulateDrawEvent();
822 ASSERT_NO_FATAL_FAILURE(observer2.WaitForNextColor(SK_ColorYELLOW));
823 device()->StopAndDeAllocate();
826 // The "happy case" test. No scaling is needed, so we should be able to change
827 // the picture emitted from the source and expect to see each delivered to the
828 // consumer. The test will alternate between the three capture paths, simulating
829 // falling in and out of accelerated compositing.
830 TEST_F(WebContentsVideoCaptureDeviceTest, GoesThroughAllTheMotions) {
831 media::VideoCaptureParams capture_params;
832 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
833 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
834 capture_params.requested_format.pixel_format =
835 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
836 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
838 for (int i = 0; i < 6; i++) {
839 const char* name = NULL;
840 switch (i % 3) {
841 case 0:
842 source()->SetCanCopyToVideoFrame(true);
843 source()->SetUseFrameSubscriber(false);
844 name = "VideoFrame";
845 break;
846 case 1:
847 source()->SetCanCopyToVideoFrame(false);
848 source()->SetUseFrameSubscriber(true);
849 name = "Subscriber";
850 break;
851 case 2:
852 source()->SetCanCopyToVideoFrame(false);
853 source()->SetUseFrameSubscriber(false);
854 name = "SkBitmap";
855 break;
856 default:
857 FAIL();
860 SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i));
862 source()->SetSolidColor(SK_ColorRED);
863 SimulateDrawEvent();
864 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
866 source()->SetSolidColor(SK_ColorGREEN);
867 SimulateDrawEvent();
868 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
870 source()->SetSolidColor(SK_ColorBLUE);
871 SimulateDrawEvent();
872 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorBLUE));
874 source()->SetSolidColor(SK_ColorBLACK);
875 SimulateDrawEvent();
876 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorBLACK));
878 device()->StopAndDeAllocate();
881 TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) {
882 media::VideoCaptureParams capture_params;
883 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
884 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
885 capture_params.requested_format.pixel_format =
886 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
887 // 1x1 is too small to process; we intend for this to result in an error.
888 source()->SetCopyResultSize(1, 1);
889 source()->SetSolidColor(SK_ColorRED);
890 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
892 // These frames ought to be dropped during the Render stage. Let
893 // several captures to happen.
894 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy());
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());
900 // Now push some good frames through; they should be processed normally.
901 source()->SetCopyResultSize(kTestWidth, kTestHeight);
902 source()->SetSolidColor(SK_ColorGREEN);
903 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN));
904 source()->SetSolidColor(SK_ColorRED);
905 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED));
907 device()->StopAndDeAllocate();
910 // Tests that, when configured with the FIXED_ASPECT_RATIO resolution change
911 // policy, the source size changes result in video frames of possibly varying
912 // resolutions, but all with the same aspect ratio.
913 TEST_F(WebContentsVideoCaptureDeviceTest, VariableResolution_FixedAspectRatio) {
914 media::VideoCaptureParams capture_params;
915 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
916 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
917 capture_params.requested_format.pixel_format =
918 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
919 capture_params.resolution_change_policy =
920 media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO;
922 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
924 source()->SetUseFrameSubscriber(true);
926 // Source size equals maximum size. Expect delivered frames to be
927 // kTestWidth by kTestHeight.
928 source()->SetSolidColor(SK_ColorRED);
929 const float device_scale_factor = GetDeviceScaleFactor();
930 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
931 device_scale_factor, gfx::Size(kTestWidth, kTestHeight)));
932 SimulateDrawEvent();
933 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
934 SK_ColorRED, gfx::Size(kTestWidth, kTestHeight)));
936 // Source size is half in both dimensions. Expect delivered frames to be of
937 // the same aspect ratio as kTestWidth by kTestHeight, but larger than the
938 // half size because the minimum height is 180 lines.
939 source()->SetSolidColor(SK_ColorGREEN);
940 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
941 device_scale_factor, gfx::Size(kTestWidth / 2, kTestHeight / 2)));
942 SimulateDrawEvent();
943 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
944 SK_ColorGREEN, gfx::Size(180 * kTestWidth / kTestHeight, 180)));
946 // Source size changes aspect ratio. Expect delivered frames to be padded
947 // in the horizontal dimension to preserve aspect ratio.
948 source()->SetSolidColor(SK_ColorBLUE);
949 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
950 device_scale_factor, gfx::Size(kTestWidth / 2, kTestHeight)));
951 SimulateDrawEvent();
952 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
953 SK_ColorBLUE, gfx::Size(kTestWidth, kTestHeight)));
955 // Source size changes aspect ratio again. Expect delivered frames to be
956 // padded in the vertical dimension to preserve aspect ratio.
957 source()->SetSolidColor(SK_ColorBLACK);
958 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
959 device_scale_factor, gfx::Size(kTestWidth, kTestHeight / 2)));
960 SimulateDrawEvent();
961 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
962 SK_ColorBLACK, gfx::Size(kTestWidth, kTestHeight)));
964 device()->StopAndDeAllocate();
967 // Tests that, when configured with the ANY_WITHIN_LIMIT resolution change
968 // policy, the source size changes result in video frames of possibly varying
969 // resolutions.
970 TEST_F(WebContentsVideoCaptureDeviceTest, VariableResolution_AnyWithinLimits) {
971 media::VideoCaptureParams capture_params;
972 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
973 capture_params.requested_format.frame_rate = kTestFramesPerSecond;
974 capture_params.requested_format.pixel_format =
975 media::VIDEO_CAPTURE_PIXEL_FORMAT_I420;
976 capture_params.resolution_change_policy =
977 media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT;
979 device()->AllocateAndStart(capture_params, client_observer()->PassClient());
981 source()->SetUseFrameSubscriber(true);
983 // Source size equals maximum size. Expect delivered frames to be
984 // kTestWidth by kTestHeight.
985 source()->SetSolidColor(SK_ColorRED);
986 const float device_scale_factor = GetDeviceScaleFactor();
987 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
988 device_scale_factor, gfx::Size(kTestWidth, kTestHeight)));
989 SimulateDrawEvent();
990 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
991 SK_ColorRED, gfx::Size(kTestWidth, kTestHeight)));
993 // Source size is half in both dimensions. Expect delivered frames to also
994 // be half in both dimensions.
995 source()->SetSolidColor(SK_ColorGREEN);
996 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(
997 device_scale_factor, gfx::Size(kTestWidth / 2, kTestHeight / 2)));
998 SimulateDrawEvent();
999 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
1000 SK_ColorGREEN, gfx::Size(kTestWidth / 2, kTestHeight / 2)));
1002 // Source size changes to something arbitrary. Since the source size is
1003 // less than the maximum size, expect delivered frames to be the same size
1004 // as the source size.
1005 source()->SetSolidColor(SK_ColorBLUE);
1006 gfx::Size arbitrary_source_size(kTestWidth / 2 + 42, kTestHeight - 10);
1007 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(device_scale_factor,
1008 arbitrary_source_size));
1009 SimulateDrawEvent();
1010 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
1011 SK_ColorBLUE, arbitrary_source_size));
1013 // Source size changes to something arbitrary that exceeds the maximum frame
1014 // size. Since the source size exceeds the maximum size, expect delivered
1015 // frames to be downscaled.
1016 source()->SetSolidColor(SK_ColorBLACK);
1017 arbitrary_source_size = gfx::Size(kTestWidth * 2, kTestHeight / 2);
1018 SimulateSourceSizeChange(gfx::ConvertSizeToDIP(device_scale_factor,
1019 arbitrary_source_size));
1020 SimulateDrawEvent();
1021 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
1022 SK_ColorBLACK, gfx::Size(kTestWidth,
1023 kTestWidth * arbitrary_source_size.height() /
1024 arbitrary_source_size.width())));
1026 device()->StopAndDeAllocate();
1029 } // namespace
1030 } // namespace content