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/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" 82 83 namespace content { 84 85 namespace { 86 87 // Compute a letterbox region, aligned to even coordinates. 88 gfx::Rect ComputeYV12LetterboxRegion(const gfx::Size& frame_size, 89 const gfx::Size& content_size) { 90 91 gfx::Rect result = media::ComputeLetterboxRegion(gfx::Rect(frame_size), 92 content_size); 93 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()))); 98 99 return result; 100 } 101 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); 109 } 110 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 { 114 public: 115 FrameSubscriber(VideoCaptureOracle::Event event_type, 116 const scoped_refptr<ThreadSafeCaptureOracle>& oracle) 117 : event_type_(event_type), 118 oracle_proxy_(oracle) {} 119 120 virtual bool ShouldCaptureFrame( 121 base::Time present_time, 122 scoped_refptr<media::VideoFrame>* storage, 123 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback* 124 deliver_frame_cb) OVERRIDE; 125 126 private: 127 const VideoCaptureOracle::Event event_type_; 128 scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_; 129 }; 130 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. 135 // 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. 142 // 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 { 147 public: 148 typedef base::Callback<void( 149 const base::Time&, 150 const scoped_refptr<media::VideoFrame>&, 151 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback&)> 152 CaptureCallback; 153 154 // Create a subscription. Whenever a manual capture is required, the 155 // subscription will invoke |capture_callback| on the UI thread to do the 156 // work. 157 ContentCaptureSubscription( 158 const RenderWidgetHost& source, 159 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy, 160 const CaptureCallback& capture_callback); 161 virtual ~ContentCaptureSubscription(); 162 163 // content::NotificationObserver implementation. 164 virtual void Observe(int type, 165 const content::NotificationSource& source, 166 const content::NotificationDetails& details) OVERRIDE; 167 168 private: 169 void OnTimer(); 170 171 const int render_process_id_; 172 const int render_view_id_; 173 174 FrameSubscriber paint_subscriber_; 175 FrameSubscriber timer_subscriber_; 176 content::NotificationRegistrar registrar_; 177 CaptureCallback capture_callback_; 178 base::Timer timer_; 179 180 DISALLOW_COPY_AND_ASSIGN(ContentCaptureSubscription); 181 }; 182 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. 187 // 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); 195 196 // Keeps track of the RenderView to be sourced, and executes copying of the 197 // backing store on the UI BrowserThread. 198 // 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> { 207 public: 208 WebContentsCaptureMachine(int render_process_id, int render_view_id); 209 virtual ~WebContentsCaptureMachine(); 210 211 // VideoCaptureMachine overrides. 212 virtual bool Start( 213 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) OVERRIDE; 214 virtual void Stop() OVERRIDE; 215 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|. 219 // 220 // This may be used as a ContentCaptureSubscription::CaptureCallback. 221 void Capture( 222 const base::Time& start_time, 223 const scoped_refptr<media::VideoFrame>& target, 224 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& 225 deliver_frame_cb); 226 227 // content::WebContentsObserver implementation. 228 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE { 229 fullscreen_widget_id_ = routing_id; 230 RenewFrameSubscription(); 231 } 232 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(); 237 } 238 239 virtual void RenderViewReady() OVERRIDE { 240 RenewFrameSubscription(); 241 } 242 243 virtual void AboutToNavigateRenderView(RenderViewHost* rvh) OVERRIDE { 244 RenewFrameSubscription(); 245 } 246 247 virtual void DidNavigateMainFrame( 248 const LoadCommittedDetails& details, 249 const FrameNavigateParams& params) OVERRIDE { 250 RenewFrameSubscription(); 251 } 252 253 virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE; 254 255 private: 256 // Starts observing the web contents, returning false if lookup fails. 257 bool StartObservingWebContents(); 258 259 // Helper function to determine the view that we are currently tracking. 260 RenderWidgetHost* GetTarget(); 261 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& 267 deliver_frame_cb, 268 bool success, 269 const SkBitmap& bitmap); 270 271 // Response callback for RWHVP::CopyFromCompositingSurfaceToVideoFrame(). 272 void DidCopyFromCompositingSurfaceToVideoFrame( 273 const base::Time& start_time, 274 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& 275 deliver_frame_cb, 276 bool success); 277 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 280 // attached views. 281 void RenewFrameSubscription(); 282 283 // Parameters saved in constructor. 284 const int initial_render_process_id_; 285 const int initial_render_view_id_; 286 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_; 290 291 // Makes all the decisions about which frames to copy, and how. 292 scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_; 293 294 // Routing ID of any active fullscreen render widget or MSG_ROUTING_NONE 295 // otherwise. 296 int fullscreen_widget_id_; 297 298 // Last known RenderView size. 299 gfx::Size last_view_size_; 300 301 // Responsible for forwarding events from the active RenderWidgetHost to the 302 // oracle, and initiating captures accordingly. 303 scoped_ptr<ContentCaptureSubscription> subscription_; 304 305 DISALLOW_COPY_AND_ASSIGN(WebContentsCaptureMachine); 306 }; 307 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 { 311 public: 312 VideoFrameDeliveryLog(); 313 314 // Treat |frame_number| as having been delivered, and update the 315 // frame rate statistics accordingly. 316 void ChronicleFrameDelivery(int frame_number); 317 318 private: 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_; 324 325 DISALLOW_COPY_AND_ASSIGN(VideoFrameDeliveryLog); 326 }; 327 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", 333 "instance", this); 334 335 ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb; 336 bool oracle_decision = oracle_proxy_->ObserveEventAndDecideCapture( 337 event_type_, present_time, storage, &capture_frame_cb); 338 339 *deliver_frame_cb = base::Bind(&InvokeCaptureFrameCallback, capture_frame_cb); 340 return oracle_decision; 341 } 342 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), 352 timer_(true, true) { 353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 354 355 RenderWidgetHostViewPort* view = 356 RenderWidgetHostViewPort::FromRWHV(source.GetView()); 357 358 // Subscribe to accelerated presents. These will be serviced directly by the 359 // oracle. 360 if (view && kAcceleratedSubscriberIsSupported) { 361 scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber( 362 new FrameSubscriber(VideoCaptureOracle::kCompositorUpdate, 363 oracle_proxy)); 364 view->BeginFrameSubscription(subscriber.Pass()); 365 } 366 367 // Subscribe to software paint events. This instance will service these by 368 // reflecting them back to the WebContentsCaptureMachine via 369 // |capture_callback|. 370 registrar_.Add( 371 this, content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, 372 Source<RenderWidgetHost>(&source)); 373 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))); 378 } 379 380 ContentCaptureSubscription::~ContentCaptureSubscription() { 381 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 382 if (kAcceleratedSubscriberIsSupported) { 383 RenderViewHost* source = RenderViewHost::FromID(render_process_id_, 384 render_view_id_); 385 if (source) { 386 RenderWidgetHostViewPort* view = 387 RenderWidgetHostViewPort::FromRWHV(source->GetView()); 388 if (view) 389 view->EndFrameSubscription(); 390 } 391 } 392 } 393 394 void ContentCaptureSubscription::Observe( 395 int type, 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); 400 401 RenderWidgetHostImpl* rwh = 402 RenderWidgetHostImpl::From(Source<RenderWidgetHost>(source).ptr()); 403 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())) 408 return; 409 410 TRACE_EVENT1("mirroring", "ContentCaptureSubscription::Observe", 411 "instance", this); 412 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, 418 &frame, 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)); 425 } 426 } 427 428 void ContentCaptureSubscription::OnTimer() { 429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 430 TRACE_EVENT0("mirroring", "ContentCaptureSubscription::OnTimer"); 431 432 scoped_refptr<media::VideoFrame> frame; 433 RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback deliver_frame_cb; 434 435 const base::Time start_time = base::Time::Now(); 436 if (timer_subscriber_.ShouldCaptureFrame(start_time, 437 &frame, 438 &deliver_frame_cb)) { 439 capture_callback_.Run(start_time, frame, deliver_frame_cb); 440 } 441 } 442 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)); 447 448 SkAutoLockPixels locker(input); 449 450 // Sanity-check the captured bitmap. 451 if (input.empty() || 452 !input.readyToDraw() || 453 input.config() != SkBitmap::kARGB_8888_Config || 454 input.width() < 2 || input.height() < 2) { 455 DVLOG(1) << "input unacceptable (size=" 456 << input.getSize() 457 << ", ready=" << input.readyToDraw() 458 << ", config=" << input.config() << ')'; 459 return; 460 } 461 462 // Sanity-check the output buffer. 463 if (output->format() != media::VideoFrame::I420) { 464 NOTREACHED(); 465 return; 466 } 467 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())); 472 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()) { 477 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 482 // nearest-neighbor. 483 method = skia::ImageOperations::RESIZE_HAMMING1; 484 } else { 485 method = skia::ImageOperations::RESIZE_BOX; 486 } 487 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()); 492 } else { 493 scaled_bitmap = input; 494 } 495 496 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", output.get(), "YUV"); 497 { 498 SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap); 499 500 media::CopyRGBToVideoFrame( 501 reinterpret_cast<uint8*>(scaled_bitmap.getPixels()), 502 scaled_bitmap.rowBytes(), 503 region_in_frame, 504 output.get()); 505 } 506 507 // The result is now ready. 508 ignore_result(failure_handler.Release()); 509 done_cb.Run(true); 510 } 511 512 VideoFrameDeliveryLog::VideoFrameDeliveryLog() 513 : last_frame_rate_log_time_(), 514 count_frames_rendered_(0), 515 last_frame_number_(0) { 516 } 517 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; 527 } else { 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; 547 } 548 } 549 } 550 551 WebContentsCaptureMachine::WebContentsCaptureMachine(int render_process_id, 552 int render_view_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) {} 557 558 WebContentsCaptureMachine::~WebContentsCaptureMachine() {} 559 560 bool WebContentsCaptureMachine::Start( 561 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) { 562 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 563 DCHECK(!started_); 564 565 DCHECK(oracle_proxy.get()); 566 oracle_proxy_ = oracle_proxy; 567 568 if (!render_thread_.Start()) { 569 DVLOG(1) << "Failed to spawn render thread."; 570 return false; 571 } 572 573 if (!StartObservingWebContents()) 574 return false; 575 576 started_ = true; 577 return true; 578 } 579 580 void WebContentsCaptureMachine::Stop() { 581 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 582 subscription_.reset(); 583 if (web_contents()) { 584 web_contents()->DecrementCapturerCount(); 585 Observe(NULL); 586 } 587 render_thread_.Stop(); 588 started_ = false; 589 } 590 591 void WebContentsCaptureMachine::Capture( 592 const base::Time& start_time, 593 const scoped_refptr<media::VideoFrame>& target, 594 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& 595 deliver_frame_cb) { 596 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 597 598 RenderWidgetHost* rwh = GetTarget(); 599 RenderWidgetHostViewPort* view = 600 rwh ? RenderWidgetHostViewPort::FromRWHV(rwh->GetView()) : NULL; 601 if (!view || !rwh) { 602 deliver_frame_cb.Run(base::Time(), false); 603 return; 604 } 605 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(); 611 } 612 if (view_size != last_view_size_) { 613 last_view_size_ = view_size; 614 615 // Measure the number of kilopixels. 616 UMA_HISTOGRAM_COUNTS_10000( 617 "TabCapture.ViewChangeKiloPixels", 618 view_size.width() * view_size.height() / 1024); 619 } 620 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( 625 gfx::Rect(), 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), 631 target, 632 base::Bind(&WebContentsCaptureMachine:: 633 DidCopyFromCompositingSurfaceToVideoFrame, 634 this->AsWeakPtr(), start_time, deliver_frame_cb)); 635 } else { 636 rwh->CopyFromBackingStore( 637 gfx::Rect(), 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)); 641 } 642 } 643 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. 647 // 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); 661 662 WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents()); 663 if (contents) { 664 contents->IncrementCapturerCount(); 665 fullscreen_widget_id_ = contents->GetFullscreenWidgetRoutingID(); 666 RenewFrameSubscription(); 667 return true; 668 } 669 670 DVLOG(1) << "WebContents::FromRenderViewHost(" << rvh << ") returned NULL."; 671 return false; 672 } 673 674 void WebContentsCaptureMachine::WebContentsDestroyed( 675 WebContents* web_contents) { 676 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 677 678 subscription_.reset(); 679 web_contents->DecrementCapturerCount(); 680 oracle_proxy_->ReportError(); 681 } 682 683 RenderWidgetHost* WebContentsCaptureMachine::GetTarget() { 684 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 685 if (!web_contents()) 686 return NULL; 687 688 RenderWidgetHost* rwh = NULL; 689 if (fullscreen_widget_id_ != MSG_ROUTING_NONE) { 690 RenderProcessHost* process = web_contents()->GetRenderProcessHost(); 691 if (process) 692 rwh = RenderWidgetHost::FromID(process->GetID(), fullscreen_widget_id_); 693 } else { 694 rwh = web_contents()->GetRenderViewHost(); 695 } 696 697 return rwh; 698 } 699 700 void WebContentsCaptureMachine::DidCopyFromBackingStore( 701 const base::Time& start_time, 702 const scoped_refptr<media::VideoFrame>& target, 703 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& 704 deliver_frame_cb, 705 bool success, 706 const SkBitmap& bitmap) { 707 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 708 709 base::Time now = base::Time::Now(); 710 if (success) { 711 UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeBitmap", now - start_time); 712 TRACE_EVENT_ASYNC_STEP_INTO0("mirroring", "Capture", target.get(), 713 "Render"); 714 render_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( 715 &RenderVideoFrame, bitmap, target, 716 base::Bind(deliver_frame_cb, start_time))); 717 } else { 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); 721 } 722 } 723 724 void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame( 725 const base::Time& start_time, 726 const RenderWidgetHostViewFrameSubscriber::DeliverFrameCallback& 727 deliver_frame_cb, 728 bool success) { 729 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 730 base::Time now = base::Time::Now(); 731 732 if (success) { 733 UMA_HISTOGRAM_TIMES("TabCapture.CopyTimeVideoFrame", now - start_time); 734 } else { 735 // Capture can fail due to transient issues, so just skip this frame. 736 DVLOG(1) << "CopyFromCompositingSurface failed; skipping frame."; 737 } 738 deliver_frame_cb.Run(start_time, success); 739 } 740 741 void WebContentsCaptureMachine::RenewFrameSubscription() { 742 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 743 744 // Always destroy the old subscription before creating a new one. 745 subscription_.reset(); 746 747 RenderWidgetHost* rwh = GetTarget(); 748 if (!rwh || !rwh->GetView()) 749 return; 750 751 subscription_.reset(new ContentCaptureSubscription(*rwh, oracle_proxy_, 752 base::Bind(&WebContentsCaptureMachine::Capture, this->AsWeakPtr()))); 753 } 754 755 } // namespace 756 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)))) {} 761 762 WebContentsVideoCaptureDevice::~WebContentsVideoCaptureDevice() { 763 DVLOG(2) << "WebContentsVideoCaptureDevice@" << this << " destroying."; 764 } 765 766 // static 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)) { 774 return NULL; 775 } 776 777 return new WebContentsVideoCaptureDevice(render_process_id, render_view_id); 778 } 779 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()); 785 } 786 787 void WebContentsVideoCaptureDevice::StopAndDeAllocate() { 788 impl_->StopAndDeAllocate(); 789 } 790 791 } // namespace content 792