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/media/capture/desktop_capture_device.h" 6 7 #include "base/bind.h" 8 #include "base/location.h" 9 #include "base/logging.h" 10 #include "base/metrics/field_trial.h" 11 #include "base/metrics/histogram.h" 12 #include "base/sequenced_task_runner.h" 13 #include "base/strings/string_number_conversions.h" 14 #include "base/synchronization/lock.h" 15 #include "base/threading/sequenced_worker_pool.h" 16 #include "base/threading/thread.h" 17 #include "content/browser/media/capture/desktop_capture_device_uma_types.h" 18 #include "content/public/browser/browser_thread.h" 19 #include "content/public/browser/desktop_media_id.h" 20 #include "media/base/video_util.h" 21 #include "third_party/libyuv/include/libyuv/scale_argb.h" 22 #include "third_party/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h" 23 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h" 24 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h" 25 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" 26 #include "third_party/webrtc/modules/desktop_capture/mouse_cursor_monitor.h" 27 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" 28 #include "third_party/webrtc/modules/desktop_capture/window_capturer.h" 29 30 namespace content { 31 32 namespace { 33 34 // Maximum CPU time percentage of a single core that can be consumed for desktop 35 // capturing. This means that on systems where screen scraping is slow we may 36 // need to capture at frame rate lower than requested. This is necessary to keep 37 // UI responsive. 38 const int kMaximumCpuConsumptionPercentage = 50; 39 40 webrtc::DesktopRect ComputeLetterboxRect( 41 const webrtc::DesktopSize& max_size, 42 const webrtc::DesktopSize& source_size) { 43 gfx::Rect result = media::ComputeLetterboxRegion( 44 gfx::Rect(0, 0, max_size.width(), max_size.height()), 45 gfx::Size(source_size.width(), source_size.height())); 46 return webrtc::DesktopRect::MakeLTRB( 47 result.x(), result.y(), result.right(), result.bottom()); 48 } 49 50 } // namespace 51 52 class DesktopCaptureDevice::Core 53 : public base::RefCountedThreadSafe<Core>, 54 public webrtc::DesktopCapturer::Callback { 55 public: 56 Core(scoped_refptr<base::SequencedTaskRunner> task_runner, 57 scoped_ptr<base::Thread> thread, 58 scoped_ptr<webrtc::DesktopCapturer> capturer, 59 DesktopMediaID::Type type); 60 61 // Implementation of VideoCaptureDevice methods. 62 void AllocateAndStart(const media::VideoCaptureParams& params, 63 scoped_ptr<Client> client); 64 void StopAndDeAllocate(); 65 66 void SetNotificationWindowId(gfx::NativeViewId window_id); 67 68 private: 69 friend class base::RefCountedThreadSafe<Core>; 70 virtual ~Core(); 71 72 // webrtc::DesktopCapturer::Callback interface 73 virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE; 74 virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE; 75 76 // Helper methods that run on the |task_runner_|. Posted from the 77 // corresponding public methods. 78 void DoAllocateAndStart(const media::VideoCaptureParams& params, 79 scoped_ptr<Client> client); 80 void DoStopAndDeAllocate(); 81 82 // Chooses new output properties based on the supplied source size and the 83 // properties requested to Allocate(), and dispatches OnFrameInfo[Changed] 84 // notifications. 85 void RefreshCaptureFormat(const webrtc::DesktopSize& frame_size); 86 87 // Method that is scheduled on |task_runner_| to be called on regular interval 88 // to capture a frame. 89 void OnCaptureTimer(); 90 91 // Captures a frame and schedules timer for the next one. 92 void CaptureFrameAndScheduleNext(); 93 94 // Captures a single frame. 95 void DoCapture(); 96 97 void DoSetNotificationWindowId(gfx::NativeViewId window_id); 98 99 // Task runner used for capturing operations. 100 scoped_refptr<base::SequencedTaskRunner> task_runner_; 101 102 // The thread on which the capturer is running. 103 scoped_ptr<base::Thread> thread_; 104 105 // The underlying DesktopCapturer instance used to capture frames. 106 scoped_ptr<webrtc::DesktopCapturer> desktop_capturer_; 107 108 // The device client which proxies device events to the controller. Accessed 109 // on the task_runner_ thread. 110 scoped_ptr<Client> client_; 111 112 // Requested video capture format (width, height, frame rate, etc). 113 media::VideoCaptureParams requested_params_; 114 115 // Actual video capture format being generated. 116 media::VideoCaptureFormat capture_format_; 117 118 // Size of frame most recently captured from the source. 119 webrtc::DesktopSize previous_frame_size_; 120 121 // DesktopFrame into which captured frames are down-scaled and/or letterboxed, 122 // depending upon the caller's requested capture capabilities. If frames can 123 // be returned to the caller directly then this is NULL. 124 scoped_ptr<webrtc::DesktopFrame> output_frame_; 125 126 // Sub-rectangle of |output_frame_| into which the source will be scaled 127 // and/or letterboxed. 128 webrtc::DesktopRect output_rect_; 129 130 // True when we have delayed OnCaptureTimer() task posted on 131 // |task_runner_|. 132 bool capture_task_posted_; 133 134 // True when waiting for |desktop_capturer_| to capture current frame. 135 bool capture_in_progress_; 136 137 // True if the first capture call has returned. Used to log the first capture 138 // result. 139 bool first_capture_returned_; 140 141 // The type of the capturer. 142 DesktopMediaID::Type capturer_type_; 143 144 scoped_ptr<webrtc::BasicDesktopFrame> black_frame_; 145 146 DISALLOW_COPY_AND_ASSIGN(Core); 147 }; 148 149 DesktopCaptureDevice::Core::Core( 150 scoped_refptr<base::SequencedTaskRunner> task_runner, 151 scoped_ptr<base::Thread> thread, 152 scoped_ptr<webrtc::DesktopCapturer> capturer, 153 DesktopMediaID::Type type) 154 : task_runner_(task_runner), 155 thread_(thread.Pass()), 156 desktop_capturer_(capturer.Pass()), 157 capture_task_posted_(false), 158 capture_in_progress_(false), 159 first_capture_returned_(false), 160 capturer_type_(type) { 161 DCHECK(!task_runner_.get() || !thread_.get()); 162 if (thread_.get()) 163 task_runner_ = thread_->message_loop_proxy(); 164 } 165 166 DesktopCaptureDevice::Core::~Core() { 167 } 168 169 void DesktopCaptureDevice::Core::AllocateAndStart( 170 const media::VideoCaptureParams& params, 171 scoped_ptr<Client> client) { 172 DCHECK_GT(params.requested_format.frame_size.GetArea(), 0); 173 DCHECK_GT(params.requested_format.frame_rate, 0); 174 175 task_runner_->PostTask( 176 FROM_HERE, 177 base::Bind( 178 &Core::DoAllocateAndStart, this, params, base::Passed(&client))); 179 } 180 181 void DesktopCaptureDevice::Core::StopAndDeAllocate() { 182 task_runner_->PostTask(FROM_HERE, 183 base::Bind(&Core::DoStopAndDeAllocate, this)); 184 } 185 186 void DesktopCaptureDevice::Core::SetNotificationWindowId( 187 gfx::NativeViewId window_id) { 188 task_runner_->PostTask( 189 FROM_HERE, base::Bind(&Core::DoSetNotificationWindowId, this, window_id)); 190 } 191 192 webrtc::SharedMemory* 193 DesktopCaptureDevice::Core::CreateSharedMemory(size_t size) { 194 return NULL; 195 } 196 197 void DesktopCaptureDevice::Core::OnCaptureCompleted( 198 webrtc::DesktopFrame* frame) { 199 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 200 DCHECK(capture_in_progress_); 201 202 if (!first_capture_returned_) { 203 first_capture_returned_ = true; 204 if (capturer_type_ == DesktopMediaID::TYPE_SCREEN) { 205 IncrementDesktopCaptureCounter(frame ? FIRST_SCREEN_CAPTURE_SUCCEEDED 206 : FIRST_SCREEN_CAPTURE_FAILED); 207 } else { 208 IncrementDesktopCaptureCounter(frame ? FIRST_WINDOW_CAPTURE_SUCCEEDED 209 : FIRST_WINDOW_CAPTURE_FAILED); 210 } 211 } 212 213 capture_in_progress_ = false; 214 215 if (!frame) { 216 std::string log("Failed to capture a frame."); 217 LOG(ERROR) << log; 218 client_->OnError(log); 219 return; 220 } 221 222 if (!client_) 223 return; 224 225 base::TimeDelta capture_time( 226 base::TimeDelta::FromMilliseconds(frame->capture_time_ms())); 227 UMA_HISTOGRAM_TIMES( 228 capturer_type_ == DesktopMediaID::TYPE_SCREEN ? kUmaScreenCaptureTime 229 : kUmaWindowCaptureTime, 230 capture_time); 231 232 scoped_ptr<webrtc::DesktopFrame> owned_frame(frame); 233 234 // On OSX We receive a 1x1 frame when the shared window is minimized. It 235 // cannot be subsampled to I420 and will be dropped downstream. So we replace 236 // it with a black frame to avoid the video appearing frozen at the last 237 // frame. 238 if (frame->size().width() == 1 || frame->size().height() == 1) { 239 if (!black_frame_.get()) { 240 black_frame_.reset( 241 new webrtc::BasicDesktopFrame( 242 webrtc::DesktopSize(capture_format_.frame_size.width(), 243 capture_format_.frame_size.height()))); 244 memset(black_frame_->data(), 245 0, 246 black_frame_->stride() * black_frame_->size().height()); 247 } 248 owned_frame.reset(); 249 frame = black_frame_.get(); 250 } 251 252 // Handle initial frame size and size changes. 253 RefreshCaptureFormat(frame->size()); 254 255 webrtc::DesktopSize output_size(capture_format_.frame_size.width(), 256 capture_format_.frame_size.height()); 257 size_t output_bytes = output_size.width() * output_size.height() * 258 webrtc::DesktopFrame::kBytesPerPixel; 259 const uint8_t* output_data = NULL; 260 scoped_ptr<uint8_t[]> flipped_frame_buffer; 261 262 if (frame->size().equals(output_size)) { 263 // If the captured frame matches the output size, we can return the pixel 264 // data directly, without scaling. 265 output_data = frame->data(); 266 267 // If the |frame| generated by the screen capturer is inverted then we need 268 // to flip |frame|. 269 // This happens only on a specific platform. Refer to crbug.com/306876. 270 if (frame->stride() < 0) { 271 int height = frame->size().height(); 272 int bytes_per_row = 273 frame->size().width() * webrtc::DesktopFrame::kBytesPerPixel; 274 flipped_frame_buffer.reset(new uint8_t[output_bytes]); 275 uint8_t* dest = flipped_frame_buffer.get(); 276 for (int row = 0; row < height; ++row) { 277 memcpy(dest, output_data, bytes_per_row); 278 dest += bytes_per_row; 279 output_data += frame->stride(); 280 } 281 output_data = flipped_frame_buffer.get(); 282 } 283 } else { 284 // Otherwise we need to down-scale and/or letterbox to the target format. 285 286 // Allocate a buffer of the correct size to scale the frame into. 287 // |output_frame_| is cleared whenever |output_rect_| changes, so we don't 288 // need to worry about clearing out stale pixel data in letterboxed areas. 289 if (!output_frame_) { 290 output_frame_.reset(new webrtc::BasicDesktopFrame(output_size)); 291 memset(output_frame_->data(), 0, output_bytes); 292 } 293 DCHECK(output_frame_->size().equals(output_size)); 294 295 // TODO(wez): Optimize this to scale only changed portions of the output, 296 // using ARGBScaleClip(). 297 uint8_t* output_rect_data = output_frame_->data() + 298 output_frame_->stride() * output_rect_.top() + 299 webrtc::DesktopFrame::kBytesPerPixel * output_rect_.left(); 300 libyuv::ARGBScale(frame->data(), frame->stride(), 301 frame->size().width(), frame->size().height(), 302 output_rect_data, output_frame_->stride(), 303 output_rect_.width(), output_rect_.height(), 304 libyuv::kFilterBilinear); 305 output_data = output_frame_->data(); 306 } 307 308 client_->OnIncomingCapturedData( 309 output_data, output_bytes, capture_format_, 0, base::TimeTicks::Now()); 310 } 311 312 void DesktopCaptureDevice::Core::DoAllocateAndStart( 313 const media::VideoCaptureParams& params, 314 scoped_ptr<Client> client) { 315 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 316 DCHECK(desktop_capturer_); 317 DCHECK(client.get()); 318 DCHECK(!client_.get()); 319 320 client_ = client.Pass(); 321 requested_params_ = params; 322 323 capture_format_ = requested_params_.requested_format; 324 325 // This capturer always outputs ARGB, non-interlaced. 326 capture_format_.pixel_format = media::PIXEL_FORMAT_ARGB; 327 328 desktop_capturer_->Start(this); 329 330 CaptureFrameAndScheduleNext(); 331 } 332 333 void DesktopCaptureDevice::Core::DoStopAndDeAllocate() { 334 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 335 client_.reset(); 336 output_frame_.reset(); 337 previous_frame_size_.set(0, 0); 338 desktop_capturer_.reset(); 339 } 340 341 void DesktopCaptureDevice::Core::RefreshCaptureFormat( 342 const webrtc::DesktopSize& frame_size) { 343 if (previous_frame_size_.equals(frame_size)) 344 return; 345 346 // Clear the output frame, if any, since it will either need resizing, or 347 // clearing of stale data in letterbox areas, anyway. 348 output_frame_.reset(); 349 350 if (previous_frame_size_.is_empty() || 351 requested_params_.allow_resolution_change) { 352 // If this is the first frame, or the receiver supports variable resolution 353 // then determine the output size by treating the requested width & height 354 // as maxima. 355 if (frame_size.width() > 356 requested_params_.requested_format.frame_size.width() || 357 frame_size.height() > 358 requested_params_.requested_format.frame_size.height()) { 359 output_rect_ = ComputeLetterboxRect( 360 webrtc::DesktopSize( 361 requested_params_.requested_format.frame_size.width(), 362 requested_params_.requested_format.frame_size.height()), 363 frame_size); 364 output_rect_.Translate(-output_rect_.left(), -output_rect_.top()); 365 } else { 366 output_rect_ = webrtc::DesktopRect::MakeSize(frame_size); 367 } 368 capture_format_.frame_size.SetSize(output_rect_.width(), 369 output_rect_.height()); 370 } else { 371 // Otherwise the output frame size cannot change, so just scale and 372 // letterbox. 373 output_rect_ = ComputeLetterboxRect( 374 webrtc::DesktopSize(capture_format_.frame_size.width(), 375 capture_format_.frame_size.height()), 376 frame_size); 377 } 378 379 previous_frame_size_ = frame_size; 380 } 381 382 void DesktopCaptureDevice::Core::OnCaptureTimer() { 383 DCHECK(capture_task_posted_); 384 capture_task_posted_ = false; 385 386 if (!client_) 387 return; 388 389 CaptureFrameAndScheduleNext(); 390 } 391 392 void DesktopCaptureDevice::Core::CaptureFrameAndScheduleNext() { 393 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 394 DCHECK(!capture_task_posted_); 395 396 base::TimeTicks started_time = base::TimeTicks::Now(); 397 DoCapture(); 398 base::TimeDelta last_capture_duration = base::TimeTicks::Now() - started_time; 399 400 // Limit frame-rate to reduce CPU consumption. 401 base::TimeDelta capture_period = std::max( 402 (last_capture_duration * 100) / kMaximumCpuConsumptionPercentage, 403 base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate); 404 405 // Schedule a task for the next frame. 406 capture_task_posted_ = true; 407 task_runner_->PostDelayedTask( 408 FROM_HERE, base::Bind(&Core::OnCaptureTimer, this), 409 capture_period - last_capture_duration); 410 } 411 412 void DesktopCaptureDevice::Core::DoCapture() { 413 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 414 DCHECK(!capture_in_progress_); 415 416 capture_in_progress_ = true; 417 desktop_capturer_->Capture(webrtc::DesktopRegion()); 418 419 // Currently only synchronous implementations of DesktopCapturer are 420 // supported. 421 DCHECK(!capture_in_progress_); 422 } 423 424 void DesktopCaptureDevice::Core::DoSetNotificationWindowId( 425 gfx::NativeViewId window_id) { 426 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 427 DCHECK(window_id); 428 desktop_capturer_->SetExcludedWindow(window_id); 429 } 430 431 // static 432 scoped_ptr<media::VideoCaptureDevice> DesktopCaptureDevice::Create( 433 const DesktopMediaID& source) { 434 scoped_ptr<base::Thread> ui_thread; 435 436 webrtc::DesktopCaptureOptions options = 437 webrtc::DesktopCaptureOptions::CreateDefault(); 438 // Leave desktop effects enabled during WebRTC captures. 439 options.set_disable_effects(false); 440 441 scoped_ptr<webrtc::DesktopCapturer> capturer; 442 443 switch (source.type) { 444 case DesktopMediaID::TYPE_SCREEN: { 445 scoped_ptr<webrtc::ScreenCapturer> screen_capturer; 446 447 #if defined(OS_WIN) 448 bool magnification_allowed = 449 base::FieldTrialList::FindFullName("ScreenCaptureUseMagnification") == 450 "Enabled"; 451 452 if (magnification_allowed) { 453 // The magnification capturer requires running on a dedicated UI thread. 454 ui_thread.reset(new base::Thread("screenCaptureUIThread")); 455 base::Thread::Options thread_options(base::MessageLoop::TYPE_UI, 0); 456 ui_thread->StartWithOptions(thread_options); 457 458 options.set_allow_use_magnification_api(true); 459 } 460 #endif 461 462 screen_capturer.reset(webrtc::ScreenCapturer::Create(options)); 463 if (screen_capturer && screen_capturer->SelectScreen(source.id)) { 464 capturer.reset(new webrtc::DesktopAndCursorComposer( 465 screen_capturer.release(), 466 webrtc::MouseCursorMonitor::CreateForScreen(options, source.id))); 467 IncrementDesktopCaptureCounter(SCREEN_CAPTURER_CREATED); 468 } 469 break; 470 } 471 472 case DesktopMediaID::TYPE_WINDOW: { 473 scoped_ptr<webrtc::WindowCapturer> window_capturer( 474 webrtc::WindowCapturer::Create(options)); 475 if (window_capturer && window_capturer->SelectWindow(source.id)) { 476 window_capturer->BringSelectedWindowToFront(); 477 capturer.reset(new webrtc::DesktopAndCursorComposer( 478 window_capturer.release(), 479 webrtc::MouseCursorMonitor::CreateForWindow(options, source.id))); 480 IncrementDesktopCaptureCounter(WINDOW_CATPTURER_CREATED); 481 } 482 break; 483 } 484 485 default: { 486 NOTREACHED(); 487 } 488 } 489 490 scoped_ptr<media::VideoCaptureDevice> result; 491 if (capturer) { 492 scoped_refptr<base::SequencedTaskRunner> task_runner; 493 if (!ui_thread.get()) { 494 scoped_refptr<base::SequencedWorkerPool> blocking_pool = 495 BrowserThread::GetBlockingPool(); 496 task_runner = blocking_pool->GetSequencedTaskRunner( 497 blocking_pool->GetSequenceToken()); 498 } 499 result.reset(new DesktopCaptureDevice( 500 task_runner, ui_thread.Pass(), capturer.Pass(), source.type)); 501 } 502 503 return result.Pass(); 504 } 505 506 DesktopCaptureDevice::~DesktopCaptureDevice() { 507 StopAndDeAllocate(); 508 } 509 510 void DesktopCaptureDevice::AllocateAndStart( 511 const media::VideoCaptureParams& params, 512 scoped_ptr<Client> client) { 513 core_->AllocateAndStart(params, client.Pass()); 514 } 515 516 void DesktopCaptureDevice::StopAndDeAllocate() { 517 core_->StopAndDeAllocate(); 518 } 519 520 void DesktopCaptureDevice::SetNotificationWindowId( 521 gfx::NativeViewId window_id) { 522 core_->SetNotificationWindowId(window_id); 523 } 524 525 DesktopCaptureDevice::DesktopCaptureDevice( 526 scoped_refptr<base::SequencedTaskRunner> task_runner, 527 scoped_ptr<base::Thread> thread, 528 scoped_ptr<webrtc::DesktopCapturer> capturer, 529 DesktopMediaID::Type type) 530 : core_(new Core(task_runner, thread.Pass(), capturer.Pass(), type)) { 531 } 532 533 } // namespace content 534