OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 // | 4 // |
5 // Implementation notes: This needs to work on a variety of hardware | 5 // Implementation notes: This needs to work on a variety of hardware |
6 // configurations where the speed of the CPU and GPU greatly affect overall | 6 // configurations where the speed of the CPU and GPU greatly affect overall |
7 // performance. Spanning several threads, the process of capturing has been | 7 // performance. Spanning several threads, the process of capturing has been |
8 // split up into four conceptual stages: | 8 // split up into four conceptual stages: |
9 // | 9 // |
10 // 1. Reserve Buffer: Before a frame can be captured, a slot in the client's | 10 // 1. Reserve Buffer: Before a frame can be captured, a slot in the client's |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
72 #include "content/public/browser/render_process_host.h" | 72 #include "content/public/browser/render_process_host.h" |
73 #include "content/public/browser/render_widget_host_view.h" | 73 #include "content/public/browser/render_widget_host_view.h" |
74 #include "content/public/browser/render_widget_host_view_frame_subscriber.h" | 74 #include "content/public/browser/render_widget_host_view_frame_subscriber.h" |
75 #include "content/public/browser/web_contents.h" | 75 #include "content/public/browser/web_contents.h" |
76 #include "media/base/video_capture_types.h" | 76 #include "media/base/video_capture_types.h" |
77 #include "media/base/video_util.h" | 77 #include "media/base/video_util.h" |
78 #include "skia/ext/image_operations.h" | 78 #include "skia/ext/image_operations.h" |
79 #include "third_party/skia/include/core/SkBitmap.h" | 79 #include "third_party/skia/include/core/SkBitmap.h" |
80 #include "third_party/skia/include/core/SkColor.h" | 80 #include "third_party/skia/include/core/SkColor.h" |
81 #include "ui/gfx/display.h" | 81 #include "ui/gfx/display.h" |
82 #include "ui/gfx/geometry/size.h" | |
83 #include "ui/gfx/geometry/size_conversions.h" | 82 #include "ui/gfx/geometry/size_conversions.h" |
84 #include "ui/gfx/screen.h" | 83 #include "ui/gfx/screen.h" |
85 | 84 |
86 namespace content { | 85 namespace content { |
87 | 86 |
88 namespace { | 87 namespace { |
89 | 88 |
90 // Compute a letterbox region, aligned to even coordinates. | |
91 gfx::Rect ComputeYV12LetterboxRegion(const gfx::Rect& visible_rect, | |
92 const gfx::Size& content_size) { | |
93 | |
94 gfx::Rect result = media::ComputeLetterboxRegion(visible_rect, content_size); | |
95 | |
96 result.set_x(MakeEven(result.x())); | |
97 result.set_y(MakeEven(result.y())); | |
98 result.set_width(std::max(kMinFrameWidth, MakeEven(result.width()))); | |
99 result.set_height(std::max(kMinFrameHeight, MakeEven(result.height()))); | |
100 | |
101 return result; | |
102 } | |
103 | |
104 void DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread, | 89 void DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread, |
105 const base::Closure& callback) { | 90 const base::Closure& callback) { |
106 render_thread.reset(); | 91 render_thread.reset(); |
107 | 92 |
108 // After thread join call the callback on UI thread. | 93 // After thread join call the callback on UI thread. |
109 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback); | 94 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback); |
110 } | 95 } |
111 | 96 |
112 // Responsible for logging the effective frame rate. | 97 // Responsible for logging the effective frame rate. |
113 class VideoFrameDeliveryLog { | 98 class VideoFrameDeliveryLog { |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
250 // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame(). | 235 // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame(). |
251 void DidCopyFromCompositingSurfaceToVideoFrame( | 236 void DidCopyFromCompositingSurfaceToVideoFrame( |
252 const base::TimeTicks& start_time, | 237 const base::TimeTicks& start_time, |
253 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& | 238 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& |
254 deliver_frame_cb, | 239 deliver_frame_cb, |
255 bool success); | 240 bool success); |
256 | 241 |
257 // Remove the old subscription, and start a new one if |rwh| is not NULL. | 242 // Remove the old subscription, and start a new one if |rwh| is not NULL. |
258 void RenewFrameSubscription(RenderWidgetHost* rwh); | 243 void RenewFrameSubscription(RenderWidgetHost* rwh); |
259 | 244 |
| 245 // Called whenever the render widget is resized. |
| 246 void UpdateCaptureSize(RenderWidgetHost* rwh); |
| 247 |
260 // Parameters saved in constructor. | 248 // Parameters saved in constructor. |
261 const int initial_render_process_id_; | 249 const int initial_render_process_id_; |
262 const int initial_main_render_frame_id_; | 250 const int initial_main_render_frame_id_; |
263 | 251 |
264 // Tracks events and calls back to RenewFrameSubscription() to maintain | 252 // Tracks events and calls back to RenewFrameSubscription() to maintain |
265 // capture on the correct RenderWidgetHost. | 253 // capture on the correct RenderWidgetHost. |
266 const scoped_refptr<WebContentsTracker> tracker_; | 254 const scoped_refptr<WebContentsTracker> tracker_; |
267 | 255 |
268 // A dedicated worker thread on which SkBitmap->VideoFrame conversion will | 256 // A dedicated worker thread on which SkBitmap->VideoFrame conversion will |
269 // occur. Only used when this activity cannot be done on the GPU. | 257 // occur. Only used when this activity cannot be done on the GPU. |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
389 } | 377 } |
390 | 378 |
391 // Sanity-check the output buffer. | 379 // Sanity-check the output buffer. |
392 if (output->format() != media::VideoFrame::I420) { | 380 if (output->format() != media::VideoFrame::I420) { |
393 NOTREACHED(); | 381 NOTREACHED(); |
394 return; | 382 return; |
395 } | 383 } |
396 | 384 |
397 // Calculate the width and height of the content region in the |output|, based | 385 // Calculate the width and height of the content region in the |output|, based |
398 // on the aspect ratio of |input|. | 386 // on the aspect ratio of |input|. |
399 gfx::Rect region_in_frame = ComputeYV12LetterboxRegion( | 387 const gfx::Rect region_in_frame = media::ComputeLetterboxRegion( |
400 output->visible_rect(), gfx::Size(input.width(), input.height())); | 388 output->visible_rect(), gfx::Size(input.width(), input.height())); |
401 | 389 |
402 // Scale the bitmap to the required size, if necessary. | 390 // Scale the bitmap to the required size, if necessary. |
403 SkBitmap scaled_bitmap; | 391 SkBitmap scaled_bitmap; |
404 if (input.width() != region_in_frame.width() || | 392 if (input.width() != region_in_frame.width() || |
405 input.height() != region_in_frame.height()) { | 393 input.height() != region_in_frame.height()) { |
406 | 394 |
407 skia::ImageOperations::ResizeMethod method; | 395 skia::ImageOperations::ResizeMethod method; |
408 if (input.width() < region_in_frame.width() || | 396 if (input.width() < region_in_frame.width() || |
409 input.height() < region_in_frame.height()) { | 397 input.height() < region_in_frame.height()) { |
410 // Avoid box filtering when magnifying, because it's actually | 398 // Avoid box filtering when magnifying, because it's actually |
411 // nearest-neighbor. | 399 // nearest-neighbor. |
412 method = skia::ImageOperations::RESIZE_HAMMING1; | 400 method = skia::ImageOperations::RESIZE_HAMMING1; |
413 } else { | 401 } else { |
414 method = skia::ImageOperations::RESIZE_BOX; | 402 method = skia::ImageOperations::RESIZE_BOX; |
415 } | 403 } |
416 | 404 |
417 TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture", | 405 TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture", |
418 "Capture", output.get(), "Scale"); | 406 "Capture", output.get(), "Scale"); |
419 scaled_bitmap = skia::ImageOperations::Resize(input, method, | 407 scaled_bitmap = skia::ImageOperations::Resize(input, method, |
420 region_in_frame.width(), | 408 region_in_frame.width(), |
421 region_in_frame.height()); | 409 region_in_frame.height()); |
422 } else { | 410 } else { |
423 scaled_bitmap = input; | 411 scaled_bitmap = input; |
424 } | 412 } |
425 | 413 |
426 TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture", "Capture", output.get(), "YUV"); | 414 TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture", "Capture", output.get(), "YUV"); |
427 { | 415 { |
| 416 // Align to 2x2 pixel boundaries, as required by |
| 417 // media::CopyRGBToVideoFrame(). |
| 418 const gfx::Rect region_in_yv12_frame(region_in_frame.x() & ~1, |
| 419 region_in_frame.y() & ~1, |
| 420 region_in_frame.width() & ~1, |
| 421 region_in_frame.height() & ~1); |
| 422 if (region_in_yv12_frame.IsEmpty()) |
| 423 return; |
| 424 |
428 SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap); | 425 SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap); |
429 | |
430 media::CopyRGBToVideoFrame( | 426 media::CopyRGBToVideoFrame( |
431 reinterpret_cast<uint8*>(scaled_bitmap.getPixels()), | 427 reinterpret_cast<uint8*>(scaled_bitmap.getPixels()), |
432 scaled_bitmap.rowBytes(), | 428 scaled_bitmap.rowBytes(), |
433 region_in_frame, | 429 region_in_yv12_frame, |
434 output.get()); | 430 output.get()); |
435 } | 431 } |
436 | 432 |
437 // The result is now ready. | 433 // The result is now ready. |
438 ignore_result(failure_handler.Release()); | 434 ignore_result(failure_handler.Release()); |
439 done_cb.Run(true); | 435 done_cb.Run(true); |
440 } | 436 } |
441 | 437 |
442 VideoFrameDeliveryLog::VideoFrameDeliveryLog() | 438 VideoFrameDeliveryLog::VideoFrameDeliveryLog() |
443 : last_frame_rate_log_time_(), | 439 : last_frame_rate_log_time_(), |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
494 | 490 |
495 render_thread_.reset(new base::Thread("WebContentsVideo_RenderThread")); | 491 render_thread_.reset(new base::Thread("WebContentsVideo_RenderThread")); |
496 if (!render_thread_->Start()) { | 492 if (!render_thread_->Start()) { |
497 DVLOG(1) << "Failed to spawn render thread."; | 493 DVLOG(1) << "Failed to spawn render thread."; |
498 render_thread_.reset(); | 494 render_thread_.reset(); |
499 return false; | 495 return false; |
500 } | 496 } |
501 | 497 |
502 // Note: Creation of the first WeakPtr in the following statement will cause | 498 // Note: Creation of the first WeakPtr in the following statement will cause |
503 // IsStarted() to return true from now on. | 499 // IsStarted() to return true from now on. |
| 500 tracker_->SetResizeChangeCallback( |
| 501 base::Bind(&WebContentsCaptureMachine::UpdateCaptureSize, |
| 502 weak_ptr_factory_.GetWeakPtr())); |
504 tracker_->Start(initial_render_process_id_, initial_main_render_frame_id_, | 503 tracker_->Start(initial_render_process_id_, initial_main_render_frame_id_, |
505 base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription, | 504 base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription, |
506 weak_ptr_factory_.GetWeakPtr())); | 505 weak_ptr_factory_.GetWeakPtr())); |
507 | 506 |
508 return true; | 507 return true; |
509 } | 508 } |
510 | 509 |
511 void WebContentsCaptureMachine::Stop(const base::Closure& callback) { | 510 void WebContentsCaptureMachine::Stop(const base::Closure& callback) { |
512 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 511 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
513 | 512 |
(...skipping 30 matching lines...) Expand all Loading... |
544 | 543 |
545 RenderWidgetHost* rwh = tracker_->GetTargetRenderWidgetHost(); | 544 RenderWidgetHost* rwh = tracker_->GetTargetRenderWidgetHost(); |
546 RenderWidgetHostViewBase* view = | 545 RenderWidgetHostViewBase* view = |
547 rwh ? static_cast<RenderWidgetHostViewBase*>(rwh->GetView()) : NULL; | 546 rwh ? static_cast<RenderWidgetHostViewBase*>(rwh->GetView()) : NULL; |
548 if (!view) { | 547 if (!view) { |
549 deliver_frame_cb.Run(base::TimeTicks(), false); | 548 deliver_frame_cb.Run(base::TimeTicks(), false); |
550 return; | 549 return; |
551 } | 550 } |
552 | 551 |
553 gfx::Size view_size = view->GetViewBounds().size(); | 552 gfx::Size view_size = view->GetViewBounds().size(); |
554 gfx::Size fitted_size; | |
555 if (!view_size.IsEmpty()) { | |
556 fitted_size = ComputeYV12LetterboxRegion(target->visible_rect(), | |
557 view_size).size(); | |
558 } | |
559 if (view_size != last_view_size_) { | 553 if (view_size != last_view_size_) { |
560 last_view_size_ = view_size; | 554 last_view_size_ = view_size; |
561 | 555 |
562 // Measure the number of kilopixels. | 556 // Measure the number of kilopixels. |
563 UMA_HISTOGRAM_COUNTS_10000( | 557 UMA_HISTOGRAM_COUNTS_10000( |
564 "TabCapture.ViewChangeKiloPixels", | 558 "TabCapture.ViewChangeKiloPixels", |
565 view_size.width() * view_size.height() / 1024); | 559 view_size.width() * view_size.height() / 1024); |
566 } | 560 } |
567 | 561 |
568 if (view->CanCopyToVideoFrame()) { | 562 if (view->CanCopyToVideoFrame()) { |
569 view->CopyFromCompositingSurfaceToVideoFrame( | 563 view->CopyFromCompositingSurfaceToVideoFrame( |
570 gfx::Rect(view_size), | 564 gfx::Rect(view_size), |
571 target, | 565 target, |
572 base::Bind(&WebContentsCaptureMachine:: | 566 base::Bind(&WebContentsCaptureMachine:: |
573 DidCopyFromCompositingSurfaceToVideoFrame, | 567 DidCopyFromCompositingSurfaceToVideoFrame, |
574 weak_ptr_factory_.GetWeakPtr(), | 568 weak_ptr_factory_.GetWeakPtr(), |
575 start_time, deliver_frame_cb)); | 569 start_time, deliver_frame_cb)); |
576 } else { | 570 } else { |
| 571 const gfx::Size fitted_size = view_size.IsEmpty() ? gfx::Size() : |
| 572 media::ComputeLetterboxRegion(target->visible_rect(), view_size).size(); |
577 rwh->CopyFromBackingStore( | 573 rwh->CopyFromBackingStore( |
578 gfx::Rect(), | 574 gfx::Rect(), |
579 fitted_size, // Size here is a request not always honored. | 575 fitted_size, // Size here is a request not always honored. |
580 base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore, | 576 base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore, |
581 weak_ptr_factory_.GetWeakPtr(), | 577 weak_ptr_factory_.GetWeakPtr(), |
582 start_time, | 578 start_time, |
583 target, | 579 target, |
584 deliver_frame_cb), | 580 deliver_frame_cb), |
585 kN32_SkColorType); | 581 kN32_SkColorType); |
586 } | 582 } |
587 } | 583 } |
588 | 584 |
589 gfx::Size WebContentsCaptureMachine::ComputeOptimalTargetSize() const { | 585 gfx::Size WebContentsCaptureMachine::ComputeOptimalTargetSize() const { |
590 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 586 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
591 | 587 |
592 gfx::Size optimal_size = oracle_proxy_->GetCaptureSize(); | 588 // TODO(miu): Propagate capture frame size changes as new "preferred size" |
| 589 // updates, rather than just using the max frame size. This value here |
| 590 // determines the size of the fullscreen-within-tab widget. |
| 591 gfx::Size optimal_size = oracle_proxy_->max_frame_size(); |
593 | 592 |
594 // If the ratio between physical and logical pixels is greater than 1:1, | 593 // If the ratio between physical and logical pixels is greater than 1:1, |
595 // shrink |optimal_size| by that amount. Then, when external code resizes the | 594 // shrink |optimal_size| by that amount. Then, when external code resizes the |
596 // render widget to the "preferred size," the widget will be physically | 595 // render widget to the "preferred size," the widget will be physically |
597 // rendered at the exact capture size, thereby eliminating unnecessary scaling | 596 // rendered at the exact capture size, thereby eliminating unnecessary scaling |
598 // operations in the graphics pipeline. | 597 // operations in the graphics pipeline. |
599 RenderWidgetHost* const rwh = tracker_->GetTargetRenderWidgetHost(); | 598 RenderWidgetHost* const rwh = tracker_->GetTargetRenderWidgetHost(); |
600 RenderWidgetHostView* const rwhv = rwh ? rwh->GetView() : NULL; | 599 RenderWidgetHostView* const rwhv = rwh ? rwh->GetView() : NULL; |
601 if (rwhv) { | 600 if (rwhv) { |
602 const gfx::NativeView view = rwhv->GetNativeView(); | 601 const gfx::NativeView view = rwhv->GetNativeView(); |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
681 if (!had_subscription && tracker_->web_contents()) { | 680 if (!had_subscription && tracker_->web_contents()) { |
682 tracker_->web_contents()->IncrementCapturerCount( | 681 tracker_->web_contents()->IncrementCapturerCount( |
683 ComputeOptimalTargetSize()); | 682 ComputeOptimalTargetSize()); |
684 } | 683 } |
685 | 684 |
686 subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_, | 685 subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_, |
687 base::Bind(&WebContentsCaptureMachine::Capture, | 686 base::Bind(&WebContentsCaptureMachine::Capture, |
688 weak_ptr_factory_.GetWeakPtr()))); | 687 weak_ptr_factory_.GetWeakPtr()))); |
689 } | 688 } |
690 | 689 |
| 690 void WebContentsCaptureMachine::UpdateCaptureSize(RenderWidgetHost* rwh) { |
| 691 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 692 |
| 693 if (!oracle_proxy_) |
| 694 return; |
| 695 RenderWidgetHostView* const view = rwh ? rwh->GetView() : nullptr; |
| 696 if (!view) |
| 697 return; |
| 698 oracle_proxy_->UpdateCaptureSize(view->GetViewBounds().size()); |
| 699 } |
| 700 |
691 } // namespace | 701 } // namespace |
692 | 702 |
693 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice( | 703 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice( |
694 int render_process_id, int main_render_frame_id) | 704 int render_process_id, int main_render_frame_id) |
695 : core_(new ContentVideoCaptureDeviceCore(scoped_ptr<VideoCaptureMachine>( | 705 : core_(new ContentVideoCaptureDeviceCore(scoped_ptr<VideoCaptureMachine>( |
696 new WebContentsCaptureMachine( | 706 new WebContentsCaptureMachine( |
697 render_process_id, main_render_frame_id)))) {} | 707 render_process_id, main_render_frame_id)))) {} |
698 | 708 |
699 WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() { | 709 WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() { |
700 DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying."; | 710 DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying."; |
(...skipping 19 matching lines...) Expand all Loading... |
720 scoped_ptr<Client> client) { | 730 scoped_ptr<Client> client) { |
721 DVLOG(1) << "Allocating " << params.requested_format.frame_size.ToString(); | 731 DVLOG(1) << "Allocating " << params.requested_format.frame_size.ToString(); |
722 core_->AllocateAndStart(params, client.Pass()); | 732 core_->AllocateAndStart(params, client.Pass()); |
723 } | 733 } |
724 | 734 |
725 void WebContentsVideoCaptureDevice::StopAndDeAllocate() { | 735 void WebContentsVideoCaptureDevice::StopAndDeAllocate() { |
726 core_->StopAndDeAllocate(); | 736 core_->StopAndDeAllocate(); |
727 } | 737 } |
728 | 738 |
729 } // namespace content | 739 } // namespace content |
OLD | NEW |