OLD | NEW |
| (Empty) |
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. | |
4 // | |
5 #include "content/browser/renderer_host/media/desktop_capture_device_ash.h" | |
6 | |
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" | |
24 | |
25 namespace content { | |
26 | |
27 namespace { | |
28 | |
29 class DesktopVideoCaptureMachine | |
30 : public VideoCaptureMachine, | |
31 public aura::WindowObserver, | |
32 public ui::CompositorObserver, | |
33 public base::SupportsWeakPtr<DesktopVideoCaptureMachine> { | |
34 public: | |
35 DesktopVideoCaptureMachine(const DesktopMediaID& source); | |
36 virtual ~DesktopVideoCaptureMachine(); | |
37 | |
38 // VideoCaptureFrameSource overrides. | |
39 virtual bool Start( | |
40 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) OVERRIDE; | |
41 virtual void Stop() OVERRIDE; | |
42 | |
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; | |
48 | |
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 {} | |
60 | |
61 private: | |
62 // Captures a frame. | |
63 // |dirty| is false for timer polls and true for compositor updates. | |
64 void Capture(bool dirty); | |
65 | |
66 // Update capture size. Must be called on the UI thread. | |
67 void UpdateCaptureSize(); | |
68 | |
69 // Response callback for cc::Layer::RequestCopyOfOutput(). | |
70 void DidCopyOutput( | |
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); | |
75 | |
76 // The window associated with the desktop. | |
77 aura::Window* desktop_window_; | |
78 | |
79 // The layer associated with the desktop. | |
80 ui::Layer* desktop_layer_; | |
81 | |
82 // The timer that kicks off period captures. | |
83 base::Timer timer_; | |
84 | |
85 // The desktop id. | |
86 DesktopMediaID desktop_id_; | |
87 | |
88 // Makes all the decisions about which frames to copy, and how. | |
89 scoped_refptr<ThreadSafeCaptureOracle> oracle_proxy_; | |
90 | |
91 // YUV readback pipeline. | |
92 scoped_ptr<content::ReadbackYUVInterface> yuv_readback_pipeline_; | |
93 | |
94 DISALLOW_COPY_AND_ASSIGN(DesktopVideoCaptureMachine); | |
95 }; | |
96 | |
97 DesktopVideoCaptureMachine::DesktopVideoCaptureMachine( | |
98 const DesktopMediaID& source) | |
99 : desktop_window_(NULL), | |
100 desktop_layer_(NULL), | |
101 timer_(true, true), | |
102 desktop_id_(source) {} | |
103 | |
104 DesktopVideoCaptureMachine::~DesktopVideoCaptureMachine() {} | |
105 | |
106 bool DesktopVideoCaptureMachine::Start( | |
107 const scoped_refptr<ThreadSafeCaptureOracle>& oracle_proxy) { | |
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
109 | |
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_) | |
116 return false; | |
117 | |
118 // If the desktop layer is already destroyed then return failure. | |
119 desktop_layer_ = desktop_window_->layer(); | |
120 if (!desktop_layer_) | |
121 return false; | |
122 | |
123 DCHECK(oracle_proxy.get()); | |
124 oracle_proxy_ = oracle_proxy; | |
125 | |
126 // Update capture size. | |
127 UpdateCaptureSize(); | |
128 | |
129 // Start observing window events. | |
130 desktop_window_->AddObserver(this); | |
131 | |
132 // Start observing compositor updates. | |
133 ui::Compositor* compositor = desktop_layer_->GetCompositor(); | |
134 if (!compositor) | |
135 return false; | |
136 | |
137 compositor->AddObserver(this); | |
138 | |
139 // Starts timer. | |
140 timer_.Start(FROM_HERE, oracle_proxy_->capture_period(), | |
141 base::Bind(&DesktopVideoCaptureMachine::Capture, AsWeakPtr(), | |
142 false)); | |
143 | |
144 started_ = true; | |
145 return true; | |
146 } | |
147 | |
148 void DesktopVideoCaptureMachine::Stop() { | |
149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
150 | |
151 // Stop observing window events. | |
152 if (desktop_window_) | |
153 desktop_window_->RemoveObserver(this); | |
154 | |
155 // Stop observing compositor updates. | |
156 if (desktop_layer_) { | |
157 ui::Compositor* compositor = desktop_layer_->GetCompositor(); | |
158 if (compositor) | |
159 compositor->RemoveObserver(this); | |
160 } | |
161 | |
162 // Stop timer. | |
163 timer_.Stop(); | |
164 | |
165 started_ = false; | |
166 } | |
167 | |
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())); | |
173 } | |
174 } | |
175 | |
176 void DesktopVideoCaptureMachine::Capture(bool dirty) { | |
177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
178 | |
179 // Do not capture if the desktop layer is already destroyed. | |
180 if (!desktop_layer_) | |
181 return; | |
182 | |
183 scoped_refptr<media::VideoFrame> frame; | |
184 ThreadSafeCaptureOracle::CaptureFrameCallback capture_frame_cb; | |
185 | |
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()); | |
200 } | |
201 } | |
202 | |
203 static void CopyOutputFinishedForVideo( | |
204 base::Time start_time, | |
205 const ThreadSafeCaptureOracle::CaptureFrameCallback& capture_frame_cb, | |
206 scoped_ptr<cc::SingleReleaseCallback> release_callback, | |
207 bool result) { | |
208 release_callback->Run(0, false); | |
209 capture_frame_cb.Run(start_time, result); | |
210 } | |
211 | |
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()) | |
218 return; | |
219 | |
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 | |
225 // pixels. | |
226 gfx::Rect region_in_frame = | |
227 media::ComputeLetterboxRegion(gfx::Rect(video_frame->coded_size()), | |
228 result->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()) | |
234 return; | |
235 | |
236 ImageTransportFactory* factory = ImageTransportFactory::GetInstance(); | |
237 GLHelper* gl_helper = factory->GetGLHelper(); | |
238 if (!gl_helper) | |
239 return; | |
240 | |
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()) | |
246 return; | |
247 | |
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, | |
255 result_rect.size(), | |
256 result_rect, | |
257 video_frame->coded_size(), | |
258 region_in_frame, | |
259 true, | |
260 true)); | |
261 } | |
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))); | |
266 } | |
267 | |
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_); | |
273 | |
274 // Post task to update capture size on UI thread. | |
275 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( | |
276 &DesktopVideoCaptureMachine::UpdateCaptureSize, AsWeakPtr())); | |
277 } | |
278 | |
279 void DesktopVideoCaptureMachine::OnWindowDestroyed(aura::Window* window) { | |
280 DCHECK(desktop_window_ && window == desktop_window_); | |
281 desktop_window_ = NULL; | |
282 desktop_layer_ = NULL; | |
283 | |
284 // Post task to stop capture on UI thread. | |
285 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( | |
286 &DesktopVideoCaptureMachine::Stop, AsWeakPtr())); | |
287 } | |
288 | |
289 void DesktopVideoCaptureMachine::OnCompositingEnded( | |
290 ui::Compositor* compositor) { | |
291 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( | |
292 &DesktopVideoCaptureMachine::Capture, AsWeakPtr(), true)); | |
293 } | |
294 | |
295 } // namespace | |
296 | |
297 DesktopCaptureDeviceAsh::DesktopCaptureDeviceAsh( | |
298 const DesktopMediaID& source) | |
299 : impl_(new VideoCaptureDeviceImpl(scoped_ptr<VideoCaptureMachine>( | |
300 new DesktopVideoCaptureMachine(source)))) {} | |
301 | |
302 DesktopCaptureDeviceAsh::~DesktopCaptureDeviceAsh() { | |
303 DVLOG(2) << "DesktopCaptureDeviceAsh@" << this << " destroying."; | |
304 } | |
305 | |
306 // static | |
307 media::VideoCaptureDevice* DesktopCaptureDeviceAsh::Create( | |
308 const DesktopMediaID& source) { | |
309 // This implementation only supports screen capture. | |
310 if (source.type != DesktopMediaID::TYPE_SCREEN) | |
311 return NULL; | |
312 | |
313 return new DesktopCaptureDeviceAsh(source); | |
314 } | |
315 | |
316 void DesktopCaptureDeviceAsh::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()); | |
321 } | |
322 | |
323 void DesktopCaptureDeviceAsh::StopAndDeAllocate() { | |
324 impl_->StopAndDeAllocate(); | |
325 } | |
326 | |
327 } // namespace content | |
OLD | NEW |