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. 4 5#include "content/browser/media/capture/web_contents_video_capture_device.h" 6 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/media/capture/video_capture_oracle.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/public/browser/notification_service.h" 20#include "content/public/browser/notification_types.h" 21#include "content/public/browser/render_widget_host_view_frame_subscriber.h" 22#include "content/public/test/mock_render_process_host.h" 23#include "content/public/test/test_browser_context.h" 24#include "content/public/test/test_browser_thread_bundle.h" 25#include "content/public/test/test_utils.h" 26#include "content/test/test_render_view_host.h" 27#include "content/test/test_web_contents.h" 28#include "media/base/video_frame.h" 29#include "media/base/video_util.h" 30#include "media/base/yuv_convert.h" 31#include "media/video/capture/video_capture_types.h" 32#include "skia/ext/platform_canvas.h" 33#include "testing/gtest/include/gtest/gtest.h" 34#include "third_party/skia/include/core/SkColor.h" 35#include "ui/gfx/display.h" 36#include "ui/gfx/screen.h" 37 38namespace content { 39namespace { 40 41const int kTestWidth = 320; 42const int kTestHeight = 240; 43const int kTestFramesPerSecond = 20; 44const float kTestDeviceScaleFactor = 2.0f; 45const SkColor kNothingYet = 0xdeadbeef; 46const SkColor kNotInterested = ~kNothingYet; 47 48void DeadlineExceeded(base::Closure quit_closure) { 49 if (!base::debug::BeingDebugged()) { 50 quit_closure.Run(); 51 FAIL() << "Deadline exceeded while waiting, quitting"; 52 } else { 53 LOG(WARNING) << "Deadline exceeded; test would fail if debugger weren't " 54 << "attached."; 55 } 56} 57 58void RunCurrentLoopWithDeadline() { 59 base::Timer deadline(false, false); 60 deadline.Start(FROM_HERE, TestTimeouts::action_max_timeout(), base::Bind( 61 &DeadlineExceeded, base::MessageLoop::current()->QuitClosure())); 62 base::MessageLoop::current()->Run(); 63 deadline.Stop(); 64} 65 66SkColor ConvertRgbToYuv(SkColor rgb) { 67 uint8 yuv[3]; 68 media::ConvertRGB32ToYUV(reinterpret_cast<uint8*>(&rgb), 69 yuv, yuv + 1, yuv + 2, 1, 1, 1, 1, 1); 70 return SkColorSetRGB(yuv[0], yuv[1], yuv[2]); 71} 72 73// Thread-safe class that controls the source pattern to be captured by the 74// system under test. The lifetime of this class is greater than the lifetime 75// of all objects that reference it, so it does not need to be reference 76// counted. 77class CaptureTestSourceController { 78 public: 79 80 CaptureTestSourceController() 81 : color_(SK_ColorMAGENTA), 82 copy_result_size_(kTestWidth, kTestHeight), 83 can_copy_to_video_frame_(false), 84 use_frame_subscriber_(false) {} 85 86 void SetSolidColor(SkColor color) { 87 base::AutoLock guard(lock_); 88 color_ = color; 89 } 90 91 SkColor GetSolidColor() { 92 base::AutoLock guard(lock_); 93 return color_; 94 } 95 96 void SetCopyResultSize(int width, int height) { 97 base::AutoLock guard(lock_); 98 copy_result_size_ = gfx::Size(width, height); 99 } 100 101 gfx::Size GetCopyResultSize() { 102 base::AutoLock guard(lock_); 103 return copy_result_size_; 104 } 105 106 void SignalCopy() { 107 // TODO(nick): This actually should always be happening on the UI thread. 108 base::AutoLock guard(lock_); 109 if (!copy_done_.is_null()) { 110 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, copy_done_); 111 copy_done_.Reset(); 112 } 113 } 114 115 void SetCanCopyToVideoFrame(bool value) { 116 base::AutoLock guard(lock_); 117 can_copy_to_video_frame_ = value; 118 } 119 120 bool CanCopyToVideoFrame() { 121 base::AutoLock guard(lock_); 122 return can_copy_to_video_frame_; 123 } 124 125 void SetUseFrameSubscriber(bool value) { 126 base::AutoLock guard(lock_); 127 use_frame_subscriber_ = value; 128 } 129 130 bool CanUseFrameSubscriber() { 131 base::AutoLock guard(lock_); 132 return use_frame_subscriber_; 133 } 134 135 void WaitForNextCopy() { 136 { 137 base::AutoLock guard(lock_); 138 copy_done_ = base::MessageLoop::current()->QuitClosure(); 139 } 140 141 RunCurrentLoopWithDeadline(); 142 } 143 144 private: 145 base::Lock lock_; // Guards changes to all members. 146 SkColor color_; 147 gfx::Size copy_result_size_; 148 bool can_copy_to_video_frame_; 149 bool use_frame_subscriber_; 150 base::Closure copy_done_; 151 152 DISALLOW_COPY_AND_ASSIGN(CaptureTestSourceController); 153}; 154 155// A stub implementation which returns solid-color bitmaps in calls to 156// CopyFromCompositingSurfaceToVideoFrame(), and which allows the video-frame 157// readback path to be switched on and off. The behavior is controlled by a 158// CaptureTestSourceController. 159class CaptureTestView : public TestRenderWidgetHostView { 160 public: 161 explicit CaptureTestView(RenderWidgetHostImpl* rwh, 162 CaptureTestSourceController* controller) 163 : TestRenderWidgetHostView(rwh), 164 controller_(controller) {} 165 166 virtual ~CaptureTestView() {} 167 168 // TestRenderWidgetHostView overrides. 169 virtual gfx::Rect GetViewBounds() const OVERRIDE { 170 return gfx::Rect(100, 100, 100 + kTestWidth, 100 + kTestHeight); 171 } 172 173 virtual bool CanCopyToVideoFrame() const OVERRIDE { 174 return controller_->CanCopyToVideoFrame(); 175 } 176 177 virtual void CopyFromCompositingSurfaceToVideoFrame( 178 const gfx::Rect& src_subrect, 179 const scoped_refptr<media::VideoFrame>& target, 180 const base::Callback<void(bool)>& callback) OVERRIDE { 181 SkColor c = ConvertRgbToYuv(controller_->GetSolidColor()); 182 media::FillYUV( 183 target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)); 184 callback.Run(true); 185 controller_->SignalCopy(); 186 } 187 188 virtual void BeginFrameSubscription( 189 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) OVERRIDE { 190 subscriber_.reset(subscriber.release()); 191 } 192 193 virtual void EndFrameSubscription() OVERRIDE { 194 subscriber_.reset(); 195 } 196 197 // Simulate a compositor paint event for our subscriber. 198 void SimulateUpdate() { 199 const base::TimeTicks present_time = base::TimeTicks::Now(); 200 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback callback; 201 scoped_refptr<media::VideoFrame> target; 202 if (subscriber_ && subscriber_->ShouldCaptureFrame( 203 gfx::Rect(), present_time, &target, &callback)) { 204 SkColor c = ConvertRgbToYuv(controller_->GetSolidColor()); 205 media::FillYUV( 206 target.get(), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)); 207 BrowserThread::PostTask(BrowserThread::UI, 208 FROM_HERE, 209 base::Bind(callback, present_time, true)); 210 controller_->SignalCopy(); 211 } 212 } 213 214 private: 215 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber_; 216 CaptureTestSourceController* const controller_; 217 218 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestView); 219}; 220 221#if defined(COMPILER_MSVC) 222// MSVC warns on diamond inheritance. See comment for same warning on 223// RenderViewHostImpl. 224#pragma warning(push) 225#pragma warning(disable: 4250) 226#endif 227 228// A stub implementation which returns solid-color bitmaps in calls to 229// CopyFromBackingStore(). The behavior is controlled by a 230// CaptureTestSourceController. 231class CaptureTestRenderViewHost : public TestRenderViewHost { 232 public: 233 CaptureTestRenderViewHost(SiteInstance* instance, 234 RenderViewHostDelegate* delegate, 235 RenderWidgetHostDelegate* widget_delegate, 236 int routing_id, 237 int main_frame_routing_id, 238 bool swapped_out, 239 CaptureTestSourceController* controller) 240 : TestRenderViewHost(instance, delegate, widget_delegate, routing_id, 241 main_frame_routing_id, swapped_out), 242 controller_(controller) { 243 // Override the default view installed by TestRenderViewHost; we need 244 // our special subclass which has mocked-out tab capture support. 245 RenderWidgetHostView* old_view = GetView(); 246 SetView(new CaptureTestView(this, controller)); 247 delete old_view; 248 } 249 250 // TestRenderViewHost overrides. 251 virtual void CopyFromBackingStore( 252 const gfx::Rect& src_rect, 253 const gfx::Size& accelerated_dst_size, 254 const base::Callback<void(bool, const SkBitmap&)>& callback, 255 const SkColorType color_type) OVERRIDE { 256 gfx::Size size = controller_->GetCopyResultSize(); 257 SkColor color = controller_->GetSolidColor(); 258 259 // Although it's not necessary, use a PlatformBitmap here (instead of a 260 // regular SkBitmap) to exercise possible threading issues. 261 skia::PlatformBitmap output; 262 EXPECT_TRUE(output.Allocate(size.width(), size.height(), false)); 263 { 264 SkAutoLockPixels locker(output.GetBitmap()); 265 output.GetBitmap().eraseColor(color); 266 } 267 callback.Run(true, output.GetBitmap()); 268 controller_->SignalCopy(); 269 } 270 271 private: 272 CaptureTestSourceController* controller_; 273 274 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHost); 275}; 276 277#if defined(COMPILER_MSVC) 278// Re-enable warning 4250 279#pragma warning(pop) 280#endif 281 282class CaptureTestRenderViewHostFactory : public RenderViewHostFactory { 283 public: 284 explicit CaptureTestRenderViewHostFactory( 285 CaptureTestSourceController* controller) : controller_(controller) { 286 RegisterFactory(this); 287 } 288 289 virtual ~CaptureTestRenderViewHostFactory() { 290 UnregisterFactory(); 291 } 292 293 // RenderViewHostFactory implementation. 294 virtual RenderViewHost* CreateRenderViewHost( 295 SiteInstance* instance, 296 RenderViewHostDelegate* delegate, 297 RenderWidgetHostDelegate* widget_delegate, 298 int routing_id, 299 int main_frame_routing_id, 300 bool swapped_out) OVERRIDE { 301 return new CaptureTestRenderViewHost(instance, delegate, widget_delegate, 302 routing_id, main_frame_routing_id, 303 swapped_out, controller_); 304 } 305 private: 306 CaptureTestSourceController* controller_; 307 308 DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestRenderViewHostFactory); 309}; 310 311// A stub consumer of captured video frames, which checks the output of 312// WebContentsVideoCaptureDevice. 313class StubClient : public media::VideoCaptureDevice::Client { 314 public: 315 StubClient(const base::Callback<void(SkColor)>& color_callback, 316 const base::Closure& error_callback) 317 : color_callback_(color_callback), 318 error_callback_(error_callback) { 319 buffer_pool_ = new VideoCaptureBufferPool(2); 320 } 321 virtual ~StubClient() {} 322 323 virtual scoped_refptr<media::VideoCaptureDevice::Client::Buffer> 324 ReserveOutputBuffer(media::VideoFrame::Format format, 325 const gfx::Size& dimensions) OVERRIDE { 326 CHECK_EQ(format, media::VideoFrame::I420); 327 const size_t frame_bytes = 328 media::VideoFrame::AllocationSize(media::VideoFrame::I420, dimensions); 329 int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; // Ignored. 330 int buffer_id = 331 buffer_pool_->ReserveForProducer(frame_bytes, &buffer_id_to_drop); 332 if (buffer_id == VideoCaptureBufferPool::kInvalidId) 333 return NULL; 334 void* data; 335 size_t size; 336 buffer_pool_->GetBufferInfo(buffer_id, &data, &size); 337 return scoped_refptr<media::VideoCaptureDevice::Client::Buffer>( 338 new PoolBuffer(buffer_pool_, buffer_id, data, size)); 339 } 340 341 virtual void OnIncomingCapturedData( 342 const uint8* data, 343 int length, 344 const media::VideoCaptureFormat& frame_format, 345 int rotation, 346 base::TimeTicks timestamp) OVERRIDE { 347 FAIL(); 348 } 349 350 virtual void OnIncomingCapturedVideoFrame( 351 const scoped_refptr<Buffer>& buffer, 352 const media::VideoCaptureFormat& buffer_format, 353 const scoped_refptr<media::VideoFrame>& frame, 354 base::TimeTicks timestamp) OVERRIDE { 355 EXPECT_EQ(gfx::Size(kTestWidth, kTestHeight), buffer_format.frame_size); 356 EXPECT_EQ(media::PIXEL_FORMAT_I420, buffer_format.pixel_format); 357 EXPECT_EQ(media::VideoFrame::I420, frame->format()); 358 uint8 yuv[3]; 359 for (int plane = 0; plane < 3; ++plane) 360 yuv[plane] = frame->data(plane)[0]; 361 // TODO(nick): We just look at the first pixel presently, because if 362 // the analysis is too slow, the backlog of frames will grow without bound 363 // and trouble erupts. http://crbug.com/174519 364 color_callback_.Run((SkColorSetRGB(yuv[0], yuv[1], yuv[2]))); 365 } 366 367 virtual void OnError(const std::string& reason) OVERRIDE { 368 error_callback_.Run(); 369 } 370 371 private: 372 class PoolBuffer : public media::VideoCaptureDevice::Client::Buffer { 373 public: 374 PoolBuffer(const scoped_refptr<VideoCaptureBufferPool>& pool, 375 int buffer_id, 376 void* data, 377 size_t size) 378 : Buffer(buffer_id, data, size), pool_(pool) {} 379 380 private: 381 virtual ~PoolBuffer() { pool_->RelinquishProducerReservation(id()); } 382 const scoped_refptr<VideoCaptureBufferPool> pool_; 383 }; 384 385 scoped_refptr<VideoCaptureBufferPool> buffer_pool_; 386 base::Callback<void(SkColor)> color_callback_; 387 base::Closure error_callback_; 388 389 DISALLOW_COPY_AND_ASSIGN(StubClient); 390}; 391 392class StubClientObserver { 393 public: 394 StubClientObserver() 395 : error_encountered_(false), 396 wait_color_yuv_(0xcafe1950) { 397 client_.reset(new StubClient( 398 base::Bind(&StubClientObserver::OnColor, base::Unretained(this)), 399 base::Bind(&StubClientObserver::OnError, base::Unretained(this)))); 400 } 401 402 virtual ~StubClientObserver() {} 403 404 scoped_ptr<media::VideoCaptureDevice::Client> PassClient() { 405 return client_.PassAs<media::VideoCaptureDevice::Client>(); 406 } 407 408 void QuitIfConditionMet(SkColor color) { 409 base::AutoLock guard(lock_); 410 if (wait_color_yuv_ == color || error_encountered_) 411 base::MessageLoop::current()->Quit(); 412 } 413 414 void WaitForNextColor(SkColor expected_color) { 415 { 416 base::AutoLock guard(lock_); 417 wait_color_yuv_ = ConvertRgbToYuv(expected_color); 418 error_encountered_ = false; 419 } 420 RunCurrentLoopWithDeadline(); 421 { 422 base::AutoLock guard(lock_); 423 ASSERT_FALSE(error_encountered_); 424 } 425 } 426 427 void WaitForError() { 428 { 429 base::AutoLock guard(lock_); 430 wait_color_yuv_ = kNotInterested; 431 error_encountered_ = false; 432 } 433 RunCurrentLoopWithDeadline(); 434 { 435 base::AutoLock guard(lock_); 436 ASSERT_TRUE(error_encountered_); 437 } 438 } 439 440 bool HasError() { 441 base::AutoLock guard(lock_); 442 return error_encountered_; 443 } 444 445 void OnError() { 446 { 447 base::AutoLock guard(lock_); 448 error_encountered_ = true; 449 } 450 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 451 &StubClientObserver::QuitIfConditionMet, 452 base::Unretained(this), 453 kNothingYet)); 454 } 455 456 void OnColor(SkColor color) { 457 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 458 &StubClientObserver::QuitIfConditionMet, 459 base::Unretained(this), 460 color)); 461 } 462 463 private: 464 base::Lock lock_; 465 bool error_encountered_; 466 SkColor wait_color_yuv_; 467 scoped_ptr<StubClient> client_; 468 469 DISALLOW_COPY_AND_ASSIGN(StubClientObserver); 470}; 471 472// A dummy implementation of gfx::Screen, since WebContentsVideoCaptureDevice 473// needs access to a gfx::Display's device scale factor. 474class FakeScreen : public gfx::Screen { 475 public: 476 FakeScreen() : the_one_display_(0x1337, gfx::Rect(0, 0, 2560, 1440)) { 477 the_one_display_.set_device_scale_factor(kTestDeviceScaleFactor); 478 } 479 virtual ~FakeScreen() {} 480 481 // gfx::Screen implementation (only what's needed for testing). 482 virtual bool IsDIPEnabled() OVERRIDE { return true; } 483 virtual gfx::Point GetCursorScreenPoint() OVERRIDE { return gfx::Point(); } 484 virtual gfx::NativeWindow GetWindowUnderCursor() OVERRIDE { return NULL; } 485 virtual gfx::NativeWindow GetWindowAtScreenPoint( 486 const gfx::Point& point) OVERRIDE { return NULL; } 487 virtual int GetNumDisplays() const OVERRIDE { return 1; } 488 virtual std::vector<gfx::Display> GetAllDisplays() const OVERRIDE { 489 return std::vector<gfx::Display>(1, the_one_display_); 490 } 491 virtual gfx::Display GetDisplayNearestWindow( 492 gfx::NativeView view) const OVERRIDE { 493 return the_one_display_; 494 } 495 virtual gfx::Display GetDisplayNearestPoint( 496 const gfx::Point& point) const OVERRIDE { 497 return the_one_display_; 498 } 499 virtual gfx::Display GetDisplayMatching( 500 const gfx::Rect& match_rect) const OVERRIDE { 501 return the_one_display_; 502 } 503 virtual gfx::Display GetPrimaryDisplay() const OVERRIDE { 504 return the_one_display_; 505 } 506 virtual void AddObserver(gfx::DisplayObserver* observer) OVERRIDE {} 507 virtual void RemoveObserver(gfx::DisplayObserver* observer) OVERRIDE {} 508 509 private: 510 gfx::Display the_one_display_; 511 512 DISALLOW_COPY_AND_ASSIGN(FakeScreen); 513}; 514 515// Test harness that sets up a minimal environment with necessary stubs. 516class WebContentsVideoCaptureDeviceTest : public testing::Test { 517 public: 518 // This is public because C++ method pointer scoping rules are silly and make 519 // this hard to use with Bind(). 520 void ResetWebContents() { 521 web_contents_.reset(); 522 } 523 524 protected: 525 virtual void SetUp() { 526 gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, &fake_screen_); 527 ASSERT_EQ(&fake_screen_, gfx::Screen::GetNativeScreen()); 528 529 // TODO(nick): Sadness and woe! Much "mock-the-world" boilerplate could be 530 // eliminated here, if only we could use RenderViewHostTestHarness. The 531 // catch is that we need our TestRenderViewHost to support a 532 // CopyFromBackingStore operation that we control. To accomplish that, 533 // either RenderViewHostTestHarness would have to support installing a 534 // custom RenderViewHostFactory, or else we implant some kind of delegated 535 // CopyFromBackingStore functionality into TestRenderViewHost itself. 536 537 render_process_host_factory_.reset(new MockRenderProcessHostFactory()); 538 // Create our (self-registering) RVH factory, so that when we create a 539 // WebContents, it in turn creates CaptureTestRenderViewHosts. 540 render_view_host_factory_.reset( 541 new CaptureTestRenderViewHostFactory(&controller_)); 542 543 browser_context_.reset(new TestBrowserContext()); 544 545 scoped_refptr<SiteInstance> site_instance = 546 SiteInstance::Create(browser_context_.get()); 547 SiteInstanceImpl::set_render_process_host_factory( 548 render_process_host_factory_.get()); 549 web_contents_.reset( 550 TestWebContents::Create(browser_context_.get(), site_instance.get())); 551 RenderFrameHost* const main_frame = web_contents_->GetMainFrame(); 552 device_.reset(WebContentsVideoCaptureDevice::Create( 553 base::StringPrintf("web-contents-media-stream://%d:%d", 554 main_frame->GetProcess()->GetID(), 555 main_frame->GetRoutingID()))); 556 557 base::RunLoop().RunUntilIdle(); 558 } 559 560 virtual void TearDown() { 561 // Tear down in opposite order of set-up. 562 563 // The device is destroyed asynchronously, and will notify the 564 // CaptureTestSourceController when it finishes destruction. 565 // Trigger this, and wait. 566 if (device_) { 567 device_->StopAndDeAllocate(); 568 device_.reset(); 569 } 570 571 base::RunLoop().RunUntilIdle(); 572 573 // Destroy the browser objects. 574 web_contents_.reset(); 575 browser_context_.reset(); 576 577 base::RunLoop().RunUntilIdle(); 578 579 SiteInstanceImpl::set_render_process_host_factory(NULL); 580 render_view_host_factory_.reset(); 581 render_process_host_factory_.reset(); 582 583 gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, NULL); 584 } 585 586 // Accessors. 587 CaptureTestSourceController* source() { return &controller_; } 588 WebContents* web_contents() const { return web_contents_.get(); } 589 media::VideoCaptureDevice* device() { return device_.get(); } 590 591 void SimulateDrawEvent() { 592 if (source()->CanUseFrameSubscriber()) { 593 // Print 594 CaptureTestView* test_view = static_cast<CaptureTestView*>( 595 web_contents_->GetRenderViewHost()->GetView()); 596 test_view->SimulateUpdate(); 597 } else { 598 // Simulate a non-accelerated paint. 599 NotificationService::current()->Notify( 600 NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, 601 Source<RenderWidgetHost>(web_contents_->GetRenderViewHost()), 602 NotificationService::NoDetails()); 603 } 604 } 605 606 void DestroyVideoCaptureDevice() { device_.reset(); } 607 608 StubClientObserver* client_observer() { 609 return &client_observer_; 610 } 611 612 private: 613 FakeScreen fake_screen_; 614 615 StubClientObserver client_observer_; 616 617 // The controller controls which pixel patterns to produce. 618 CaptureTestSourceController controller_; 619 620 // Self-registering RenderProcessHostFactory. 621 scoped_ptr<MockRenderProcessHostFactory> render_process_host_factory_; 622 623 // Creates capture-capable RenderViewHosts whose pixel content production is 624 // under the control of |controller_|. 625 scoped_ptr<CaptureTestRenderViewHostFactory> render_view_host_factory_; 626 627 // A mocked-out browser and tab. 628 scoped_ptr<TestBrowserContext> browser_context_; 629 scoped_ptr<WebContents> web_contents_; 630 631 // Finally, the WebContentsVideoCaptureDevice under test. 632 scoped_ptr<media::VideoCaptureDevice> device_; 633 634 TestBrowserThreadBundle thread_bundle_; 635}; 636 637TEST_F(WebContentsVideoCaptureDeviceTest, InvalidInitialWebContentsError) { 638 // Before the installs itself on the UI thread up to start capturing, we'll 639 // delete the web contents. This should trigger an error which can happen in 640 // practice; we should be able to recover gracefully. 641 ResetWebContents(); 642 643 media::VideoCaptureParams capture_params; 644 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); 645 capture_params.requested_format.frame_rate = kTestFramesPerSecond; 646 capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 647 device()->AllocateAndStart(capture_params, client_observer()->PassClient()); 648 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError()); 649 device()->StopAndDeAllocate(); 650} 651 652TEST_F(WebContentsVideoCaptureDeviceTest, WebContentsDestroyed) { 653 const gfx::Size capture_preferred_size( 654 static_cast<int>(kTestWidth / kTestDeviceScaleFactor), 655 static_cast<int>(kTestHeight / kTestDeviceScaleFactor)); 656 ASSERT_NE(capture_preferred_size, web_contents()->GetPreferredSize()); 657 658 // We'll simulate the tab being closed after the capture pipeline is up and 659 // running. 660 media::VideoCaptureParams capture_params; 661 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); 662 capture_params.requested_format.frame_rate = kTestFramesPerSecond; 663 capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 664 device()->AllocateAndStart(capture_params, client_observer()->PassClient()); 665 // Do one capture to prove 666 source()->SetSolidColor(SK_ColorRED); 667 SimulateDrawEvent(); 668 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED)); 669 670 base::RunLoop().RunUntilIdle(); 671 672 // Check that the preferred size of the WebContents matches the one provided 673 // by WebContentsVideoCaptureDevice. 674 EXPECT_EQ(capture_preferred_size, web_contents()->GetPreferredSize()); 675 676 // Post a task to close the tab. We should see an error reported to the 677 // consumer. 678 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 679 base::Bind(&WebContentsVideoCaptureDeviceTest::ResetWebContents, 680 base::Unretained(this))); 681 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError()); 682 device()->StopAndDeAllocate(); 683} 684 685TEST_F(WebContentsVideoCaptureDeviceTest, 686 StopDeviceBeforeCaptureMachineCreation) { 687 media::VideoCaptureParams capture_params; 688 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); 689 capture_params.requested_format.frame_rate = kTestFramesPerSecond; 690 capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 691 device()->AllocateAndStart(capture_params, client_observer()->PassClient()); 692 693 // Make a point of not running the UI messageloop here. 694 device()->StopAndDeAllocate(); 695 DestroyVideoCaptureDevice(); 696 697 // Currently, there should be CreateCaptureMachineOnUIThread() and 698 // DestroyCaptureMachineOnUIThread() tasks pending on the current (UI) message 699 // loop. These should both succeed without crashing, and the machine should 700 // wind up in the idle state. 701 base::RunLoop().RunUntilIdle(); 702} 703 704TEST_F(WebContentsVideoCaptureDeviceTest, StopWithRendererWorkToDo) { 705 // Set up the test to use RGB copies and an normal 706 source()->SetCanCopyToVideoFrame(false); 707 source()->SetUseFrameSubscriber(false); 708 media::VideoCaptureParams capture_params; 709 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); 710 capture_params.requested_format.frame_rate = kTestFramesPerSecond; 711 capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 712 device()->AllocateAndStart(capture_params, client_observer()->PassClient()); 713 714 base::RunLoop().RunUntilIdle(); 715 716 for (int i = 0; i < 10; ++i) 717 SimulateDrawEvent(); 718 719 ASSERT_FALSE(client_observer()->HasError()); 720 device()->StopAndDeAllocate(); 721 ASSERT_FALSE(client_observer()->HasError()); 722 base::RunLoop().RunUntilIdle(); 723 ASSERT_FALSE(client_observer()->HasError()); 724} 725 726TEST_F(WebContentsVideoCaptureDeviceTest, DeviceRestart) { 727 media::VideoCaptureParams capture_params; 728 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); 729 capture_params.requested_format.frame_rate = kTestFramesPerSecond; 730 capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 731 device()->AllocateAndStart(capture_params, client_observer()->PassClient()); 732 base::RunLoop().RunUntilIdle(); 733 source()->SetSolidColor(SK_ColorRED); 734 SimulateDrawEvent(); 735 SimulateDrawEvent(); 736 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED)); 737 SimulateDrawEvent(); 738 SimulateDrawEvent(); 739 source()->SetSolidColor(SK_ColorGREEN); 740 SimulateDrawEvent(); 741 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN)); 742 device()->StopAndDeAllocate(); 743 744 // Device is stopped, but content can still be animating. 745 SimulateDrawEvent(); 746 SimulateDrawEvent(); 747 base::RunLoop().RunUntilIdle(); 748 749 StubClientObserver observer2; 750 device()->AllocateAndStart(capture_params, observer2.PassClient()); 751 source()->SetSolidColor(SK_ColorBLUE); 752 SimulateDrawEvent(); 753 ASSERT_NO_FATAL_FAILURE(observer2.WaitForNextColor(SK_ColorBLUE)); 754 source()->SetSolidColor(SK_ColorYELLOW); 755 SimulateDrawEvent(); 756 ASSERT_NO_FATAL_FAILURE(observer2.WaitForNextColor(SK_ColorYELLOW)); 757 device()->StopAndDeAllocate(); 758} 759 760// The "happy case" test. No scaling is needed, so we should be able to change 761// the picture emitted from the source and expect to see each delivered to the 762// consumer. The test will alternate between the three capture paths, simulating 763// falling in and out of accelerated compositing. 764TEST_F(WebContentsVideoCaptureDeviceTest, GoesThroughAllTheMotions) { 765 media::VideoCaptureParams capture_params; 766 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); 767 capture_params.requested_format.frame_rate = kTestFramesPerSecond; 768 capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 769 device()->AllocateAndStart(capture_params, client_observer()->PassClient()); 770 771 for (int i = 0; i < 6; i++) { 772 const char* name = NULL; 773 switch (i % 3) { 774 case 0: 775 source()->SetCanCopyToVideoFrame(true); 776 source()->SetUseFrameSubscriber(false); 777 name = "VideoFrame"; 778 break; 779 case 1: 780 source()->SetCanCopyToVideoFrame(false); 781 source()->SetUseFrameSubscriber(true); 782 name = "Subscriber"; 783 break; 784 case 2: 785 source()->SetCanCopyToVideoFrame(false); 786 source()->SetUseFrameSubscriber(false); 787 name = "SkBitmap"; 788 break; 789 default: 790 FAIL(); 791 } 792 793 SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i)); 794 795 source()->SetSolidColor(SK_ColorRED); 796 SimulateDrawEvent(); 797 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED)); 798 799 source()->SetSolidColor(SK_ColorGREEN); 800 SimulateDrawEvent(); 801 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN)); 802 803 source()->SetSolidColor(SK_ColorBLUE); 804 SimulateDrawEvent(); 805 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorBLUE)); 806 807 source()->SetSolidColor(SK_ColorBLACK); 808 SimulateDrawEvent(); 809 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorBLACK)); 810 } 811 device()->StopAndDeAllocate(); 812} 813 814TEST_F(WebContentsVideoCaptureDeviceTest, RejectsInvalidAllocateParams) { 815 media::VideoCaptureParams capture_params; 816 capture_params.requested_format.frame_size.SetSize(1280, 720); 817 capture_params.requested_format.frame_rate = -2; 818 capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 819 BrowserThread::PostTask( 820 BrowserThread::UI, 821 FROM_HERE, 822 base::Bind(&media::VideoCaptureDevice::AllocateAndStart, 823 base::Unretained(device()), 824 capture_params, 825 base::Passed(client_observer()->PassClient()))); 826 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForError()); 827 BrowserThread::PostTask( 828 BrowserThread::UI, 829 FROM_HERE, 830 base::Bind(&media::VideoCaptureDevice::StopAndDeAllocate, 831 base::Unretained(device()))); 832 base::RunLoop().RunUntilIdle(); 833} 834 835TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) { 836 media::VideoCaptureParams capture_params; 837 capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight); 838 capture_params.requested_format.frame_rate = kTestFramesPerSecond; 839 capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 840 // 1x1 is too small to process; we intend for this to result in an error. 841 source()->SetCopyResultSize(1, 1); 842 source()->SetSolidColor(SK_ColorRED); 843 device()->AllocateAndStart(capture_params, client_observer()->PassClient()); 844 845 // These frames ought to be dropped during the Render stage. Let 846 // several captures to happen. 847 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy()); 848 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy()); 849 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy()); 850 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy()); 851 ASSERT_NO_FATAL_FAILURE(source()->WaitForNextCopy()); 852 853 // Now push some good frames through; they should be processed normally. 854 source()->SetCopyResultSize(kTestWidth, kTestHeight); 855 source()->SetSolidColor(SK_ColorGREEN); 856 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorGREEN)); 857 source()->SetSolidColor(SK_ColorRED); 858 ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColor(SK_ColorRED)); 859 860 device()->StopAndDeAllocate(); 861} 862 863} // namespace 864} // namespace content 865