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 // Implementation notes: This needs to work on a variety of hardware
6 // configurations where the speed of the CPU and GPU greatly affect overall
7 // performance. Spanning several threads, the process of capturing has been
8 // split up into four conceptual stages:
10 // 1. Reserve Buffer: Before a frame can be captured, a slot in the client's
11 // shared-memory IPC buffer is reserved. There are only a few of these;
12 // when they run out, it indicates that the downstream client -- likely a
13 // video encoder -- is the performance bottleneck, and that the rate of
14 // frame capture should be throttled back.
16 // 2. Capture: A bitmap is snapshotted/copied from the RenderWidget's backing
17 // store. This is initiated on the UI BrowserThread, and often occurs
18 // asynchronously. Where supported, the GPU scales and color converts
19 // frames to our desired size, and the readback happens directly into the
20 // shared-memory buffer. But this is not always possible, particularly when
21 // accelerated compositing is disabled.
23 // 3. Render (if needed): If the web contents cannot be captured directly into
24 // our target size and color format, scaling and colorspace conversion must
25 // be done on the CPU. A dedicated thread is used for this operation, to
26 // avoid blocking the UI thread. The Render stage always reads from a
27 // bitmap returned by Capture, and writes into the reserved slot in the
28 // shared-memory buffer.
30 // 4. Deliver: The rendered video frame is returned to the client (which
31 // implements the VideoCaptureDevice::Client interface). Because all
32 // paths have written the frame into the IPC buffer, this step should
33 // never need to do an additional copy of the pixel data.
35 // In the best-performing case, the Render step is bypassed: Capture produces
36 // ready-to-Deliver frames. But when accelerated readback is not possible, the
37 // system is designed so that Capture and Render may run concurrently. A timing
38 // diagram helps illustrate this point (@30 FPS):
40 // Time: 0ms 33ms 66ms 99ms
41 // thread1: |-Capture-f1------v |-Capture-f2------v |-Capture-f3----v |-Capt
42 // thread2: |-Render-f1-----v |-Render-f2-----v |-Render-f3
44 // In the above example, both capturing and rendering *each* take almost the
45 // full 33 ms available between frames, yet we see that the required throughput
48 // Turning on verbose logging will cause the effective frame rate to be logged
49 // at 5-second intervals.
51 #include "content/browser/media/capture/web_contents_video_capture_device.h"
53 #include "base/basictypes.h"
54 #include "base/bind.h"
55 #include "base/callback_helpers.h"
56 #include "base/logging.h"
57 #include "base/memory/scoped_ptr.h"
58 #include "base/memory/weak_ptr.h"
59 #include "base/message_loop/message_loop_proxy.h"
60 #include "base/metrics/histogram.h"
61 #include "base/sequenced_task_runner.h"
62 #include "base/threading/thread.h"
63 #include "base/threading/thread_checker.h"
64 #include "base/time/time.h"
65 #include "content/browser/media/capture/content_video_capture_device_core.h"
66 #include "content/browser/media/capture/video_capture_oracle.h"
67 #include "content/browser/media/capture/web_contents_capture_util.h"
68 #include "content/browser/media/capture/web_contents_tracker.h"
69 #include "content/browser/renderer_host/render_widget_host_impl.h"
70 #include "content/browser/renderer_host/render_widget_host_view_base.h"
71 #include "content/public/browser/browser_thread.h"
72 #include "content/public/browser/notification_observer.h"
73 #include "content/public/browser/notification_registrar.h"
74 #include "content/public/browser/notification_source.h"
75 #include "content/public/browser/notification_types.h"
76 #include "content/public/browser/render_process_host.h"
77 #include "content/public/browser/render_widget_host_view.h"
78 #include "content/public/browser/render_widget_host_view_frame_subscriber.h"
79 #include "content/public/browser/web_contents.h"
80 #include "media/base/video_capture_types.h"
81 #include "media/base/video_util.h"
82 #include "skia/ext/image_operations.h"
83 #include "third_party/skia/include/core/SkBitmap.h"
84 #include "third_party/skia/include/core/SkColor.h"
85 #include "ui/gfx/display.h"
86 #include "ui/gfx/geometry/size.h"
87 #include "ui/gfx/geometry/size_conversions.h"
88 #include "ui/gfx/screen.h"
94 // Compute a letterbox region, aligned to even coordinates.
95 gfx::Rect
ComputeYV12LetterboxRegion(const gfx::Rect
& visible_rect
,
96 const gfx::Size
& content_size
) {
98 gfx::Rect result
= media::ComputeLetterboxRegion(visible_rect
, content_size
);
100 result
.set_x(MakeEven(result
.x()));
101 result
.set_y(MakeEven(result
.y()));
102 result
.set_width(std::max(kMinFrameWidth
, MakeEven(result
.width())));
103 result
.set_height(std::max(kMinFrameHeight
, MakeEven(result
.height())));
108 void DeleteOnWorkerThread(scoped_ptr
<base::Thread
> render_thread
,
109 const base::Closure
& callback
) {
110 render_thread
.reset();
112 // After thread join call the callback on UI thread.
113 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, callback
);
116 // Responsible for logging the effective frame rate.
117 class VideoFrameDeliveryLog
{
119 VideoFrameDeliveryLog();
121 // Report that the frame posted with |frame_time| has been delivered.
122 void ChronicleFrameDelivery(base::TimeTicks frame_time
);
125 // The following keep track of and log the effective frame rate whenever
126 // verbose logging is turned on.
127 base::TimeTicks last_frame_rate_log_time_
;
128 int count_frames_rendered_
;
130 DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog
);
133 // FrameSubscriber is a proxy to the ThreadSafeCaptureOracle that's compatible
134 // with RenderWidgetHostViewFrameSubscriber. We create one per event type.
135 class FrameSubscriber
: public RenderWidgetHostViewFrameSubscriber
{
137 FrameSubscriber(VideoCaptureOracle::Event event_type
,
138 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle
,
139 VideoFrameDeliveryLog
* delivery_log
)
140 : event_type_(event_type
),
141 oracle_proxy_(oracle
),
142 delivery_log_(delivery_log
) {}
144 bool ShouldCaptureFrame(
145 const gfx::Rect
& damage_rect
,
146 base::TimeTicks present_time
,
147 scoped_refptr
<media::VideoFrame
>* storage
,
148 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
*
149 deliver_frame_cb
) override
;
152 const VideoCaptureOracle::Event event_type_
;
153 scoped_refptr
<ThreadSafeCaptureOracle
> oracle_proxy_
;
154 VideoFrameDeliveryLog
* const delivery_log_
;
157 // ContentCaptureSubscription is the relationship between a RenderWidgetHost
158 // whose content is updating, a subscriber that is deciding which of these
159 // updates to capture (and where to deliver them to), and a callback that
160 // knows how to do the capture and prepare the result for delivery.
162 // In practice, this means (a) installing a RenderWidgetHostFrameSubscriber in
163 // the RenderWidgetHostView, to process updates that occur via accelerated
164 // compositing, (b) installing itself as an observer of updates to the
165 // RenderWidgetHost's backing store, to hook updates that occur via software
166 // rendering, and (c) running a timer to possibly initiate non-event-driven
167 // captures that the subscriber might request.
169 // All of this happens on the UI thread, although the
170 // RenderWidgetHostViewFrameSubscriber we install may be dispatching updates
171 // autonomously on some other thread.
172 class ContentCaptureSubscription
: public content::NotificationObserver
{
174 typedef base::Callback
<
175 void(const base::TimeTicks
&,
176 const scoped_refptr
<media::VideoFrame
>&,
177 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&)>
180 // Create a subscription. Whenever a manual capture is required, the
181 // subscription will invoke |capture_callback| on the UI thread to do the
183 ContentCaptureSubscription(
184 const RenderWidgetHost
& source
,
185 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
,
186 const CaptureCallback
& capture_callback
);
187 ~ContentCaptureSubscription() override
;
189 // content::NotificationObserver implementation.
190 void Observe(int type
,
191 const content::NotificationSource
& source
,
192 const content::NotificationDetails
& details
) override
;
197 // Maintain a weak reference to the RenderWidgetHost (via its routing ID),
198 // since the instance could be destroyed externally during the lifetime of
200 const int render_process_id_
;
201 const int render_widget_id_
;
203 VideoFrameDeliveryLog delivery_log_
;
204 FrameSubscriber paint_subscriber_
;
205 FrameSubscriber timer_subscriber_
;
206 content::NotificationRegistrar registrar_
;
207 CaptureCallback capture_callback_
;
210 DISALLOW_COPY_AND_ASSIGN(ContentCaptureSubscription
);
213 // Render the SkBitmap |input| into the given VideoFrame buffer |output|, then
214 // invoke |done_cb| to indicate success or failure. |input| is expected to be
215 // ARGB. |output| must be YV12 or I420. Colorspace conversion is always done.
216 // Scaling and letterboxing will be done as needed.
218 // This software implementation should be used only when GPU acceleration of
219 // these activities is not possible. This operation may be expensive (tens to
220 // hundreds of milliseconds), so the caller should ensure that it runs on a
221 // thread where such a pause would cause UI jank.
222 void RenderVideoFrame(const SkBitmap
& input
,
223 const scoped_refptr
<media::VideoFrame
>& output
,
224 const base::Callback
<void(bool)>& done_cb
);
226 // Renews capture subscriptions based on feedback from WebContentsTracker, and
227 // also executes copying of the backing store on the UI BrowserThread.
228 class WebContentsCaptureMachine
: public VideoCaptureMachine
{
230 WebContentsCaptureMachine(int render_process_id
, int main_render_frame_id
);
231 ~WebContentsCaptureMachine() override
;
233 // VideoCaptureMachine overrides.
234 bool Start(const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
,
235 const media::VideoCaptureParams
& params
) override
;
236 void Stop(const base::Closure
& callback
) override
;
238 // Starts a copy from the backing store or the composited surface. Must be run
239 // on the UI BrowserThread. |deliver_frame_cb| will be run when the operation
240 // completes. The copy will occur to |target|.
242 // This may be used as a ContentCaptureSubscription::CaptureCallback.
243 void Capture(const base::TimeTicks
& start_time
,
244 const scoped_refptr
<media::VideoFrame
>& target
,
245 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
249 bool IsStarted() const;
251 // Computes the preferred size of the target RenderWidget for optimal capture.
252 gfx::Size
ComputeOptimalTargetSize() const;
254 // Response callback for RenderWidgetHost::CopyFromBackingStore().
255 void DidCopyFromBackingStore(
256 const base::TimeTicks
& start_time
,
257 const scoped_refptr
<media::VideoFrame
>& target
,
258 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
260 const SkBitmap
& bitmap
,
261 ReadbackResponse response
);
263 // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame().
264 void DidCopyFromCompositingSurfaceToVideoFrame(
265 const base::TimeTicks
& start_time
,
266 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
270 // Remove the old subscription, and start a new one if |rwh| is not NULL.
271 void RenewFrameSubscription(RenderWidgetHost
* rwh
);
273 // Parameters saved in constructor.
274 const int initial_render_process_id_
;
275 const int initial_main_render_frame_id_
;
277 // Tracks events and calls back to RenewFrameSubscription() to maintain
278 // capture on the correct RenderWidgetHost.
279 const scoped_refptr
<WebContentsTracker
> tracker_
;
281 // A dedicated worker thread on which SkBitmap->VideoFrame conversion will
282 // occur. Only used when this activity cannot be done on the GPU.
283 scoped_ptr
<base::Thread
> render_thread_
;
285 // Makes all the decisions about which frames to copy, and how.
286 scoped_refptr
<ThreadSafeCaptureOracle
> oracle_proxy_
;
288 // Video capture parameters that this machine is started with.
289 media::VideoCaptureParams capture_params_
;
291 // Last known RenderView size.
292 gfx::Size last_view_size_
;
294 // Responsible for forwarding events from the active RenderWidgetHost to the
295 // oracle, and initiating captures accordingly.
296 scoped_ptr
<ContentCaptureSubscription
> subscription_
;
298 // Weak pointer factory used to invalidate callbacks.
299 // NOTE: Weak pointers must be invalidated before all other member variables.
300 base::WeakPtrFactory
<WebContentsCaptureMachine
> weak_ptr_factory_
;
302 DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureMachine
);
305 bool FrameSubscriber::ShouldCaptureFrame(
306 const gfx::Rect
& damage_rect
,
307 base::TimeTicks present_time
,
308 scoped_refptr
<media::VideoFrame
>* storage
,
309 DeliverFrameCallback
* deliver_frame_cb
) {
310 TRACE_EVENT1("mirroring", "FrameSubscriber::ShouldCaptureFrame",
313 ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb
;
314 bool oracle_decision
= oracle_proxy_
->ObserveEventAndDecideCapture(
315 event_type_
, damage_rect
, present_time
, storage
, &capture_frame_cb
);
317 if (!capture_frame_cb
.is_null())
318 *deliver_frame_cb
= base::Bind(capture_frame_cb
, *storage
);
320 delivery_log_
->ChronicleFrameDelivery(present_time
);
321 return oracle_decision
;
324 ContentCaptureSubscription::ContentCaptureSubscription(
325 const RenderWidgetHost
& source
,
326 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
,
327 const CaptureCallback
& capture_callback
)
328 : render_process_id_(source
.GetProcess()->GetID()),
329 render_widget_id_(source
.GetRoutingID()),
331 paint_subscriber_(VideoCaptureOracle::kSoftwarePaint
, oracle_proxy
,
333 timer_subscriber_(VideoCaptureOracle::kTimerPoll
, oracle_proxy
,
335 capture_callback_(capture_callback
),
337 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
339 RenderWidgetHostView
* const view
= source
.GetView();
341 // Subscribe to accelerated presents. These will be serviced directly by the
344 scoped_ptr
<RenderWidgetHostViewFrameSubscriber
> subscriber(
345 new FrameSubscriber(VideoCaptureOracle::kCompositorUpdate
,
346 oracle_proxy
, &delivery_log_
));
347 view
->BeginFrameSubscription(subscriber
.Pass());
350 // Subscribe to software paint events. This instance will service these by
351 // reflecting them back to the WebContentsCaptureMachine via
352 // |capture_callback|.
354 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
,
355 Source
<RenderWidgetHost
>(&source
));
357 // Subscribe to timer events. This instance will service these as well.
358 timer_
.Start(FROM_HERE
, oracle_proxy
->min_capture_period(),
359 base::Bind(&ContentCaptureSubscription::OnTimer
,
360 base::Unretained(this)));
363 ContentCaptureSubscription::~ContentCaptureSubscription() {
364 // If the BrowserThreads have been torn down, then the browser is in the final
365 // stages of exiting and it is dangerous to take any further action. We must
366 // return early. http://crbug.com/396413
367 if (!BrowserThread::IsMessageLoopValid(BrowserThread::UI
))
370 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
371 RenderWidgetHost
* const source
=
372 RenderWidgetHost::FromID(render_process_id_
, render_widget_id_
);
373 RenderWidgetHostView
* const view
= source
? source
->GetView() : NULL
;
375 view
->EndFrameSubscription();
378 void ContentCaptureSubscription::Observe(
380 const content::NotificationSource
& source
,
381 const content::NotificationDetails
& details
) {
382 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
383 DCHECK_EQ(NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
, type
);
385 RenderWidgetHostImpl
* rwh
=
386 RenderWidgetHostImpl::From(Source
<RenderWidgetHost
>(source
).ptr());
388 // This message occurs on window resizes and visibility changes even when
389 // accelerated compositing is active, so we need to filter out these cases.
390 if (!rwh
|| !rwh
->GetView())
392 // Mac sends DID_UPDATE_BACKING_STORE messages to inform the capture system
393 // of new software compositor frames, so always treat these messages as
394 // signals of a new frame on Mac.
395 // http://crbug.com/333986
396 #if !defined(OS_MACOSX)
397 if (rwh
->GetView()->IsSurfaceAvailableForCopy())
401 TRACE_EVENT1("mirroring", "ContentCaptureSubscription::Observe",
404 base::Closure copy_done_callback
;
405 scoped_refptr
<media::VideoFrame
> frame
;
406 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb
;
407 const base::TimeTicks start_time
= base::TimeTicks::Now();
408 if (paint_subscriber_
.ShouldCaptureFrame(gfx::Rect(),
411 &deliver_frame_cb
)) {
412 // This message happens just before paint. If we post a task to do the copy,
413 // it should run soon after the paint.
414 BrowserThread::PostTask(
415 BrowserThread::UI
, FROM_HERE
,
416 base::Bind(capture_callback_
, start_time
, frame
, deliver_frame_cb
));
420 void ContentCaptureSubscription::OnTimer() {
421 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
422 TRACE_EVENT0("mirroring", "ContentCaptureSubscription::OnTimer");
424 scoped_refptr
<media::VideoFrame
> frame
;
425 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb
;
427 const base::TimeTicks start_time
= base::TimeTicks::Now();
428 if (timer_subscriber_
.ShouldCaptureFrame(gfx::Rect(),
431 &deliver_frame_cb
)) {
432 capture_callback_
.Run(start_time
, frame
, deliver_frame_cb
);
436 void RenderVideoFrame(const SkBitmap
& input
,
437 const scoped_refptr
<media::VideoFrame
>& output
,
438 const base::Callback
<void(bool)>& done_cb
) {
439 base::ScopedClosureRunner
failure_handler(base::Bind(done_cb
, false));
441 SkAutoLockPixels
locker(input
);
443 // Sanity-check the captured bitmap.
445 !input
.readyToDraw() ||
446 input
.colorType() != kN32_SkColorType
||
447 input
.width() < 2 || input
.height() < 2) {
448 DVLOG(1) << "input unacceptable (size="
450 << ", ready=" << input
.readyToDraw()
451 << ", colorType=" << input
.colorType() << ')';
455 // Sanity-check the output buffer.
456 if (output
->format() != media::VideoFrame::I420
) {
461 // Calculate the width and height of the content region in the |output|, based
462 // on the aspect ratio of |input|.
463 gfx::Rect region_in_frame
= ComputeYV12LetterboxRegion(
464 output
->visible_rect(), gfx::Size(input
.width(), input
.height()));
466 // Scale the bitmap to the required size, if necessary.
467 SkBitmap scaled_bitmap
;
468 if (input
.width() != region_in_frame
.width() ||
469 input
.height() != region_in_frame
.height()) {
471 skia::ImageOperations::ResizeMethod method
;
472 if (input
.width() < region_in_frame
.width() ||
473 input
.height() < region_in_frame
.height()) {
474 // Avoid box filtering when magnifying, because it's actually
476 method
= skia::ImageOperations::RESIZE_HAMMING1
;
478 method
= skia::ImageOperations::RESIZE_BOX
;
481 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output
.get(), "Scale");
482 scaled_bitmap
= skia::ImageOperations::Resize(input
, method
,
483 region_in_frame
.width(),
484 region_in_frame
.height());
486 scaled_bitmap
= input
;
489 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output
.get(), "YUV");
491 SkAutoLockPixels
scaled_bitmap_locker(scaled_bitmap
);
493 media::CopyRGBToVideoFrame(
494 reinterpret_cast<uint8
*>(scaled_bitmap
.getPixels()),
495 scaled_bitmap
.rowBytes(),
500 // The result is now ready.
501 ignore_result(failure_handler
.Release());
505 VideoFrameDeliveryLog::VideoFrameDeliveryLog()
506 : last_frame_rate_log_time_(),
507 count_frames_rendered_(0) {
510 void VideoFrameDeliveryLog::ChronicleFrameDelivery(base::TimeTicks frame_time
) {
511 // Log frame rate, if verbose logging is turned on.
512 static const base::TimeDelta kFrameRateLogInterval
=
513 base::TimeDelta::FromSeconds(10);
514 if (last_frame_rate_log_time_
.is_null()) {
515 last_frame_rate_log_time_
= frame_time
;
516 count_frames_rendered_
= 0;
518 ++count_frames_rendered_
;
519 const base::TimeDelta elapsed
= frame_time
- last_frame_rate_log_time_
;
520 if (elapsed
>= kFrameRateLogInterval
) {
521 const double measured_fps
=
522 count_frames_rendered_
/ elapsed
.InSecondsF();
523 UMA_HISTOGRAM_COUNTS(
524 "TabCapture.FrameRate",
525 static_cast<int>(measured_fps
));
526 VLOG(1) << "Current measured frame rate for "
527 << "WebContentsVideoCaptureDevice is " << measured_fps
<< " FPS.";
528 last_frame_rate_log_time_
= frame_time
;
529 count_frames_rendered_
= 0;
534 WebContentsCaptureMachine::WebContentsCaptureMachine(int render_process_id
,
535 int main_render_frame_id
)
536 : initial_render_process_id_(render_process_id
),
537 initial_main_render_frame_id_(main_render_frame_id
),
538 tracker_(new WebContentsTracker(true)),
539 weak_ptr_factory_(this) {}
541 WebContentsCaptureMachine::~WebContentsCaptureMachine() {}
543 bool WebContentsCaptureMachine::IsStarted() const {
544 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
545 return weak_ptr_factory_
.HasWeakPtrs();
548 bool WebContentsCaptureMachine::Start(
549 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
,
550 const media::VideoCaptureParams
& params
) {
551 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
552 DCHECK(!IsStarted());
554 DCHECK(oracle_proxy
.get());
555 oracle_proxy_
= oracle_proxy
;
556 capture_params_
= params
;
558 render_thread_
.reset(new base::Thread("WebContentsVideo_RenderThread"));
559 if (!render_thread_
->Start()) {
560 DVLOG(1) << "Failed to spawn render thread.";
561 render_thread_
.reset();
565 // Note: Creation of the first WeakPtr in the following statement will cause
566 // IsStarted() to return true from now on.
567 tracker_
->Start(initial_render_process_id_
, initial_main_render_frame_id_
,
568 base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription
,
569 weak_ptr_factory_
.GetWeakPtr()));
574 void WebContentsCaptureMachine::Stop(const base::Closure
& callback
) {
575 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
582 // The following cancels any outstanding callbacks and causes IsStarted() to
583 // return false from here onward.
584 weak_ptr_factory_
.InvalidateWeakPtrs();
586 // Note: RenewFrameSubscription() must be called before stopping |tracker_| so
587 // the web_contents() can be notified that the capturing is ending.
588 RenewFrameSubscription(NULL
);
591 // The render thread cannot be stopped on the UI thread, so post a message
592 // to the thread pool used for blocking operations.
593 if (render_thread_
.get()) {
594 BrowserThread::PostBlockingPoolTask(
596 base::Bind(&DeleteOnWorkerThread
, base::Passed(&render_thread_
),
601 void WebContentsCaptureMachine::Capture(
602 const base::TimeTicks
& start_time
,
603 const scoped_refptr
<media::VideoFrame
>& target
,
604 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
606 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
608 RenderWidgetHost
* rwh
= tracker_
->GetTargetRenderWidgetHost();
609 RenderWidgetHostViewBase
* view
=
610 rwh
? static_cast<RenderWidgetHostViewBase
*>(rwh
->GetView()) : NULL
;
612 deliver_frame_cb
.Run(base::TimeTicks(), false);
616 gfx::Size view_size
= view
->GetViewBounds().size();
617 gfx::Size fitted_size
;
618 if (!view_size
.IsEmpty()) {
619 fitted_size
= ComputeYV12LetterboxRegion(target
->visible_rect(),
622 if (view_size
!= last_view_size_
) {
623 last_view_size_
= view_size
;
625 // Measure the number of kilopixels.
626 UMA_HISTOGRAM_COUNTS_10000(
627 "TabCapture.ViewChangeKiloPixels",
628 view_size
.width() * view_size
.height() / 1024);
631 if (view
->CanCopyToVideoFrame()) {
632 view
->CopyFromCompositingSurfaceToVideoFrame(
633 gfx::Rect(view_size
),
635 base::Bind(&WebContentsCaptureMachine::
636 DidCopyFromCompositingSurfaceToVideoFrame
,
637 weak_ptr_factory_
.GetWeakPtr(),
638 start_time
, deliver_frame_cb
));
640 rwh
->CopyFromBackingStore(
642 fitted_size
, // Size here is a request not always honored.
643 base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore
,
644 weak_ptr_factory_
.GetWeakPtr(),
652 gfx::Size
WebContentsCaptureMachine::ComputeOptimalTargetSize() const {
653 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
655 gfx::Size optimal_size
= oracle_proxy_
->GetCaptureSize();
657 // If the ratio between physical and logical pixels is greater than 1:1,
658 // shrink |optimal_size| by that amount. Then, when external code resizes the
659 // render widget to the "preferred size," the widget will be physically
660 // rendered at the exact capture size, thereby eliminating unnecessary scaling
661 // operations in the graphics pipeline.
662 RenderWidgetHost
* const rwh
= tracker_
->GetTargetRenderWidgetHost();
663 RenderWidgetHostView
* const rwhv
= rwh
? rwh
->GetView() : NULL
;
665 const gfx::NativeView view
= rwhv
->GetNativeView();
666 gfx::Screen
* const screen
= gfx::Screen::GetScreenFor(view
);
667 const gfx::Display display
= screen
->GetDisplayNearestWindow(view
);
668 const float scale
= display
.device_scale_factor();
670 const gfx::Size
shrunk_size(
671 gfx::ToFlooredSize(gfx::ScaleSize(optimal_size
, 1.0f
/ scale
)));
672 if (shrunk_size
.width() > 0 && shrunk_size
.height() > 0)
673 optimal_size
= shrunk_size
;
677 VLOG(1) << "Computed optimal target size: " << optimal_size
.ToString();
681 void WebContentsCaptureMachine::DidCopyFromBackingStore(
682 const base::TimeTicks
& start_time
,
683 const scoped_refptr
<media::VideoFrame
>& target
,
684 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
686 const SkBitmap
& bitmap
,
687 ReadbackResponse response
) {
688 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
690 base::TimeTicks now
= base::TimeTicks::Now();
691 DCHECK(render_thread_
.get());
692 if (response
== READBACK_SUCCESS
) {
693 UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now
- start_time
);
694 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", target
.get(),
696 render_thread_
->message_loop_proxy()->PostTask(FROM_HERE
, base::Bind(
697 &RenderVideoFrame
, bitmap
, target
,
698 base::Bind(deliver_frame_cb
, start_time
)));
700 // Capture can fail due to transient issues, so just skip this frame.
701 DVLOG(1) << "CopyFromBackingStore failed; skipping frame.";
702 deliver_frame_cb
.Run(start_time
, false);
706 void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
707 const base::TimeTicks
& start_time
,
708 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
711 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
712 base::TimeTicks now
= base::TimeTicks::Now();
715 UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeVideoFrame", now
- start_time
);
717 // Capture can fail due to transient issues, so just skip this frame.
718 DVLOG(1) << "CopyFromCompositingSurface failed; skipping frame.";
720 deliver_frame_cb
.Run(start_time
, success
);
723 void WebContentsCaptureMachine::RenewFrameSubscription(RenderWidgetHost
* rwh
) {
724 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
726 // Always destroy the old subscription before creating a new one.
727 const bool had_subscription
= !!subscription_
;
728 subscription_
.reset();
730 DVLOG(1) << "Renewing frame subscription to RWH@" << rwh
731 << ", had_subscription=" << had_subscription
;
734 if (had_subscription
&& tracker_
->web_contents())
735 tracker_
->web_contents()->DecrementCapturerCount();
737 // Tracking of WebContents and/or its main frame has failed before Stop()
738 // was called, so report this as an error:
739 oracle_proxy_
->ReportError("WebContents and/or main frame are gone.");
744 if (!had_subscription
&& tracker_
->web_contents()) {
745 tracker_
->web_contents()->IncrementCapturerCount(
746 ComputeOptimalTargetSize());
749 subscription_
.reset(new ContentCaptureSubscription(*rwh
, oracle_proxy_
,
750 base::Bind(&WebContentsCaptureMachine::Capture
,
751 weak_ptr_factory_
.GetWeakPtr())));
756 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
757 int render_process_id
, int main_render_frame_id
)
758 : core_(new ContentVideoCaptureDeviceCore(scoped_ptr
<VideoCaptureMachine
>(
759 new WebContentsCaptureMachine(
760 render_process_id
, main_render_frame_id
)))) {}
762 WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() {
763 DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying.";
767 media::VideoCaptureDevice
* WebContentsVideoCaptureDevice::Create(
768 const std::string
& device_id
) {
769 // Parse device_id into render_process_id and main_render_frame_id.
770 int render_process_id
= -1;
771 int main_render_frame_id
= -1;
772 if (!WebContentsCaptureUtil::ExtractTabCaptureTarget(
773 device_id
, &render_process_id
, &main_render_frame_id
)) {
777 return new WebContentsVideoCaptureDevice(
778 render_process_id
, main_render_frame_id
);
781 void WebContentsVideoCaptureDevice::AllocateAndStart(
782 const media::VideoCaptureParams
& params
,
783 scoped_ptr
<Client
> client
) {
784 DVLOG(1) << "Allocating " << params
.requested_format
.frame_size
.ToString();
785 core_
->AllocateAndStart(params
, client
.Pass());
788 void WebContentsVideoCaptureDevice::StopAndDeAllocate() {
789 core_
->StopAndDeAllocate();
792 } // namespace content