1 // Copyright 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_aura.h"
7 #include "base/logging.h"
8 #include "base/timer/timer.h"
9 #include "cc/output/copy_output_request.h"
10 #include "cc/output/copy_output_result.h"
11 #include "content/browser/aura/image_transport_factory.h"
12 #include "content/browser/renderer_host/media/video_capture_device_impl.h"
13 #include "content/common/gpu/client/gl_helper.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "media/base/video_util.h"
16 #include "media/video/capture/video_capture_types.h"
17 #include "skia/ext/image_operations.h"
18 #include "third_party/skia/include/core/SkBitmap.h"
19 #include "ui/aura/env.h"
20 #include "ui/aura/root_window.h"
21 #include "ui/aura/window.h"
22 #include "ui/aura/window_observer.h"
23 #include "ui/base/cursor/cursors_aura.h"
24 #include "ui/compositor/compositor.h"
25 #include "ui/compositor/dip_util.h"
26 #include "ui/compositor/layer.h"
27 #include "ui/gfx/screen.h"
33 int clip_byte(int x
) {
34 return std::max(0, std::min(x
, 255));
37 int alpha_blend(int alpha
, int src
, int dst
) {
38 return (src
* alpha
+ dst
* (255 - alpha
)) / 255;
41 // Helper function to composite a cursor bitmap on a YUV420 video frame.
42 void RenderCursorOnVideoFrame(
43 const scoped_refptr
<media::VideoFrame
>& target
,
44 const SkBitmap
& cursor_bitmap
,
45 const gfx::Point
& cursor_position
) {
47 DCHECK(!cursor_bitmap
.isNull());
49 gfx::Rect rect
= gfx::IntersectRects(
50 gfx::Rect(cursor_bitmap
.width(), cursor_bitmap
.height()) +
51 gfx::Vector2d(cursor_position
.x(), cursor_position
.y()),
52 target
->visible_rect());
54 cursor_bitmap
.lockPixels();
55 for (int y
= rect
.y(); y
< rect
.bottom(); ++y
) {
56 int cursor_y
= y
- cursor_position
.y();
57 uint8
* yplane
= target
->data(media::VideoFrame::kYPlane
) +
58 y
* target
->row_bytes(media::VideoFrame::kYPlane
);
59 uint8
* uplane
= target
->data(media::VideoFrame::kUPlane
) +
60 (y
/ 2) * target
->row_bytes(media::VideoFrame::kUPlane
);
61 uint8
* vplane
= target
->data(media::VideoFrame::kVPlane
) +
62 (y
/ 2) * target
->row_bytes(media::VideoFrame::kVPlane
);
63 for (int x
= rect
.x(); x
< rect
.right(); ++x
) {
64 int cursor_x
= x
- cursor_position
.x();
65 SkColor color
= cursor_bitmap
.getColor(cursor_x
, cursor_y
);
66 int alpha
= SkColorGetA(color
);
67 int color_r
= SkColorGetR(color
);
68 int color_g
= SkColorGetG(color
);
69 int color_b
= SkColorGetB(color
);
70 int color_y
= clip_byte(((color_r
* 66 + color_g
* 129 + color_b
* 25 +
72 yplane
[x
] = alpha_blend(alpha
, color_y
, yplane
[x
]);
74 // Only sample U and V at even coordinates.
75 if ((x
% 2 == 0) && (y
% 2 == 0)) {
76 int color_u
= clip_byte(((color_r
* -38 + color_g
* -74 +
77 color_b
* 112 + 128) >> 8) + 128);
78 int color_v
= clip_byte(((color_r
* 112 + color_g
* -94 +
79 color_b
* -18 + 128) >> 8) + 128);
80 uplane
[x
/ 2] = alpha_blend(alpha
, color_u
, uplane
[x
/ 2]);
81 vplane
[x
/ 2] = alpha_blend(alpha
, color_v
, vplane
[x
/ 2]);
85 cursor_bitmap
.unlockPixels();
88 class DesktopVideoCaptureMachine
89 : public VideoCaptureMachine
,
90 public aura::WindowObserver
,
91 public ui::CompositorObserver
,
92 public base::SupportsWeakPtr
<DesktopVideoCaptureMachine
> {
94 DesktopVideoCaptureMachine(const DesktopMediaID
& source
);
95 virtual ~DesktopVideoCaptureMachine();
97 // VideoCaptureFrameSource overrides.
99 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
) OVERRIDE
;
100 virtual void Stop() OVERRIDE
;
102 // Implements aura::WindowObserver.
103 virtual void OnWindowBoundsChanged(aura::Window
* window
,
104 const gfx::Rect
& old_bounds
,
105 const gfx::Rect
& new_bounds
) OVERRIDE
;
106 virtual void OnWindowDestroyed(aura::Window
* window
) OVERRIDE
;
108 // Implements ui::CompositorObserver.
109 virtual void OnCompositingDidCommit(ui::Compositor
* compositor
) OVERRIDE
{}
110 virtual void OnCompositingStarted(ui::Compositor
* compositor
,
111 base::TimeTicks start_time
) OVERRIDE
{}
112 virtual void OnCompositingEnded(ui::Compositor
* compositor
) OVERRIDE
;
113 virtual void OnCompositingAborted(ui::Compositor
* compositor
) OVERRIDE
{}
114 virtual void OnCompositingLockStateChanged(
115 ui::Compositor
* compositor
) OVERRIDE
{}
116 virtual void OnUpdateVSyncParameters(ui::Compositor
* compositor
,
117 base::TimeTicks timebase
,
118 base::TimeDelta interval
) OVERRIDE
{}
122 // |dirty| is false for timer polls and true for compositor updates.
123 void Capture(bool dirty
);
125 // Update capture size. Must be called on the UI thread.
126 void UpdateCaptureSize();
128 // Response callback for cc::Layer::RequestCopyOfOutput().
130 scoped_refptr
<media::VideoFrame
> video_frame
,
131 base::TimeTicks start_time
,
132 const ThreadSafeCaptureOracle::CaptureFrameCallback
& capture_frame_cb
,
133 scoped_ptr
<cc::CopyOutputResult
> result
);
135 // Helper function to update cursor state.
136 // |region_in_frame| defines the desktop bound in the captured frame.
137 // Returns the current cursor position in captured frame.
138 gfx::Point
UpdateCursorState(const gfx::Rect
& region_in_frame
);
140 // Clears cursor state.
141 void ClearCursorState();
143 // The window associated with the desktop.
144 aura::Window
* desktop_window_
;
146 // The layer associated with the desktop.
147 ui::Layer
* desktop_layer_
;
149 // The timer that kicks off period captures.
152 // The id of the window being captured.
153 DesktopMediaID window_id_
;
155 // Makes all the decisions about which frames to copy, and how.
156 scoped_refptr
<ThreadSafeCaptureOracle
> oracle_proxy_
;
158 // YUV readback pipeline.
159 scoped_ptr
<content::ReadbackYUVInterface
> yuv_readback_pipeline_
;
162 ui::Cursor last_cursor_
;
163 gfx::Point cursor_hot_point_
;
164 SkBitmap scaled_cursor_bitmap_
;
166 DISALLOW_COPY_AND_ASSIGN(DesktopVideoCaptureMachine
);
169 DesktopVideoCaptureMachine::DesktopVideoCaptureMachine(
170 const DesktopMediaID
& source
)
171 : desktop_window_(NULL
),
172 desktop_layer_(NULL
),
174 window_id_(source
) {}
176 DesktopVideoCaptureMachine::~DesktopVideoCaptureMachine() {}
178 bool DesktopVideoCaptureMachine::Start(
179 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
) {
180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
182 desktop_window_
= content::DesktopMediaID::GetAuraWindowById(window_id_
);
183 if (!desktop_window_
)
186 // If the desktop layer is already destroyed then return failure.
187 desktop_layer_
= desktop_window_
->layer();
191 DCHECK(oracle_proxy
.get());
192 oracle_proxy_
= oracle_proxy
;
194 // Update capture size.
197 // Start observing window events.
198 desktop_window_
->AddObserver(this);
200 // Start observing compositor updates.
201 ui::Compositor
* compositor
= desktop_layer_
->GetCompositor();
205 compositor
->AddObserver(this);
208 timer_
.Start(FROM_HERE
, oracle_proxy_
->capture_period(),
209 base::Bind(&DesktopVideoCaptureMachine::Capture
, AsWeakPtr(),
216 void DesktopVideoCaptureMachine::Stop() {
217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
219 // Stop observing window events.
220 if (desktop_window_
) {
221 desktop_window_
->RemoveObserver(this);
222 desktop_window_
= NULL
;
225 // Stop observing compositor updates.
226 if (desktop_layer_
) {
227 ui::Compositor
* compositor
= desktop_layer_
->GetCompositor();
229 compositor
->RemoveObserver(this);
230 desktop_layer_
= NULL
;
239 void DesktopVideoCaptureMachine::UpdateCaptureSize() {
240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
241 if (oracle_proxy_
&& desktop_layer_
) {
242 oracle_proxy_
->UpdateCaptureSize(ui::ConvertSizeToPixel(
243 desktop_layer_
, desktop_layer_
->bounds().size()));
248 void DesktopVideoCaptureMachine::Capture(bool dirty
) {
249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
251 // Do not capture if the desktop layer is already destroyed.
255 scoped_refptr
<media::VideoFrame
> frame
;
256 ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb
;
258 const base::TimeTicks start_time
= base::TimeTicks::Now();
259 const VideoCaptureOracle::Event event
=
260 dirty
? VideoCaptureOracle::kCompositorUpdate
261 : VideoCaptureOracle::kTimerPoll
;
262 if (oracle_proxy_
->ObserveEventAndDecideCapture(
263 event
, start_time
, &frame
, &capture_frame_cb
)) {
264 scoped_ptr
<cc::CopyOutputRequest
> request
=
265 cc::CopyOutputRequest::CreateRequest(
266 base::Bind(&DesktopVideoCaptureMachine::DidCopyOutput
,
267 AsWeakPtr(), frame
, start_time
, capture_frame_cb
));
268 gfx::Rect window_rect
=
269 ui::ConvertRectToPixel(desktop_window_
->layer(),
270 gfx::Rect(desktop_window_
->bounds().width(),
271 desktop_window_
->bounds().height()));
272 request
->set_area(window_rect
);
273 desktop_layer_
->RequestCopyOfOutput(request
.Pass());
277 void CopyOutputFinishedForVideo(
278 base::TimeTicks start_time
,
279 const ThreadSafeCaptureOracle::CaptureFrameCallback
& capture_frame_cb
,
280 const scoped_refptr
<media::VideoFrame
>& target
,
281 const SkBitmap
& cursor_bitmap
,
282 const gfx::Point
& cursor_position
,
283 scoped_ptr
<cc::SingleReleaseCallback
> release_callback
,
285 if (!cursor_bitmap
.isNull())
286 RenderCursorOnVideoFrame(target
, cursor_bitmap
, cursor_position
);
287 release_callback
->Run(0, false);
288 capture_frame_cb
.Run(start_time
, result
);
291 void DesktopVideoCaptureMachine::DidCopyOutput(
292 scoped_refptr
<media::VideoFrame
> video_frame
,
293 base::TimeTicks start_time
,
294 const ThreadSafeCaptureOracle::CaptureFrameCallback
& capture_frame_cb
,
295 scoped_ptr
<cc::CopyOutputResult
> result
) {
296 if (result
->IsEmpty() || result
->size().IsEmpty() || !desktop_layer_
)
299 // Compute the dest size we want after the letterboxing resize. Make the
300 // coordinates and sizes even because we letterbox in YUV space
301 // (see CopyRGBToVideoFrame). They need to be even for the UV samples to
302 // line up correctly.
303 // The video frame's coded_size() and the result's size() are both physical
305 gfx::Rect region_in_frame
=
306 media::ComputeLetterboxRegion(gfx::Rect(video_frame
->coded_size()),
308 region_in_frame
= gfx::Rect(region_in_frame
.x() & ~1,
309 region_in_frame
.y() & ~1,
310 region_in_frame
.width() & ~1,
311 region_in_frame
.height() & ~1);
312 if (region_in_frame
.IsEmpty())
315 ImageTransportFactory
* factory
= ImageTransportFactory::GetInstance();
316 GLHelper
* gl_helper
= factory
->GetGLHelper();
320 cc::TextureMailbox texture_mailbox
;
321 scoped_ptr
<cc::SingleReleaseCallback
> release_callback
;
322 result
->TakeTexture(&texture_mailbox
, &release_callback
);
323 DCHECK(texture_mailbox
.IsTexture());
324 if (!texture_mailbox
.IsTexture())
327 gfx::Rect
result_rect(result
->size());
328 if (!yuv_readback_pipeline_
||
329 yuv_readback_pipeline_
->scaler()->SrcSize() != result_rect
.size() ||
330 yuv_readback_pipeline_
->scaler()->SrcSubrect() != result_rect
||
331 yuv_readback_pipeline_
->scaler()->DstSize() != region_in_frame
.size()) {
332 yuv_readback_pipeline_
.reset(
333 gl_helper
->CreateReadbackPipelineYUV(GLHelper::SCALER_QUALITY_FAST
,
336 video_frame
->coded_size(),
342 gfx::Point cursor_position_in_frame
= UpdateCursorState(region_in_frame
);
343 yuv_readback_pipeline_
->ReadbackYUV(
344 texture_mailbox
.name(), texture_mailbox
.sync_point(), video_frame
.get(),
345 base::Bind(&CopyOutputFinishedForVideo
, start_time
, capture_frame_cb
,
346 video_frame
, scaled_cursor_bitmap_
, cursor_position_in_frame
,
347 base::Passed(&release_callback
)));
350 gfx::Point
DesktopVideoCaptureMachine::UpdateCursorState(
351 const gfx::Rect
& region_in_frame
) {
352 const gfx::Rect desktop_bounds
= desktop_layer_
->bounds();
353 gfx::NativeCursor cursor
= desktop_window_
->GetDispatcher()->last_cursor();
354 if (last_cursor_
!= cursor
) {
355 SkBitmap cursor_bitmap
;
356 if (ui::GetCursorBitmap(cursor
, &cursor_bitmap
, &cursor_hot_point_
)) {
357 scaled_cursor_bitmap_
= skia::ImageOperations::Resize(
359 skia::ImageOperations::RESIZE_BEST
,
360 cursor_bitmap
.width() * region_in_frame
.width() /
361 desktop_bounds
.width(),
362 cursor_bitmap
.height() * region_in_frame
.height() /
363 desktop_bounds
.height());
364 last_cursor_
= cursor
;
366 // Clear cursor state if ui::GetCursorBitmap failed so that we do not
367 // render cursor on the captured frame.
372 gfx::Point cursor_position
= aura::Env::GetInstance()->last_mouse_location();
373 const gfx::Point hot_point_in_dip
= ui::ConvertPointToDIP(
374 desktop_layer_
, cursor_hot_point_
);
375 cursor_position
.Offset(-desktop_bounds
.x() - hot_point_in_dip
.x(),
376 -desktop_bounds
.y() - hot_point_in_dip
.y());
378 region_in_frame
.x() + cursor_position
.x() * region_in_frame
.width() /
379 desktop_bounds
.width(),
380 region_in_frame
.y() + cursor_position
.y() * region_in_frame
.height() /
381 desktop_bounds
.height());
384 void DesktopVideoCaptureMachine::ClearCursorState() {
385 last_cursor_
= ui::Cursor();
386 cursor_hot_point_
= gfx::Point();
387 scaled_cursor_bitmap_
.reset();
390 void DesktopVideoCaptureMachine::OnWindowBoundsChanged(
391 aura::Window
* window
,
392 const gfx::Rect
& old_bounds
,
393 const gfx::Rect
& new_bounds
) {
394 DCHECK(desktop_window_
&& window
== desktop_window_
);
396 // Post task to update capture size on UI thread.
397 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, base::Bind(
398 &DesktopVideoCaptureMachine::UpdateCaptureSize
, AsWeakPtr()));
401 void DesktopVideoCaptureMachine::OnWindowDestroyed(aura::Window
* window
) {
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
406 oracle_proxy_
->ReportError();
409 void DesktopVideoCaptureMachine::OnCompositingEnded(
410 ui::Compositor
* compositor
) {
411 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, base::Bind(
412 &DesktopVideoCaptureMachine::Capture
, AsWeakPtr(), true));
417 DesktopCaptureDeviceAura::DesktopCaptureDeviceAura(
418 const DesktopMediaID
& source
)
419 : impl_(new VideoCaptureDeviceImpl(scoped_ptr
<VideoCaptureMachine
>(
420 new DesktopVideoCaptureMachine(source
)))) {}
422 DesktopCaptureDeviceAura::~DesktopCaptureDeviceAura() {
423 DVLOG(2) << "DesktopCaptureDeviceAura@" << this << " destroying.";
427 media::VideoCaptureDevice
* DesktopCaptureDeviceAura::Create(
428 const DesktopMediaID
& source
) {
429 return new DesktopCaptureDeviceAura(source
);
432 void DesktopCaptureDeviceAura::AllocateAndStart(
433 const media::VideoCaptureParams
& params
,
434 scoped_ptr
<Client
> client
) {
435 DVLOG(1) << "Allocating " << params
.requested_format
.frame_size
.ToString();
436 impl_
->AllocateAndStart(params
, client
.Pass());
439 void DesktopCaptureDeviceAura::StopAndDeAllocate() {
440 impl_
->StopAndDeAllocate();
443 } // namespace content