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 RenderView'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/renderer_host/media/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/message_loop/message_loop_proxy.h"
59 #include "base/metrics/histogram.h"
60 #include "base/sequenced_task_runner.h"
61 #include "base/threading/thread.h"
62 #include "base/threading/thread_checker.h"
63 #include "base/time/time.h"
64 #include "content/browser/renderer_host/media/video_capture_device_impl.h"
65 #include "content/browser/renderer_host/media/video_capture_oracle.h"
66 #include "content/browser/renderer_host/media/web_contents_capture_util.h"
67 #include "content/browser/renderer_host/render_widget_host_impl.h"
68 #include "content/browser/web_contents/web_contents_impl.h"
69 #include "content/port/browser/render_widget_host_view_frame_subscriber.h"
70 #include "content/port/browser/render_widget_host_view_port.h"
71 #include "content/public/browser/browser_thread.h"
72 #include "content/public/browser/notification_source.h"
73 #include "content/public/browser/notification_types.h"
74 #include "content/public/browser/render_view_host.h"
75 #include "content/public/browser/render_widget_host_view.h"
76 #include "content/public/browser/web_contents_observer.h"
77 #include "media/base/video_util.h"
78 #include "media/video/capture/video_capture_types.h"
79 #include "skia/ext/image_operations.h"
80 #include "third_party/skia/include/core/SkBitmap.h"
81 #include "third_party/skia/include/core/SkColor.h"
87 // Compute a letterbox region, aligned to even coordinates.
88 gfx::Rect
ComputeYV12LetterboxRegion(const gfx::Size
& frame_size
,
89 const gfx::Size
& content_size
) {
91 gfx::Rect result
= media::ComputeLetterboxRegion(gfx::Rect(frame_size
),
94 result
.set_x(MakeEven(result
.x()));
95 result
.set_y(MakeEven(result
.y()));
96 result
.set_width(std::max(kMinFrameWidth
, MakeEven(result
.width())));
97 result
.set_height(std::max(kMinFrameHeight
, MakeEven(result
.height())));
102 // Wrapper function to invoke ThreadSafeCaptureOracle::CaptureFrameCallback, is
103 // compatible with RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback.
104 void InvokeCaptureFrameCallback(
105 const ThreadSafeCaptureOracle::CaptureFrameCallback
& capture_frame_cb
,
106 base::Time timestamp
,
107 bool frame_captured
) {
108 capture_frame_cb
.Run(timestamp
, frame_captured
);
111 // FrameSubscriber is a proxy to the ThreadSafeCaptureOracle that's compatible
112 // with RenderWidgetHostViewFrameSubscriber. We create one per event type.
113 class FrameSubscriber
: public RenderWidgetHostViewFrameSubscriber
{
115 FrameSubscriber(VideoCaptureOracle::Event event_type
,
116 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle
)
117 : event_type_(event_type
),
118 oracle_proxy_(oracle
) {}
120 virtual bool ShouldCaptureFrame(
121 base::Time present_time
,
122 scoped_refptr
<media::VideoFrame
>* storage
,
123 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
*
124 deliver_frame_cb
) OVERRIDE
;
127 const VideoCaptureOracle::Event event_type_
;
128 scoped_refptr
<ThreadSafeCaptureOracle
> oracle_proxy_
;
131 // ContentCaptureSubscription is the relationship between a RenderWidgetHost
132 // whose content is updating, a subscriber that is deciding which of these
133 // updates to capture (and where to deliver them to), and a callback that
134 // knows how to do the capture and prepare the result for delivery.
136 // In practice, this means (a) installing a RenderWidgetHostFrameSubscriber in
137 // the RenderWidgetHostView, to process updates that occur via accelerated
138 // compositing, (b) installing itself as an observer of updates to the
139 // RenderWidgetHost's backing store, to hook updates that occur via software
140 // rendering, and (c) running a timer to possibly initiate non-event-driven
141 // captures that the subscriber might request.
143 // All of this happens on the UI thread, although the
144 // RenderWidgetHostViewFrameSubscriber we install may be dispatching updates
145 // autonomously on some other thread.
146 class ContentCaptureSubscription
: public content::NotificationObserver
{
148 typedef base::Callback
<void(
150 const scoped_refptr
<media::VideoFrame
>&,
151 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&)>
154 // Create a subscription. Whenever a manual capture is required, the
155 // subscription will invoke |capture_callback| on the UI thread to do the
157 ContentCaptureSubscription(
158 const RenderWidgetHost
& source
,
159 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
,
160 const CaptureCallback
& capture_callback
);
161 virtual ~ContentCaptureSubscription();
163 // content::NotificationObserver implementation.
164 virtual void Observe(int type
,
165 const content::NotificationSource
& source
,
166 const content::NotificationDetails
& details
) OVERRIDE
;
171 const int render_process_id_
;
172 const int render_view_id_
;
174 FrameSubscriber paint_subscriber_
;
175 FrameSubscriber timer_subscriber_
;
176 content::NotificationRegistrar registrar_
;
177 CaptureCallback capture_callback_
;
180 DISALLOW_COPY_AND_ASSIGN(ContentCaptureSubscription
);
183 // Render the SkBitmap |input| into the given VideoFrame buffer |output|, then
184 // invoke |done_cb| to indicate success or failure. |input| is expected to be
185 // ARGB. |output| must be YV12 or I420. Colorspace conversion is always done.
186 // Scaling and letterboxing will be done as needed.
188 // This software implementation should be used only when GPU acceleration of
189 // these activities is not possible. This operation may be expensive (tens to
190 // hundreds of milliseconds), so the caller should ensure that it runs on a
191 // thread where such a pause would cause UI jank.
192 void RenderVideoFrame(const SkBitmap
& input
,
193 const scoped_refptr
<media::VideoFrame
>& output
,
194 const base::Callback
<void(bool)>& done_cb
);
196 // Keeps track of the RenderView to be sourced, and executes copying of the
197 // backing store on the UI BrowserThread.
199 // TODO(nick): It would be nice to merge this with WebContentsTracker, but its
200 // implementation is currently asynchronous -- in our case, the "rvh changed"
201 // notification would get posted back to the UI thread and processed later, and
202 // this seems disadvantageous.
203 class WebContentsCaptureMachine
204 : public VideoCaptureMachine
,
205 public WebContentsObserver
,
206 public base::SupportsWeakPtr
<WebContentsCaptureMachine
> {
208 WebContentsCaptureMachine(int render_process_id
, int render_view_id
);
209 virtual ~WebContentsCaptureMachine();
211 // VideoCaptureMachine overrides.
213 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
) OVERRIDE
;
214 virtual void Stop() OVERRIDE
;
216 // Starts a copy from the backing store or the composited surface. Must be run
217 // on the UI BrowserThread. |deliver_frame_cb| will be run when the operation
218 // completes. The copy will occur to |target|.
220 // This may be used as a ContentCaptureSubscription::CaptureCallback.
222 const base::Time
& start_time
,
223 const scoped_refptr
<media::VideoFrame
>& target
,
224 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
227 // content::WebContentsObserver implementation.
228 virtual void DidShowFullscreenWidget(int routing_id
) OVERRIDE
{
229 fullscreen_widget_id_
= routing_id
;
230 RenewFrameSubscription();
233 virtual void DidDestroyFullscreenWidget(int routing_id
) OVERRIDE
{
234 DCHECK_EQ(fullscreen_widget_id_
, routing_id
);
235 fullscreen_widget_id_
= MSG_ROUTING_NONE
;
236 RenewFrameSubscription();
239 virtual void RenderViewReady() OVERRIDE
{
240 RenewFrameSubscription();
243 virtual void AboutToNavigateRenderView(RenderViewHost
* rvh
) OVERRIDE
{
244 RenewFrameSubscription();
247 virtual void DidNavigateMainFrame(
248 const LoadCommittedDetails
& details
,
249 const FrameNavigateParams
& params
) OVERRIDE
{
250 RenewFrameSubscription();
253 virtual void WebContentsDestroyed(WebContents
* web_contents
) OVERRIDE
;
256 // Starts observing the web contents, returning false if lookup fails.
257 bool StartObservingWebContents();
259 // Helper function to determine the view that we are currently tracking.
260 RenderWidgetHost
* GetTarget();
262 // Response callback for RenderWidgetHost::CopyFromBackingStore().
263 void DidCopyFromBackingStore(
264 const base::Time
& start_time
,
265 const scoped_refptr
<media::VideoFrame
>& target
,
266 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
269 const SkBitmap
& bitmap
);
271 // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame().
272 void DidCopyFromCompositingSurfaceToVideoFrame(
273 const base::Time
& start_time
,
274 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
278 // Remove the old subscription, and start a new one. This should be called
279 // after any change to the WebContents that affects the RenderWidgetHost or
281 void RenewFrameSubscription();
283 // Parameters saved in constructor.
284 const int initial_render_process_id_
;
285 const int initial_render_view_id_
;
287 // A dedicated worker thread on which SkBitmap->VideoFrame conversion will
288 // occur. Only used when this activity cannot be done on the GPU.
289 base::Thread render_thread_
;
291 // Makes all the decisions about which frames to copy, and how.
292 scoped_refptr
<ThreadSafeCaptureOracle
> oracle_proxy_
;
294 // Routing ID of any active fullscreen render widget or MSG_ROUTING_NONE
296 int fullscreen_widget_id_
;
298 // Last known RenderView size.
299 gfx::Size last_view_size_
;
301 // Responsible for forwarding events from the active RenderWidgetHost to the
302 // oracle, and initiating captures accordingly.
303 scoped_ptr
<ContentCaptureSubscription
> subscription_
;
305 DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureMachine
);
308 // Responsible for logging the effective frame rate.
309 // TODO(nick): Make this compatible with the push model and hook it back up.
310 class VideoFrameDeliveryLog
{
312 VideoFrameDeliveryLog();
314 // Treat |frame_number| as having been delivered, and update the
315 // frame rate statistics accordingly.
316 void ChronicleFrameDelivery(int frame_number
);
319 // The following keep track of and log the effective frame rate whenever
320 // verbose logging is turned on.
321 base::Time last_frame_rate_log_time_
;
322 int count_frames_rendered_
;
323 int last_frame_number_
;
325 DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog
);
328 bool FrameSubscriber::ShouldCaptureFrame(
329 base::Time present_time
,
330 scoped_refptr
<media::VideoFrame
>* storage
,
331 DeliverFrameCallback
* deliver_frame_cb
) {
332 TRACE_EVENT1("mirroring", "FrameSubscriber::ShouldCaptureFrame",
335 ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb
;
336 bool oracle_decision
= oracle_proxy_
->ObserveEventAndDecideCapture(
337 event_type_
, present_time
, storage
, &capture_frame_cb
);
339 *deliver_frame_cb
= base::Bind(&InvokeCaptureFrameCallback
, capture_frame_cb
);
340 return oracle_decision
;
343 ContentCaptureSubscription::ContentCaptureSubscription(
344 const RenderWidgetHost
& source
,
345 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
,
346 const CaptureCallback
& capture_callback
)
347 : render_process_id_(source
.GetProcess()->GetID()),
348 render_view_id_(source
.GetRoutingID()),
349 paint_subscriber_(VideoCaptureOracle::kSoftwarePaint
, oracle_proxy
),
350 timer_subscriber_(VideoCaptureOracle::kTimerPoll
, oracle_proxy
),
351 capture_callback_(capture_callback
),
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
355 RenderWidgetHostViewPort
* view
=
356 RenderWidgetHostViewPort::FromRWHV(source
.GetView());
358 // Subscribe to accelerated presents. These will be serviced directly by the
360 if (view
&& kAcceleratedSubscriberIsSupported
) {
361 scoped_ptr
<RenderWidgetHostViewFrameSubscriber
> subscriber(
362 new FrameSubscriber(VideoCaptureOracle::kCompositorUpdate
,
364 view
->BeginFrameSubscription(subscriber
.Pass());
367 // Subscribe to software paint events. This instance will service these by
368 // reflecting them back to the WebContentsCaptureMachine via
369 // |capture_callback|.
371 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
,
372 Source
<RenderWidgetHost
>(&source
));
374 // Subscribe to timer events. This instance will service these as well.
375 timer_
.Start(FROM_HERE
, oracle_proxy
->capture_period(),
376 base::Bind(&ContentCaptureSubscription::OnTimer
,
377 base::Unretained(this)));
380 ContentCaptureSubscription::~ContentCaptureSubscription() {
381 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
382 if (kAcceleratedSubscriberIsSupported
) {
383 RenderViewHost
* source
= RenderViewHost::FromID(render_process_id_
,
386 RenderWidgetHostViewPort
* view
=
387 RenderWidgetHostViewPort::FromRWHV(source
->GetView());
389 view
->EndFrameSubscription();
394 void ContentCaptureSubscription::Observe(
396 const content::NotificationSource
& source
,
397 const content::NotificationDetails
& details
) {
398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
399 DCHECK_EQ(NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
, type
);
401 RenderWidgetHostImpl
* rwh
=
402 RenderWidgetHostImpl::From(Source
<RenderWidgetHost
>(source
).ptr());
404 // This message occurs on window resizes and visibility changes even when
405 // accelerated compositing is active, so we need to filter out these cases.
406 if (!rwh
|| !rwh
->GetView() || (rwh
->is_accelerated_compositing_active() &&
407 rwh
->GetView()->IsSurfaceAvailableForCopy()))
410 TRACE_EVENT1("mirroring", "ContentCaptureSubscription::Observe",
413 base::Closure copy_done_callback
;
414 scoped_refptr
<media::VideoFrame
> frame
;
415 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb
;
416 const base::Time start_time
= base::Time::Now();
417 if (paint_subscriber_
.ShouldCaptureFrame(start_time
,
419 &deliver_frame_cb
)) {
420 // This message happens just before paint. If we post a task to do the copy,
421 // it should run soon after the paint.
422 BrowserThread::PostTask(
423 BrowserThread::UI
, FROM_HERE
,
424 base::Bind(capture_callback_
, start_time
, frame
, deliver_frame_cb
));
428 void ContentCaptureSubscription::OnTimer() {
429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
430 TRACE_EVENT0("mirroring", "ContentCaptureSubscription::OnTimer");
432 scoped_refptr
<media::VideoFrame
> frame
;
433 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb
;
435 const base::Time start_time
= base::Time::Now();
436 if (timer_subscriber_
.ShouldCaptureFrame(start_time
,
438 &deliver_frame_cb
)) {
439 capture_callback_
.Run(start_time
, frame
, deliver_frame_cb
);
443 void RenderVideoFrame(const SkBitmap
& input
,
444 const scoped_refptr
<media::VideoFrame
>& output
,
445 const base::Callback
<void(bool)>& done_cb
) {
446 base::ScopedClosureRunner
failure_handler(base::Bind(done_cb
, false));
448 SkAutoLockPixels
locker(input
);
450 // Sanity-check the captured bitmap.
452 !input
.readyToDraw() ||
453 input
.config() != SkBitmap::kARGB_8888_Config
||
454 input
.width() < 2 || input
.height() < 2) {
455 DVLOG(1) << "input unacceptable (size="
457 << ", ready=" << input
.readyToDraw()
458 << ", config=" << input
.config() << ')';
462 // Sanity-check the output buffer.
463 if (output
->format() != media::VideoFrame::I420
) {
468 // Calculate the width and height of the content region in the |output|, based
469 // on the aspect ratio of |input|.
470 gfx::Rect region_in_frame
= ComputeYV12LetterboxRegion(
471 output
->coded_size(), gfx::Size(input
.width(), input
.height()));
473 // Scale the bitmap to the required size, if necessary.
474 SkBitmap scaled_bitmap
;
475 if (input
.width() != region_in_frame
.width() ||
476 input
.height() != region_in_frame
.height()) {
478 skia::ImageOperations::ResizeMethod method
;
479 if (input
.width() < region_in_frame
.width() ||
480 input
.height() < region_in_frame
.height()) {
481 // Avoid box filtering when magnifying, because it's actually
483 method
= skia::ImageOperations::RESIZE_HAMMING1
;
485 method
= skia::ImageOperations::RESIZE_BOX
;
488 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output
.get(), "Scale");
489 scaled_bitmap
= skia::ImageOperations::Resize(input
, method
,
490 region_in_frame
.width(),
491 region_in_frame
.height());
493 scaled_bitmap
= input
;
496 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output
.get(), "YUV");
498 SkAutoLockPixels
scaled_bitmap_locker(scaled_bitmap
);
500 media::CopyRGBToVideoFrame(
501 reinterpret_cast<uint8
*>(scaled_bitmap
.getPixels()),
502 scaled_bitmap
.rowBytes(),
507 // The result is now ready.
508 ignore_result(failure_handler
.Release());
512 VideoFrameDeliveryLog::VideoFrameDeliveryLog()
513 : last_frame_rate_log_time_(),
514 count_frames_rendered_(0),
515 last_frame_number_(0) {
518 void VideoFrameDeliveryLog::ChronicleFrameDelivery(int frame_number
) {
519 // Log frame rate, if verbose logging is turned on.
520 static const base::TimeDelta kFrameRateLogInterval
=
521 base::TimeDelta::FromSeconds(10);
522 const base::Time now
= base::Time::Now();
523 if (last_frame_rate_log_time_
.is_null()) {
524 last_frame_rate_log_time_
= now
;
525 count_frames_rendered_
= 0;
526 last_frame_number_
= frame_number
;
528 ++count_frames_rendered_
;
529 const base::TimeDelta elapsed
= now
- last_frame_rate_log_time_
;
530 if (elapsed
>= kFrameRateLogInterval
) {
531 const double measured_fps
=
532 count_frames_rendered_
/ elapsed
.InSecondsF();
533 const int frames_elapsed
= frame_number
- last_frame_number_
;
534 const int count_frames_dropped
= frames_elapsed
- count_frames_rendered_
;
535 DCHECK_LE(0, count_frames_dropped
);
536 UMA_HISTOGRAM_PERCENTAGE(
537 "TabCapture.FrameDropPercentage",
538 (count_frames_dropped
* 100 + frames_elapsed
/ 2) / frames_elapsed
);
539 UMA_HISTOGRAM_COUNTS(
540 "TabCapture.FrameRate",
541 static_cast<int>(measured_fps
));
542 VLOG(1) << "Current measured frame rate for "
543 << "WebContentsVideoCaptureDevice is " << measured_fps
<< " FPS.";
544 last_frame_rate_log_time_
= now
;
545 count_frames_rendered_
= 0;
546 last_frame_number_
= frame_number
;
551 WebContentsCaptureMachine::WebContentsCaptureMachine(int render_process_id
,
553 : initial_render_process_id_(render_process_id
),
554 initial_render_view_id_(render_view_id
),
555 render_thread_("WebContentsVideo_RenderThread"),
556 fullscreen_widget_id_(MSG_ROUTING_NONE
) {}
558 WebContentsCaptureMachine::~WebContentsCaptureMachine() {}
560 bool WebContentsCaptureMachine::Start(
561 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
) {
562 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
565 DCHECK(oracle_proxy
.get());
566 oracle_proxy_
= oracle_proxy
;
568 if (!render_thread_
.Start()) {
569 DVLOG(1) << "Failed to spawn render thread.";
573 if (!StartObservingWebContents())
580 void WebContentsCaptureMachine::Stop() {
581 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
582 subscription_
.reset();
583 if (web_contents()) {
584 web_contents()->DecrementCapturerCount();
587 render_thread_
.Stop();
591 void WebContentsCaptureMachine::Capture(
592 const base::Time
& start_time
,
593 const scoped_refptr
<media::VideoFrame
>& target
,
594 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
596 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
598 RenderWidgetHost
* rwh
= GetTarget();
599 RenderWidgetHostViewPort
* view
=
600 rwh
? RenderWidgetHostViewPort::FromRWHV(rwh
->GetView()) : NULL
;
602 deliver_frame_cb
.Run(base::Time(), false);
606 gfx::Size video_size
= target
->coded_size();
607 gfx::Size view_size
= view
->GetViewBounds().size();
608 gfx::Size fitted_size
;
609 if (!view_size
.IsEmpty()) {
610 fitted_size
= ComputeYV12LetterboxRegion(video_size
, view_size
).size();
612 if (view_size
!= last_view_size_
) {
613 last_view_size_
= view_size
;
615 // Measure the number of kilopixels.
616 UMA_HISTOGRAM_COUNTS_10000(
617 "TabCapture.ViewChangeKiloPixels",
618 view_size
.width() * view_size
.height() / 1024);
621 if (!view
->IsSurfaceAvailableForCopy()) {
622 // Fallback to the more expensive renderer-side copy if the surface and
623 // backing store are not accessible.
624 rwh
->GetSnapshotFromRenderer(
626 base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore
,
627 this->AsWeakPtr(), start_time
, target
, deliver_frame_cb
));
628 } else if (view
->CanCopyToVideoFrame()) {
629 view
->CopyFromCompositingSurfaceToVideoFrame(
630 gfx::Rect(view_size
),
632 base::Bind(&WebContentsCaptureMachine::
633 DidCopyFromCompositingSurfaceToVideoFrame
,
634 this->AsWeakPtr(), start_time
, deliver_frame_cb
));
636 rwh
->CopyFromBackingStore(
638 fitted_size
, // Size here is a request not always honored.
639 base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore
,
640 this->AsWeakPtr(), start_time
, target
, deliver_frame_cb
));
644 bool WebContentsCaptureMachine::StartObservingWebContents() {
645 // Look-up the RenderViewHost and, from that, the WebContents that wraps it.
646 // If successful, begin observing the WebContents instance.
648 // Why this can be unsuccessful: The request for mirroring originates in a
649 // render process, and this request is based on the current RenderView
650 // associated with a tab. However, by the time we get up-and-running here,
651 // there have been multiple back-and-forth IPCs between processes, as well as
652 // a bit of indirection across threads. It's easily possible that, in the
653 // meantime, the original RenderView may have gone away.
654 RenderViewHost
* const rvh
=
655 RenderViewHost::FromID(initial_render_process_id_
,
656 initial_render_view_id_
);
657 DVLOG_IF(1, !rvh
) << "RenderViewHost::FromID("
658 << initial_render_process_id_
<< ", "
659 << initial_render_view_id_
<< ") returned NULL.";
660 Observe(rvh
? WebContents::FromRenderViewHost(rvh
) : NULL
);
662 WebContentsImpl
* contents
= static_cast<WebContentsImpl
*>(web_contents());
664 contents
->IncrementCapturerCount();
665 fullscreen_widget_id_
= contents
->GetFullscreenWidgetRoutingID();
666 RenewFrameSubscription();
670 DVLOG(1) << "WebContents::FromRenderViewHost(" << rvh
<< ") returned NULL.";
674 void WebContentsCaptureMachine::WebContentsDestroyed(
675 WebContents
* web_contents
) {
676 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
678 subscription_
.reset();
679 web_contents
->DecrementCapturerCount();
680 oracle_proxy_
->ReportError();
683 RenderWidgetHost
* WebContentsCaptureMachine::GetTarget() {
684 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
688 RenderWidgetHost
* rwh
= NULL
;
689 if (fullscreen_widget_id_
!= MSG_ROUTING_NONE
) {
690 RenderProcessHost
* process
= web_contents()->GetRenderProcessHost();
692 rwh
= RenderWidgetHost::FromID(process
->GetID(), fullscreen_widget_id_
);
694 rwh
= web_contents()->GetRenderViewHost();
700 void WebContentsCaptureMachine::DidCopyFromBackingStore(
701 const base::Time
& start_time
,
702 const scoped_refptr
<media::VideoFrame
>& target
,
703 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
706 const SkBitmap
& bitmap
) {
707 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
709 base::Time now
= base::Time::Now();
711 UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now
- start_time
);
712 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", target
.get(),
714 render_thread_
.message_loop_proxy()->PostTask(FROM_HERE
, base::Bind(
715 &RenderVideoFrame
, bitmap
, target
,
716 base::Bind(deliver_frame_cb
, start_time
)));
718 // Capture can fail due to transient issues, so just skip this frame.
719 DVLOG(1) << "CopyFromBackingStore failed; skipping frame.";
720 deliver_frame_cb
.Run(start_time
, false);
724 void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
725 const base::Time
& start_time
,
726 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback
&
729 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
730 base::Time now
= base::Time::Now();
733 UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeVideoFrame", now
- start_time
);
735 // Capture can fail due to transient issues, so just skip this frame.
736 DVLOG(1) << "CopyFromCompositingSurface failed; skipping frame.";
738 deliver_frame_cb
.Run(start_time
, success
);
741 void WebContentsCaptureMachine::RenewFrameSubscription() {
742 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
744 // Always destroy the old subscription before creating a new one.
745 subscription_
.reset();
747 RenderWidgetHost
* rwh
= GetTarget();
748 if (!rwh
|| !rwh
->GetView())
751 subscription_
.reset(new ContentCaptureSubscription(*rwh
, oracle_proxy_
,
752 base::Bind(&WebContentsCaptureMachine::Capture
, this->AsWeakPtr())));
757 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
758 int render_process_id
, int render_view_id
)
759 : impl_(new VideoCaptureDeviceImpl(scoped_ptr
<VideoCaptureMachine
>(
760 new WebContentsCaptureMachine(render_process_id
, render_view_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 render_view_id.
770 int render_process_id
= -1;
771 int render_view_id
= -1;
772 if (!WebContentsCaptureUtil::ExtractTabCaptureTarget(
773 device_id
, &render_process_id
, &render_view_id
)) {
777 return new WebContentsVideoCaptureDevice(render_process_id
, render_view_id
);
780 void WebContentsVideoCaptureDevice::AllocateAndStart(
781 const media::VideoCaptureParams
& params
,
782 scoped_ptr
<Client
> client
) {
783 DVLOG(1) << "Allocating " << params
.requested_format
.frame_size
.ToString();
784 impl_
->AllocateAndStart(params
, client
.Pass());
787 void WebContentsVideoCaptureDevice::StopAndDeAllocate() {
788 impl_
->StopAndDeAllocate();
791 } // namespace content