• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 // 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:
9 //
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.
15 //
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.
22 //
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.
29 //
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.
34 //
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):
39 //
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
43 //
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
46 // is obtained.
47 //
48 // Turning on verbose logging will cause the effective frame rate to be logged
49 // at 5-second intervals.
50 
51 #include "content/browser/media/capture/web_contents_video_capture_device.h"
52 
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_util.h"
81 #include "media/video/capture/video_capture_types.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"
89 
90 namespace content {
91 
92 namespace {
93 
94 // Compute a letterbox region, aligned to even coordinates.
ComputeYV12LetterboxRegion(const gfx::Size & frame_size,const gfx::Size & content_size)95 gfx::Rect ComputeYV12LetterboxRegion(const gfx::Size& frame_size,
96                                      const gfx::Size& content_size) {
97 
98   gfx::Rect result = media::ComputeLetterboxRegion(gfx::Rect(frame_size),
99                                                    content_size);
100 
101   result.set_x(MakeEven(result.x()));
102   result.set_y(MakeEven(result.y()));
103   result.set_width(std::max(kMinFrameWidth, MakeEven(result.width())));
104   result.set_height(std::max(kMinFrameHeight, MakeEven(result.height())));
105 
106   return result;
107 }
108 
DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread,const base::Closure & callback)109 void DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread,
110                           const base::Closure& callback) {
111   render_thread.reset();
112 
113   // After thread join call the callback on UI thread.
114   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback);
115 }
116 
117 // Responsible for logging the effective frame rate.
118 class VideoFrameDeliveryLog {
119  public:
120   VideoFrameDeliveryLog();
121 
122   // Report that the frame posted with |frame_time| has been delivered.
123   void ChronicleFrameDelivery(base::TimeTicks frame_time);
124 
125  private:
126   // The following keep track of and log the effective frame rate whenever
127   // verbose logging is turned on.
128   base::TimeTicks last_frame_rate_log_time_;
129   int count_frames_rendered_;
130 
131   DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog);
132 };
133 
134 // FrameSubscriber is a proxy to the ThreadSafeCaptureOracle that's compatible
135 // with RenderWidgetHostViewFrameSubscriber. We create one per event type.
136 class FrameSubscriber : public RenderWidgetHostViewFrameSubscriber {
137  public:
FrameSubscriber(VideoCaptureOracle::Event event_type,const scoped_refptr<ThreadSafeCaptureOracle> & oracle,VideoFrameDeliveryLog * delivery_log)138   FrameSubscriber(VideoCaptureOracle::Event event_type,
139                   const scoped_refptr<ThreadSafeCaptureOracle>& oracle,
140                   VideoFrameDeliveryLog* delivery_log)
141       : event_type_(event_type),
142         oracle_proxy_(oracle),
143         delivery_log_(delivery_log) {}
144 
145   virtual bool ShouldCaptureFrame(
146       const gfx::Rect& damage_rect,
147       base::TimeTicks present_time,
148       scoped_refptr<media::VideoFrame>* storage,
149       RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback*
150           deliver_frame_cb) OVERRIDE;
151 
152  private:
153   const VideoCaptureOracle::Event event_type_;
154   scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
155   VideoFrameDeliveryLog* const delivery_log_;
156 };
157 
158 // ContentCaptureSubscription is the relationship between a RenderWidgetHost
159 // whose content is updating, a subscriber that is deciding which of these
160 // updates to capture (and where to deliver them to), and a callback that
161 // knows how to do the capture and prepare the result for delivery.
162 //
163 // In practice, this means (a) installing a RenderWidgetHostFrameSubscriber in
164 // the RenderWidgetHostView, to process updates that occur via accelerated
165 // compositing, (b) installing itself as an observer of updates to the
166 // RenderWidgetHost's backing store, to hook updates that occur via software
167 // rendering, and (c) running a timer to possibly initiate non-event-driven
168 // captures that the subscriber might request.
169 //
170 // All of this happens on the UI thread, although the
171 // RenderWidgetHostViewFrameSubscriber we install may be dispatching updates
172 // autonomously on some other thread.
173 class ContentCaptureSubscription : public content::NotificationObserver {
174  public:
175   typedef base::Callback<
176       void(const base::TimeTicks&,
177            const scoped_refptr<media::VideoFrame>&,
178            const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&)>
179       CaptureCallback;
180 
181   // Create a subscription. Whenever a manual capture is required, the
182   // subscription will invoke |capture_callback| on the UI thread to do the
183   // work.
184   ContentCaptureSubscription(
185       const RenderWidgetHost& source,
186       const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy,
187       const CaptureCallback& capture_callback);
188   virtual ~ContentCaptureSubscription();
189 
190   // content::NotificationObserver implementation.
191   virtual void Observe(int type,
192                        const content::NotificationSource& source,
193                        const content::NotificationDetails& details) OVERRIDE;
194 
195  private:
196   void OnTimer();
197 
198   // Maintain a weak reference to the RenderWidgetHost (via its routing ID),
199   // since the instance could be destroyed externally during the lifetime of
200   // |this|.
201   const int render_process_id_;
202   const int render_widget_id_;
203 
204   VideoFrameDeliveryLog delivery_log_;
205   FrameSubscriber paint_subscriber_;
206   FrameSubscriber timer_subscriber_;
207   content::NotificationRegistrar registrar_;
208   CaptureCallback capture_callback_;
209   base::Timer timer_;
210 
211   DISALLOW_COPY_AND_ASSIGN(ContentCaptureSubscription);
212 };
213 
214 // Render the SkBitmap |input| into the given VideoFrame buffer |output|, then
215 // invoke |done_cb| to indicate success or failure. |input| is expected to be
216 // ARGB. |output| must be YV12 or I420. Colorspace conversion is always done.
217 // Scaling and letterboxing will be done as needed.
218 //
219 // This software implementation should be used only when GPU acceleration of
220 // these activities is not possible. This operation may be expensive (tens to
221 // hundreds of milliseconds), so the caller should ensure that it runs on a
222 // thread where such a pause would cause UI jank.
223 void RenderVideoFrame(const SkBitmap& input,
224                       const scoped_refptr<media::VideoFrame>& output,
225                       const base::Callback<void(bool)>& done_cb);
226 
227 // Renews capture subscriptions based on feedback from WebContentsTracker, and
228 // also executes copying of the backing store on the UI BrowserThread.
229 class WebContentsCaptureMachine : public VideoCaptureMachine {
230  public:
231   WebContentsCaptureMachine(int render_process_id, int main_render_frame_id);
232   virtual ~WebContentsCaptureMachine();
233 
234   // VideoCaptureMachine overrides.
235   virtual bool Start(const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy,
236                      const media::VideoCaptureParams& params) OVERRIDE;
237   virtual void Stop(const base::Closure& callback) OVERRIDE;
238 
239   // Starts a copy from the backing store or the composited surface. Must be run
240   // on the UI BrowserThread. |deliver_frame_cb| will be run when the operation
241   // completes. The copy will occur to |target|.
242   //
243   // This may be used as a ContentCaptureSubscription::CaptureCallback.
244   void Capture(const base::TimeTicks& start_time,
245                const scoped_refptr<media::VideoFrame>& target,
246                const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
247                    deliver_frame_cb);
248 
249  private:
250   bool IsStarted() const;
251 
252   // Computes the preferred size of the target RenderWidget for optimal capture.
253   gfx::Size ComputeOptimalTargetSize() const;
254 
255   // Response callback for RenderWidgetHost::CopyFromBackingStore().
256   void DidCopyFromBackingStore(
257       const base::TimeTicks& start_time,
258       const scoped_refptr<media::VideoFrame>& target,
259       const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
260           deliver_frame_cb,
261       bool success,
262       const SkBitmap& bitmap);
263 
264   // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame().
265   void DidCopyFromCompositingSurfaceToVideoFrame(
266       const base::TimeTicks& start_time,
267       const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
268           deliver_frame_cb,
269       bool success);
270 
271   // Remove the old subscription, and start a new one if |rwh| is not NULL.
272   void RenewFrameSubscription(RenderWidgetHost* rwh);
273 
274   // Parameters saved in constructor.
275   const int initial_render_process_id_;
276   const int initial_main_render_frame_id_;
277 
278   // Tracks events and calls back to RenewFrameSubscription() to maintain
279   // capture on the correct RenderWidgetHost.
280   const scoped_refptr<WebContentsTracker> tracker_;
281 
282   // A dedicated worker thread on which SkBitmap->VideoFrame conversion will
283   // occur. Only used when this activity cannot be done on the GPU.
284   scoped_ptr<base::Thread> render_thread_;
285 
286   // Makes all the decisions about which frames to copy, and how.
287   scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_;
288 
289   // Video capture parameters that this machine is started with.
290   media::VideoCaptureParams capture_params_;
291 
292   // Last known RenderView size.
293   gfx::Size last_view_size_;
294 
295   // Responsible for forwarding events from the active RenderWidgetHost to the
296   // oracle, and initiating captures accordingly.
297   scoped_ptr<ContentCaptureSubscription> subscription_;
298 
299   // Weak pointer factory used to invalidate callbacks.
300   // NOTE: Weak pointers must be invalidated before all other member variables.
301   base::WeakPtrFactory<WebContentsCaptureMachine> weak_ptr_factory_;
302 
303   DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureMachine);
304 };
305 
ShouldCaptureFrame(const gfx::Rect & damage_rect,base::TimeTicks present_time,scoped_refptr<media::VideoFrame> * storage,DeliverFrameCallback * deliver_frame_cb)306 bool FrameSubscriber::ShouldCaptureFrame(
307     const gfx::Rect& damage_rect,
308     base::TimeTicks present_time,
309     scoped_refptr<media::VideoFrame>* storage,
310     DeliverFrameCallback* deliver_frame_cb) {
311   TRACE_EVENT1("mirroring", "FrameSubscriber::ShouldCaptureFrame",
312                "instance", this);
313 
314   ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb;
315   bool oracle_decision = oracle_proxy_->ObserveEventAndDecideCapture(
316       event_type_, damage_rect, present_time, storage, &capture_frame_cb);
317 
318   if (!capture_frame_cb.is_null())
319     *deliver_frame_cb = base::Bind(capture_frame_cb, *storage);
320   if (oracle_decision)
321     delivery_log_->ChronicleFrameDelivery(present_time);
322   return oracle_decision;
323 }
324 
ContentCaptureSubscription(const RenderWidgetHost & source,const scoped_refptr<ThreadSafeCaptureOracle> & oracle_proxy,const CaptureCallback & capture_callback)325 ContentCaptureSubscription::ContentCaptureSubscription(
326     const RenderWidgetHost& source,
327     const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy,
328     const CaptureCallback& capture_callback)
329     : render_process_id_(source.GetProcess()->GetID()),
330       render_widget_id_(source.GetRoutingID()),
331       delivery_log_(),
332       paint_subscriber_(VideoCaptureOracle::kSoftwarePaint, oracle_proxy,
333                         &delivery_log_),
334       timer_subscriber_(VideoCaptureOracle::kTimerPoll, oracle_proxy,
335                         &delivery_log_),
336       capture_callback_(capture_callback),
337       timer_(true, true) {
338   DCHECK_CURRENTLY_ON(BrowserThread::UI);
339 
340   RenderWidgetHostView* const view = source.GetView();
341 
342   // Subscribe to accelerated presents. These will be serviced directly by the
343   // oracle.
344   if (view && kAcceleratedSubscriberIsSupported) {
345     scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber(
346         new FrameSubscriber(VideoCaptureOracle::kCompositorUpdate,
347             oracle_proxy, &delivery_log_));
348     view->BeginFrameSubscription(subscriber.Pass());
349   }
350 
351   // Subscribe to software paint events. This instance will service these by
352   // reflecting them back to the WebContentsCaptureMachine via
353   // |capture_callback|.
354   registrar_.Add(
355       this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
356       Source<RenderWidgetHost>(&source));
357 
358   // Subscribe to timer events. This instance will service these as well.
359   timer_.Start(FROM_HERE, oracle_proxy->min_capture_period(),
360                base::Bind(&ContentCaptureSubscription::OnTimer,
361                           base::Unretained(this)));
362 }
363 
~ContentCaptureSubscription()364 ContentCaptureSubscription::~ContentCaptureSubscription() {
365   // If the BrowserThreads have been torn down, then the browser is in the final
366   // stages of exiting and it is dangerous to take any further action.  We must
367   // return early.  http://crbug.com/396413
368   if (!BrowserThread::IsMessageLoopValid(BrowserThread::UI))
369     return;
370 
371   DCHECK_CURRENTLY_ON(BrowserThread::UI);
372   if (kAcceleratedSubscriberIsSupported) {
373     RenderWidgetHost* const source =
374         RenderWidgetHost::FromID(render_process_id_, render_widget_id_);
375     RenderWidgetHostView* const view = source ? source->GetView() : NULL;
376     if (view)
377       view->EndFrameSubscription();
378   }
379 }
380 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)381 void ContentCaptureSubscription::Observe(
382     int type,
383     const content::NotificationSource& source,
384     const content::NotificationDetails& details) {
385   DCHECK_CURRENTLY_ON(BrowserThread::UI);
386   DCHECK_EQ(NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, type);
387 
388   RenderWidgetHostImpl* rwh =
389       RenderWidgetHostImpl::From(Source<RenderWidgetHost>(source).ptr());
390 
391   // This message occurs on window resizes and visibility changes even when
392   // accelerated compositing is active, so we need to filter out these cases.
393   if (!rwh || !rwh->GetView())
394     return;
395   // Mac sends DID_UPDATE_BACKING_STORE messages to inform the capture system
396   // of new software compositor frames, so always treat these messages as
397   // signals of a new frame on Mac.
398   // http://crbug.com/333986
399 #if !defined(OS_MACOSX)
400   if (rwh->GetView()->IsSurfaceAvailableForCopy())
401     return;
402 #endif
403 
404   TRACE_EVENT1("mirroring", "ContentCaptureSubscription::Observe",
405                "instance", this);
406 
407   base::Closure copy_done_callback;
408   scoped_refptr<media::VideoFrame> frame;
409   RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb;
410   const base::TimeTicks start_time = base::TimeTicks::Now();
411   if (paint_subscriber_.ShouldCaptureFrame(gfx::Rect(),
412                                            start_time,
413                                            &frame,
414                                            &deliver_frame_cb)) {
415     // This message happens just before paint. If we post a task to do the copy,
416     // it should run soon after the paint.
417     BrowserThread::PostTask(
418         BrowserThread::UI, FROM_HERE,
419         base::Bind(capture_callback_, start_time, frame, deliver_frame_cb));
420   }
421 }
422 
OnTimer()423 void ContentCaptureSubscription::OnTimer() {
424   DCHECK_CURRENTLY_ON(BrowserThread::UI);
425   TRACE_EVENT0("mirroring", "ContentCaptureSubscription::OnTimer");
426 
427   scoped_refptr<media::VideoFrame> frame;
428   RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb;
429 
430   const base::TimeTicks start_time = base::TimeTicks::Now();
431   if (timer_subscriber_.ShouldCaptureFrame(gfx::Rect(),
432                                            start_time,
433                                            &frame,
434                                            &deliver_frame_cb)) {
435     capture_callback_.Run(start_time, frame, deliver_frame_cb);
436   }
437 }
438 
RenderVideoFrame(const SkBitmap & input,const scoped_refptr<media::VideoFrame> & output,const base::Callback<void (bool)> & done_cb)439 void RenderVideoFrame(const SkBitmap& input,
440                       const scoped_refptr<media::VideoFrame>& output,
441                       const base::Callback<void(bool)>& done_cb) {
442   base::ScopedClosureRunner failure_handler(base::Bind(done_cb, false));
443 
444   SkAutoLockPixels locker(input);
445 
446   // Sanity-check the captured bitmap.
447   if (input.empty() ||
448       !input.readyToDraw() ||
449       input.colorType() != kN32_SkColorType ||
450       input.width() < 2 || input.height() < 2) {
451     DVLOG(1) << "input unacceptable (size="
452              << input.getSize()
453              << ", ready=" << input.readyToDraw()
454              << ", colorType=" << input.colorType() << ')';
455     return;
456   }
457 
458   // Sanity-check the output buffer.
459   if (output->format() != media::VideoFrame::I420) {
460     NOTREACHED();
461     return;
462   }
463 
464   // Calculate the width and height of the content region in the |output|, based
465   // on the aspect ratio of |input|.
466   gfx::Rect region_in_frame = ComputeYV12LetterboxRegion(
467       output->coded_size(), gfx::Size(input.width(), input.height()));
468 
469   // Scale the bitmap to the required size, if necessary.
470   SkBitmap scaled_bitmap;
471   if (input.width() != region_in_frame.width() ||
472       input.height() != region_in_frame.height()) {
473 
474     skia::ImageOperations::ResizeMethod method;
475     if (input.width() < region_in_frame.width() ||
476         input.height() < region_in_frame.height()) {
477       // Avoid box filtering when magnifying, because it's actually
478       // nearest-neighbor.
479       method = skia::ImageOperations::RESIZE_HAMMING1;
480     } else {
481       method = skia::ImageOperations::RESIZE_BOX;
482     }
483 
484     TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output.get(), "Scale");
485     scaled_bitmap = skia::ImageOperations::Resize(input, method,
486                                                   region_in_frame.width(),
487                                                   region_in_frame.height());
488   } else {
489     scaled_bitmap = input;
490   }
491 
492   TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output.get(), "YUV");
493   {
494     SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap);
495 
496     media::CopyRGBToVideoFrame(
497         reinterpret_cast<uint8*>(scaled_bitmap.getPixels()),
498         scaled_bitmap.rowBytes(),
499         region_in_frame,
500         output.get());
501   }
502 
503   // The result is now ready.
504   ignore_result(failure_handler.Release());
505   done_cb.Run(true);
506 }
507 
VideoFrameDeliveryLog()508 VideoFrameDeliveryLog::VideoFrameDeliveryLog()
509     : last_frame_rate_log_time_(),
510       count_frames_rendered_(0) {
511 }
512 
ChronicleFrameDelivery(base::TimeTicks frame_time)513 void VideoFrameDeliveryLog::ChronicleFrameDelivery(base::TimeTicks frame_time) {
514   // Log frame rate, if verbose logging is turned on.
515   static const base::TimeDelta kFrameRateLogInterval =
516       base::TimeDelta::FromSeconds(10);
517   if (last_frame_rate_log_time_.is_null()) {
518     last_frame_rate_log_time_ = frame_time;
519     count_frames_rendered_ = 0;
520   } else {
521     ++count_frames_rendered_;
522     const base::TimeDelta elapsed = frame_time - last_frame_rate_log_time_;
523     if (elapsed >= kFrameRateLogInterval) {
524       const double measured_fps =
525           count_frames_rendered_ / elapsed.InSecondsF();
526       UMA_HISTOGRAM_COUNTS(
527           "TabCapture.FrameRate",
528           static_cast<int>(measured_fps));
529       VLOG(1) << "Current measured frame rate for "
530               << "WebContentsVideoCaptureDevice is " << measured_fps << " FPS.";
531       last_frame_rate_log_time_ = frame_time;
532       count_frames_rendered_ = 0;
533     }
534   }
535 }
536 
WebContentsCaptureMachine(int render_process_id,int main_render_frame_id)537 WebContentsCaptureMachine::WebContentsCaptureMachine(int render_process_id,
538                                                      int main_render_frame_id)
539     : initial_render_process_id_(render_process_id),
540       initial_main_render_frame_id_(main_render_frame_id),
541       tracker_(new WebContentsTracker(true)),
542       weak_ptr_factory_(this) {}
543 
~WebContentsCaptureMachine()544 WebContentsCaptureMachine::~WebContentsCaptureMachine() {}
545 
IsStarted() const546 bool WebContentsCaptureMachine::IsStarted() const {
547   DCHECK_CURRENTLY_ON(BrowserThread::UI);
548   return weak_ptr_factory_.HasWeakPtrs();
549 }
550 
Start(const scoped_refptr<ThreadSafeCaptureOracle> & oracle_proxy,const media::VideoCaptureParams & params)551 bool WebContentsCaptureMachine::Start(
552     const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy,
553     const media::VideoCaptureParams& params) {
554   DCHECK_CURRENTLY_ON(BrowserThread::UI);
555   DCHECK(!IsStarted());
556 
557   DCHECK(oracle_proxy.get());
558   oracle_proxy_ = oracle_proxy;
559   capture_params_ = params;
560 
561   render_thread_.reset(new base::Thread("WebContentsVideo_RenderThread"));
562   if (!render_thread_->Start()) {
563     DVLOG(1) << "Failed to spawn render thread.";
564     render_thread_.reset();
565     return false;
566   }
567 
568   // Note: Creation of the first WeakPtr in the following statement will cause
569   // IsStarted() to return true from now on.
570   tracker_->Start(initial_render_process_id_, initial_main_render_frame_id_,
571                   base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription,
572                              weak_ptr_factory_.GetWeakPtr()));
573 
574   return true;
575 }
576 
Stop(const base::Closure & callback)577 void WebContentsCaptureMachine::Stop(const base::Closure& callback) {
578   DCHECK_CURRENTLY_ON(BrowserThread::UI);
579 
580   if (!IsStarted()) {
581     callback.Run();
582     return;
583   }
584 
585   // The following cancels any outstanding callbacks and causes IsStarted() to
586   // return false from here onward.
587   weak_ptr_factory_.InvalidateWeakPtrs();
588 
589   // Note: RenewFrameSubscription() must be called before stopping |tracker_| so
590   // the web_contents() can be notified that the capturing is ending.
591   RenewFrameSubscription(NULL);
592   tracker_->Stop();
593 
594   // The render thread cannot be stopped on the UI thread, so post a message
595   // to the thread pool used for blocking operations.
596   if (render_thread_.get()) {
597     BrowserThread::PostBlockingPoolTask(
598         FROM_HERE,
599         base::Bind(&DeleteOnWorkerThread, base::Passed(&render_thread_),
600                    callback));
601   }
602 }
603 
Capture(const base::TimeTicks & start_time,const scoped_refptr<media::VideoFrame> & target,const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback & deliver_frame_cb)604 void WebContentsCaptureMachine::Capture(
605     const base::TimeTicks& start_time,
606     const scoped_refptr<media::VideoFrame>& target,
607     const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
608         deliver_frame_cb) {
609   DCHECK_CURRENTLY_ON(BrowserThread::UI);
610 
611   RenderWidgetHost* rwh = tracker_->GetTargetRenderWidgetHost();
612   RenderWidgetHostViewBase* view =
613       rwh ? static_cast<RenderWidgetHostViewBase*>(rwh->GetView()) : NULL;
614   if (!view) {
615     deliver_frame_cb.Run(base::TimeTicks(), false);
616     return;
617   }
618 
619   gfx::Size video_size = target->coded_size();
620   gfx::Size view_size = view->GetViewBounds().size();
621   gfx::Size fitted_size;
622   if (!view_size.IsEmpty()) {
623     fitted_size = ComputeYV12LetterboxRegion(video_size, view_size).size();
624   }
625   if (view_size != last_view_size_) {
626     last_view_size_ = view_size;
627 
628     // Measure the number of kilopixels.
629     UMA_HISTOGRAM_COUNTS_10000(
630         "TabCapture.ViewChangeKiloPixels",
631         view_size.width() * view_size.height() / 1024);
632   }
633 
634   if (view->CanCopyToVideoFrame()) {
635     view->CopyFromCompositingSurfaceToVideoFrame(
636         gfx::Rect(view_size),
637         target,
638         base::Bind(&WebContentsCaptureMachine::
639                         DidCopyFromCompositingSurfaceToVideoFrame,
640                    weak_ptr_factory_.GetWeakPtr(),
641                    start_time, deliver_frame_cb));
642   } else {
643     rwh->CopyFromBackingStore(
644         gfx::Rect(),
645         fitted_size,  // Size here is a request not always honored.
646         base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore,
647                    weak_ptr_factory_.GetWeakPtr(),
648                    start_time,
649                    target,
650                    deliver_frame_cb),
651         kN32_SkColorType);
652   }
653 }
654 
ComputeOptimalTargetSize() const655 gfx::Size WebContentsCaptureMachine::ComputeOptimalTargetSize() const {
656   DCHECK_CURRENTLY_ON(BrowserThread::UI);
657 
658   gfx::Size optimal_size = oracle_proxy_->GetCaptureSize();
659 
660   // If the ratio between physical and logical pixels is greater than 1:1,
661   // shrink |optimal_size| by that amount.  Then, when external code resizes the
662   // render widget to the "preferred size," the widget will be physically
663   // rendered at the exact capture size, thereby eliminating unnecessary scaling
664   // operations in the graphics pipeline.
665   RenderWidgetHost* const rwh = tracker_->GetTargetRenderWidgetHost();
666   RenderWidgetHostView* const rwhv = rwh ? rwh->GetView() : NULL;
667   if (rwhv) {
668     const gfx::NativeView view = rwhv->GetNativeView();
669     gfx::Screen* const screen = gfx::Screen::GetScreenFor(view);
670     if (screen->IsDIPEnabled()) {
671       const gfx::Display display = screen->GetDisplayNearestWindow(view);
672       const float scale = display.device_scale_factor();
673       if (scale > 1.0f) {
674         const gfx::Size shrunk_size(
675             gfx::ToFlooredSize(gfx::ScaleSize(optimal_size, 1.0f / scale)));
676         if (shrunk_size.width() > 0 && shrunk_size.height() > 0)
677           optimal_size = shrunk_size;
678       }
679     }
680   }
681 
682   VLOG(1) << "Computed optimal target size: " << optimal_size.ToString();
683   return optimal_size;
684 }
685 
DidCopyFromBackingStore(const base::TimeTicks & start_time,const scoped_refptr<media::VideoFrame> & target,const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback & deliver_frame_cb,bool success,const SkBitmap & bitmap)686 void WebContentsCaptureMachine::DidCopyFromBackingStore(
687     const base::TimeTicks& start_time,
688     const scoped_refptr<media::VideoFrame>& target,
689     const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
690         deliver_frame_cb,
691     bool success,
692     const SkBitmap& bitmap) {
693   DCHECK_CURRENTLY_ON(BrowserThread::UI);
694 
695   base::TimeTicks now = base::TimeTicks::Now();
696   DCHECK(render_thread_.get());
697   if (success) {
698     UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now - start_time);
699     TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", target.get(),
700                                  "Render");
701     render_thread_->message_loop_proxy()->PostTask(FROM_HERE, base::Bind(
702         &RenderVideoFrame, bitmap, target,
703         base::Bind(deliver_frame_cb, start_time)));
704   } else {
705     // Capture can fail due to transient issues, so just skip this frame.
706     DVLOG(1) << "CopyFromBackingStore failed; skipping frame.";
707     deliver_frame_cb.Run(start_time, false);
708   }
709 }
710 
DidCopyFromCompositingSurfaceToVideoFrame(const base::TimeTicks & start_time,const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback & deliver_frame_cb,bool success)711 void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
712     const base::TimeTicks& start_time,
713     const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&
714         deliver_frame_cb,
715     bool success) {
716   DCHECK_CURRENTLY_ON(BrowserThread::UI);
717   base::TimeTicks now = base::TimeTicks::Now();
718 
719   if (success) {
720     UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeVideoFrame", now - start_time);
721   } else {
722     // Capture can fail due to transient issues, so just skip this frame.
723     DVLOG(1) << "CopyFromCompositingSurface failed; skipping frame.";
724   }
725   deliver_frame_cb.Run(start_time, success);
726 }
727 
RenewFrameSubscription(RenderWidgetHost * rwh)728 void WebContentsCaptureMachine::RenewFrameSubscription(RenderWidgetHost* rwh) {
729   DCHECK_CURRENTLY_ON(BrowserThread::UI);
730 
731   // Always destroy the old subscription before creating a new one.
732   const bool had_subscription = !!subscription_;
733   subscription_.reset();
734 
735   DVLOG(1) << "Renewing frame subscription to RWH@" << rwh
736            << ", had_subscription=" << had_subscription;
737 
738   if (!rwh) {
739     if (had_subscription && tracker_->web_contents())
740       tracker_->web_contents()->DecrementCapturerCount();
741     if (IsStarted()) {
742       // Tracking of WebContents and/or its main frame has failed before Stop()
743       // was called, so report this as an error:
744       oracle_proxy_->ReportError("WebContents and/or main frame are gone.");
745     }
746     return;
747   }
748 
749   if (!had_subscription && tracker_->web_contents()) {
750     tracker_->web_contents()->IncrementCapturerCount(
751         ComputeOptimalTargetSize());
752   }
753 
754   subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_,
755       base::Bind(&WebContentsCaptureMachine::Capture,
756                  weak_ptr_factory_.GetWeakPtr())));
757 }
758 
759 }  // namespace
760 
WebContentsVideoCaptureDevice(int render_process_id,int main_render_frame_id)761 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
762     int render_process_id, int main_render_frame_id)
763     : core_(new ContentVideoCaptureDeviceCore(scoped_ptr<VideoCaptureMachine>(
764         new WebContentsCaptureMachine(
765             render_process_id, main_render_frame_id)))) {}
766 
~WebContentsVideoCaptureDevice()767 WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() {
768   DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying.";
769 }
770 
771 // static
Create(const std::string & device_id)772 media::VideoCaptureDevice* WebContentsVideoCaptureDevice::Create(
773     const std::string& device_id) {
774   // Parse device_id into render_process_id and main_render_frame_id.
775   int render_process_id = -1;
776   int main_render_frame_id = -1;
777   if (!WebContentsCaptureUtil::ExtractTabCaptureTarget(
778            device_id, &render_process_id, &main_render_frame_id)) {
779     return NULL;
780   }
781 
782   return new WebContentsVideoCaptureDevice(
783       render_process_id, main_render_frame_id);
784 }
785 
AllocateAndStart(const media::VideoCaptureParams & params,scoped_ptr<Client> client)786 void WebContentsVideoCaptureDevice::AllocateAndStart(
787     const media::VideoCaptureParams& params,
788     scoped_ptr<Client> client) {
789   DVLOG(1) << "Allocating " << params.requested_format.frame_size.ToString();
790   core_->AllocateAndStart(params, client.Pass());
791 }
792 
StopAndDeAllocate()793 void WebContentsVideoCaptureDevice::StopAndDeAllocate() {
794   core_->StopAndDeAllocate();
795 }
796 
797 }  // namespace content
798