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/media/capture/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();
60 void SetNotificationWindowId(gfx::NativeViewId window_id
);
63 friend class base::RefCountedThreadSafe
<Core
>;
66 // webrtc::DesktopCapturer::Callback interface
67 virtual webrtc::SharedMemory
* CreateSharedMemory(size_t size
) OVERRIDE
;
68 virtual void OnCaptureCompleted(webrtc::DesktopFrame
* frame
) OVERRIDE
;
70 // Helper methods that run on the |task_runner_|. Posted from the
71 // corresponding public methods.
72 void DoAllocateAndStart(const media::VideoCaptureParams
& params
,
73 scoped_ptr
<Client
> client
);
74 void DoStopAndDeAllocate();
76 // Chooses new output properties based on the supplied source size and the
77 // properties requested to Allocate(), and dispatches OnFrameInfo[Changed]
79 void RefreshCaptureFormat(const webrtc::DesktopSize
& frame_size
);
81 // Method that is scheduled on |task_runner_| to be called on regular interval
82 // to capture a frame.
83 void OnCaptureTimer();
85 // Captures a frame and schedules timer for the next one.
86 void CaptureFrameAndScheduleNext();
88 // Captures a single frame.
91 void DoSetNotificationWindowId(gfx::NativeViewId window_id
);
93 // Task runner used for capturing operations.
94 scoped_refptr
<base::SequencedTaskRunner
> task_runner_
;
96 // The underlying DesktopCapturer instance used to capture frames.
97 scoped_ptr
<webrtc::DesktopCapturer
> desktop_capturer_
;
99 // The device client which proxies device events to the controller. Accessed
100 // on the task_runner_ thread.
101 scoped_ptr
<Client
> client_
;
103 // Requested video capture format (width, height, frame rate, etc).
104 media::VideoCaptureParams requested_params_
;
106 // Actual video capture format being generated.
107 media::VideoCaptureFormat capture_format_
;
109 // Size of frame most recently captured from the source.
110 webrtc::DesktopSize previous_frame_size_
;
112 // DesktopFrame into which captured frames are down-scaled and/or letterboxed,
113 // depending upon the caller's requested capture capabilities. If frames can
114 // be returned to the caller directly then this is NULL.
115 scoped_ptr
<webrtc::DesktopFrame
> output_frame_
;
117 // Sub-rectangle of |output_frame_| into which the source will be scaled
118 // and/or letterboxed.
119 webrtc::DesktopRect output_rect_
;
121 // True when we have delayed OnCaptureTimer() task posted on
123 bool capture_task_posted_
;
125 // True when waiting for |desktop_capturer_| to capture current frame.
126 bool capture_in_progress_
;
128 DISALLOW_COPY_AND_ASSIGN(Core
);
131 DesktopCaptureDevice::Core::Core(
132 scoped_refptr
<base::SequencedTaskRunner
> task_runner
,
133 scoped_ptr
<webrtc::DesktopCapturer
> capturer
)
134 : task_runner_(task_runner
),
135 desktop_capturer_(capturer
.Pass()),
136 capture_task_posted_(false),
137 capture_in_progress_(false) {}
139 DesktopCaptureDevice::Core::~Core() {
142 void DesktopCaptureDevice::Core::AllocateAndStart(
143 const media::VideoCaptureParams
& params
,
144 scoped_ptr
<Client
> client
) {
145 DCHECK_GT(params
.requested_format
.frame_size
.GetArea(), 0);
146 DCHECK_GT(params
.requested_format
.frame_rate
, 0);
148 task_runner_
->PostTask(
151 &Core::DoAllocateAndStart
, this, params
, base::Passed(&client
)));
154 void DesktopCaptureDevice::Core::StopAndDeAllocate() {
155 task_runner_
->PostTask(FROM_HERE
,
156 base::Bind(&Core::DoStopAndDeAllocate
, this));
159 void DesktopCaptureDevice::Core::SetNotificationWindowId(
160 gfx::NativeViewId window_id
) {
161 task_runner_
->PostTask(
162 FROM_HERE
, base::Bind(&Core::DoSetNotificationWindowId
, this, window_id
));
165 webrtc::SharedMemory
*
166 DesktopCaptureDevice::Core::CreateSharedMemory(size_t size
) {
170 void DesktopCaptureDevice::Core::OnCaptureCompleted(
171 webrtc::DesktopFrame
* frame
) {
172 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
173 DCHECK(capture_in_progress_
);
175 capture_in_progress_
= false;
178 std::string
log("Failed to capture a frame.");
180 client_
->OnError(log
);
187 scoped_ptr
<webrtc::DesktopFrame
> owned_frame(frame
);
189 // Handle initial frame size and size changes.
190 RefreshCaptureFormat(frame
->size());
192 webrtc::DesktopSize
output_size(capture_format_
.frame_size
.width(),
193 capture_format_
.frame_size
.height());
194 size_t output_bytes
= output_size
.width() * output_size
.height() *
195 webrtc::DesktopFrame::kBytesPerPixel
;
196 const uint8_t* output_data
= NULL
;
197 scoped_ptr
<uint8_t[]> flipped_frame_buffer
;
199 if (frame
->size().equals(output_size
)) {
200 // If the captured frame matches the output size, we can return the pixel
201 // data directly, without scaling.
202 output_data
= frame
->data();
204 // If the |frame| generated by the screen capturer is inverted then we need
206 // This happens only on a specific platform. Refer to crbug.com/306876.
207 if (frame
->stride() < 0) {
208 int height
= frame
->size().height();
210 frame
->size().width() * webrtc::DesktopFrame::kBytesPerPixel
;
211 flipped_frame_buffer
.reset(new uint8_t[output_bytes
]);
212 uint8_t* dest
= flipped_frame_buffer
.get();
213 for (int row
= 0; row
< height
; ++row
) {
214 memcpy(dest
, output_data
, bytes_per_row
);
215 dest
+= bytes_per_row
;
216 output_data
+= frame
->stride();
218 output_data
= flipped_frame_buffer
.get();
221 // Otherwise we need to down-scale and/or letterbox to the target format.
223 // Allocate a buffer of the correct size to scale the frame into.
224 // |output_frame_| is cleared whenever |output_rect_| changes, so we don't
225 // need to worry about clearing out stale pixel data in letterboxed areas.
226 if (!output_frame_
) {
227 output_frame_
.reset(new webrtc::BasicDesktopFrame(output_size
));
228 memset(output_frame_
->data(), 0, output_bytes
);
230 DCHECK(output_frame_
->size().equals(output_size
));
232 // TODO(wez): Optimize this to scale only changed portions of the output,
233 // using ARGBScaleClip().
234 uint8_t* output_rect_data
= output_frame_
->data() +
235 output_frame_
->stride() * output_rect_
.top() +
236 webrtc::DesktopFrame::kBytesPerPixel
* output_rect_
.left();
237 libyuv::ARGBScale(frame
->data(), frame
->stride(),
238 frame
->size().width(), frame
->size().height(),
239 output_rect_data
, output_frame_
->stride(),
240 output_rect_
.width(), output_rect_
.height(),
241 libyuv::kFilterBilinear
);
242 output_data
= output_frame_
->data();
245 client_
->OnIncomingCapturedData(
246 output_data
, output_bytes
, capture_format_
, 0, base::TimeTicks::Now());
249 void DesktopCaptureDevice::Core::DoAllocateAndStart(
250 const media::VideoCaptureParams
& params
,
251 scoped_ptr
<Client
> client
) {
252 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
253 DCHECK(desktop_capturer_
);
254 DCHECK(client
.get());
255 DCHECK(!client_
.get());
257 client_
= client
.Pass();
258 requested_params_
= params
;
260 capture_format_
= requested_params_
.requested_format
;
262 // This capturer always outputs ARGB, non-interlaced.
263 capture_format_
.pixel_format
= media::PIXEL_FORMAT_ARGB
;
265 desktop_capturer_
->Start(this);
267 CaptureFrameAndScheduleNext();
270 void DesktopCaptureDevice::Core::DoStopAndDeAllocate() {
271 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
273 output_frame_
.reset();
274 previous_frame_size_
.set(0, 0);
275 desktop_capturer_
.reset();
278 void DesktopCaptureDevice::Core::RefreshCaptureFormat(
279 const webrtc::DesktopSize
& frame_size
) {
280 if (previous_frame_size_
.equals(frame_size
))
283 // Clear the output frame, if any, since it will either need resizing, or
284 // clearing of stale data in letterbox areas, anyway.
285 output_frame_
.reset();
287 if (previous_frame_size_
.is_empty() ||
288 requested_params_
.allow_resolution_change
) {
289 // If this is the first frame, or the receiver supports variable resolution
290 // then determine the output size by treating the requested width & height
292 if (frame_size
.width() >
293 requested_params_
.requested_format
.frame_size
.width() ||
294 frame_size
.height() >
295 requested_params_
.requested_format
.frame_size
.height()) {
296 output_rect_
= ComputeLetterboxRect(
298 requested_params_
.requested_format
.frame_size
.width(),
299 requested_params_
.requested_format
.frame_size
.height()),
301 output_rect_
.Translate(-output_rect_
.left(), -output_rect_
.top());
303 output_rect_
= webrtc::DesktopRect::MakeSize(frame_size
);
305 capture_format_
.frame_size
.SetSize(output_rect_
.width(),
306 output_rect_
.height());
308 // Otherwise the output frame size cannot change, so just scale and
310 output_rect_
= ComputeLetterboxRect(
311 webrtc::DesktopSize(capture_format_
.frame_size
.width(),
312 capture_format_
.frame_size
.height()),
316 previous_frame_size_
= frame_size
;
319 void DesktopCaptureDevice::Core::OnCaptureTimer() {
320 DCHECK(capture_task_posted_
);
321 capture_task_posted_
= false;
326 CaptureFrameAndScheduleNext();
329 void DesktopCaptureDevice::Core::CaptureFrameAndScheduleNext() {
330 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
331 DCHECK(!capture_task_posted_
);
333 base::TimeTicks started_time
= base::TimeTicks::Now();
335 base::TimeDelta last_capture_duration
= base::TimeTicks::Now() - started_time
;
337 // Limit frame-rate to reduce CPU consumption.
338 base::TimeDelta capture_period
= std::max(
339 (last_capture_duration
* 100) / kMaximumCpuConsumptionPercentage
,
340 base::TimeDelta::FromSeconds(1) / capture_format_
.frame_rate
);
342 // Schedule a task for the next frame.
343 capture_task_posted_
= true;
344 task_runner_
->PostDelayedTask(
345 FROM_HERE
, base::Bind(&Core::OnCaptureTimer
, this),
346 capture_period
- last_capture_duration
);
349 void DesktopCaptureDevice::Core::DoCapture() {
350 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
351 DCHECK(!capture_in_progress_
);
353 capture_in_progress_
= true;
354 desktop_capturer_
->Capture(webrtc::DesktopRegion());
356 // Currently only synchronous implementations of DesktopCapturer are
358 DCHECK(!capture_in_progress_
);
361 void DesktopCaptureDevice::Core::DoSetNotificationWindowId(
362 gfx::NativeViewId window_id
) {
363 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
365 desktop_capturer_
->SetExcludedWindow(window_id
);
369 scoped_ptr
<media::VideoCaptureDevice
> DesktopCaptureDevice::Create(
370 const DesktopMediaID
& source
) {
371 scoped_refptr
<base::SequencedWorkerPool
> blocking_pool
=
372 BrowserThread::GetBlockingPool();
373 scoped_refptr
<base::SequencedTaskRunner
> task_runner
=
374 blocking_pool
->GetSequencedTaskRunner(
375 blocking_pool
->GetSequenceToken());
377 webrtc::DesktopCaptureOptions options
=
378 webrtc::DesktopCaptureOptions::CreateDefault();
379 // Leave desktop effects enabled during WebRTC captures.
380 options
.set_disable_effects(false);
382 scoped_ptr
<webrtc::DesktopCapturer
> capturer
;
384 switch (source
.type
) {
385 case DesktopMediaID::TYPE_SCREEN
: {
386 scoped_ptr
<webrtc::ScreenCapturer
> screen_capturer
;
387 screen_capturer
.reset(webrtc::ScreenCapturer::Create(options
));
388 if (screen_capturer
&& screen_capturer
->SelectScreen(source
.id
)) {
389 capturer
.reset(new webrtc::DesktopAndCursorComposer(
390 screen_capturer
.release(),
391 webrtc::MouseCursorMonitor::CreateForScreen(options
, source
.id
)));
396 case DesktopMediaID::TYPE_WINDOW
: {
397 scoped_ptr
<webrtc::WindowCapturer
> window_capturer(
398 webrtc::WindowCapturer::Create(options
));
399 if (window_capturer
&& window_capturer
->SelectWindow(source
.id
)) {
400 window_capturer
->BringSelectedWindowToFront();
401 capturer
.reset(new webrtc::DesktopAndCursorComposer(
402 window_capturer
.release(),
403 webrtc::MouseCursorMonitor::CreateForWindow(options
, source
.id
)));
413 scoped_ptr
<media::VideoCaptureDevice
> result
;
415 result
.reset(new DesktopCaptureDevice(task_runner
, capturer
.Pass()));
417 return result
.Pass();
420 DesktopCaptureDevice::DesktopCaptureDevice(
421 scoped_refptr
<base::SequencedTaskRunner
> task_runner
,
422 scoped_ptr
<webrtc::DesktopCapturer
> capturer
)
423 : core_(new Core(task_runner
, capturer
.Pass())) {}
425 DesktopCaptureDevice::~DesktopCaptureDevice() {
429 void DesktopCaptureDevice::AllocateAndStart(
430 const media::VideoCaptureParams
& params
,
431 scoped_ptr
<Client
> client
) {
432 core_
->AllocateAndStart(params
, client
.Pass());
435 void DesktopCaptureDevice::StopAndDeAllocate() {
436 core_
->StopAndDeAllocate();
439 void DesktopCaptureDevice::SetNotificationWindowId(
440 gfx::NativeViewId window_id
) {
441 core_
->SetNotificationWindowId(window_id
);
444 } // namespace content