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