| OLD | NEW |
| (Empty) |
| 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 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. | |
| 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/renderer_host/media/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/renderer_host/media/content_video_capture_device_core.
h" | |
| 66 #include "content/browser/renderer_host/media/video_capture_oracle.h" | |
| 67 #include "content/browser/renderer_host/media/web_contents_capture_util.h" | |
| 68 #include "content/browser/renderer_host/render_widget_host_impl.h" | |
| 69 #include "content/browser/web_contents/web_contents_impl.h" | |
| 70 #include "content/port/browser/render_widget_host_view_frame_subscriber.h" | |
| 71 #include "content/port/browser/render_widget_host_view_port.h" | |
| 72 #include "content/public/browser/browser_thread.h" | |
| 73 #include "content/public/browser/notification_source.h" | |
| 74 #include "content/public/browser/notification_types.h" | |
| 75 #include "content/public/browser/render_view_host.h" | |
| 76 #include "content/public/browser/render_widget_host_view.h" | |
| 77 #include "content/public/browser/web_contents_observer.h" | |
| 78 #include "media/base/video_util.h" | |
| 79 #include "media/video/capture/video_capture_types.h" | |
| 80 #include "skia/ext/image_operations.h" | |
| 81 #include "third_party/skia/include/core/SkBitmap.h" | |
| 82 #include "third_party/skia/include/core/SkColor.h" | |
| 83 | |
| 84 namespace content { | |
| 85 | |
| 86 namespace { | |
| 87 | |
| 88 // Compute a letterbox region, aligned to even coordinates. | |
| 89 gfx::Rect ComputeYV12LetterboxRegion(const gfx::Size& frame_size, | |
| 90 const gfx::Size& content_size) { | |
| 91 | |
| 92 gfx::Rect result = media::ComputeLetterboxRegion(gfx::Rect(frame_size), | |
| 93 content_size); | |
| 94 | |
| 95 result.set_x(MakeEven(result.x())); | |
| 96 result.set_y(MakeEven(result.y())); | |
| 97 result.set_width(std::max(kMinFrameWidth, MakeEven(result.width()))); | |
| 98 result.set_height(std::max(kMinFrameHeight, MakeEven(result.height()))); | |
| 99 | |
| 100 return result; | |
| 101 } | |
| 102 | |
| 103 // Wrapper function to invoke ThreadSafeCaptureOracle::CaptureFrameCallback, is | |
| 104 // compatible with RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback. | |
| 105 void InvokeCaptureFrameCallback( | |
| 106 const ThreadSafeCaptureOracle::CaptureFrameCallback& capture_frame_cb, | |
| 107 base::TimeTicks timestamp, | |
| 108 bool frame_captured) { | |
| 109 capture_frame_cb.Run(timestamp, frame_captured); | |
| 110 } | |
| 111 | |
| 112 void DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread, | |
| 113 const base::Closure& callback) { | |
| 114 render_thread.reset(); | |
| 115 | |
| 116 // After thread join call the callback on UI thread. | |
| 117 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback); | |
| 118 } | |
| 119 | |
| 120 // FrameSubscriber is a proxy to the ThreadSafeCaptureOracle that's compatible | |
| 121 // with RenderWidgetHostViewFrameSubscriber. We create one per event type. | |
| 122 class FrameSubscriber : public RenderWidgetHostViewFrameSubscriber { | |
| 123 public: | |
| 124 FrameSubscriber(VideoCaptureOracle::Event event_type, | |
| 125 const scoped_refptr<ThreadSafeCaptureOracle>& oracle) | |
| 126 : event_type_(event_type), | |
| 127 oracle_proxy_(oracle) {} | |
| 128 | |
| 129 virtual bool ShouldCaptureFrame( | |
| 130 base::TimeTicks present_time, | |
| 131 scoped_refptr<media::VideoFrame>* storage, | |
| 132 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback* | |
| 133 deliver_frame_cb) OVERRIDE; | |
| 134 | |
| 135 private: | |
| 136 const VideoCaptureOracle::Event event_type_; | |
| 137 scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_; | |
| 138 }; | |
| 139 | |
| 140 // ContentCaptureSubscription is the relationship between a RenderWidgetHost | |
| 141 // whose content is updating, a subscriber that is deciding which of these | |
| 142 // updates to capture (and where to deliver them to), and a callback that | |
| 143 // knows how to do the capture and prepare the result for delivery. | |
| 144 // | |
| 145 // In practice, this means (a) installing a RenderWidgetHostFrameSubscriber in | |
| 146 // the RenderWidgetHostView, to process updates that occur via accelerated | |
| 147 // compositing, (b) installing itself as an observer of updates to the | |
| 148 // RenderWidgetHost's backing store, to hook updates that occur via software | |
| 149 // rendering, and (c) running a timer to possibly initiate non-event-driven | |
| 150 // captures that the subscriber might request. | |
| 151 // | |
| 152 // All of this happens on the UI thread, although the | |
| 153 // RenderWidgetHostViewFrameSubscriber we install may be dispatching updates | |
| 154 // autonomously on some other thread. | |
| 155 class ContentCaptureSubscription : public content::NotificationObserver { | |
| 156 public: | |
| 157 typedef base::Callback< | |
| 158 void(const base::TimeTicks&, | |
| 159 const scoped_refptr<media::VideoFrame>&, | |
| 160 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&)> | |
| 161 CaptureCallback; | |
| 162 | |
| 163 // Create a subscription. Whenever a manual capture is required, the | |
| 164 // subscription will invoke |capture_callback| on the UI thread to do the | |
| 165 // work. | |
| 166 ContentCaptureSubscription( | |
| 167 const RenderWidgetHost& source, | |
| 168 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy, | |
| 169 const CaptureCallback& capture_callback); | |
| 170 virtual ~ContentCaptureSubscription(); | |
| 171 | |
| 172 // content::NotificationObserver implementation. | |
| 173 virtual void Observe(int type, | |
| 174 const content::NotificationSource& source, | |
| 175 const content::NotificationDetails& details) OVERRIDE; | |
| 176 | |
| 177 private: | |
| 178 void OnTimer(); | |
| 179 | |
| 180 const int render_process_id_; | |
| 181 const int render_view_id_; | |
| 182 | |
| 183 FrameSubscriber paint_subscriber_; | |
| 184 FrameSubscriber timer_subscriber_; | |
| 185 content::NotificationRegistrar registrar_; | |
| 186 CaptureCallback capture_callback_; | |
| 187 base::Timer timer_; | |
| 188 | |
| 189 DISALLOW_COPY_AND_ASSIGN(ContentCaptureSubscription); | |
| 190 }; | |
| 191 | |
| 192 // Render the SkBitmap |input| into the given VideoFrame buffer |output|, then | |
| 193 // invoke |done_cb| to indicate success or failure. |input| is expected to be | |
| 194 // ARGB. |output| must be YV12 or I420. Colorspace conversion is always done. | |
| 195 // Scaling and letterboxing will be done as needed. | |
| 196 // | |
| 197 // This software implementation should be used only when GPU acceleration of | |
| 198 // these activities is not possible. This operation may be expensive (tens to | |
| 199 // hundreds of milliseconds), so the caller should ensure that it runs on a | |
| 200 // thread where such a pause would cause UI jank. | |
| 201 void RenderVideoFrame(const SkBitmap& input, | |
| 202 const scoped_refptr<media::VideoFrame>& output, | |
| 203 const base::Callback<void(bool)>& done_cb); | |
| 204 | |
| 205 // Keeps track of the RenderView to be sourced, and executes copying of the | |
| 206 // backing store on the UI BrowserThread. | |
| 207 // | |
| 208 // TODO(nick): It would be nice to merge this with WebContentsTracker, but its | |
| 209 // implementation is currently asynchronous -- in our case, the "rvh changed" | |
| 210 // notification would get posted back to the UI thread and processed later, and | |
| 211 // this seems disadvantageous. | |
| 212 class WebContentsCaptureMachine | |
| 213 : public VideoCaptureMachine, | |
| 214 public WebContentsObserver { | |
| 215 public: | |
| 216 WebContentsCaptureMachine(int render_process_id, int render_view_id); | |
| 217 virtual ~WebContentsCaptureMachine(); | |
| 218 | |
| 219 // VideoCaptureMachine overrides. | |
| 220 virtual bool Start( | |
| 221 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) OVERRIDE; | |
| 222 virtual void Stop(const base::Closure& callback) OVERRIDE; | |
| 223 | |
| 224 // Starts a copy from the backing store or the composited surface. Must be run | |
| 225 // on the UI BrowserThread. |deliver_frame_cb| will be run when the operation | |
| 226 // completes. The copy will occur to |target|. | |
| 227 // | |
| 228 // This may be used as a ContentCaptureSubscription::CaptureCallback. | |
| 229 void Capture(const base::TimeTicks& start_time, | |
| 230 const scoped_refptr<media::VideoFrame>& target, | |
| 231 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& | |
| 232 deliver_frame_cb); | |
| 233 | |
| 234 // content::WebContentsObserver implementation. | |
| 235 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE { | |
| 236 fullscreen_widget_id_ = routing_id; | |
| 237 RenewFrameSubscription(); | |
| 238 } | |
| 239 | |
| 240 virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE { | |
| 241 DCHECK_EQ(fullscreen_widget_id_, routing_id); | |
| 242 fullscreen_widget_id_ = MSG_ROUTING_NONE; | |
| 243 RenewFrameSubscription(); | |
| 244 } | |
| 245 | |
| 246 virtual void RenderViewReady() OVERRIDE { | |
| 247 RenewFrameSubscription(); | |
| 248 } | |
| 249 | |
| 250 virtual void AboutToNavigateRenderView(RenderViewHost* rvh) OVERRIDE { | |
| 251 RenewFrameSubscription(); | |
| 252 } | |
| 253 | |
| 254 virtual void DidNavigateMainFrame( | |
| 255 const LoadCommittedDetails& details, | |
| 256 const FrameNavigateParams& params) OVERRIDE { | |
| 257 RenewFrameSubscription(); | |
| 258 } | |
| 259 | |
| 260 virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE; | |
| 261 | |
| 262 private: | |
| 263 // Starts observing the web contents, returning false if lookup fails. | |
| 264 bool StartObservingWebContents(); | |
| 265 | |
| 266 // Helper function to determine the view that we are currently tracking. | |
| 267 RenderWidgetHost* GetTarget(); | |
| 268 | |
| 269 // Response callback for RenderWidgetHost::CopyFromBackingStore(). | |
| 270 void DidCopyFromBackingStore( | |
| 271 const base::TimeTicks& start_time, | |
| 272 const scoped_refptr<media::VideoFrame>& target, | |
| 273 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& | |
| 274 deliver_frame_cb, | |
| 275 bool success, | |
| 276 const SkBitmap& bitmap); | |
| 277 | |
| 278 // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame(). | |
| 279 void DidCopyFromCompositingSurfaceToVideoFrame( | |
| 280 const base::TimeTicks& start_time, | |
| 281 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& | |
| 282 deliver_frame_cb, | |
| 283 bool success); | |
| 284 | |
| 285 // Remove the old subscription, and start a new one. This should be called | |
| 286 // after any change to the WebContents that affects the RenderWidgetHost or | |
| 287 // attached views. | |
| 288 void RenewFrameSubscription(); | |
| 289 | |
| 290 // Parameters saved in constructor. | |
| 291 const int initial_render_process_id_; | |
| 292 const int initial_render_view_id_; | |
| 293 | |
| 294 // A dedicated worker thread on which SkBitmap->VideoFrame conversion will | |
| 295 // occur. Only used when this activity cannot be done on the GPU. | |
| 296 scoped_ptr<base::Thread> render_thread_; | |
| 297 | |
| 298 // Makes all the decisions about which frames to copy, and how. | |
| 299 scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_; | |
| 300 | |
| 301 // Routing ID of any active fullscreen render widget or MSG_ROUTING_NONE | |
| 302 // otherwise. | |
| 303 int fullscreen_widget_id_; | |
| 304 | |
| 305 // Last known RenderView size. | |
| 306 gfx::Size last_view_size_; | |
| 307 | |
| 308 // Responsible for forwarding events from the active RenderWidgetHost to the | |
| 309 // oracle, and initiating captures accordingly. | |
| 310 scoped_ptr<ContentCaptureSubscription> subscription_; | |
| 311 | |
| 312 // Weak pointer factory used to invalidate callbacks. | |
| 313 base::WeakPtrFactory<WebContentsCaptureMachine> weak_ptr_factory_; | |
| 314 | |
| 315 DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureMachine); | |
| 316 }; | |
| 317 | |
| 318 // Responsible for logging the effective frame rate. | |
| 319 // TODO(nick): Make this compatible with the push model and hook it back up. | |
| 320 class VideoFrameDeliveryLog { | |
| 321 public: | |
| 322 VideoFrameDeliveryLog(); | |
| 323 | |
| 324 // Treat |frame_number| as having been delivered, and update the | |
| 325 // frame rate statistics accordingly. | |
| 326 void ChronicleFrameDelivery(int frame_number); | |
| 327 | |
| 328 private: | |
| 329 // The following keep track of and log the effective frame rate whenever | |
| 330 // verbose logging is turned on. | |
| 331 base::TimeTicks last_frame_rate_log_time_; | |
| 332 int count_frames_rendered_; | |
| 333 int last_frame_number_; | |
| 334 | |
| 335 DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog); | |
| 336 }; | |
| 337 | |
| 338 bool FrameSubscriber::ShouldCaptureFrame( | |
| 339 base::TimeTicks present_time, | |
| 340 scoped_refptr<media::VideoFrame>* storage, | |
| 341 DeliverFrameCallback* deliver_frame_cb) { | |
| 342 TRACE_EVENT1("mirroring", "FrameSubscriber::ShouldCaptureFrame", | |
| 343 "instance", this); | |
| 344 | |
| 345 ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb; | |
| 346 bool oracle_decision = oracle_proxy_->ObserveEventAndDecideCapture( | |
| 347 event_type_, present_time, storage, &capture_frame_cb); | |
| 348 | |
| 349 *deliver_frame_cb = base::Bind(&InvokeCaptureFrameCallback, capture_frame_cb); | |
| 350 return oracle_decision; | |
| 351 } | |
| 352 | |
| 353 ContentCaptureSubscription::ContentCaptureSubscription( | |
| 354 const RenderWidgetHost& source, | |
| 355 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy, | |
| 356 const CaptureCallback& capture_callback) | |
| 357 : render_process_id_(source.GetProcess()->GetID()), | |
| 358 render_view_id_(source.GetRoutingID()), | |
| 359 paint_subscriber_(VideoCaptureOracle::kSoftwarePaint, oracle_proxy), | |
| 360 timer_subscriber_(VideoCaptureOracle::kTimerPoll, oracle_proxy), | |
| 361 capture_callback_(capture_callback), | |
| 362 timer_(true, true) { | |
| 363 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 364 | |
| 365 RenderWidgetHostViewPort* view = | |
| 366 RenderWidgetHostViewPort::FromRWHV(source.GetView()); | |
| 367 | |
| 368 // Subscribe to accelerated presents. These will be serviced directly by the | |
| 369 // oracle. | |
| 370 if (view && kAcceleratedSubscriberIsSupported) { | |
| 371 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber( | |
| 372 new FrameSubscriber(VideoCaptureOracle::kCompositorUpdate, | |
| 373 oracle_proxy)); | |
| 374 view->BeginFrameSubscription(subscriber.Pass()); | |
| 375 } | |
| 376 | |
| 377 // Subscribe to software paint events. This instance will service these by | |
| 378 // reflecting them back to the WebContentsCaptureMachine via | |
| 379 // |capture_callback|. | |
| 380 registrar_.Add( | |
| 381 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, | |
| 382 Source<RenderWidgetHost>(&source)); | |
| 383 | |
| 384 // Subscribe to timer events. This instance will service these as well. | |
| 385 timer_.Start(FROM_HERE, oracle_proxy->capture_period(), | |
| 386 base::Bind(&ContentCaptureSubscription::OnTimer, | |
| 387 base::Unretained(this))); | |
| 388 } | |
| 389 | |
| 390 ContentCaptureSubscription::~ContentCaptureSubscription() { | |
| 391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 392 if (kAcceleratedSubscriberIsSupported) { | |
| 393 RenderViewHost* source = RenderViewHost::FromID(render_process_id_, | |
| 394 render_view_id_); | |
| 395 if (source) { | |
| 396 RenderWidgetHostViewPort* view = | |
| 397 RenderWidgetHostViewPort::FromRWHV(source->GetView()); | |
| 398 if (view) | |
| 399 view->EndFrameSubscription(); | |
| 400 } | |
| 401 } | |
| 402 } | |
| 403 | |
| 404 void ContentCaptureSubscription::Observe( | |
| 405 int type, | |
| 406 const content::NotificationSource& source, | |
| 407 const content::NotificationDetails& details) { | |
| 408 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 409 DCHECK_EQ(NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, type); | |
| 410 | |
| 411 RenderWidgetHostImpl* rwh = | |
| 412 RenderWidgetHostImpl::From(Source<RenderWidgetHost>(source).ptr()); | |
| 413 | |
| 414 // This message occurs on window resizes and visibility changes even when | |
| 415 // accelerated compositing is active, so we need to filter out these cases. | |
| 416 if (!rwh || !rwh->GetView() || (rwh->is_accelerated_compositing_active() && | |
| 417 rwh->GetView()->IsSurfaceAvailableForCopy())) | |
| 418 return; | |
| 419 | |
| 420 TRACE_EVENT1("mirroring", "ContentCaptureSubscription::Observe", | |
| 421 "instance", this); | |
| 422 | |
| 423 base::Closure copy_done_callback; | |
| 424 scoped_refptr<media::VideoFrame> frame; | |
| 425 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb; | |
| 426 const base::TimeTicks start_time = base::TimeTicks::Now(); | |
| 427 if (paint_subscriber_.ShouldCaptureFrame(start_time, | |
| 428 &frame, | |
| 429 &deliver_frame_cb)) { | |
| 430 // This message happens just before paint. If we post a task to do the copy, | |
| 431 // it should run soon after the paint. | |
| 432 BrowserThread::PostTask( | |
| 433 BrowserThread::UI, FROM_HERE, | |
| 434 base::Bind(capture_callback_, start_time, frame, deliver_frame_cb)); | |
| 435 } | |
| 436 } | |
| 437 | |
| 438 void ContentCaptureSubscription::OnTimer() { | |
| 439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 440 TRACE_EVENT0("mirroring", "ContentCaptureSubscription::OnTimer"); | |
| 441 | |
| 442 scoped_refptr<media::VideoFrame> frame; | |
| 443 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb; | |
| 444 | |
| 445 const base::TimeTicks start_time = base::TimeTicks::Now(); | |
| 446 if (timer_subscriber_.ShouldCaptureFrame(start_time, | |
| 447 &frame, | |
| 448 &deliver_frame_cb)) { | |
| 449 capture_callback_.Run(start_time, frame, deliver_frame_cb); | |
| 450 } | |
| 451 } | |
| 452 | |
| 453 void RenderVideoFrame(const SkBitmap& input, | |
| 454 const scoped_refptr<media::VideoFrame>& output, | |
| 455 const base::Callback<void(bool)>& done_cb) { | |
| 456 base::ScopedClosureRunner failure_handler(base::Bind(done_cb, false)); | |
| 457 | |
| 458 SkAutoLockPixels locker(input); | |
| 459 | |
| 460 // Sanity-check the captured bitmap. | |
| 461 if (input.empty() || | |
| 462 !input.readyToDraw() || | |
| 463 input.config() != SkBitmap::kARGB_8888_Config || | |
| 464 input.width() < 2 || input.height() < 2) { | |
| 465 DVLOG(1) << "input unacceptable (size=" | |
| 466 << input.getSize() | |
| 467 << ", ready=" << input.readyToDraw() | |
| 468 << ", config=" << input.config() << ')'; | |
| 469 return; | |
| 470 } | |
| 471 | |
| 472 // Sanity-check the output buffer. | |
| 473 if (output->format() != media::VideoFrame::I420) { | |
| 474 NOTREACHED(); | |
| 475 return; | |
| 476 } | |
| 477 | |
| 478 // Calculate the width and height of the content region in the |output|, based | |
| 479 // on the aspect ratio of |input|. | |
| 480 gfx::Rect region_in_frame = ComputeYV12LetterboxRegion( | |
| 481 output->coded_size(), gfx::Size(input.width(), input.height())); | |
| 482 | |
| 483 // Scale the bitmap to the required size, if necessary. | |
| 484 SkBitmap scaled_bitmap; | |
| 485 if (input.width() != region_in_frame.width() || | |
| 486 input.height() != region_in_frame.height()) { | |
| 487 | |
| 488 skia::ImageOperations::ResizeMethod method; | |
| 489 if (input.width() < region_in_frame.width() || | |
| 490 input.height() < region_in_frame.height()) { | |
| 491 // Avoid box filtering when magnifying, because it's actually | |
| 492 // nearest-neighbor. | |
| 493 method = skia::ImageOperations::RESIZE_HAMMING1; | |
| 494 } else { | |
| 495 method = skia::ImageOperations::RESIZE_BOX; | |
| 496 } | |
| 497 | |
| 498 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output.get(), "Scale"); | |
| 499 scaled_bitmap = skia::ImageOperations::Resize(input, method, | |
| 500 region_in_frame.width(), | |
| 501 region_in_frame.height()); | |
| 502 } else { | |
| 503 scaled_bitmap = input; | |
| 504 } | |
| 505 | |
| 506 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output.get(), "YUV"); | |
| 507 { | |
| 508 SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap); | |
| 509 | |
| 510 media::CopyRGBToVideoFrame( | |
| 511 reinterpret_cast<uint8*>(scaled_bitmap.getPixels()), | |
| 512 scaled_bitmap.rowBytes(), | |
| 513 region_in_frame, | |
| 514 output.get()); | |
| 515 } | |
| 516 | |
| 517 // The result is now ready. | |
| 518 ignore_result(failure_handler.Release()); | |
| 519 done_cb.Run(true); | |
| 520 } | |
| 521 | |
| 522 VideoFrameDeliveryLog::VideoFrameDeliveryLog() | |
| 523 : last_frame_rate_log_time_(), | |
| 524 count_frames_rendered_(0), | |
| 525 last_frame_number_(0) { | |
| 526 } | |
| 527 | |
| 528 void VideoFrameDeliveryLog::ChronicleFrameDelivery(int frame_number) { | |
| 529 // Log frame rate, if verbose logging is turned on. | |
| 530 static const base::TimeDelta kFrameRateLogInterval = | |
| 531 base::TimeDelta::FromSeconds(10); | |
| 532 const base::TimeTicks now = base::TimeTicks::Now(); | |
| 533 if (last_frame_rate_log_time_.is_null()) { | |
| 534 last_frame_rate_log_time_ = now; | |
| 535 count_frames_rendered_ = 0; | |
| 536 last_frame_number_ = frame_number; | |
| 537 } else { | |
| 538 ++count_frames_rendered_; | |
| 539 const base::TimeDelta elapsed = now - last_frame_rate_log_time_; | |
| 540 if (elapsed >= kFrameRateLogInterval) { | |
| 541 const double measured_fps = | |
| 542 count_frames_rendered_ / elapsed.InSecondsF(); | |
| 543 const int frames_elapsed = frame_number - last_frame_number_; | |
| 544 const int count_frames_dropped = frames_elapsed - count_frames_rendered_; | |
| 545 DCHECK_LE(0, count_frames_dropped); | |
| 546 UMA_HISTOGRAM_PERCENTAGE( | |
| 547 "TabCapture.FrameDropPercentage", | |
| 548 (count_frames_dropped * 100 + frames_elapsed / 2) / frames_elapsed); | |
| 549 UMA_HISTOGRAM_COUNTS( | |
| 550 "TabCapture.FrameRate", | |
| 551 static_cast<int>(measured_fps)); | |
| 552 VLOG(1) << "Current measured frame rate for " | |
| 553 << "WebContentsVideoCaptureDevice is " << measured_fps << " FPS."; | |
| 554 last_frame_rate_log_time_ = now; | |
| 555 count_frames_rendered_ = 0; | |
| 556 last_frame_number_ = frame_number; | |
| 557 } | |
| 558 } | |
| 559 } | |
| 560 | |
| 561 WebContentsCaptureMachine::WebContentsCaptureMachine(int render_process_id, | |
| 562 int render_view_id) | |
| 563 : initial_render_process_id_(render_process_id), | |
| 564 initial_render_view_id_(render_view_id), | |
| 565 fullscreen_widget_id_(MSG_ROUTING_NONE), | |
| 566 weak_ptr_factory_(this) {} | |
| 567 | |
| 568 WebContentsCaptureMachine::~WebContentsCaptureMachine() { | |
| 569 BrowserThread::PostBlockingPoolTask( | |
| 570 FROM_HERE, | |
| 571 base::Bind(&DeleteOnWorkerThread, base::Passed(&render_thread_), | |
| 572 base::Bind(&base::DoNothing))); | |
| 573 } | |
| 574 | |
| 575 bool WebContentsCaptureMachine::Start( | |
| 576 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) { | |
| 577 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 578 DCHECK(!started_); | |
| 579 | |
| 580 DCHECK(oracle_proxy.get()); | |
| 581 oracle_proxy_ = oracle_proxy; | |
| 582 | |
| 583 render_thread_.reset(new base::Thread("WebContentsVideo_RenderThread")); | |
| 584 if (!render_thread_->Start()) { | |
| 585 DVLOG(1) << "Failed to spawn render thread."; | |
| 586 render_thread_.reset(); | |
| 587 return false; | |
| 588 } | |
| 589 | |
| 590 if (!StartObservingWebContents()) { | |
| 591 DVLOG(1) << "Failed to observe web contents."; | |
| 592 render_thread_.reset(); | |
| 593 return false; | |
| 594 } | |
| 595 | |
| 596 started_ = true; | |
| 597 return true; | |
| 598 } | |
| 599 | |
| 600 void WebContentsCaptureMachine::Stop(const base::Closure& callback) { | |
| 601 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 602 subscription_.reset(); | |
| 603 if (web_contents()) { | |
| 604 web_contents()->DecrementCapturerCount(); | |
| 605 Observe(NULL); | |
| 606 } | |
| 607 | |
| 608 // Any callback that intend to use render_thread_ will not work after it is | |
| 609 // passed. | |
| 610 weak_ptr_factory_.InvalidateWeakPtrs(); | |
| 611 | |
| 612 // The render thread cannot be stopped on the UI thread, so post a message | |
| 613 // to the thread pool used for blocking operations. | |
| 614 BrowserThread::PostBlockingPoolTask( | |
| 615 FROM_HERE, | |
| 616 base::Bind(&DeleteOnWorkerThread, base::Passed(&render_thread_), | |
| 617 callback)); | |
| 618 | |
| 619 started_ = false; | |
| 620 } | |
| 621 | |
| 622 void WebContentsCaptureMachine::Capture( | |
| 623 const base::TimeTicks& start_time, | |
| 624 const scoped_refptr<media::VideoFrame>& target, | |
| 625 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& | |
| 626 deliver_frame_cb) { | |
| 627 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 628 | |
| 629 RenderWidgetHost* rwh = GetTarget(); | |
| 630 RenderWidgetHostViewPort* view = | |
| 631 rwh ? RenderWidgetHostViewPort::FromRWHV(rwh->GetView()) : NULL; | |
| 632 if (!view || !rwh) { | |
| 633 deliver_frame_cb.Run(base::TimeTicks(), false); | |
| 634 return; | |
| 635 } | |
| 636 | |
| 637 gfx::Size video_size = target->coded_size(); | |
| 638 gfx::Size view_size = view->GetViewBounds().size(); | |
| 639 gfx::Size fitted_size; | |
| 640 if (!view_size.IsEmpty()) { | |
| 641 fitted_size = ComputeYV12LetterboxRegion(video_size, view_size).size(); | |
| 642 } | |
| 643 if (view_size != last_view_size_) { | |
| 644 last_view_size_ = view_size; | |
| 645 | |
| 646 // Measure the number of kilopixels. | |
| 647 UMA_HISTOGRAM_COUNTS_10000( | |
| 648 "TabCapture.ViewChangeKiloPixels", | |
| 649 view_size.width() * view_size.height() / 1024); | |
| 650 } | |
| 651 | |
| 652 if (!view->IsSurfaceAvailableForCopy()) { | |
| 653 // Fallback to the more expensive renderer-side copy if the surface and | |
| 654 // backing store are not accessible. | |
| 655 rwh->GetSnapshotFromRenderer( | |
| 656 gfx::Rect(), | |
| 657 base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore, | |
| 658 weak_ptr_factory_.GetWeakPtr(), | |
| 659 start_time, target, deliver_frame_cb)); | |
| 660 } else if (view->CanCopyToVideoFrame()) { | |
| 661 view->CopyFromCompositingSurfaceToVideoFrame( | |
| 662 gfx::Rect(view_size), | |
| 663 target, | |
| 664 base::Bind(&WebContentsCaptureMachine:: | |
| 665 DidCopyFromCompositingSurfaceToVideoFrame, | |
| 666 weak_ptr_factory_.GetWeakPtr(), | |
| 667 start_time, deliver_frame_cb)); | |
| 668 } else { | |
| 669 rwh->CopyFromBackingStore( | |
| 670 gfx::Rect(), | |
| 671 fitted_size, // Size here is a request not always honored. | |
| 672 base::Bind(&WebContentsCaptureMachine::DidCopyFromBackingStore, | |
| 673 weak_ptr_factory_.GetWeakPtr(), | |
| 674 start_time, target, deliver_frame_cb)); | |
| 675 } | |
| 676 } | |
| 677 | |
| 678 bool WebContentsCaptureMachine::StartObservingWebContents() { | |
| 679 // Look-up the RenderViewHost and, from that, the WebContents that wraps it. | |
| 680 // If successful, begin observing the WebContents instance. | |
| 681 // | |
| 682 // Why this can be unsuccessful: The request for mirroring originates in a | |
| 683 // render process, and this request is based on the current RenderView | |
| 684 // associated with a tab. However, by the time we get up-and-running here, | |
| 685 // there have been multiple back-and-forth IPCs between processes, as well as | |
| 686 // a bit of indirection across threads. It's easily possible that, in the | |
| 687 // meantime, the original RenderView may have gone away. | |
| 688 RenderViewHost* const rvh = | |
| 689 RenderViewHost::FromID(initial_render_process_id_, | |
| 690 initial_render_view_id_); | |
| 691 DVLOG_IF(1, !rvh) << "RenderViewHost::FromID(" | |
| 692 << initial_render_process_id_ << ", " | |
| 693 << initial_render_view_id_ << ") returned NULL."; | |
| 694 Observe(rvh ? WebContents::FromRenderViewHost(rvh) : NULL); | |
| 695 | |
| 696 WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents()); | |
| 697 if (contents) { | |
| 698 contents->IncrementCapturerCount(oracle_proxy_->GetCaptureSize()); | |
| 699 fullscreen_widget_id_ = contents->GetFullscreenWidgetRoutingID(); | |
| 700 RenewFrameSubscription(); | |
| 701 return true; | |
| 702 } | |
| 703 | |
| 704 DVLOG(1) << "WebContents::FromRenderViewHost(" << rvh << ") returned NULL."; | |
| 705 return false; | |
| 706 } | |
| 707 | |
| 708 void WebContentsCaptureMachine::WebContentsDestroyed( | |
| 709 WebContents* web_contents) { | |
| 710 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 711 | |
| 712 subscription_.reset(); | |
| 713 web_contents->DecrementCapturerCount(); | |
| 714 oracle_proxy_->ReportError("WebContentsDestroyed()"); | |
| 715 } | |
| 716 | |
| 717 RenderWidgetHost* WebContentsCaptureMachine::GetTarget() { | |
| 718 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 719 if (!web_contents()) | |
| 720 return NULL; | |
| 721 | |
| 722 RenderWidgetHost* rwh = NULL; | |
| 723 if (fullscreen_widget_id_ != MSG_ROUTING_NONE) { | |
| 724 RenderProcessHost* process = web_contents()->GetRenderProcessHost(); | |
| 725 if (process) | |
| 726 rwh = RenderWidgetHost::FromID(process->GetID(), fullscreen_widget_id_); | |
| 727 } else { | |
| 728 rwh = web_contents()->GetRenderViewHost(); | |
| 729 } | |
| 730 | |
| 731 return rwh; | |
| 732 } | |
| 733 | |
| 734 void WebContentsCaptureMachine::DidCopyFromBackingStore( | |
| 735 const base::TimeTicks& start_time, | |
| 736 const scoped_refptr<media::VideoFrame>& target, | |
| 737 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& | |
| 738 deliver_frame_cb, | |
| 739 bool success, | |
| 740 const SkBitmap& bitmap) { | |
| 741 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 742 | |
| 743 base::TimeTicks now = base::TimeTicks::Now(); | |
| 744 DCHECK(render_thread_.get()); | |
| 745 if (success) { | |
| 746 UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now - start_time); | |
| 747 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", target.get(), | |
| 748 "Render"); | |
| 749 render_thread_->message_loop_proxy()->PostTask(FROM_HERE, base::Bind( | |
| 750 &RenderVideoFrame, bitmap, target, | |
| 751 base::Bind(deliver_frame_cb, start_time))); | |
| 752 } else { | |
| 753 // Capture can fail due to transient issues, so just skip this frame. | |
| 754 DVLOG(1) << "CopyFromBackingStore failed; skipping frame."; | |
| 755 deliver_frame_cb.Run(start_time, false); | |
| 756 } | |
| 757 } | |
| 758 | |
| 759 void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame( | |
| 760 const base::TimeTicks& start_time, | |
| 761 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& | |
| 762 deliver_frame_cb, | |
| 763 bool success) { | |
| 764 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 765 base::TimeTicks now = base::TimeTicks::Now(); | |
| 766 | |
| 767 if (success) { | |
| 768 UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeVideoFrame", now - start_time); | |
| 769 } else { | |
| 770 // Capture can fail due to transient issues, so just skip this frame. | |
| 771 DVLOG(1) << "CopyFromCompositingSurface failed; skipping frame."; | |
| 772 } | |
| 773 deliver_frame_cb.Run(start_time, success); | |
| 774 } | |
| 775 | |
| 776 void WebContentsCaptureMachine::RenewFrameSubscription() { | |
| 777 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 778 | |
| 779 // Always destroy the old subscription before creating a new one. | |
| 780 subscription_.reset(); | |
| 781 | |
| 782 RenderWidgetHost* rwh = GetTarget(); | |
| 783 if (!rwh || !rwh->GetView()) | |
| 784 return; | |
| 785 | |
| 786 subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_, | |
| 787 base::Bind(&WebContentsCaptureMachine::Capture, | |
| 788 weak_ptr_factory_.GetWeakPtr()))); | |
| 789 } | |
| 790 | |
| 791 } // namespace | |
| 792 | |
| 793 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice( | |
| 794 int render_process_id, int render_view_id) | |
| 795 : core_(new ContentVideoCaptureDeviceCore(scoped_ptr<VideoCaptureMachine>( | |
| 796 new WebContentsCaptureMachine(render_process_id, render_view_id)))) {} | |
| 797 | |
| 798 WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() { | |
| 799 DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying."; | |
| 800 } | |
| 801 | |
| 802 // static | |
| 803 media::VideoCaptureDevice* WebContentsVideoCaptureDevice::Create( | |
| 804 const std::string& device_id) { | |
| 805 // Parse device_id into render_process_id and render_view_id. | |
| 806 int render_process_id = -1; | |
| 807 int render_view_id = -1; | |
| 808 if (!WebContentsCaptureUtil::ExtractTabCaptureTarget( | |
| 809 device_id, &render_process_id, &render_view_id)) { | |
| 810 return NULL; | |
| 811 } | |
| 812 | |
| 813 return new WebContentsVideoCaptureDevice(render_process_id, render_view_id); | |
| 814 } | |
| 815 | |
| 816 void WebContentsVideoCaptureDevice::AllocateAndStart( | |
| 817 const media::VideoCaptureParams& params, | |
| 818 scoped_ptr<Client> client) { | |
| 819 DVLOG(1) << "Allocating " << params.requested_format.frame_size.ToString(); | |
| 820 core_->AllocateAndStart(params, client.Pass()); | |
| 821 } | |
| 822 | |
| 823 void WebContentsVideoCaptureDevice::StopAndDeAllocate() { | |
| 824 core_->StopAndDeAllocate(); | |
| 825 } | |
| 826 | |
| 827 } // namespace content | |
| OLD | NEW |