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.
5 #include "content/browser/renderer_host/media/desktop_capture_device.h"
8 #include "base/location.h"
9 #include "base/logging.h"
10 #include "base/sequenced_task_runner.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/synchronization/lock.h"
13 #include "base/threading/sequenced_worker_pool.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/desktop_media_id.h"
16 #include "media/base/video_util.h"
17 #include "third_party/libyuv/include/libyuv/scale_argb.h"
18 #include "third_party/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h"
19 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
20 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
21 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
22 #include "third_party/webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
23 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
24 #include "third_party/webrtc/modules/desktop_capture/window_capturer.h"
30 // Maximum CPU time percentage of a single core that can be consumed for desktop
31 // capturing. This means that on systems where screen scraping is slow we may
32 // need to capture at frame rate lower than requested. This is necessary to keep
34 const int kMaximumCpuConsumptionPercentage
= 50;
36 webrtc::DesktopRect
ComputeLetterboxRect(
37 const webrtc::DesktopSize
& max_size
,
38 const webrtc::DesktopSize
& source_size
) {
39 gfx::Rect result
= media::ComputeLetterboxRegion(
40 gfx::Rect(0, 0, max_size
.width(), max_size
.height()),
41 gfx::Size(source_size
.width(), source_size
.height()));
42 return webrtc::DesktopRect::MakeLTRB(
43 result
.x(), result
.y(), result
.right(), result
.bottom());
48 class DesktopCaptureDevice::Core
49 : public base::RefCountedThreadSafe
<Core
>,
50 public webrtc::DesktopCapturer::Callback
{
52 Core(scoped_refptr
<base::SequencedTaskRunner
> task_runner
,
53 scoped_ptr
<webrtc::DesktopCapturer
> capturer
);
55 // Implementation of VideoCaptureDevice methods.
56 void AllocateAndStart(const media::VideoCaptureParams
& params
,
57 scoped_ptr
<Client
> client
);
58 void StopAndDeAllocate();
61 friend class base::RefCountedThreadSafe
<Core
>;
64 // webrtc::DesktopCapturer::Callback interface
65 virtual webrtc::SharedMemory
* CreateSharedMemory(size_t size
) OVERRIDE
;
66 virtual void OnCaptureCompleted(webrtc::DesktopFrame
* frame
) OVERRIDE
;
68 // Helper methods that run on the |task_runner_|. Posted from the
69 // corresponding public methods.
70 void DoAllocateAndStart(const media::VideoCaptureParams
& params
,
71 scoped_ptr
<Client
> client
);
72 void DoStopAndDeAllocate();
74 // Chooses new output properties based on the supplied source size and the
75 // properties requested to Allocate(), and dispatches OnFrameInfo[Changed]
77 void RefreshCaptureFormat(const webrtc::DesktopSize
& frame_size
);
79 // Method that is scheduled on |task_runner_| to be called on regular interval
80 // to capture a frame.
81 void OnCaptureTimer();
83 // Captures a frame and schedules timer for the next one.
84 void CaptureFrameAndScheduleNext();
86 // Captures a single frame.
89 // Task runner used for capturing operations.
90 scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
92 // The underlying DesktopCapturer instance used to capture frames.
93 scoped_ptr
<webrtc::DesktopCapturer
> desktop_capturer_
;
95 // The device client which proxies device events to the controller. Accessed
96 // on the task_runner_ thread.
97 scoped_ptr
<Client
> client_
;
99 // Requested video capture format (width, height, frame rate, etc).
100 media::VideoCaptureParams requested_params_
;
102 // Actual video capture format being generated.
103 media::VideoCaptureFormat capture_format_
;
105 // Size of frame most recently captured from the source.
106 webrtc::DesktopSize previous_frame_size_
;
108 // DesktopFrame into which captured frames are down-scaled and/or letterboxed,
109 // depending upon the caller's requested capture capabilities. If frames can
110 // be returned to the caller directly then this is NULL.
111 scoped_ptr
<webrtc::DesktopFrame
> output_frame_
;
113 // Sub-rectangle of |output_frame_| into which the source will be scaled
114 // and/or letterboxed.
115 webrtc::DesktopRect output_rect_
;
117 // True when we have delayed OnCaptureTimer() task posted on
119 bool capture_task_posted_
;
121 // True when waiting for |desktop_capturer_| to capture current frame.
122 bool capture_in_progress_
;
124 DISALLOW_COPY_AND_ASSIGN(Core
);
127 DesktopCaptureDevice::Core::Core(
128 scoped_refptr
<base::SequencedTaskRunner
> task_runner
,
129 scoped_ptr
<webrtc::DesktopCapturer
> capturer
)
130 : task_runner_(task_runner
),
131 desktop_capturer_(capturer
.Pass()),
132 capture_task_posted_(false),
133 capture_in_progress_(false) {}
135 DesktopCaptureDevice::Core::~Core() {
138 void DesktopCaptureDevice::Core::AllocateAndStart(
139 const media::VideoCaptureParams
& params
,
140 scoped_ptr
<Client
> client
) {
141 DCHECK_GT(params
.requested_format
.frame_size
.GetArea(), 0);
142 DCHECK_GT(params
.requested_format
.frame_rate
, 0);
144 task_runner_
->PostTask(
147 &Core::DoAllocateAndStart
, this, params
, base::Passed(&client
)));
150 void DesktopCaptureDevice::Core::StopAndDeAllocate() {
151 task_runner_
->PostTask(FROM_HERE
,
152 base::Bind(&Core::DoStopAndDeAllocate
, this));
155 webrtc::SharedMemory
*
156 DesktopCaptureDevice::Core::CreateSharedMemory(size_t size
) {
160 void DesktopCaptureDevice::Core::OnCaptureCompleted(
161 webrtc::DesktopFrame
* frame
) {
162 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
163 DCHECK(capture_in_progress_
);
165 capture_in_progress_
= false;
168 LOG(ERROR
) << "Failed to capture a frame.";
176 scoped_ptr
<webrtc::DesktopFrame
> owned_frame(frame
);
178 // Handle initial frame size and size changes.
179 RefreshCaptureFormat(frame
->size());
181 webrtc::DesktopSize
output_size(capture_format_
.frame_size
.width(),
182 capture_format_
.frame_size
.height());
183 size_t output_bytes
= output_size
.width() * output_size
.height() *
184 webrtc::DesktopFrame::kBytesPerPixel
;
185 const uint8_t* output_data
= NULL
;
186 scoped_ptr
<uint8_t[]> flipped_frame_buffer
;
188 if (frame
->size().equals(output_size
)) {
189 // If the captured frame matches the output size, we can return the pixel
190 // data directly, without scaling.
191 output_data
= frame
->data();
193 // If the |frame| generated by the screen capturer is inverted then we need
195 // This happens only on a specific platform. Refer to crbug.com/306876.
196 if (frame
->stride() < 0) {
197 int height
= frame
->size().height();
199 frame
->size().width() * webrtc::DesktopFrame::kBytesPerPixel
;
200 flipped_frame_buffer
.reset(new uint8_t[output_bytes
]);
201 uint8_t* dest
= flipped_frame_buffer
.get();
202 for (int row
= 0; row
< height
; ++row
) {
203 memcpy(dest
, output_data
, bytes_per_row
);
204 dest
+= bytes_per_row
;
205 output_data
+= frame
->stride();
207 output_data
= flipped_frame_buffer
.get();
210 // Otherwise we need to down-scale and/or letterbox to the target format.
212 // Allocate a buffer of the correct size to scale the frame into.
213 // |output_frame_| is cleared whenever |output_rect_| changes, so we don't
214 // need to worry about clearing out stale pixel data in letterboxed areas.
215 if (!output_frame_
) {
216 output_frame_
.reset(new webrtc::BasicDesktopFrame(output_size
));
217 memset(output_frame_
->data(), 0, output_bytes
);
219 DCHECK(output_frame_
->size().equals(output_size
));
221 // TODO(wez): Optimize this to scale only changed portions of the output,
222 // using ARGBScaleClip().
223 uint8_t* output_rect_data
= output_frame_
->data() +
224 output_frame_
->stride() * output_rect_
.top() +
225 webrtc::DesktopFrame::kBytesPerPixel
* output_rect_
.left();
226 libyuv::ARGBScale(frame
->data(), frame
->stride(),
227 frame
->size().width(), frame
->size().height(),
228 output_rect_data
, output_frame_
->stride(),
229 output_rect_
.width(), output_rect_
.height(),
230 libyuv::kFilterBilinear
);
231 output_data
= output_frame_
->data();
234 client_
->OnIncomingCapturedFrame(
235 output_data
, output_bytes
, base::TimeTicks::Now(), 0, capture_format_
);
238 void DesktopCaptureDevice::Core::DoAllocateAndStart(
239 const media::VideoCaptureParams
& params
,
240 scoped_ptr
<Client
> client
) {
241 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
242 DCHECK(desktop_capturer_
);
243 DCHECK(client
.get());
244 DCHECK(!client_
.get());
246 client_
= client
.Pass();
247 requested_params_
= params
;
249 capture_format_
= requested_params_
.requested_format
;
251 // This capturer always outputs ARGB, non-interlaced.
252 capture_format_
.pixel_format
= media::PIXEL_FORMAT_ARGB
;
254 desktop_capturer_
->Start(this);
256 CaptureFrameAndScheduleNext();
259 void DesktopCaptureDevice::Core::DoStopAndDeAllocate() {
260 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
262 output_frame_
.reset();
263 previous_frame_size_
.set(0, 0);
264 desktop_capturer_
.reset();
267 void DesktopCaptureDevice::Core::RefreshCaptureFormat(
268 const webrtc::DesktopSize
& frame_size
) {
269 if (previous_frame_size_
.equals(frame_size
))
272 // Clear the output frame, if any, since it will either need resizing, or
273 // clearing of stale data in letterbox areas, anyway.
274 output_frame_
.reset();
276 if (previous_frame_size_
.is_empty() ||
277 requested_params_
.allow_resolution_change
) {
278 // If this is the first frame, or the receiver supports variable resolution
279 // then determine the output size by treating the requested width & height
281 if (frame_size
.width() >
282 requested_params_
.requested_format
.frame_size
.width() ||
283 frame_size
.height() >
284 requested_params_
.requested_format
.frame_size
.height()) {
285 output_rect_
= ComputeLetterboxRect(
287 requested_params_
.requested_format
.frame_size
.width(),
288 requested_params_
.requested_format
.frame_size
.height()),
290 output_rect_
.Translate(-output_rect_
.left(), -output_rect_
.top());
292 output_rect_
= webrtc::DesktopRect::MakeSize(frame_size
);
294 capture_format_
.frame_size
.SetSize(output_rect_
.width(),
295 output_rect_
.height());
297 // Otherwise the output frame size cannot change, so just scale and
299 output_rect_
= ComputeLetterboxRect(
300 webrtc::DesktopSize(capture_format_
.frame_size
.width(),
301 capture_format_
.frame_size
.height()),
305 previous_frame_size_
= frame_size
;
308 void DesktopCaptureDevice::Core::OnCaptureTimer() {
309 DCHECK(capture_task_posted_
);
310 capture_task_posted_
= false;
315 CaptureFrameAndScheduleNext();
318 void DesktopCaptureDevice::Core::CaptureFrameAndScheduleNext() {
319 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
320 DCHECK(!capture_task_posted_
);
322 base::TimeTicks started_time
= base::TimeTicks::Now();
324 base::TimeDelta last_capture_duration
= base::TimeTicks::Now() - started_time
;
326 // Limit frame-rate to reduce CPU consumption.
327 base::TimeDelta capture_period
= std::max(
328 (last_capture_duration
* 100) / kMaximumCpuConsumptionPercentage
,
329 base::TimeDelta::FromSeconds(1) / capture_format_
.frame_rate
);
331 // Schedule a task for the next frame.
332 capture_task_posted_
= true;
333 task_runner_
->PostDelayedTask(
334 FROM_HERE
, base::Bind(&Core::OnCaptureTimer
, this),
335 capture_period
- last_capture_duration
);
338 void DesktopCaptureDevice::Core::DoCapture() {
339 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
340 DCHECK(!capture_in_progress_
);
342 capture_in_progress_
= true;
343 desktop_capturer_
->Capture(webrtc::DesktopRegion());
345 // Currently only synchronous implementations of DesktopCapturer are
347 DCHECK(!capture_in_progress_
);
351 scoped_ptr
<media::VideoCaptureDevice
> DesktopCaptureDevice::Create(
352 const DesktopMediaID
& source
) {
353 scoped_refptr
<base::SequencedWorkerPool
> blocking_pool
=
354 BrowserThread::GetBlockingPool();
355 scoped_refptr
<base::SequencedTaskRunner
> task_runner
=
356 blocking_pool
->GetSequencedTaskRunner(
357 blocking_pool
->GetSequenceToken());
359 webrtc::DesktopCaptureOptions options
=
360 webrtc::DesktopCaptureOptions::CreateDefault();
361 // Leave desktop effects enabled during WebRTC captures.
362 options
.set_disable_effects(false);
364 scoped_ptr
<webrtc::DesktopCapturer
> capturer
;
366 switch (source
.type
) {
367 case DesktopMediaID::TYPE_SCREEN
: {
368 scoped_ptr
<webrtc::DesktopCapturer
> screen_capturer
;
369 screen_capturer
.reset(webrtc::ScreenCapturer::Create(options
));
370 if (screen_capturer
) {
371 capturer
.reset(new webrtc::DesktopAndCursorComposer(
372 screen_capturer
.release(),
373 webrtc::MouseCursorMonitor::CreateForScreen(options
)));
378 case DesktopMediaID::TYPE_WINDOW
: {
379 scoped_ptr
<webrtc::WindowCapturer
> window_capturer(
380 webrtc::WindowCapturer::Create(options
));
381 if (window_capturer
&& window_capturer
->SelectWindow(source
.id
)) {
382 capturer
.reset(new webrtc::DesktopAndCursorComposer(
383 window_capturer
.release(),
384 webrtc::MouseCursorMonitor::CreateForWindow(options
, source
.id
)));
394 scoped_ptr
<media::VideoCaptureDevice
> result
;
396 result
.reset(new DesktopCaptureDevice(task_runner
, capturer
.Pass()));
398 return result
.Pass();
401 DesktopCaptureDevice::DesktopCaptureDevice(
402 scoped_refptr
<base::SequencedTaskRunner
> task_runner
,
403 scoped_ptr
<webrtc::DesktopCapturer
> capturer
)
404 : core_(new Core(task_runner
, capturer
.Pass())) {}
406 DesktopCaptureDevice::~DesktopCaptureDevice() {
410 void DesktopCaptureDevice::AllocateAndStart(
411 const media::VideoCaptureParams
& params
,
412 scoped_ptr
<Client
> client
) {
413 core_
->AllocateAndStart(params
, client
.Pass());
416 void DesktopCaptureDevice::StopAndDeAllocate() {
417 core_
->StopAndDeAllocate();
420 } // namespace content