• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2013 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 #include "content/browser/media/capture/desktop_capture_device.h"
6 
7 #include "base/bind.h"
8 #include "base/location.h"
9 #include "base/logging.h"
10 #include "base/metrics/histogram.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/synchronization/lock.h"
13 #include "base/threading/thread.h"
14 #include "base/timer/timer.h"
15 #include "content/browser/media/capture/desktop_capture_device_uma_types.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/desktop_media_id.h"
18 #include "content/public/browser/power_save_blocker.h"
19 #include "media/base/video_util.h"
20 #include "third_party/libyuv/include/libyuv/scale_argb.h"
21 #include "third_party/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h"
22 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
23 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
24 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
25 #include "third_party/webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
26 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
27 #include "third_party/webrtc/modules/desktop_capture/window_capturer.h"
28 
29 namespace content {
30 
31 namespace {
32 
33 // Maximum CPU time percentage of a single core that can be consumed for desktop
34 // capturing. This means that on systems where screen scraping is slow we may
35 // need to capture at frame rate lower than requested. This is necessary to keep
36 // UI responsive.
37 const int kMaximumCpuConsumptionPercentage = 50;
38 
ComputeLetterboxRect(const webrtc::DesktopSize & max_size,const webrtc::DesktopSize & source_size)39 webrtc::DesktopRect ComputeLetterboxRect(
40     const webrtc::DesktopSize& max_size,
41     const webrtc::DesktopSize& source_size) {
42   gfx::Rect result = media::ComputeLetterboxRegion(
43       gfx::Rect(0, 0, max_size.width(), max_size.height()),
44       gfx::Size(source_size.width(), source_size.height()));
45   return webrtc::DesktopRect::MakeLTRB(
46       result.x(), result.y(), result.right(), result.bottom());
47 }
48 
49 }  // namespace
50 
51 class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback {
52  public:
53   Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
54        scoped_ptr<webrtc::DesktopCapturer> capturer,
55        DesktopMediaID::Type type);
56   virtual ~Core();
57 
58   // Implementation of VideoCaptureDevice methods.
59   void AllocateAndStart(const media::VideoCaptureParams& params,
60                         scoped_ptr<Client> client);
61 
62   void SetNotificationWindowId(gfx::NativeViewId window_id);
63 
64  private:
65 
66   // webrtc::DesktopCapturer::Callback interface
67   virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE;
68   virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE;
69 
70   // Chooses new output properties based on the supplied source size and the
71   // properties requested to Allocate(), and dispatches OnFrameInfo[Changed]
72   // notifications.
73   void RefreshCaptureFormat(const webrtc::DesktopSize& frame_size);
74 
75   // Method that is scheduled on |task_runner_| to be called on regular interval
76   // to capture a frame.
77   void OnCaptureTimer();
78 
79   // Captures a frame and schedules timer for the next one.
80   void CaptureFrameAndScheduleNext();
81 
82   // Captures a single frame.
83   void DoCapture();
84 
85   // Task runner used for capturing operations.
86   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
87 
88   // The underlying DesktopCapturer instance used to capture frames.
89   scoped_ptr<webrtc::DesktopCapturer> desktop_capturer_;
90 
91   // The device client which proxies device events to the controller. Accessed
92   // on the task_runner_ thread.
93   scoped_ptr<Client> client_;
94 
95   // Requested video capture format (width, height, frame rate, etc).
96   media::VideoCaptureParams requested_params_;
97 
98   // Actual video capture format being generated.
99   media::VideoCaptureFormat capture_format_;
100 
101   // Size of frame most recently captured from the source.
102   webrtc::DesktopSize previous_frame_size_;
103 
104   // DesktopFrame into which captured frames are down-scaled and/or letterboxed,
105   // depending upon the caller's requested capture capabilities. If frames can
106   // be returned to the caller directly then this is NULL.
107   scoped_ptr<webrtc::DesktopFrame> output_frame_;
108 
109   // Sub-rectangle of |output_frame_| into which the source will be scaled
110   // and/or letterboxed.
111   webrtc::DesktopRect output_rect_;
112 
113   // Timer used to capture the frame.
114   base::OneShotTimer<Core> capture_timer_;
115 
116   // True when waiting for |desktop_capturer_| to capture current frame.
117   bool capture_in_progress_;
118 
119   // True if the first capture call has returned. Used to log the first capture
120   // result.
121   bool first_capture_returned_;
122 
123   // The type of the capturer.
124   DesktopMediaID::Type capturer_type_;
125 
126   scoped_ptr<webrtc::BasicDesktopFrame> black_frame_;
127 
128   // TODO(jiayl): Remove power_save_blocker_ when there is an API to keep the
129   // screen from sleeping for the drive-by web.
130   scoped_ptr<PowerSaveBlocker> power_save_blocker_;
131 
132   DISALLOW_COPY_AND_ASSIGN(Core);
133 };
134 
Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner,scoped_ptr<webrtc::DesktopCapturer> capturer,DesktopMediaID::Type type)135 DesktopCaptureDevice::Core::Core(
136     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
137     scoped_ptr<webrtc::DesktopCapturer> capturer,
138     DesktopMediaID::Type type)
139     : task_runner_(task_runner),
140       desktop_capturer_(capturer.Pass()),
141       capture_in_progress_(false),
142       first_capture_returned_(false),
143       capturer_type_(type) {
144 }
145 
~Core()146 DesktopCaptureDevice::Core::~Core() {
147   DCHECK(task_runner_->BelongsToCurrentThread());
148   client_.reset();
149   output_frame_.reset();
150   previous_frame_size_.set(0, 0);
151   desktop_capturer_.reset();
152 }
153 
AllocateAndStart(const media::VideoCaptureParams & params,scoped_ptr<Client> client)154 void DesktopCaptureDevice::Core::AllocateAndStart(
155     const media::VideoCaptureParams& params,
156     scoped_ptr<Client> client) {
157   DCHECK(task_runner_->BelongsToCurrentThread());
158   DCHECK_GT(params.requested_format.frame_size.GetArea(), 0);
159   DCHECK_GT(params.requested_format.frame_rate, 0);
160   DCHECK(desktop_capturer_);
161   DCHECK(client.get());
162   DCHECK(!client_.get());
163 
164   client_ = client.Pass();
165   requested_params_ = params;
166 
167   capture_format_ = requested_params_.requested_format;
168 
169   // This capturer always outputs ARGB, non-interlaced.
170   capture_format_.pixel_format = media::PIXEL_FORMAT_ARGB;
171 
172   power_save_blocker_.reset(PowerSaveBlocker::Create(
173       PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep,
174       "DesktopCaptureDevice is running").release());
175 
176   desktop_capturer_->Start(this);
177 
178   CaptureFrameAndScheduleNext();
179 }
180 
SetNotificationWindowId(gfx::NativeViewId window_id)181 void DesktopCaptureDevice::Core::SetNotificationWindowId(
182     gfx::NativeViewId window_id) {
183   DCHECK(task_runner_->BelongsToCurrentThread());
184   DCHECK(window_id);
185   desktop_capturer_->SetExcludedWindow(window_id);
186 }
187 
188 webrtc::SharedMemory*
CreateSharedMemory(size_t size)189 DesktopCaptureDevice::Core::CreateSharedMemory(size_t size) {
190   return NULL;
191 }
192 
OnCaptureCompleted(webrtc::DesktopFrame * frame)193 void DesktopCaptureDevice::Core::OnCaptureCompleted(
194     webrtc::DesktopFrame* frame) {
195   DCHECK(task_runner_->BelongsToCurrentThread());
196   DCHECK(capture_in_progress_);
197 
198   if (!first_capture_returned_) {
199     first_capture_returned_ = true;
200     if (capturer_type_ == DesktopMediaID::TYPE_SCREEN) {
201       IncrementDesktopCaptureCounter(frame ? FIRST_SCREEN_CAPTURE_SUCCEEDED
202                                            : FIRST_SCREEN_CAPTURE_FAILED);
203     } else {
204       IncrementDesktopCaptureCounter(frame ? FIRST_WINDOW_CAPTURE_SUCCEEDED
205                                            : FIRST_WINDOW_CAPTURE_FAILED);
206     }
207   }
208 
209   capture_in_progress_ = false;
210 
211   if (!frame) {
212     std::string log("Failed to capture a frame.");
213     LOG(ERROR) << log;
214     client_->OnError(log);
215     return;
216   }
217 
218   if (!client_)
219     return;
220 
221   base::TimeDelta capture_time(
222       base::TimeDelta::FromMilliseconds(frame->capture_time_ms()));
223 
224   // The two UMA_ blocks must be put in its own scope since it creates a static
225   // variable which expected constant histogram name.
226   if (capturer_type_ == DesktopMediaID::TYPE_SCREEN) {
227     UMA_HISTOGRAM_TIMES(kUmaScreenCaptureTime, capture_time);
228   } else {
229     UMA_HISTOGRAM_TIMES(kUmaWindowCaptureTime, capture_time);
230   }
231 
232   scoped_ptr<webrtc::DesktopFrame> owned_frame(frame);
233 
234   // On OSX We receive a 1x1 frame when the shared window is minimized. It
235   // cannot be subsampled to I420 and will be dropped downstream. So we replace
236   // it with a black frame to avoid the video appearing frozen at the last
237   // frame.
238   if (frame->size().width() == 1 || frame->size().height() == 1) {
239     if (!black_frame_.get()) {
240       black_frame_.reset(
241           new webrtc::BasicDesktopFrame(
242               webrtc::DesktopSize(capture_format_.frame_size.width(),
243                                   capture_format_.frame_size.height())));
244       memset(black_frame_->data(),
245              0,
246              black_frame_->stride() * black_frame_->size().height());
247     }
248     owned_frame.reset();
249     frame = black_frame_.get();
250   }
251 
252   // Handle initial frame size and size changes.
253   RefreshCaptureFormat(frame->size());
254 
255   webrtc::DesktopSize output_size(capture_format_.frame_size.width(),
256                                   capture_format_.frame_size.height());
257   size_t output_bytes = output_size.width() * output_size.height() *
258       webrtc::DesktopFrame::kBytesPerPixel;
259   const uint8_t* output_data = NULL;
260   scoped_ptr<uint8_t[]> flipped_frame_buffer;
261 
262   if (frame->size().equals(output_size)) {
263     // If the captured frame matches the output size, we can return the pixel
264     // data directly, without scaling.
265     output_data = frame->data();
266 
267     // If the |frame| generated by the screen capturer is inverted then we need
268     // to flip |frame|.
269     // This happens only on a specific platform. Refer to crbug.com/306876.
270     if (frame->stride() < 0) {
271       int height = frame->size().height();
272       int bytes_per_row =
273           frame->size().width() * webrtc::DesktopFrame::kBytesPerPixel;
274       flipped_frame_buffer.reset(new uint8_t[output_bytes]);
275       uint8_t* dest = flipped_frame_buffer.get();
276       for (int row = 0; row < height; ++row) {
277         memcpy(dest, output_data, bytes_per_row);
278         dest += bytes_per_row;
279         output_data += frame->stride();
280       }
281       output_data = flipped_frame_buffer.get();
282     }
283   } else {
284     // Otherwise we need to down-scale and/or letterbox to the target format.
285 
286     // Allocate a buffer of the correct size to scale the frame into.
287     // |output_frame_| is cleared whenever |output_rect_| changes, so we don't
288     // need to worry about clearing out stale pixel data in letterboxed areas.
289     if (!output_frame_) {
290       output_frame_.reset(new webrtc::BasicDesktopFrame(output_size));
291       memset(output_frame_->data(), 0, output_bytes);
292     }
293     DCHECK(output_frame_->size().equals(output_size));
294 
295     // TODO(wez): Optimize this to scale only changed portions of the output,
296     // using ARGBScaleClip().
297     uint8_t* output_rect_data = output_frame_->data() +
298         output_frame_->stride() * output_rect_.top() +
299         webrtc::DesktopFrame::kBytesPerPixel * output_rect_.left();
300     libyuv::ARGBScale(frame->data(), frame->stride(),
301                       frame->size().width(), frame->size().height(),
302                       output_rect_data, output_frame_->stride(),
303                       output_rect_.width(), output_rect_.height(),
304                       libyuv::kFilterBilinear);
305     output_data = output_frame_->data();
306   }
307 
308   client_->OnIncomingCapturedData(
309       output_data, output_bytes, capture_format_, 0, base::TimeTicks::Now());
310 }
311 
RefreshCaptureFormat(const webrtc::DesktopSize & frame_size)312 void DesktopCaptureDevice::Core::RefreshCaptureFormat(
313     const webrtc::DesktopSize& frame_size) {
314   if (previous_frame_size_.equals(frame_size))
315     return;
316 
317   // Clear the output frame, if any, since it will either need resizing, or
318   // clearing of stale data in letterbox areas, anyway.
319   output_frame_.reset();
320 
321   if (previous_frame_size_.is_empty() ||
322       requested_params_.resolution_change_policy ==
323       media::RESOLUTION_POLICY_DYNAMIC_WITHIN_LIMIT) {
324     // If this is the first frame, or the receiver supports variable resolution
325     // then determine the output size by treating the requested width & height
326     // as maxima.
327     if (frame_size.width() >
328             requested_params_.requested_format.frame_size.width() ||
329         frame_size.height() >
330             requested_params_.requested_format.frame_size.height()) {
331       output_rect_ = ComputeLetterboxRect(
332           webrtc::DesktopSize(
333               requested_params_.requested_format.frame_size.width(),
334               requested_params_.requested_format.frame_size.height()),
335           frame_size);
336       output_rect_.Translate(-output_rect_.left(), -output_rect_.top());
337     } else {
338       output_rect_ = webrtc::DesktopRect::MakeSize(frame_size);
339     }
340     capture_format_.frame_size.SetSize(output_rect_.width(),
341                                        output_rect_.height());
342   } else {
343     // Otherwise the output frame size cannot change, so just scale and
344     // letterbox.
345     output_rect_ = ComputeLetterboxRect(
346         webrtc::DesktopSize(capture_format_.frame_size.width(),
347                             capture_format_.frame_size.height()),
348         frame_size);
349   }
350 
351   previous_frame_size_ = frame_size;
352 }
353 
OnCaptureTimer()354 void DesktopCaptureDevice::Core::OnCaptureTimer() {
355   DCHECK(task_runner_->BelongsToCurrentThread());
356 
357   if (!client_)
358     return;
359 
360   CaptureFrameAndScheduleNext();
361 }
362 
CaptureFrameAndScheduleNext()363 void DesktopCaptureDevice::Core::CaptureFrameAndScheduleNext() {
364   DCHECK(task_runner_->BelongsToCurrentThread());
365 
366   base::TimeTicks started_time = base::TimeTicks::Now();
367   DoCapture();
368   base::TimeDelta last_capture_duration = base::TimeTicks::Now() - started_time;
369 
370   // Limit frame-rate to reduce CPU consumption.
371   base::TimeDelta capture_period = std::max(
372     (last_capture_duration * 100) / kMaximumCpuConsumptionPercentage,
373     base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate);
374 
375   // Schedule a task for the next frame.
376   capture_timer_.Start(FROM_HERE, capture_period - last_capture_duration,
377                        this, &Core::OnCaptureTimer);
378 }
379 
DoCapture()380 void DesktopCaptureDevice::Core::DoCapture() {
381   DCHECK(task_runner_->BelongsToCurrentThread());
382   DCHECK(!capture_in_progress_);
383 
384   capture_in_progress_ = true;
385   desktop_capturer_->Capture(webrtc::DesktopRegion());
386 
387   // Currently only synchronous implementations of DesktopCapturer are
388   // supported.
389   DCHECK(!capture_in_progress_);
390 }
391 
392 // static
Create(const DesktopMediaID & source)393 scoped_ptr<media::VideoCaptureDevice> DesktopCaptureDevice::Create(
394     const DesktopMediaID& source) {
395   webrtc::DesktopCaptureOptions options =
396       webrtc::DesktopCaptureOptions::CreateDefault();
397   // Leave desktop effects enabled during WebRTC captures.
398   options.set_disable_effects(false);
399 
400   scoped_ptr<webrtc::DesktopCapturer> capturer;
401 
402   switch (source.type) {
403     case DesktopMediaID::TYPE_SCREEN: {
404 #if defined(OS_WIN)
405       options.set_allow_use_magnification_api(true);
406 #endif
407       scoped_ptr<webrtc::ScreenCapturer> screen_capturer(
408           webrtc::ScreenCapturer::Create(options));
409       if (screen_capturer && screen_capturer->SelectScreen(source.id)) {
410         capturer.reset(new webrtc::DesktopAndCursorComposer(
411             screen_capturer.release(),
412             webrtc::MouseCursorMonitor::CreateForScreen(options, source.id)));
413         IncrementDesktopCaptureCounter(SCREEN_CAPTURER_CREATED);
414       }
415       break;
416     }
417 
418     case DesktopMediaID::TYPE_WINDOW: {
419       scoped_ptr<webrtc::WindowCapturer> window_capturer(
420           webrtc::WindowCapturer::Create(options));
421       if (window_capturer && window_capturer->SelectWindow(source.id)) {
422         window_capturer->BringSelectedWindowToFront();
423         capturer.reset(new webrtc::DesktopAndCursorComposer(
424             window_capturer.release(),
425             webrtc::MouseCursorMonitor::CreateForWindow(options, source.id)));
426         IncrementDesktopCaptureCounter(WINDOW_CAPTURER_CREATED);
427       }
428       break;
429     }
430 
431     default: {
432       NOTREACHED();
433     }
434   }
435 
436   scoped_ptr<media::VideoCaptureDevice> result;
437   if (capturer)
438     result.reset(new DesktopCaptureDevice(capturer.Pass(), source.type));
439 
440   return result.Pass();
441 }
442 
~DesktopCaptureDevice()443 DesktopCaptureDevice::~DesktopCaptureDevice() {
444   DCHECK(!core_);
445 }
446 
AllocateAndStart(const media::VideoCaptureParams & params,scoped_ptr<Client> client)447 void DesktopCaptureDevice::AllocateAndStart(
448     const media::VideoCaptureParams& params,
449     scoped_ptr<Client> client) {
450   thread_.message_loop_proxy()->PostTask(
451       FROM_HERE,
452       base::Bind(&Core::AllocateAndStart, base::Unretained(core_.get()), params,
453                  base::Passed(&client)));
454 }
455 
StopAndDeAllocate()456 void DesktopCaptureDevice::StopAndDeAllocate() {
457   if (core_) {
458     thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, core_.release());
459     thread_.Stop();
460   }
461 }
462 
SetNotificationWindowId(gfx::NativeViewId window_id)463 void DesktopCaptureDevice::SetNotificationWindowId(
464     gfx::NativeViewId window_id) {
465   thread_.message_loop_proxy()->PostTask(
466       FROM_HERE,
467       base::Bind(&Core::SetNotificationWindowId, base::Unretained(core_.get()),
468                  window_id));
469 }
470 
DesktopCaptureDevice(scoped_ptr<webrtc::DesktopCapturer> capturer,DesktopMediaID::Type type)471 DesktopCaptureDevice::DesktopCaptureDevice(
472     scoped_ptr<webrtc::DesktopCapturer> capturer,
473     DesktopMediaID::Type type)
474     : thread_("desktopCaptureThread") {
475 #if defined(OS_WIN)
476   // On Windows the thread must be a UI thread.
477   base::MessageLoop::Type thread_type = base::MessageLoop::TYPE_UI;
478 #else
479   base::MessageLoop::Type thread_type = base::MessageLoop::TYPE_DEFAULT;
480 #endif
481 
482   thread_.StartWithOptions(base::Thread::Options(thread_type, 0));
483 
484   core_.reset(new Core(thread_.message_loop_proxy(), capturer.Pass(), type));
485 }
486 
487 }  // namespace content
488