| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "content/browser/renderer_host/media/desktop_capture_device.h" | |
| 6 | |
| 7 #include "base/bind.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" | |
| 25 | |
| 26 namespace content { | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 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 | |
| 33 // UI responsive. | |
| 34 const int kMaximumCpuConsumptionPercentage = 50; | |
| 35 | |
| 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()); | |
| 44 } | |
| 45 | |
| 46 } // namespace | |
| 47 | |
| 48 class DesktopCaptureDevice::Core | |
| 49 : public base::RefCountedThreadSafe<Core>, | |
| 50 public webrtc::DesktopCapturer::Callback { | |
| 51 public: | |
| 52 Core(scoped_refptr<base::SequencedTaskRunner> task_runner, | |
| 53 scoped_ptr<webrtc::DesktopCapturer> capturer); | |
| 54 | |
| 55 // Implementation of VideoCaptureDevice methods. | |
| 56 void AllocateAndStart(const media::VideoCaptureParams& params, | |
| 57 scoped_ptr<Client> client); | |
| 58 void StopAndDeAllocate(); | |
| 59 | |
| 60 private: | |
| 61 friend class base::RefCountedThreadSafe<Core>; | |
| 62 virtual ~Core(); | |
| 63 | |
| 64 // webrtc::DesktopCapturer::Callback interface | |
| 65 virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE; | |
| 66 virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE; | |
| 67 | |
| 68 // Helper methods that run on the |task_runner_|. Posted from the | |
| 69 // corresponding public methods. | |
| 70 void DoAllocateAndStart(const media::VideoCaptureParams& params, | |
| 71 scoped_ptr<Client> client); | |
| 72 void DoStopAndDeAllocate(); | |
| 73 | |
| 74 // Chooses new output properties based on the supplied source size and the | |
| 75 // properties requested to Allocate(), and dispatches OnFrameInfo[Changed] | |
| 76 // notifications. | |
| 77 void RefreshCaptureFormat(const webrtc::DesktopSize& frame_size); | |
| 78 | |
| 79 // Method that is scheduled on |task_runner_| to be called on regular interval | |
| 80 // to capture a frame. | |
| 81 void OnCaptureTimer(); | |
| 82 | |
| 83 // Captures a frame and schedules timer for the next one. | |
| 84 void CaptureFrameAndScheduleNext(); | |
| 85 | |
| 86 // Captures a single frame. | |
| 87 void DoCapture(); | |
| 88 | |
| 89 // Task runner used for capturing operations. | |
| 90 scoped_refptr<base::SequencedTaskRunner> task_runner_; | |
| 91 | |
| 92 // The underlying DesktopCapturer instance used to capture frames. | |
| 93 scoped_ptr<webrtc::DesktopCapturer> desktop_capturer_; | |
| 94 | |
| 95 // The device client which proxies device events to the controller. Accessed | |
| 96 // on the task_runner_ thread. | |
| 97 scoped_ptr<Client> client_; | |
| 98 | |
| 99 // Requested video capture format (width, height, frame rate, etc). | |
| 100 media::VideoCaptureParams requested_params_; | |
| 101 | |
| 102 // Actual video capture format being generated. | |
| 103 media::VideoCaptureFormat capture_format_; | |
| 104 | |
| 105 // Size of frame most recently captured from the source. | |
| 106 webrtc::DesktopSize previous_frame_size_; | |
| 107 | |
| 108 // DesktopFrame into which captured frames are down-scaled and/or letterboxed, | |
| 109 // depending upon the caller's requested capture capabilities. If frames can | |
| 110 // be returned to the caller directly then this is NULL. | |
| 111 scoped_ptr<webrtc::DesktopFrame> output_frame_; | |
| 112 | |
| 113 // Sub-rectangle of |output_frame_| into which the source will be scaled | |
| 114 // and/or letterboxed. | |
| 115 webrtc::DesktopRect output_rect_; | |
| 116 | |
| 117 // True when we have delayed OnCaptureTimer() task posted on | |
| 118 // |task_runner_|. | |
| 119 bool capture_task_posted_; | |
| 120 | |
| 121 // True when waiting for |desktop_capturer_| to capture current frame. | |
| 122 bool capture_in_progress_; | |
| 123 | |
| 124 DISALLOW_COPY_AND_ASSIGN(Core); | |
| 125 }; | |
| 126 | |
| 127 DesktopCaptureDevice::Core::Core( | |
| 128 scoped_refptr<base::SequencedTaskRunner> task_runner, | |
| 129 scoped_ptr<webrtc::DesktopCapturer> capturer) | |
| 130 : task_runner_(task_runner), | |
| 131 desktop_capturer_(capturer.Pass()), | |
| 132 capture_task_posted_(false), | |
| 133 capture_in_progress_(false) {} | |
| 134 | |
| 135 DesktopCaptureDevice::Core::~Core() { | |
| 136 } | |
| 137 | |
| 138 void DesktopCaptureDevice::Core::AllocateAndStart( | |
| 139 const media::VideoCaptureParams& params, | |
| 140 scoped_ptr<Client> client) { | |
| 141 DCHECK_GT(params.requested_format.frame_size.GetArea(), 0); | |
| 142 DCHECK_GT(params.requested_format.frame_rate, 0); | |
| 143 | |
| 144 task_runner_->PostTask( | |
| 145 FROM_HERE, | |
| 146 base::Bind( | |
| 147 &Core::DoAllocateAndStart, this, params, base::Passed(&client))); | |
| 148 } | |
| 149 | |
| 150 void DesktopCaptureDevice::Core::StopAndDeAllocate() { | |
| 151 task_runner_->PostTask(FROM_HERE, | |
| 152 base::Bind(&Core::DoStopAndDeAllocate, this)); | |
| 153 } | |
| 154 | |
| 155 webrtc::SharedMemory* | |
| 156 DesktopCaptureDevice::Core::CreateSharedMemory(size_t size) { | |
| 157 return NULL; | |
| 158 } | |
| 159 | |
| 160 void DesktopCaptureDevice::Core::OnCaptureCompleted( | |
| 161 webrtc::DesktopFrame* frame) { | |
| 162 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
| 163 DCHECK(capture_in_progress_); | |
| 164 | |
| 165 capture_in_progress_ = false; | |
| 166 | |
| 167 if (!frame) { | |
| 168 std::string log("Failed to capture a frame."); | |
| 169 LOG(ERROR) << log; | |
| 170 client_->OnError(log); | |
| 171 return; | |
| 172 } | |
| 173 | |
| 174 if (!client_) | |
| 175 return; | |
| 176 | |
| 177 scoped_ptr<webrtc::DesktopFrame> owned_frame(frame); | |
| 178 | |
| 179 // Handle initial frame size and size changes. | |
| 180 RefreshCaptureFormat(frame->size()); | |
| 181 | |
| 182 webrtc::DesktopSize output_size(capture_format_.frame_size.width(), | |
| 183 capture_format_.frame_size.height()); | |
| 184 size_t output_bytes = output_size.width() * output_size.height() * | |
| 185 webrtc::DesktopFrame::kBytesPerPixel; | |
| 186 const uint8_t* output_data = NULL; | |
| 187 scoped_ptr<uint8_t[]> flipped_frame_buffer; | |
| 188 | |
| 189 if (frame->size().equals(output_size)) { | |
| 190 // If the captured frame matches the output size, we can return the pixel | |
| 191 // data directly, without scaling. | |
| 192 output_data = frame->data(); | |
| 193 | |
| 194 // If the |frame| generated by the screen capturer is inverted then we need | |
| 195 // to flip |frame|. | |
| 196 // This happens only on a specific platform. Refer to crbug.com/306876. | |
| 197 if (frame->stride() < 0) { | |
| 198 int height = frame->size().height(); | |
| 199 int bytes_per_row = | |
| 200 frame->size().width() * webrtc::DesktopFrame::kBytesPerPixel; | |
| 201 flipped_frame_buffer.reset(new uint8_t[output_bytes]); | |
| 202 uint8_t* dest = flipped_frame_buffer.get(); | |
| 203 for (int row = 0; row < height; ++row) { | |
| 204 memcpy(dest, output_data, bytes_per_row); | |
| 205 dest += bytes_per_row; | |
| 206 output_data += frame->stride(); | |
| 207 } | |
| 208 output_data = flipped_frame_buffer.get(); | |
| 209 } | |
| 210 } else { | |
| 211 // Otherwise we need to down-scale and/or letterbox to the target format. | |
| 212 | |
| 213 // Allocate a buffer of the correct size to scale the frame into. | |
| 214 // |output_frame_| is cleared whenever |output_rect_| changes, so we don't | |
| 215 // need to worry about clearing out stale pixel data in letterboxed areas. | |
| 216 if (!output_frame_) { | |
| 217 output_frame_.reset(new webrtc::BasicDesktopFrame(output_size)); | |
| 218 memset(output_frame_->data(), 0, output_bytes); | |
| 219 } | |
| 220 DCHECK(output_frame_->size().equals(output_size)); | |
| 221 | |
| 222 // TODO(wez): Optimize this to scale only changed portions of the output, | |
| 223 // using ARGBScaleClip(). | |
| 224 uint8_t* output_rect_data = output_frame_->data() + | |
| 225 output_frame_->stride() * output_rect_.top() + | |
| 226 webrtc::DesktopFrame::kBytesPerPixel * output_rect_.left(); | |
| 227 libyuv::ARGBScale(frame->data(), frame->stride(), | |
| 228 frame->size().width(), frame->size().height(), | |
| 229 output_rect_data, output_frame_->stride(), | |
| 230 output_rect_.width(), output_rect_.height(), | |
| 231 libyuv::kFilterBilinear); | |
| 232 output_data = output_frame_->data(); | |
| 233 } | |
| 234 | |
| 235 client_->OnIncomingCapturedFrame( | |
| 236 output_data, output_bytes, base::TimeTicks::Now(), 0, capture_format_); | |
| 237 } | |
| 238 | |
| 239 void DesktopCaptureDevice::Core::DoAllocateAndStart( | |
| 240 const media::VideoCaptureParams& params, | |
| 241 scoped_ptr<Client> client) { | |
| 242 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
| 243 DCHECK(desktop_capturer_); | |
| 244 DCHECK(client.get()); | |
| 245 DCHECK(!client_.get()); | |
| 246 | |
| 247 client_ = client.Pass(); | |
| 248 requested_params_ = params; | |
| 249 | |
| 250 capture_format_ = requested_params_.requested_format; | |
| 251 | |
| 252 // This capturer always outputs ARGB, non-interlaced. | |
| 253 capture_format_.pixel_format = media::PIXEL_FORMAT_ARGB; | |
| 254 | |
| 255 desktop_capturer_->Start(this); | |
| 256 | |
| 257 CaptureFrameAndScheduleNext(); | |
| 258 } | |
| 259 | |
| 260 void DesktopCaptureDevice::Core::DoStopAndDeAllocate() { | |
| 261 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
| 262 client_.reset(); | |
| 263 output_frame_.reset(); | |
| 264 previous_frame_size_.set(0, 0); | |
| 265 desktop_capturer_.reset(); | |
| 266 } | |
| 267 | |
| 268 void DesktopCaptureDevice::Core::RefreshCaptureFormat( | |
| 269 const webrtc::DesktopSize& frame_size) { | |
| 270 if (previous_frame_size_.equals(frame_size)) | |
| 271 return; | |
| 272 | |
| 273 // Clear the output frame, if any, since it will either need resizing, or | |
| 274 // clearing of stale data in letterbox areas, anyway. | |
| 275 output_frame_.reset(); | |
| 276 | |
| 277 if (previous_frame_size_.is_empty() || | |
| 278 requested_params_.allow_resolution_change) { | |
| 279 // If this is the first frame, or the receiver supports variable resolution | |
| 280 // then determine the output size by treating the requested width & height | |
| 281 // as maxima. | |
| 282 if (frame_size.width() > | |
| 283 requested_params_.requested_format.frame_size.width() || | |
| 284 frame_size.height() > | |
| 285 requested_params_.requested_format.frame_size.height()) { | |
| 286 output_rect_ = ComputeLetterboxRect( | |
| 287 webrtc::DesktopSize( | |
| 288 requested_params_.requested_format.frame_size.width(), | |
| 289 requested_params_.requested_format.frame_size.height()), | |
| 290 frame_size); | |
| 291 output_rect_.Translate(-output_rect_.left(), -output_rect_.top()); | |
| 292 } else { | |
| 293 output_rect_ = webrtc::DesktopRect::MakeSize(frame_size); | |
| 294 } | |
| 295 capture_format_.frame_size.SetSize(output_rect_.width(), | |
| 296 output_rect_.height()); | |
| 297 } else { | |
| 298 // Otherwise the output frame size cannot change, so just scale and | |
| 299 // letterbox. | |
| 300 output_rect_ = ComputeLetterboxRect( | |
| 301 webrtc::DesktopSize(capture_format_.frame_size.width(), | |
| 302 capture_format_.frame_size.height()), | |
| 303 frame_size); | |
| 304 } | |
| 305 | |
| 306 previous_frame_size_ = frame_size; | |
| 307 } | |
| 308 | |
| 309 void DesktopCaptureDevice::Core::OnCaptureTimer() { | |
| 310 DCHECK(capture_task_posted_); | |
| 311 capture_task_posted_ = false; | |
| 312 | |
| 313 if (!client_) | |
| 314 return; | |
| 315 | |
| 316 CaptureFrameAndScheduleNext(); | |
| 317 } | |
| 318 | |
| 319 void DesktopCaptureDevice::Core::CaptureFrameAndScheduleNext() { | |
| 320 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
| 321 DCHECK(!capture_task_posted_); | |
| 322 | |
| 323 base::TimeTicks started_time = base::TimeTicks::Now(); | |
| 324 DoCapture(); | |
| 325 base::TimeDelta last_capture_duration = base::TimeTicks::Now() - started_time; | |
| 326 | |
| 327 // Limit frame-rate to reduce CPU consumption. | |
| 328 base::TimeDelta capture_period = std::max( | |
| 329 (last_capture_duration * 100) / kMaximumCpuConsumptionPercentage, | |
| 330 base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate); | |
| 331 | |
| 332 // Schedule a task for the next frame. | |
| 333 capture_task_posted_ = true; | |
| 334 task_runner_->PostDelayedTask( | |
| 335 FROM_HERE, base::Bind(&Core::OnCaptureTimer, this), | |
| 336 capture_period - last_capture_duration); | |
| 337 } | |
| 338 | |
| 339 void DesktopCaptureDevice::Core::DoCapture() { | |
| 340 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
| 341 DCHECK(!capture_in_progress_); | |
| 342 | |
| 343 capture_in_progress_ = true; | |
| 344 desktop_capturer_->Capture(webrtc::DesktopRegion()); | |
| 345 | |
| 346 // Currently only synchronous implementations of DesktopCapturer are | |
| 347 // supported. | |
| 348 DCHECK(!capture_in_progress_); | |
| 349 } | |
| 350 | |
| 351 // static | |
| 352 scoped_ptr<media::VideoCaptureDevice> DesktopCaptureDevice::Create( | |
| 353 const DesktopMediaID& source) { | |
| 354 scoped_refptr<base::SequencedWorkerPool> blocking_pool = | |
| 355 BrowserThread::GetBlockingPool(); | |
| 356 scoped_refptr<base::SequencedTaskRunner> task_runner = | |
| 357 blocking_pool->GetSequencedTaskRunner( | |
| 358 blocking_pool->GetSequenceToken()); | |
| 359 | |
| 360 webrtc::DesktopCaptureOptions options = | |
| 361 webrtc::DesktopCaptureOptions::CreateDefault(); | |
| 362 // Leave desktop effects enabled during WebRTC captures. | |
| 363 options.set_disable_effects(false); | |
| 364 | |
| 365 scoped_ptr<webrtc::DesktopCapturer> capturer; | |
| 366 | |
| 367 switch (source.type) { | |
| 368 case DesktopMediaID::TYPE_SCREEN: { | |
| 369 scoped_ptr<webrtc::ScreenCapturer> screen_capturer; | |
| 370 screen_capturer.reset(webrtc::ScreenCapturer::Create(options)); | |
| 371 if (screen_capturer && screen_capturer->SelectScreen(source.id)) { | |
| 372 capturer.reset(new webrtc::DesktopAndCursorComposer( | |
| 373 screen_capturer.release(), | |
| 374 webrtc::MouseCursorMonitor::CreateForScreen(options, source.id))); | |
| 375 } | |
| 376 break; | |
| 377 } | |
| 378 | |
| 379 case DesktopMediaID::TYPE_WINDOW: { | |
| 380 scoped_ptr<webrtc::WindowCapturer> window_capturer( | |
| 381 webrtc::WindowCapturer::Create(options)); | |
| 382 if (window_capturer && window_capturer->SelectWindow(source.id)) { | |
| 383 capturer.reset(new webrtc::DesktopAndCursorComposer( | |
| 384 window_capturer.release(), | |
| 385 webrtc::MouseCursorMonitor::CreateForWindow(options, source.id))); | |
| 386 } | |
| 387 break; | |
| 388 } | |
| 389 | |
| 390 default: { | |
| 391 NOTREACHED(); | |
| 392 } | |
| 393 } | |
| 394 | |
| 395 scoped_ptr<media::VideoCaptureDevice> result; | |
| 396 if (capturer) | |
| 397 result.reset(new DesktopCaptureDevice(task_runner, capturer.Pass())); | |
| 398 | |
| 399 return result.Pass(); | |
| 400 } | |
| 401 | |
| 402 DesktopCaptureDevice::DesktopCaptureDevice( | |
| 403 scoped_refptr<base::SequencedTaskRunner> task_runner, | |
| 404 scoped_ptr<webrtc::DesktopCapturer> capturer) | |
| 405 : core_(new Core(task_runner, capturer.Pass())) {} | |
| 406 | |
| 407 DesktopCaptureDevice::~DesktopCaptureDevice() { | |
| 408 StopAndDeAllocate(); | |
| 409 } | |
| 410 | |
| 411 void DesktopCaptureDevice::AllocateAndStart( | |
| 412 const media::VideoCaptureParams& params, | |
| 413 scoped_ptr<Client> client) { | |
| 414 core_->AllocateAndStart(params, client.Pass()); | |
| 415 } | |
| 416 | |
| 417 void DesktopCaptureDevice::StopAndDeAllocate() { | |
| 418 core_->StopAndDeAllocate(); | |
| 419 } | |
| 420 | |
| 421 } // namespace content | |
| OLD | NEW |