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 "third_party/skia/include/core/SkBitmap.h"
18 #include "ui/aura/window.h"
19 #include "ui/aura/window_observer.h"
20 #include "ui/compositor/compositor.h"
21 #include "ui/compositor/dip_util.h"
22 #include "ui/compositor/layer.h"
23 #include "ui/gfx/screen.h"
29 class DesktopVideoCaptureMachine
30 : public VideoCaptureMachine
,
31 public aura::WindowObserver
,
32 public ui::CompositorObserver
,
33 public base::SupportsWeakPtr
<DesktopVideoCaptureMachine
> {
35 DesktopVideoCaptureMachine(const DesktopMediaID
& source
);
36 virtual ~DesktopVideoCaptureMachine();
38 // VideoCaptureFrameSource overrides.
40 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
) OVERRIDE
;
41 virtual void Stop() OVERRIDE
;
43 // Implements aura::WindowObserver.
44 virtual void OnWindowBoundsChanged(aura::Window
* window
,
45 const gfx::Rect
& old_bounds
,
46 const gfx::Rect
& new_bounds
) OVERRIDE
;
47 virtual void OnWindowDestroyed(aura::Window
* window
) OVERRIDE
;
49 // Implements ui::CompositorObserver.
50 virtual void OnCompositingDidCommit(ui::Compositor
* compositor
) OVERRIDE
{}
51 virtual void OnCompositingStarted(ui::Compositor
* compositor
,
52 base::TimeTicks start_time
) OVERRIDE
{}
53 virtual void OnCompositingEnded(ui::Compositor
* compositor
) OVERRIDE
;
54 virtual void OnCompositingAborted(ui::Compositor
* compositor
) OVERRIDE
{}
55 virtual void OnCompositingLockStateChanged(
56 ui::Compositor
* compositor
) OVERRIDE
{}
57 virtual void OnUpdateVSyncParameters(ui::Compositor
* compositor
,
58 base::TimeTicks timebase
,
59 base::TimeDelta interval
) OVERRIDE
{}
63 // |dirty| is false for timer polls and true for compositor updates.
64 void Capture(bool dirty
);
66 // Update capture size. Must be called on the UI thread.
67 void UpdateCaptureSize();
69 // Response callback for cc::Layer::RequestCopyOfOutput().
71 scoped_refptr
<media::VideoFrame
> video_frame
,
72 base::Time start_time
,
73 const ThreadSafeCaptureOracle::CaptureFrameCallback
& capture_frame_cb
,
74 scoped_ptr
<cc::CopyOutputResult
> result
);
76 // The window associated with the desktop.
77 aura::Window
* desktop_window_
;
79 // The layer associated with the desktop.
80 ui::Layer
* desktop_layer_
;
82 // The timer that kicks off period captures.
86 DesktopMediaID desktop_id_
;
88 // Makes all the decisions about which frames to copy, and how.
89 scoped_refptr
<ThreadSafeCaptureOracle
> oracle_proxy_
;
91 // YUV readback pipeline.
92 scoped_ptr
<content::ReadbackYUVInterface
> yuv_readback_pipeline_
;
94 DISALLOW_COPY_AND_ASSIGN(DesktopVideoCaptureMachine
);
97 DesktopVideoCaptureMachine::DesktopVideoCaptureMachine(
98 const DesktopMediaID
& source
)
99 : desktop_window_(NULL
),
100 desktop_layer_(NULL
),
102 desktop_id_(source
) {}
104 DesktopVideoCaptureMachine::~DesktopVideoCaptureMachine() {}
106 bool DesktopVideoCaptureMachine::Start(
107 const scoped_refptr
<ThreadSafeCaptureOracle
>& oracle_proxy
) {
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
110 // TODO(hshi): get the correct display specified by |desktop_id_|.
111 const gfx::Display
& primary_display
=
112 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
113 desktop_window_
= gfx::Screen::GetNativeScreen()->GetWindowAtScreenPoint(
114 primary_display
.bounds().CenterPoint())->GetRootWindow();
115 if (!desktop_window_
)
118 // If the desktop layer is already destroyed then return failure.
119 desktop_layer_
= desktop_window_
->layer();
123 DCHECK(oracle_proxy
.get());
124 oracle_proxy_
= oracle_proxy
;
126 // Update capture size.
129 // Start observing window events.
130 desktop_window_
->AddObserver(this);
132 // Start observing compositor updates.
133 ui::Compositor
* compositor
= desktop_layer_
->GetCompositor();
137 compositor
->AddObserver(this);
140 timer_
.Start(FROM_HERE
, oracle_proxy_
->capture_period(),
141 base::Bind(&DesktopVideoCaptureMachine::Capture
, AsWeakPtr(),
148 void DesktopVideoCaptureMachine::Stop() {
149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
151 // Stop observing window events.
153 desktop_window_
->RemoveObserver(this);
155 // Stop observing compositor updates.
156 if (desktop_layer_
) {
157 ui::Compositor
* compositor
= desktop_layer_
->GetCompositor();
159 compositor
->RemoveObserver(this);
168 void DesktopVideoCaptureMachine::UpdateCaptureSize() {
169 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
170 if (oracle_proxy_
&& desktop_layer_
) {
171 oracle_proxy_
->UpdateCaptureSize(ui::ConvertSizeToPixel(
172 desktop_layer_
, desktop_layer_
->bounds().size()));
176 void DesktopVideoCaptureMachine::Capture(bool dirty
) {
177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
179 // Do not capture if the desktop layer is already destroyed.
183 scoped_refptr
<media::VideoFrame
> frame
;
184 ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb
;
186 const base::Time start_time
= base::Time::Now();
187 const VideoCaptureOracle::Event event
=
188 dirty
? VideoCaptureOracle::kCompositorUpdate
189 : VideoCaptureOracle::kTimerPoll
;
190 if (oracle_proxy_
->ObserveEventAndDecideCapture(
191 event
, start_time
, &frame
, &capture_frame_cb
)) {
192 scoped_ptr
<cc::CopyOutputRequest
> request
=
193 cc::CopyOutputRequest::CreateRequest(
194 base::Bind(&DesktopVideoCaptureMachine::DidCopyOutput
,
195 AsWeakPtr(), frame
, start_time
, capture_frame_cb
));
196 gfx::Rect desktop_size
= ui::ConvertRectToPixel(
197 desktop_layer_
, desktop_layer_
->bounds());
198 request
->set_area(desktop_size
);
199 desktop_layer_
->RequestCopyOfOutput(request
.Pass());
203 static void CopyOutputFinishedForVideo(
204 base::Time start_time
,
205 const ThreadSafeCaptureOracle::CaptureFrameCallback
& capture_frame_cb
,
206 scoped_ptr
<cc::SingleReleaseCallback
> release_callback
,
208 release_callback
->Run(0, false);
209 capture_frame_cb
.Run(start_time
, result
);
212 void DesktopVideoCaptureMachine::DidCopyOutput(
213 scoped_refptr
<media::VideoFrame
> video_frame
,
214 base::Time start_time
,
215 const ThreadSafeCaptureOracle::CaptureFrameCallback
& capture_frame_cb
,
216 scoped_ptr
<cc::CopyOutputResult
> result
) {
217 if (result
->IsEmpty() || result
->size().IsEmpty())
220 // Compute the dest size we want after the letterboxing resize. Make the
221 // coordinates and sizes even because we letterbox in YUV space
222 // (see CopyRGBToVideoFrame). They need to be even for the UV samples to
223 // line up correctly.
224 // The video frame's coded_size() and the result's size() are both physical
226 gfx::Rect region_in_frame
=
227 media::ComputeLetterboxRegion(gfx::Rect(video_frame
->coded_size()),
229 region_in_frame
= gfx::Rect(region_in_frame
.x() & ~1,
230 region_in_frame
.y() & ~1,
231 region_in_frame
.width() & ~1,
232 region_in_frame
.height() & ~1);
233 if (region_in_frame
.IsEmpty())
236 ImageTransportFactory
* factory
= ImageTransportFactory::GetInstance();
237 GLHelper
* gl_helper
= factory
->GetGLHelper();
241 cc::TextureMailbox texture_mailbox
;
242 scoped_ptr
<cc::SingleReleaseCallback
> release_callback
;
243 result
->TakeTexture(&texture_mailbox
, &release_callback
);
244 DCHECK(texture_mailbox
.IsTexture());
245 if (!texture_mailbox
.IsTexture())
248 gfx::Rect
result_rect(result
->size());
249 if (!yuv_readback_pipeline_
||
250 yuv_readback_pipeline_
->scaler()->SrcSize() != result_rect
.size() ||
251 yuv_readback_pipeline_
->scaler()->SrcSubrect() != result_rect
||
252 yuv_readback_pipeline_
->scaler()->DstSize() != region_in_frame
.size()) {
253 yuv_readback_pipeline_
.reset(
254 gl_helper
->CreateReadbackPipelineYUV(GLHelper::SCALER_QUALITY_FAST
,
257 video_frame
->coded_size(),
262 yuv_readback_pipeline_
->ReadbackYUV(
263 texture_mailbox
.name(), texture_mailbox
.sync_point(), video_frame
.get(),
264 base::Bind(&CopyOutputFinishedForVideo
, start_time
, capture_frame_cb
,
265 base::Passed(&release_callback
)));
268 void DesktopVideoCaptureMachine::OnWindowBoundsChanged(
269 aura::Window
* window
,
270 const gfx::Rect
& old_bounds
,
271 const gfx::Rect
& new_bounds
) {
272 DCHECK(desktop_window_
&& window
== desktop_window_
);
274 // Post task to update capture size on UI thread.
275 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, base::Bind(
276 &DesktopVideoCaptureMachine::UpdateCaptureSize
, AsWeakPtr()));
279 void DesktopVideoCaptureMachine::OnWindowDestroyed(aura::Window
* window
) {
280 DCHECK(desktop_window_
&& window
== desktop_window_
);
281 desktop_window_
= NULL
;
282 desktop_layer_
= NULL
;
284 // Post task to stop capture on UI thread.
285 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, base::Bind(
286 &DesktopVideoCaptureMachine::Stop
, AsWeakPtr()));
289 void DesktopVideoCaptureMachine::OnCompositingEnded(
290 ui::Compositor
* compositor
) {
291 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, base::Bind(
292 &DesktopVideoCaptureMachine::Capture
, AsWeakPtr(), true));
297 DesktopCaptureDeviceAura::DesktopCaptureDeviceAura(
298 const DesktopMediaID
& source
)
299 : impl_(new VideoCaptureDeviceImpl(scoped_ptr
<VideoCaptureMachine
>(
300 new DesktopVideoCaptureMachine(source
)))) {}
302 DesktopCaptureDeviceAura::~DesktopCaptureDeviceAura() {
303 DVLOG(2) << "DesktopCaptureDeviceAura@" << this << " destroying.";
307 media::VideoCaptureDevice
* DesktopCaptureDeviceAura::Create(
308 const DesktopMediaID
& source
) {
309 // This implementation only supports screen capture.
310 if (source
.type
!= DesktopMediaID::TYPE_SCREEN
)
313 return new DesktopCaptureDeviceAura(source
);
316 void DesktopCaptureDeviceAura::AllocateAndStart(
317 const media::VideoCaptureParams
& params
,
318 scoped_ptr
<Client
> client
) {
319 DVLOG(1) << "Allocating " << params
.requested_format
.frame_size
.ToString();
320 impl_
->AllocateAndStart(params
, client
.Pass());
323 void DesktopCaptureDeviceAura::StopAndDeAllocate() {
324 impl_
->StopAndDeAllocate();
327 } // namespace content