| 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 |