Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(544)

Side by Side Diff: content/browser/renderer_host/media/web_contents_video_capture_device.cc

Issue 12090109: Tab Capture: Backing store readbacks to YV12 VideoFrames. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Trim some trailing whitespace. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 // 4 //
5 // Implementation notes: This needs to work on a variety of hardware 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 6 // configurations where the speed of the CPU and GPU greatly affect overall
7 // performance. Therefore, the process of capturing has been split up into a 7 // performance. Therefore, the process of capturing has been split up into a
8 // pipeline of three stages. Each stage executes on its own thread: 8 // pipeline of three stages. Each stage executes on its own thread:
9 // 9 //
10 // 1. Capture: A bitmap is snapshotted/copied from the RenderView's backing 10 // 1. Capture: A bitmap is snapshotted/copied from the RenderView's backing
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
57 #include "base/time.h" 57 #include "base/time.h"
58 #include "content/browser/renderer_host/media/web_contents_capture_util.h" 58 #include "content/browser/renderer_host/media/web_contents_capture_util.h"
59 #include "content/browser/web_contents/web_contents_impl.h" 59 #include "content/browser/web_contents/web_contents_impl.h"
60 #include "content/public/browser/browser_thread.h" 60 #include "content/public/browser/browser_thread.h"
61 #include "content/public/browser/render_process_host.h" 61 #include "content/public/browser/render_process_host.h"
62 #include "content/public/browser/render_view_host.h" 62 #include "content/public/browser/render_view_host.h"
63 #include "content/public/browser/render_widget_host_view.h" 63 #include "content/public/browser/render_widget_host_view.h"
64 #include "content/public/browser/web_contents.h" 64 #include "content/public/browser/web_contents.h"
65 #include "content/public/browser/web_contents_observer.h" 65 #include "content/public/browser/web_contents_observer.h"
66 #include "media/base/bind_to_loop.h" 66 #include "media/base/bind_to_loop.h"
67 #include "media/base/video_frame.h"
67 #include "media/video/capture/video_capture_types.h" 68 #include "media/video/capture/video_capture_types.h"
68 #include "skia/ext/image_operations.h" 69 #include "skia/ext/image_operations.h"
69 #include "third_party/skia/include/core/SkBitmap.h" 70 #include "third_party/skia/include/core/SkBitmap.h"
70 #include "third_party/skia/include/core/SkColor.h" 71 #include "third_party/skia/include/core/SkColor.h"
71 #include "ui/gfx/rect.h" 72 #include "ui/gfx/rect.h"
73 #include "ui/gfx/skia_util.h"
72 74
73 // Used to self-trampoline invocation of methods to the approprate thread. This 75 // Used to self-trampoline invocation of methods to the approprate thread. This
74 // should be used sparingly, only when it's not clear which thread is invoking a 76 // should be used sparingly, only when it's not clear which thread is invoking a
75 // method. 77 // method.
76 #define ENSURE_INVOKED_ON_THREAD(thread, ...) { \ 78 #define ENSURE_INVOKED_ON_THREAD(thread, ...) { \
77 DCHECK(thread.IsRunning()); \ 79 DCHECK(thread.IsRunning()); \
78 if (MessageLoop::current() != thread.message_loop()) { \ 80 if (MessageLoop::current() != thread.message_loop()) { \
79 thread.message_loop()->PostTask(FROM_HERE, base::Bind(__VA_ARGS__)); \ 81 thread.message_loop()->PostTask(FROM_HERE, base::Bind(__VA_ARGS__)); \
80 return; \ 82 return; \
81 } \ 83 } \
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
132 fitted_width = std::max(kMinFrameWidth, MakeEven(fitted_width)); 134 fitted_width = std::max(kMinFrameWidth, MakeEven(fitted_width));
133 fitted_height = std::max(kMinFrameHeight, MakeEven(fitted_height)); 135 fitted_height = std::max(kMinFrameHeight, MakeEven(fitted_height));
134 136
135 *fitted_size = gfx::Size(fitted_width, fitted_height); 137 *fitted_size = gfx::Size(fitted_width, fitted_height);
136 } 138 }
137 139
138 // Keeps track of the RenderView to be sourced, and executes copying of the 140 // Keeps track of the RenderView to be sourced, and executes copying of the
139 // backing store on the UI BrowserThread. 141 // backing store on the UI BrowserThread.
140 class BackingStoreCopier : public WebContentsObserver { 142 class BackingStoreCopier : public WebContentsObserver {
141 public: 143 public:
142 // Result status and done callback used with StartCopy().
143 enum Result {
144 OK,
145 TRANSIENT_ERROR,
146 NO_SOURCE,
147 };
148 typedef base::Callback<void(Result result,
149 const SkBitmap& capture,
150 const base::Time& capture_time)> DoneCB;
151
152 BackingStoreCopier(int render_process_id, int render_view_id); 144 BackingStoreCopier(int render_process_id, int render_view_id);
153 145
154 // If non-NULL, use the given |override| to access the backing store. 146 // If non-NULL, use the given |override| to access the backing store.
155 // This is used for unit testing. 147 // This is used for unit testing.
156 void SetRenderWidgetHostForTesting(RenderWidgetHost* override); 148 void SetRenderWidgetHostForTesting(RenderWidgetHost* override);
157 149
158 // Starts the copy from the backing store. Must be run on the UI 150 // Starts the copy from the backing store. Must be run on the UI
159 // BrowserThread. |done_cb| is invoked with result status. When successful 151 // BrowserThread. Resulting frame is conveyed back to |consumer|.
160 // (OK), the bitmap of the capture is transferred to the callback along with 152 void StartCopy(CaptureMachine* consumer,
scherkus (not reviewing) 2013/02/05 22:40:44 nit: scoped-const-ref-& here + below
ncarter (slow) 2013/02/06 23:54:44 Done.
161 // the timestamp at which the capture was completed. 153 int frame_number,
162 void StartCopy(int frame_number, int desired_width, int desired_height, 154 int desired_width,
163 const DoneCB& done_cb); 155 int desired_height);
164 156
157 // content::WebContentsObserver implementation.
165 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE { 158 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE {
166 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
167 fullscreen_widget_id_ = routing_id; 160 fullscreen_widget_id_ = routing_id;
168 } 161 }
169 162
170 virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE { 163 virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE {
171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
172 DCHECK_EQ(fullscreen_widget_id_, routing_id); 165 DCHECK_EQ(fullscreen_widget_id_, routing_id);
173 fullscreen_widget_id_ = MSG_ROUTING_NONE; 166 fullscreen_widget_id_ = MSG_ROUTING_NONE;
174 } 167 }
175 168
176 private: 169 private:
177 void LookUpAndObserveWebContents(); 170 void LookUpAndObserveWebContents();
178 171
179 void CopyFromBackingStoreComplete(int frame_number, 172 // Response callback for RenderWidgetHost::CopyFromBackingStore.
180 const DoneCB& done_cb, 173 void DidCopyFromBackingStore(CaptureMachine* consumer,
181 bool success, 174 int frame_number,
182 const SkBitmap& result); 175 const base::Time& start_time,
176 bool success,
177 const SkBitmap& frame);
178
179 // Response callback for RenderWidgetHost::CopyFromBackingStoreToVideoFrame.
180 void DidCopyFromBackingStoreToVideoFrame(
181 CaptureMachine* consumer,
182 int frame_number,
183 const base::Time& start_time,
184 media::VideoFrame* frame);
183 185
184 // The "starting point" to find the capture source. 186 // The "starting point" to find the capture source.
185 const int render_process_id_; 187 const int render_process_id_;
186 const int render_view_id_; 188 const int render_view_id_;
187 189
188 // Routing ID of any active fullscreen render widget or MSG_ROUTING_NONE 190 // Routing ID of any active fullscreen render widget or MSG_ROUTING_NONE
189 // otherwise. 191 // otherwise.
190 int fullscreen_widget_id_; 192 int fullscreen_widget_id_;
191 193
192 // Last known RenderView size. 194 // Last known RenderView size.
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
242 class SynchronizedConsumer { 244 class SynchronizedConsumer {
243 public: 245 public:
244 SynchronizedConsumer(); 246 SynchronizedConsumer();
245 247
246 void SetConsumer(media::VideoCaptureDevice::EventHandler* consumer); 248 void SetConsumer(media::VideoCaptureDevice::EventHandler* consumer);
247 249
248 void OnFrameInfo(const media::VideoCaptureCapability& info); 250 void OnFrameInfo(const media::VideoCaptureCapability& info);
249 void OnError(); 251 void OnError();
250 void OnIncomingCapturedFrame(const uint8* pixels, int size, 252 void OnIncomingCapturedFrame(const uint8* pixels, int size,
251 const base::Time& timestamp); 253 const base::Time& timestamp);
254 void OnIncomingCapturedVideoFrame(media::VideoFrame* video_frame,
255 const base::Time& timestamp);
252 256
253 private: 257 private:
254 base::Lock consumer_lock_; 258 base::Lock consumer_lock_;
255 media::VideoCaptureDevice::EventHandler* wrapped_consumer_; 259 media::VideoCaptureDevice::EventHandler* wrapped_consumer_;
256 260
257 DISALLOW_COPY_AND_ASSIGN(SynchronizedConsumer); 261 DISALLOW_COPY_AND_ASSIGN(SynchronizedConsumer);
258 }; 262 };
259 263
260 // Delivers rendered video frames to a consumer on a separate thread. Also 264 // Delivers rendered video frames to a consumer on a separate thread. Also
261 // responsible for logging the effective frame rate. 265 // responsible for logging the effective frame rate.
262 class VideoFrameDeliverer { 266 class VideoFrameDeliverer {
263 public: 267 public:
264 explicit VideoFrameDeliverer(SynchronizedConsumer* consumer); 268 explicit VideoFrameDeliverer(SynchronizedConsumer* consumer);
265 269
270 // Deliver a fully rendered ARGB frame, using SkBitmap as a container.
271 // |done_cb| will be invoked after delivery is complete.
266 void Deliver(int frame_number, 272 void Deliver(int frame_number,
267 const SkBitmap& frame_buffer, const base::Time& frame_timestamp, 273 const SkBitmap& frame_buffer,
274 const base::Time& frame_timestamp,
268 const base::Closure& done_cb); 275 const base::Closure& done_cb);
276 // Deliver a fully rendered frame YV12 frame, using VideoFrame as a container.
277 // A refcount is taken on |frame| until delivery is complete.
278 void Deliver(int frame_number,
279 media::VideoFrame* frame,
280 const base::Time& frame_timestamp);
269 281
270 private: 282 private:
271 void DeliverOnDeliverThread(int frame_number, 283 void DeliverOnDeliverThread(int frame_number,
272 const SkBitmap& frame_buffer, 284 const SkBitmap& frame,
273 const base::Time& frame_timestamp, 285 const base::Time& frame_timestamp,
274 const base::Closure& done_cb); 286 const base::Closure& done_cb);
287 void DeliverVideoFrameOnDeliverThread(int frame_number,
288 media::VideoFrame* frame,
289 const base::Time& frame_timestamp);
290
291 // Treat |frame_number| as having been delivered, and update the
292 // frame rate statistics accordingly.
293 void ChronicleFrameDelivery(int frame_number);
275 294
276 base::Thread deliver_thread_; 295 base::Thread deliver_thread_;
277 SynchronizedConsumer* const consumer_; 296 SynchronizedConsumer* const consumer_;
278 297
279 // The following keep track of and log the effective frame rate (from the 298 // The following keep track of and log the effective frame rate (from the
280 // deliver stage) whenever verbose logging is turned on. 299 // deliver stage) whenever verbose logging is turned on.
281 base::Time last_frame_rate_log_time_; 300 base::Time last_frame_rate_log_time_;
282 int count_frames_rendered_; 301 int count_frames_rendered_;
283 int last_frame_number_; 302 int last_frame_number_;
284 303
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
316 fullscreen_widget_id_ = static_cast<WebContentsImpl*>(web_contents())-> 335 fullscreen_widget_id_ = static_cast<WebContentsImpl*>(web_contents())->
317 GetFullscreenWidgetRoutingID(); 336 GetFullscreenWidgetRoutingID();
318 } 337 }
319 } 338 }
320 339
321 void BackingStoreCopier::SetRenderWidgetHostForTesting( 340 void BackingStoreCopier::SetRenderWidgetHostForTesting(
322 RenderWidgetHost* override) { 341 RenderWidgetHost* override) {
323 rwh_for_testing_ = override; 342 rwh_for_testing_ = override;
324 } 343 }
325 344
326 void BackingStoreCopier::StartCopy(int frame_number,
327 int desired_width, int desired_height,
328 const DoneCB& done_cb) {
329 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
330
331 TRACE_EVENT_ASYNC_BEGIN1("mirroring", "Capture", this,
332 "frame_number", frame_number);
333
334 RenderWidgetHost* rwh;
335 if (rwh_for_testing_) {
336 rwh = rwh_for_testing_;
337 } else {
338 if (!web_contents()) { // No source yet.
339 LookUpAndObserveWebContents();
340 if (!web_contents()) { // No source ever.
341 done_cb.Run(NO_SOURCE, SkBitmap(), base::Time());
342 return;
343 }
344 }
345
346 if (fullscreen_widget_id_ != MSG_ROUTING_NONE) {
347 RenderProcessHost* process = web_contents()->GetRenderProcessHost();
348 rwh = process ? process->GetRenderWidgetHostByID(fullscreen_widget_id_)
349 : NULL;
350 } else {
351 rwh = web_contents()->GetRenderViewHost();
352 }
353
354 if (!rwh) {
355 // Transient failure state (e.g., a RenderView is being replaced).
356 done_cb.Run(TRANSIENT_ERROR, SkBitmap(), base::Time());
357 return;
358 }
359 }
360
361 gfx::Size fitted_size;
362 if (RenderWidgetHostView* const view = rwh->GetView()) {
363 const gfx::Size& view_size = view->GetViewBounds().size();
364 if (!view_size.IsEmpty()) {
365 CalculateFittedSize(view_size.width(), view_size.height(),
366 desired_width, desired_height,
367 &fitted_size);
368 }
369 if (view_size != last_view_size_) {
370 last_view_size_ = view_size;
371
372 // Measure the number of kilopixels.
373 UMA_HISTOGRAM_COUNTS_10000(
374 "TabCapture.ViewChangeKiloPixels",
375 view_size.width() * view_size.height() / 1024);
376 }
377 }
378
379 rwh->CopyFromBackingStore(
380 gfx::Rect(),
381 fitted_size,
382 base::Bind(&BackingStoreCopier::CopyFromBackingStoreComplete,
383 base::Unretained(this),
384 frame_number, done_cb));
385
386 // TODO(miu): When a tab is not visible to the user, rendering stops. For
387 // mirroring, however, it's important that rendering continues to happen.
388 }
389
390 void BackingStoreCopier::CopyFromBackingStoreComplete(
391 int frame_number,
392 const DoneCB& done_cb,
393 bool success,
394 const SkBitmap& frame) {
395 // Note: No restriction on which thread invokes this method but, currently,
396 // it's always the UI BrowserThread.
397 TRACE_EVENT_ASYNC_END1("mirroring", "Capture", this,
398 "frame_number", frame_number);
399 if (success) {
400 done_cb.Run(OK, frame, base::Time::Now());
401 } else {
402 // Capture can fail due to transient issues, so just skip this frame.
403 DVLOG(1) << "CopyFromBackingStore was not successful; skipping frame.";
404 done_cb.Run(TRANSIENT_ERROR, SkBitmap(), base::Time());
405 }
406 }
407
408 VideoFrameRenderer::VideoFrameRenderer() 345 VideoFrameRenderer::VideoFrameRenderer()
409 : render_thread_("WebContentsVideo_RenderThread") { 346 : render_thread_("WebContentsVideo_RenderThread") {
410 output_[0].in_use = false; 347 output_[0].in_use = false;
411 output_[1].in_use = false; 348 output_[1].in_use = false;
412 render_thread_.Start(); 349 render_thread_.Start();
413 } 350 }
414 351
415 void VideoFrameRenderer::Render(int frame_number, 352 void VideoFrameRenderer::Render(int frame_number,
416 const SkBitmap& capture, 353 const SkBitmap& capture,
417 int frame_width, int frame_height, 354 int frame_width, int frame_height,
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after
567 } 504 }
568 505
569 void SynchronizedConsumer::OnIncomingCapturedFrame( 506 void SynchronizedConsumer::OnIncomingCapturedFrame(
570 const uint8* pixels, int size, const base::Time& timestamp) { 507 const uint8* pixels, int size, const base::Time& timestamp) {
571 base::AutoLock guard(consumer_lock_); 508 base::AutoLock guard(consumer_lock_);
572 if (wrapped_consumer_) { 509 if (wrapped_consumer_) {
573 wrapped_consumer_->OnIncomingCapturedFrame(pixels, size, timestamp); 510 wrapped_consumer_->OnIncomingCapturedFrame(pixels, size, timestamp);
574 } 511 }
575 } 512 }
576 513
514 void SynchronizedConsumer::OnIncomingCapturedVideoFrame(
515 media::VideoFrame* video_frame,
516 const base::Time& timestamp) {
517 base::AutoLock guard(consumer_lock_);
518 if (wrapped_consumer_) {
519 wrapped_consumer_->OnIncomingCapturedVideoFrame(video_frame, timestamp);
520 }
521 }
522
577 VideoFrameDeliverer::VideoFrameDeliverer(SynchronizedConsumer* consumer) 523 VideoFrameDeliverer::VideoFrameDeliverer(SynchronizedConsumer* consumer)
578 : deliver_thread_("WebContentsVideo_DeliverThread"), 524 : deliver_thread_("WebContentsVideo_DeliverThread"),
579 consumer_(consumer), 525 consumer_(consumer),
580 last_frame_number_(0) { 526 last_frame_number_(0) {
581 DCHECK(consumer_); 527 DCHECK(consumer_);
582 deliver_thread_.Start(); 528 deliver_thread_.Start();
583 } 529 }
584 530
585 void VideoFrameDeliverer::Deliver( 531 void VideoFrameDeliverer::Deliver(
586 int frame_number, 532 int frame_number,
587 const SkBitmap& frame_buffer, const base::Time& frame_timestamp, 533 const SkBitmap& frame_buffer, const base::Time& frame_timestamp,
588 const base::Closure& done_cb) { 534 const base::Closure& done_cb) {
589 deliver_thread_.message_loop()->PostTask( 535 deliver_thread_.message_loop()->PostTask(
590 FROM_HERE, 536 FROM_HERE,
591 base::Bind(&VideoFrameDeliverer::DeliverOnDeliverThread, 537 base::Bind(&VideoFrameDeliverer::DeliverOnDeliverThread,
592 base::Unretained(this), 538 base::Unretained(this),
593 frame_number, base::ConstRef(frame_buffer), frame_timestamp, 539 frame_number, base::ConstRef(frame_buffer), frame_timestamp,
594 done_cb)); 540 done_cb));
595 } 541 }
596 542
543 void VideoFrameDeliverer::Deliver(
544 int frame_number,
545 media::VideoFrame* frame,
546 const base::Time& frame_timestamp) {
547 deliver_thread_.message_loop()->PostTask(
548 FROM_HERE,
549 base::Bind(&VideoFrameDeliverer::DeliverVideoFrameOnDeliverThread,
550 base::Unretained(this),
551 frame_number, make_scoped_refptr(frame), frame_timestamp));
scherkus (not reviewing) 2013/02/05 22:40:44 see my comments in video_capture_device.h about us
ncarter (slow) 2013/02/06 23:54:44 Done.
552 }
553
597 void VideoFrameDeliverer::DeliverOnDeliverThread( 554 void VideoFrameDeliverer::DeliverOnDeliverThread(
598 int frame_number, 555 int frame_number,
599 const SkBitmap& frame_buffer, const base::Time& frame_timestamp, 556 const SkBitmap& frame_buffer,
557 const base::Time& frame_timestamp,
600 const base::Closure& done_cb) { 558 const base::Closure& done_cb) {
601 DCHECK_EQ(deliver_thread_.message_loop(), MessageLoop::current()); 559 DCHECK_EQ(deliver_thread_.message_loop(), MessageLoop::current());
602 560
603 TRACE_EVENT1("mirroring", "DeliverFrame", "frame_number", frame_number); 561 TRACE_EVENT1("mirroring", "DeliverFrame", "frame_number", frame_number);
604 562
605 // Send the frame to the consumer. 563 // Send the frame to the consumer.
606 // Note: The consumer will do an ARGB-->YUV conversion in this callback, 564 // Note: The consumer will do an ARGB-->YUV conversion in this callback,
607 // blocking the current thread for a bit. 565 // blocking the current thread for a bit.
608 SkAutoLockPixels frame_buffer_locker(frame_buffer); 566 SkAutoLockPixels frame_buffer_locker(frame_buffer);
609 consumer_->OnIncomingCapturedFrame( 567 consumer_->OnIncomingCapturedFrame(
610 static_cast<const uint8*>(frame_buffer.getPixels()), 568 static_cast<const uint8*>(frame_buffer.getPixels()),
611 frame_buffer.getSize(), 569 frame_buffer.getSize(),
612 frame_timestamp); 570 frame_timestamp);
613 571
572 ChronicleFrameDelivery(frame_number);
573
574 // All done.
575 done_cb.Run();
576 }
577
578 void VideoFrameDeliverer::DeliverVideoFrameOnDeliverThread(
579 int frame_number,
580 media::VideoFrame* frame,
581 const base::Time& frame_timestamp) {
582 DCHECK_EQ(deliver_thread_.message_loop(), MessageLoop::current());
583
584 TRACE_EVENT1("mirroring", "DeliverFrame", "frame_number", frame_number);
585
586 // Send the frame to the consumer.
587 consumer_->OnIncomingCapturedVideoFrame(frame, frame_timestamp);
588
589 ChronicleFrameDelivery(frame_number);
590 }
591
592 void VideoFrameDeliverer::ChronicleFrameDelivery(int frame_number) {
614 // Log frame rate, if verbose logging is turned on. 593 // Log frame rate, if verbose logging is turned on.
615 static const base::TimeDelta kFrameRateLogInterval = 594 static const base::TimeDelta kFrameRateLogInterval =
616 base::TimeDelta::FromSeconds(10); 595 base::TimeDelta::FromSeconds(10);
617 const base::Time& now = base::Time::Now(); 596 const base::Time& now = base::Time::Now();
618 if (last_frame_rate_log_time_.is_null()) { 597 if (last_frame_rate_log_time_.is_null()) {
619 last_frame_rate_log_time_ = now; 598 last_frame_rate_log_time_ = now;
620 count_frames_rendered_ = 0; 599 count_frames_rendered_ = 0;
621 last_frame_number_ = frame_number; 600 last_frame_number_ = frame_number;
622 } else { 601 } else {
623 ++count_frames_rendered_; 602 ++count_frames_rendered_;
(...skipping 10 matching lines...) Expand all
634 UMA_HISTOGRAM_COUNTS( 613 UMA_HISTOGRAM_COUNTS(
635 "TabCapture.FrameRate", 614 "TabCapture.FrameRate",
636 static_cast<int>(measured_fps)); 615 static_cast<int>(measured_fps));
637 VLOG(1) << "Current measured frame rate for CaptureMachine@" << this 616 VLOG(1) << "Current measured frame rate for CaptureMachine@" << this
638 << " is " << measured_fps << " FPS."; 617 << " is " << measured_fps << " FPS.";
639 last_frame_rate_log_time_ = now; 618 last_frame_rate_log_time_ = now;
640 count_frames_rendered_ = 0; 619 count_frames_rendered_ = 0;
641 last_frame_number_ = frame_number; 620 last_frame_number_ = frame_number;
642 } 621 }
643 } 622 }
644
645 // All done.
646 done_cb.Run();
647 } 623 }
648 624
649 } // namespace 625 } // namespace
650 626
651 // The "meat" of the video capture implementation, which is a ref-counted class. 627 // The "meat" of the video capture implementation, which is a ref-counted class.
652 // Separating this from the "shell class" WebContentsVideoCaptureDevice allows 628 // Separating this from the "shell class" WebContentsVideoCaptureDevice allows
653 // safe destruction without needing to block any threads (e.g., the IO 629 // safe destruction without needing to block any threads (e.g., the IO
654 // BrowserThread). 630 // BrowserThread).
655 // 631 //
656 // CaptureMachine manages a simple state machine and the pipeline (see notes at 632 // CaptureMachine manages a simple state machine and the pipeline (see notes at
657 // top of this file). It times the start of successive captures and 633 // top of this file). It times the start of successive captures and
658 // facilitates the processing of each through the stages of the pipeline. 634 // facilitates the processing of each through the stages of the pipeline.
659 class CaptureMachine 635 class CaptureMachine
660 : public base::RefCountedThreadSafe<CaptureMachine, CaptureMachine> { 636 : public base::RefCountedThreadSafe<CaptureMachine, CaptureMachine> {
661 public: 637 public:
638 enum SnapshotError {
639 NO_SOURCE,
640 TRANSIENT_ERROR
641 };
642
662 CaptureMachine(int render_process_id, int render_view_id); 643 CaptureMachine(int render_process_id, int render_view_id);
663 644
664 // Sets the capture source to the given |override| for unit testing. 645 // Sets the capture source to the given |override| for unit testing.
665 // Also, |destroy_cb| will be invoked after CaptureMachine is fully destroyed 646 // Also, |destroy_cb| will be invoked after CaptureMachine is fully destroyed
666 // (to synchronize tear-down). 647 // (to synchronize tear-down).
667 void InitializeForTesting(RenderWidgetHost* override, 648 void InitializeForTesting(RenderWidgetHost* override,
668 const base::Closure& destroy_cb); 649 const base::Closure& destroy_cb);
669 650
670 // Synchronously sets/unsets the consumer. Pass |consumer| as NULL to remove 651 // Synchronously sets/unsets the consumer. Pass |consumer| as NULL to remove
671 // the reference to the consumer; then, once this method returns, 652 // the reference to the consumer; then, once this method returns,
672 // CaptureMachine will no longer invoke callbacks on the old consumer from any 653 // CaptureMachine will no longer invoke callbacks on the old consumer from any
673 // thread. 654 // thread.
674 void SetConsumer(media::VideoCaptureDevice::EventHandler* consumer); 655 void SetConsumer(media::VideoCaptureDevice::EventHandler* consumer);
675 656
676 // Asynchronous requests to change CaptureMachine state. 657 // Asynchronous requests to change CaptureMachine state.
677 void Allocate(int width, int height, int frame_rate); 658 void Allocate(int width, int height, int frame_rate);
678 void Start(); 659 void Start();
679 void Stop(); 660 void Stop();
680 void DeAllocate(); 661 void DeAllocate();
681 662
663 // Snapshot result events.
664 void OnSnapshotComplete(int frame_number,
665 const base::Time& start_time,
666 const base::TimeDelta& duration,
667 const SkBitmap& frame);
668 void OnSnapshotComplete(int frame_number,
669 const base::Time& start_time,
670 const base::TimeDelta& duration,
671 media::VideoFrame* frame);
672 void OnSnapshotFailed(SnapshotError error,
scherkus (not reviewing) 2013/02/05 22:40:44 hmm.... I believe one of my original aspirations f
ncarter (slow) 2013/02/06 23:54:44 I considered your suggested approach when writing
scherkus (not reviewing) 2013/02/07 18:25:03 SGTM
673 int frame_number);
674
682 private: 675 private:
683 friend class base::RefCountedThreadSafe<CaptureMachine, CaptureMachine>; 676 friend class base::RefCountedThreadSafe<CaptureMachine, CaptureMachine>;
684 677
685 // Flag indicating current state. 678 // Flag indicating current state.
686 enum State { 679 enum State {
687 kIdle, 680 kIdle,
688 kAllocated, 681 kAllocated,
689 kCapturing, 682 kCapturing,
690 kError, 683 kError,
691 kDestroyed 684 kDestroyed
692 }; 685 };
693 686
694 virtual ~CaptureMachine(); 687 virtual ~CaptureMachine();
695 688
696 void TransitionStateTo(State next_state); 689 void TransitionStateTo(State next_state);
697 690
698 // Stops capturing and notifies consumer_ of an error state. 691 // Stops capturing and notifies consumer_ of an error state.
699 void Error(); 692 void Error();
700 693
701 // Schedules the next frame capture off of the system clock, skipping frames 694 // Schedules the next frame capture off of the system clock, skipping frames
702 // to catch-up if necessary. 695 // to catch-up if necessary.
703 void ScheduleNextFrameCapture(); 696 void ScheduleNextFrameCapture();
704 697
705 // The glue between the pipeline stages. 698 // The glue between the pipeline stages.
706 void StartSnapshot(); 699 void StartSnapshot();
707 void SnapshotComplete(int frame_number, 700 bool FinishSnapshot();
708 const base::Time& start_time, 701 void SnapshotCompleteBitmap(int frame_number,
709 BackingStoreCopier::Result result, 702 const base::Time& start_time,
710 const SkBitmap& capture, 703 const base::TimeDelta& duration,
711 const base::Time& capture_time); 704 const SkBitmap& frame);
705 void SnapshotCompleteVideoFrame(int frame_number,
706 const base::Time& start_time,
707 const base::TimeDelta& duration,
708 media::VideoFrame* frame);
709 void SnapshotFailed(SnapshotError error, int frame_number);
710
712 void RenderComplete(int frame_number, 711 void RenderComplete(int frame_number,
713 const base::Time& capture_time, 712 const base::Time& capture_time,
714 const SkBitmap* frame_buffer); 713 const SkBitmap* frame_buffer);
715 void DeliverComplete(const SkBitmap* frame_buffer); 714 void DeliverComplete(const SkBitmap* frame_buffer);
716 715
717 // Specialized RefCounted traits for CaptureMachine, so that operator delete 716 // Specialized RefCounted traits for CaptureMachine, so that operator delete
718 // is called from an "outside" thread. See comments for "traits" in 717 // is called from an "outside" thread. See comments for "traits" in
719 // base/memory/ref_counted.h. 718 // base/memory/ref_counted.h.
720 static void Destruct(const CaptureMachine* x); 719 static void Destruct(const CaptureMachine* x);
721 static void DeleteFromOutsideThread(const CaptureMachine* x); 720 static void DeleteFromOutsideThread(const CaptureMachine* x);
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
789 if (width < kMinFrameWidth || height < kMinFrameHeight) { 788 if (width < kMinFrameWidth || height < kMinFrameHeight) {
790 DVLOG(1) << "invalid width (" << width << ") and/or height (" 789 DVLOG(1) << "invalid width (" << width << ") and/or height ("
791 << height << ")"; 790 << height << ")";
792 Error(); 791 Error();
793 return; 792 return;
794 } 793 }
795 794
796 settings_.width = width; 795 settings_.width = width;
797 settings_.height = height; 796 settings_.height = height;
798 settings_.frame_rate = frame_rate; 797 settings_.frame_rate = frame_rate;
798 // Sets the color format used by OnIncomingCapturedFrame.
scherkus (not reviewing) 2013/02/05 22:40:44 nit: () to function names
ncarter (slow) 2013/02/06 23:54:44 Done.
799 // Does not apply to OnIncomingCapturedVideoFrame.
799 settings_.color = media::VideoCaptureCapability::kARGB; 800 settings_.color = media::VideoCaptureCapability::kARGB;
800 settings_.expected_capture_delay = 0; 801 settings_.expected_capture_delay = 0;
801 settings_.interlaced = false; 802 settings_.interlaced = false;
802 803
803 capture_period_ = base::TimeDelta::FromMicroseconds( 804 capture_period_ = base::TimeDelta::FromMicroseconds(
804 1000000.0 / settings_.frame_rate + 0.5); 805 1000000.0 / settings_.frame_rate + 0.5);
805 806
806 consumer_.OnFrameInfo(settings_); 807 consumer_.OnFrameInfo(settings_);
807 808
808 TransitionStateTo(kAllocated); 809 TransitionStateTo(kAllocated);
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after
933 void CaptureMachine::StartSnapshot() { 934 void CaptureMachine::StartSnapshot() {
934 DCHECK_EQ(manager_thread_.message_loop(), MessageLoop::current()); 935 DCHECK_EQ(manager_thread_.message_loop(), MessageLoop::current());
935 936
936 if (state_ != kCapturing) { 937 if (state_ != kCapturing) {
937 return; 938 return;
938 } 939 }
939 940
940 if (!is_snapshotting_) { 941 if (!is_snapshotting_) {
941 is_snapshotting_ = true; 942 is_snapshotting_ = true;
942 943
943 const BackingStoreCopier::DoneCB& done_cb = 944 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
scherkus (not reviewing) 2013/02/05 22:40:44 nit: formatting + indentation is incorrect (techni
ncarter (slow) 2013/02/06 23:54:44 My reading of the style guide allows what I put or
scherkus (not reviewing) 2013/02/07 18:25:03 While most media-ish code uses [B1] (having more r
944 media::BindToLoop(manager_thread_.message_loop_proxy(),
945 base::Bind(&CaptureMachine::SnapshotComplete, this,
946 frame_number_, base::Time::Now()));
947 const base::Closure& start_cb =
948 base::Bind(&BackingStoreCopier::StartCopy, 945 base::Bind(&BackingStoreCopier::StartCopy,
949 base::Unretained(&copier_), 946 base::Unretained(&copier_), make_scoped_refptr(this),
950 frame_number_, settings_.width, settings_.height, done_cb); 947 frame_number_, settings_.width, settings_.height)
951 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, start_cb); 948 );
952 } 949 }
953 950
954 ScheduleNextFrameCapture(); 951 ScheduleNextFrameCapture();
955 } 952 }
956 953
957 void CaptureMachine::SnapshotComplete(int frame_number, 954 bool CaptureMachine::FinishSnapshot() {
958 const base::Time& start_time, 955 DCHECK_EQ(manager_thread_.message_loop(), MessageLoop::current());
959 BackingStoreCopier::Result result, 956
960 const SkBitmap& capture, 957 DCHECK(is_snapshotting_);
961 const base::Time& capture_time) { 958 is_snapshotting_ = false;
959
960 return state_ == kCapturing;
961 }
962
963 void CaptureMachine::OnSnapshotComplete(int frame_number,
964 const base::Time& start_time,
965 const base::TimeDelta& duration,
966 const SkBitmap& frame) {
967 manager_thread_.message_loop()->PostTask(FROM_HERE,
968 base::Bind(&CaptureMachine::SnapshotCompleteBitmap, this,
scherkus (not reviewing) 2013/02/05 22:40:44 ditto on formatting
ncarter (slow) 2013/02/06 23:54:44 Done.
969 frame_number, start_time, duration, frame));
scherkus (not reviewing) 2013/02/05 22:40:44 FYI the SkBitmap copy constructor says the pixels
ncarter (slow) 2013/02/06 23:54:44 The pixel-sharing behavior is intended. Think of S
970 }
971
972 void CaptureMachine::SnapshotCompleteBitmap(int frame_number,
973 const base::Time& start_time,
974 const base::TimeDelta& duration,
975 const SkBitmap& capture) {
976 if (!FinishSnapshot())
977 return;
978
979 UMA_HISTOGRAM_TIMES("TabCapture.SnapshotTime", duration);
980 if (num_renders_pending_ <= 1) {
981 ++num_renders_pending_;
982 renderer_.Render(
983 frame_number,
984 capture,
985 settings_.width, settings_.height,
986 media::BindToLoop(manager_thread_.message_loop_proxy(),
987 base::Bind(&CaptureMachine::RenderComplete, this,
988 frame_number, start_time + duration)));
989 }
990 }
991
992 void CaptureMachine::OnSnapshotComplete(int frame_number,
993 const base::Time& start_time,
994 const base::TimeDelta& duration,
995 media::VideoFrame* frame) {
996 manager_thread_.message_loop()->PostTask(FROM_HERE,
scherkus (not reviewing) 2013/02/05 22:40:44 ditto on formatting
ncarter (slow) 2013/02/06 23:54:44 Done.
997 base::Bind(&CaptureMachine::SnapshotCompleteVideoFrame, this,
998 frame_number, start_time, duration,
999 make_scoped_refptr(frame)));
1000 }
1001
1002 void CaptureMachine::SnapshotCompleteVideoFrame(
1003 int frame_number,
1004 const base::Time& start_time,
1005 const base::TimeDelta& duration,
1006 media::VideoFrame* frame) {
1007 if (!FinishSnapshot())
1008 return;
1009
1010 UMA_HISTOGRAM_TIMES("TabCapture.SnapshotTime", duration);
1011
1012 deliverer_.Deliver(frame_number, frame, start_time + duration);
1013 }
1014
1015 void CaptureMachine::OnSnapshotFailed(CaptureMachine::SnapshotError error,
1016 int frame_number) {
1017 manager_thread_.message_loop()->PostTask(FROM_HERE,
scherkus (not reviewing) 2013/02/05 22:40:44 ditto on formatting
ncarter (slow) 2013/02/06 23:54:44 Done.
1018 base::Bind(&CaptureMachine::SnapshotFailed, this, error, frame_number));
1019 }
1020
1021 void CaptureMachine::SnapshotFailed(CaptureMachine::SnapshotError error,
1022 int frame_number) {
962 DCHECK_EQ(manager_thread_.message_loop(), MessageLoop::current()); 1023 DCHECK_EQ(manager_thread_.message_loop(), MessageLoop::current());
963 1024
964 DCHECK(is_snapshotting_); 1025 DCHECK(is_snapshotting_);
965 is_snapshotting_ = false; 1026 is_snapshotting_ = false;
966 1027
967 if (state_ != kCapturing) { 1028 if (state_ != kCapturing) {
scherkus (not reviewing) 2013/02/05 22:40:44 you can use FinishSnapshot() here
ncarter (slow) 2013/02/06 23:54:44 Done.
968 return; 1029 return;
969 } 1030 }
970 1031
971 switch (result) { 1032 if (error == NO_SOURCE)
972 case BackingStoreCopier::OK: 1033 Error();
973 UMA_HISTOGRAM_TIMES("TabCapture.SnapshotTime",
974 base::Time::Now() - start_time);
975 if (num_renders_pending_ <= 1) {
976 ++num_renders_pending_;
977 DCHECK(!capture_time.is_null());
978 renderer_.Render(
979 frame_number,
980 capture,
981 settings_.width, settings_.height,
982 media::BindToLoop(manager_thread_.message_loop_proxy(),
983 base::Bind(&CaptureMachine::RenderComplete, this,
984 frame_number, capture_time)));
985 }
986 break;
987
988 case BackingStoreCopier::TRANSIENT_ERROR:
989 // Skip this frame.
990 break;
991
992 case BackingStoreCopier::NO_SOURCE:
993 DVLOG(1) << "no capture source";
994 Error();
995 break;
996 }
997 } 1034 }
998 1035
999 void CaptureMachine::RenderComplete(int frame_number, 1036 void CaptureMachine::RenderComplete(int frame_number,
1000 const base::Time& capture_time, 1037 const base::Time& capture_time,
1001 const SkBitmap* frame_buffer) { 1038 const SkBitmap* frame_buffer) {
1002 DCHECK_EQ(manager_thread_.message_loop(), MessageLoop::current()); 1039 DCHECK_EQ(manager_thread_.message_loop(), MessageLoop::current());
1003 1040
1004 --num_renders_pending_; 1041 --num_renders_pending_;
1005 DCHECK_LE(0, num_renders_pending_); 1042 DCHECK_LE(0, num_renders_pending_);
1006 1043
1007 if (state_ != kCapturing || !frame_buffer) { 1044 if (state_ != kCapturing || !frame_buffer) {
1008 return; 1045 return;
1009 } 1046 }
1010 1047
1011 DCHECK(!capture_time.is_null()); 1048 DCHECK(!capture_time.is_null());
1012 DCHECK(frame_buffer); 1049 DCHECK(frame_buffer);
1013 deliverer_.Deliver( 1050 deliverer_.Deliver(
1014 frame_number, *frame_buffer, capture_time, 1051 frame_number, *frame_buffer, capture_time,
1015 base::Bind(&CaptureMachine::DeliverComplete, this, frame_buffer)); 1052 base::Bind(&CaptureMachine::DeliverComplete, this, frame_buffer));
1016 } 1053 }
1017 1054
1018 void CaptureMachine::DeliverComplete(const SkBitmap* frame_buffer) { 1055 void CaptureMachine::DeliverComplete(const SkBitmap* frame_buffer) {
1019 renderer_.Release(frame_buffer); 1056 renderer_.Release(frame_buffer);
1020 } 1057 }
1021 1058
1059 void BackingStoreCopier::StartCopy(CaptureMachine* consumer,
1060 int frame_number,
1061 int desired_width,
1062 int desired_height) {
1063 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1064
1065 RenderWidgetHost* rwh;
1066 if (rwh_for_testing_) {
1067 rwh = rwh_for_testing_;
1068 } else {
1069 if (!web_contents()) { // No source yet.
1070 LookUpAndObserveWebContents();
1071 if (!web_contents()) { // No source ever.
1072 consumer->OnSnapshotFailed(CaptureMachine::NO_SOURCE, frame_number);
1073 return;
1074 }
1075 }
1076
1077 if (fullscreen_widget_id_ != MSG_ROUTING_NONE) {
1078 RenderProcessHost* process = web_contents()->GetRenderProcessHost();
1079 rwh = process ? process->GetRenderWidgetHostByID(fullscreen_widget_id_)
1080 : NULL;
1081 } else {
1082 rwh = web_contents()->GetRenderViewHost();
1083 }
1084
1085 if (!rwh) {
1086 // Transient failure state (e.g., a RenderView is being replaced).
1087 consumer->OnSnapshotFailed(CaptureMachine::TRANSIENT_ERROR, frame_number);
1088 return;
1089 }
1090 }
1091
1092 gfx::Size fitted_size;
1093 if (RenderWidgetHostView* const view = rwh->GetView()) {
1094 const gfx::Size& view_size = view->GetViewBounds().size();
1095 if (!view_size.IsEmpty()) {
1096 CalculateFittedSize(view_size.width(), view_size.height(),
1097 desired_width, desired_height,
1098 &fitted_size);
1099 }
1100 if (view_size != last_view_size_) {
1101 last_view_size_ = view_size;
1102
1103 // Measure the number of kilopixels.
1104 UMA_HISTOGRAM_COUNTS_10000(
1105 "TabCapture.ViewChangeKiloPixels",
1106 view_size.width() * view_size.height() / 1024);
1107 }
1108 }
1109
1110 TRACE_EVENT_ASYNC_BEGIN1("mirroring", "Capture", this,
1111 "frame_number", frame_number);
1112 if (rwh->CanCopyToVideoFrame()) {
1113 rwh->CopyFromBackingStoreToVideoFrame(
1114 gfx::Rect(),
1115 gfx::Size(desired_width, desired_height),
1116 base::Bind(&BackingStoreCopier::DidCopyFromBackingStoreToVideoFrame,
1117 base::Unretained(this), make_scoped_refptr(consumer),
1118 frame_number, base::Time::Now()));
1119 } else {
1120 rwh->CopyFromBackingStore(
1121 gfx::Rect(),
1122 fitted_size,
1123 base::Bind(&BackingStoreCopier::DidCopyFromBackingStore,
1124 base::Unretained(this), make_scoped_refptr(consumer),
1125 frame_number, base::Time::Now()));
1126 }
1127 // TODO(miu): When a tab is not visible to the user, rendering stops. For
1128 // mirroring, however, it's important that rendering continues to happen.
1129 }
1130
1131 void BackingStoreCopier::DidCopyFromBackingStore(
1132 CaptureMachine* consumer,
1133 int frame_number,
1134 const base::Time& start_time,
1135 bool success,
1136 const SkBitmap& frame) {
1137 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1138 // Note: No restriction on which thread invokes this method but, currently,
1139 // it's always the UI BrowserThread.
1140 TRACE_EVENT_ASYNC_END1("mirroring", "Capture", this,
1141 "frame_number", frame_number);
1142
1143 if (success) {
1144 consumer->OnSnapshotComplete(
1145 frame_number, start_time, base::Time::Now() - start_time, frame);
1146 } else {
1147 // Capture can fail due to transient issues, so just skip this frame.
1148 DVLOG(1) << "CopyFromBackingStore was not successful; skipping frame.";
1149 consumer->OnSnapshotFailed(CaptureMachine::TRANSIENT_ERROR, frame_number);
1150 }
1151 }
1152
1153 void BackingStoreCopier::DidCopyFromBackingStoreToVideoFrame(
1154 CaptureMachine* consumer,
1155 int frame_number,
1156 const base::Time& start_time,
1157 media::VideoFrame* frame) {
1158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1159 // Note: No restriction on which thread invokes this method but, currently,
1160 // it's always the UI BrowserThread.
1161 TRACE_EVENT_ASYNC_END1("mirroring", "Capture", this,
1162 "frame_number", frame_number);
1163
1164 if (frame && frame->format() != media::VideoFrame::INVALID &&
1165 frame->format() != media::VideoFrame::EMPTY) {
1166 consumer->OnSnapshotComplete(
1167 frame_number, start_time, base::Time::Now() - start_time, frame);
1168 } else {
1169 // Capture can fail due to transient issues, so just skip this frame.
1170 DVLOG(1) << "CopyFromBackingStoreToVideoFrame failure; skipping frame.";
1171 consumer->OnSnapshotFailed(
1172 CaptureMachine::TRANSIENT_ERROR, frame_number);
1173 }
1174 }
1175
1022 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice( 1176 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
1023 const media::VideoCaptureDevice::Name& name, 1177 const media::VideoCaptureDevice::Name& name,
1024 int render_process_id, int render_view_id) 1178 int render_process_id, int render_view_id)
1025 : device_name_(name), 1179 : device_name_(name),
1026 capturer_(new CaptureMachine(render_process_id, render_view_id)) {} 1180 capturer_(new CaptureMachine(render_process_id, render_view_id)) {}
1027 1181
1028 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice( 1182 WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
1029 RenderWidgetHost* test_source, const base::Closure& destroy_cb) 1183 RenderWidgetHost* test_source, const base::Closure& destroy_cb)
1030 : capturer_(new CaptureMachine(-1, -1)) { 1184 : capturer_(new CaptureMachine(-1, -1)) {
1031 device_name_.device_name = "WebContentsForTesting"; 1185 device_name_.device_name = "WebContentsForTesting";
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
1087 capturer_->SetConsumer(NULL); 1241 capturer_->SetConsumer(NULL);
1088 capturer_->DeAllocate(); 1242 capturer_->DeAllocate();
1089 } 1243 }
1090 1244
1091 const media::VideoCaptureDevice::Name& 1245 const media::VideoCaptureDevice::Name&
1092 WebContentsVideoCaptureDevice::device_name() { 1246 WebContentsVideoCaptureDevice::device_name() {
1093 return device_name_; 1247 return device_name_;
1094 } 1248 }
1095 1249
1096 } // namespace content 1250 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698