1/* 2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#include "webrtc/modules/desktop_capture/screen_capturer.h" 12 13#include <stddef.h> 14#include <set> 15 16#include <ApplicationServices/ApplicationServices.h> 17#include <Cocoa/Cocoa.h> 18#include <dlfcn.h> 19#include <IOKit/pwr_mgt/IOPMLib.h> 20#include <OpenGL/CGLMacro.h> 21#include <OpenGL/OpenGL.h> 22 23#include "webrtc/base/macutils.h" 24#include "webrtc/base/scoped_ptr.h" 25#include "webrtc/modules/desktop_capture/desktop_capture_options.h" 26#include "webrtc/modules/desktop_capture/desktop_frame.h" 27#include "webrtc/modules/desktop_capture/desktop_geometry.h" 28#include "webrtc/modules/desktop_capture/desktop_region.h" 29#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" 30#include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h" 31#include "webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h" 32#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" 33#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" 34#include "webrtc/system_wrappers/include/logging.h" 35#include "webrtc/system_wrappers/include/tick_util.h" 36 37namespace webrtc { 38 39namespace { 40 41// Definitions used to dynamic-link to deprecated OS 10.6 functions. 42const char* kApplicationServicesLibraryName = 43 "/System/Library/Frameworks/ApplicationServices.framework/" 44 "ApplicationServices"; 45typedef void* (*CGDisplayBaseAddressFunc)(CGDirectDisplayID); 46typedef size_t (*CGDisplayBytesPerRowFunc)(CGDirectDisplayID); 47typedef size_t (*CGDisplayBitsPerPixelFunc)(CGDirectDisplayID); 48const char* kOpenGlLibraryName = 49 "/System/Library/Frameworks/OpenGL.framework/OpenGL"; 50typedef CGLError (*CGLSetFullScreenFunc)(CGLContextObj); 51 52// Standard Mac displays have 72dpi, but we report 96dpi for 53// consistency with Windows and Linux. 54const int kStandardDPI = 96; 55 56// Scales all coordinates of a rect by a specified factor. 57DesktopRect ScaleAndRoundCGRect(const CGRect& rect, float scale) { 58 return DesktopRect::MakeLTRB( 59 static_cast<int>(floor(rect.origin.x * scale)), 60 static_cast<int>(floor(rect.origin.y * scale)), 61 static_cast<int>(ceil((rect.origin.x + rect.size.width) * scale)), 62 static_cast<int>(ceil((rect.origin.y + rect.size.height) * scale))); 63} 64 65// Copy pixels in the |rect| from |src_place| to |dest_plane|. |rect| should be 66// relative to the origin of |src_plane| and |dest_plane|. 67void CopyRect(const uint8_t* src_plane, 68 int src_plane_stride, 69 uint8_t* dest_plane, 70 int dest_plane_stride, 71 int bytes_per_pixel, 72 const DesktopRect& rect) { 73 // Get the address of the starting point. 74 const int src_y_offset = src_plane_stride * rect.top(); 75 const int dest_y_offset = dest_plane_stride * rect.top(); 76 const int x_offset = bytes_per_pixel * rect.left(); 77 src_plane += src_y_offset + x_offset; 78 dest_plane += dest_y_offset + x_offset; 79 80 // Copy pixels in the rectangle line by line. 81 const int bytes_per_line = bytes_per_pixel * rect.width(); 82 const int height = rect.height(); 83 for (int i = 0 ; i < height; ++i) { 84 memcpy(dest_plane, src_plane, bytes_per_line); 85 src_plane += src_plane_stride; 86 dest_plane += dest_plane_stride; 87 } 88} 89 90// Returns an array of CGWindowID for all the on-screen windows except 91// |window_to_exclude|, or NULL if the window is not found or it fails. The 92// caller should release the returned CFArrayRef. 93CFArrayRef CreateWindowListWithExclusion(CGWindowID window_to_exclude) { 94 if (!window_to_exclude) 95 return NULL; 96 97 CFArrayRef all_windows = CGWindowListCopyWindowInfo( 98 kCGWindowListOptionOnScreenOnly, kCGNullWindowID); 99 if (!all_windows) 100 return NULL; 101 102 CFMutableArrayRef returned_array = CFArrayCreateMutable( 103 NULL, CFArrayGetCount(all_windows), NULL); 104 105 bool found = false; 106 for (CFIndex i = 0; i < CFArrayGetCount(all_windows); ++i) { 107 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 108 CFArrayGetValueAtIndex(all_windows, i)); 109 110 CFNumberRef id_ref = reinterpret_cast<CFNumberRef>( 111 CFDictionaryGetValue(window, kCGWindowNumber)); 112 113 CGWindowID id; 114 CFNumberGetValue(id_ref, kCFNumberIntType, &id); 115 if (id == window_to_exclude) { 116 found = true; 117 continue; 118 } 119 CFArrayAppendValue(returned_array, reinterpret_cast<void *>(id)); 120 } 121 CFRelease(all_windows); 122 123 if (!found) { 124 CFRelease(returned_array); 125 returned_array = NULL; 126 } 127 return returned_array; 128} 129 130// Returns the bounds of |window| in physical pixels, enlarged by a small amount 131// on four edges to take account of the border/shadow effects. 132DesktopRect GetExcludedWindowPixelBounds(CGWindowID window, 133 float dip_to_pixel_scale) { 134 // The amount of pixels to add to the actual window bounds to take into 135 // account of the border/shadow effects. 136 static const int kBorderEffectSize = 20; 137 CGRect rect; 138 CGWindowID ids[1]; 139 ids[0] = window; 140 141 CFArrayRef window_id_array = 142 CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL); 143 CFArrayRef window_array = 144 CGWindowListCreateDescriptionFromArray(window_id_array); 145 146 if (CFArrayGetCount(window_array) > 0) { 147 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 148 CFArrayGetValueAtIndex(window_array, 0)); 149 CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>( 150 CFDictionaryGetValue(window, kCGWindowBounds)); 151 CGRectMakeWithDictionaryRepresentation(bounds_ref, &rect); 152 } 153 154 CFRelease(window_id_array); 155 CFRelease(window_array); 156 157 rect.origin.x -= kBorderEffectSize; 158 rect.origin.y -= kBorderEffectSize; 159 rect.size.width += kBorderEffectSize * 2; 160 rect.size.height += kBorderEffectSize * 2; 161 // |rect| is in DIP, so convert to physical pixels. 162 return ScaleAndRoundCGRect(rect, dip_to_pixel_scale); 163} 164 165// Create an image of the given region using the given |window_list|. 166// |pixel_bounds| should be in the primary display's coordinate in physical 167// pixels. The caller should release the returned CGImageRef and CFDataRef. 168CGImageRef CreateExcludedWindowRegionImage(const DesktopRect& pixel_bounds, 169 float dip_to_pixel_scale, 170 CFArrayRef window_list, 171 CFDataRef* data_ref) { 172 CGRect window_bounds; 173 // The origin is in DIP while the size is in physical pixels. That's what 174 // CGWindowListCreateImageFromArray expects. 175 window_bounds.origin.x = pixel_bounds.left() / dip_to_pixel_scale; 176 window_bounds.origin.y = pixel_bounds.top() / dip_to_pixel_scale; 177 window_bounds.size.width = pixel_bounds.width(); 178 window_bounds.size.height = pixel_bounds.height(); 179 180 CGImageRef excluded_image = CGWindowListCreateImageFromArray( 181 window_bounds, window_list, kCGWindowImageDefault); 182 183 CGDataProviderRef provider = CGImageGetDataProvider(excluded_image); 184 *data_ref = CGDataProviderCopyData(provider); 185 assert(*data_ref); 186 return excluded_image; 187} 188 189// A class to perform video frame capturing for mac. 190class ScreenCapturerMac : public ScreenCapturer { 191 public: 192 explicit ScreenCapturerMac( 193 rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor); 194 virtual ~ScreenCapturerMac(); 195 196 bool Init(); 197 198 // Overridden from ScreenCapturer: 199 void Start(Callback* callback) override; 200 void Capture(const DesktopRegion& region) override; 201 void SetExcludedWindow(WindowId window) override; 202 bool GetScreenList(ScreenList* screens) override; 203 bool SelectScreen(ScreenId id) override; 204 205 private: 206 void GlBlitFast(const DesktopFrame& frame, 207 const DesktopRegion& region); 208 void GlBlitSlow(const DesktopFrame& frame); 209 void CgBlitPreLion(const DesktopFrame& frame, 210 const DesktopRegion& region); 211 // Returns false if the selected screen is no longer valid. 212 bool CgBlitPostLion(const DesktopFrame& frame, 213 const DesktopRegion& region); 214 215 // Called when the screen configuration is changed. 216 void ScreenConfigurationChanged(); 217 218 bool RegisterRefreshAndMoveHandlers(); 219 void UnregisterRefreshAndMoveHandlers(); 220 221 void ScreenRefresh(CGRectCount count, const CGRect *rect_array); 222 void ScreenUpdateMove(CGScreenUpdateMoveDelta delta, 223 size_t count, 224 const CGRect *rect_array); 225 static void ScreenRefreshCallback(CGRectCount count, 226 const CGRect *rect_array, 227 void *user_parameter); 228 static void ScreenUpdateMoveCallback(CGScreenUpdateMoveDelta delta, 229 size_t count, 230 const CGRect *rect_array, 231 void *user_parameter); 232 void ReleaseBuffers(); 233 234 DesktopFrame* CreateFrame(); 235 236 Callback* callback_; 237 238 CGLContextObj cgl_context_; 239 ScopedPixelBufferObject pixel_buffer_object_; 240 241 // Queue of the frames buffers. 242 ScreenCaptureFrameQueue queue_; 243 244 // Current display configuration. 245 MacDesktopConfiguration desktop_config_; 246 247 // Currently selected display, or 0 if the full desktop is selected. On OS X 248 // 10.6 and before, this is always 0. 249 CGDirectDisplayID current_display_; 250 251 // The physical pixel bounds of the current screen. 252 DesktopRect screen_pixel_bounds_; 253 254 // The dip to physical pixel scale of the current screen. 255 float dip_to_pixel_scale_; 256 257 // A thread-safe list of invalid rectangles, and the size of the most 258 // recently captured screen. 259 ScreenCapturerHelper helper_; 260 261 // Contains an invalid region from the previous capture. 262 DesktopRegion last_invalid_region_; 263 264 // Monitoring display reconfiguration. 265 rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor_; 266 267 // Power management assertion to prevent the screen from sleeping. 268 IOPMAssertionID power_assertion_id_display_; 269 270 // Power management assertion to indicate that the user is active. 271 IOPMAssertionID power_assertion_id_user_; 272 273 // Dynamically link to deprecated APIs for Mac OS X 10.6 support. 274 void* app_services_library_; 275 CGDisplayBaseAddressFunc cg_display_base_address_; 276 CGDisplayBytesPerRowFunc cg_display_bytes_per_row_; 277 CGDisplayBitsPerPixelFunc cg_display_bits_per_pixel_; 278 void* opengl_library_; 279 CGLSetFullScreenFunc cgl_set_full_screen_; 280 281 CGWindowID excluded_window_; 282 283 RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac); 284}; 285 286// DesktopFrame wrapper that flips wrapped frame upside down by inverting 287// stride. 288class InvertedDesktopFrame : public DesktopFrame { 289 public: 290 // Takes ownership of |frame|. 291 InvertedDesktopFrame(DesktopFrame* frame) 292 : DesktopFrame( 293 frame->size(), -frame->stride(), 294 frame->data() + (frame->size().height() - 1) * frame->stride(), 295 frame->shared_memory()), 296 original_frame_(frame) { 297 set_dpi(frame->dpi()); 298 set_capture_time_ms(frame->capture_time_ms()); 299 mutable_updated_region()->Swap(frame->mutable_updated_region()); 300 } 301 virtual ~InvertedDesktopFrame() {} 302 303 private: 304 rtc::scoped_ptr<DesktopFrame> original_frame_; 305 306 RTC_DISALLOW_COPY_AND_ASSIGN(InvertedDesktopFrame); 307}; 308 309ScreenCapturerMac::ScreenCapturerMac( 310 rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor) 311 : callback_(NULL), 312 cgl_context_(NULL), 313 current_display_(0), 314 dip_to_pixel_scale_(1.0f), 315 desktop_config_monitor_(desktop_config_monitor), 316 power_assertion_id_display_(kIOPMNullAssertionID), 317 power_assertion_id_user_(kIOPMNullAssertionID), 318 app_services_library_(NULL), 319 cg_display_base_address_(NULL), 320 cg_display_bytes_per_row_(NULL), 321 cg_display_bits_per_pixel_(NULL), 322 opengl_library_(NULL), 323 cgl_set_full_screen_(NULL), 324 excluded_window_(0) { 325} 326 327ScreenCapturerMac::~ScreenCapturerMac() { 328 if (power_assertion_id_display_ != kIOPMNullAssertionID) { 329 IOPMAssertionRelease(power_assertion_id_display_); 330 power_assertion_id_display_ = kIOPMNullAssertionID; 331 } 332 if (power_assertion_id_user_ != kIOPMNullAssertionID) { 333 IOPMAssertionRelease(power_assertion_id_user_); 334 power_assertion_id_user_ = kIOPMNullAssertionID; 335 } 336 337 ReleaseBuffers(); 338 UnregisterRefreshAndMoveHandlers(); 339 dlclose(app_services_library_); 340 dlclose(opengl_library_); 341} 342 343bool ScreenCapturerMac::Init() { 344 if (!RegisterRefreshAndMoveHandlers()) { 345 return false; 346 } 347 desktop_config_monitor_->Lock(); 348 desktop_config_ = desktop_config_monitor_->desktop_configuration(); 349 desktop_config_monitor_->Unlock(); 350 ScreenConfigurationChanged(); 351 return true; 352} 353 354void ScreenCapturerMac::ReleaseBuffers() { 355 if (cgl_context_) { 356 pixel_buffer_object_.Release(); 357 CGLDestroyContext(cgl_context_); 358 cgl_context_ = NULL; 359 } 360 // The buffers might be in use by the encoder, so don't delete them here. 361 // Instead, mark them as "needs update"; next time the buffers are used by 362 // the capturer, they will be recreated if necessary. 363 queue_.Reset(); 364} 365 366void ScreenCapturerMac::Start(Callback* callback) { 367 assert(!callback_); 368 assert(callback); 369 370 callback_ = callback; 371 372 // Create power management assertions to wake the display and prevent it from 373 // going to sleep on user idle. 374 // TODO(jamiewalch): Use IOPMAssertionDeclareUserActivity on 10.7.3 and above 375 // instead of the following two assertions. 376 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, 377 kIOPMAssertionLevelOn, 378 CFSTR("Chrome Remote Desktop connection active"), 379 &power_assertion_id_display_); 380 // This assertion ensures that the display is woken up if it already asleep 381 // (as used by Apple Remote Desktop). 382 IOPMAssertionCreateWithName(CFSTR("UserIsActive"), 383 kIOPMAssertionLevelOn, 384 CFSTR("Chrome Remote Desktop connection active"), 385 &power_assertion_id_user_); 386} 387 388void ScreenCapturerMac::Capture(const DesktopRegion& region_to_capture) { 389 TickTime capture_start_time = TickTime::Now(); 390 391 queue_.MoveToNextFrame(); 392 393 desktop_config_monitor_->Lock(); 394 MacDesktopConfiguration new_config = 395 desktop_config_monitor_->desktop_configuration(); 396 if (!desktop_config_.Equals(new_config)) { 397 desktop_config_ = new_config; 398 // If the display configuraiton has changed then refresh capturer data 399 // structures. Occasionally, the refresh and move handlers are lost when 400 // the screen mode changes, so re-register them here. 401 UnregisterRefreshAndMoveHandlers(); 402 RegisterRefreshAndMoveHandlers(); 403 ScreenConfigurationChanged(); 404 } 405 406 DesktopRegion region; 407 helper_.TakeInvalidRegion(®ion); 408 409 // If the current buffer is from an older generation then allocate a new one. 410 // Note that we can't reallocate other buffers at this point, since the caller 411 // may still be reading from them. 412 if (!queue_.current_frame()) 413 queue_.ReplaceCurrentFrame(CreateFrame()); 414 415 DesktopFrame* current_frame = queue_.current_frame(); 416 417 bool flip = false; // GL capturers need flipping. 418 if (rtc::GetOSVersionName() >= rtc::kMacOSLion) { 419 // Lion requires us to use their new APIs for doing screen capture. These 420 // APIS currently crash on 10.6.8 if there is no monitor attached. 421 if (!CgBlitPostLion(*current_frame, region)) { 422 desktop_config_monitor_->Unlock(); 423 callback_->OnCaptureCompleted(NULL); 424 return; 425 } 426 } else if (cgl_context_) { 427 flip = true; 428 if (pixel_buffer_object_.get() != 0) { 429 GlBlitFast(*current_frame, region); 430 } else { 431 // See comment in ScopedPixelBufferObject::Init about why the slow 432 // path is always used on 10.5. 433 GlBlitSlow(*current_frame); 434 } 435 } else { 436 CgBlitPreLion(*current_frame, region); 437 } 438 439 DesktopFrame* new_frame = queue_.current_frame()->Share(); 440 *new_frame->mutable_updated_region() = region; 441 442 if (flip) 443 new_frame = new InvertedDesktopFrame(new_frame); 444 445 helper_.set_size_most_recent(new_frame->size()); 446 447 // Signal that we are done capturing data from the display framebuffer, 448 // and accessing display structures. 449 desktop_config_monitor_->Unlock(); 450 451 new_frame->set_capture_time_ms( 452 (TickTime::Now() - capture_start_time).Milliseconds()); 453 callback_->OnCaptureCompleted(new_frame); 454} 455 456void ScreenCapturerMac::SetExcludedWindow(WindowId window) { 457 excluded_window_ = window; 458} 459 460bool ScreenCapturerMac::GetScreenList(ScreenList* screens) { 461 assert(screens->size() == 0); 462 if (rtc::GetOSVersionName() < rtc::kMacOSLion) { 463 // Single monitor cast is not supported on pre OS X 10.7. 464 Screen screen; 465 screen.id = kFullDesktopScreenId; 466 screens->push_back(screen); 467 return true; 468 } 469 470 for (MacDisplayConfigurations::iterator it = desktop_config_.displays.begin(); 471 it != desktop_config_.displays.end(); ++it) { 472 Screen screen; 473 screen.id = static_cast<ScreenId>(it->id); 474 screens->push_back(screen); 475 } 476 return true; 477} 478 479bool ScreenCapturerMac::SelectScreen(ScreenId id) { 480 if (rtc::GetOSVersionName() < rtc::kMacOSLion) { 481 // Ignore the screen selection on unsupported OS. 482 assert(!current_display_); 483 return id == kFullDesktopScreenId; 484 } 485 486 if (id == kFullDesktopScreenId) { 487 current_display_ = 0; 488 } else { 489 const MacDisplayConfiguration* config = 490 desktop_config_.FindDisplayConfigurationById( 491 static_cast<CGDirectDisplayID>(id)); 492 if (!config) 493 return false; 494 current_display_ = config->id; 495 } 496 497 ScreenConfigurationChanged(); 498 return true; 499} 500 501void ScreenCapturerMac::GlBlitFast(const DesktopFrame& frame, 502 const DesktopRegion& region) { 503 // Clip to the size of our current screen. 504 DesktopRect clip_rect = DesktopRect::MakeSize(frame.size()); 505 if (queue_.previous_frame()) { 506 // We are doing double buffer for the capture data so we just need to copy 507 // the invalid region from the previous capture in the current buffer. 508 // TODO(hclam): We can reduce the amount of copying here by subtracting 509 // |capturer_helper_|s region from |last_invalid_region_|. 510 // http://crbug.com/92354 511 512 // Since the image obtained from OpenGL is upside-down, need to do some 513 // magic here to copy the correct rectangle. 514 const int y_offset = (frame.size().height() - 1) * frame.stride(); 515 for (DesktopRegion::Iterator i(last_invalid_region_); 516 !i.IsAtEnd(); i.Advance()) { 517 DesktopRect copy_rect = i.rect(); 518 copy_rect.IntersectWith(clip_rect); 519 if (!copy_rect.is_empty()) { 520 CopyRect(queue_.previous_frame()->data() + y_offset, 521 -frame.stride(), 522 frame.data() + y_offset, 523 -frame.stride(), 524 DesktopFrame::kBytesPerPixel, 525 copy_rect); 526 } 527 } 528 } 529 last_invalid_region_ = region; 530 531 CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; 532 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_.get()); 533 glReadPixels(0, 0, frame.size().width(), frame.size().height(), GL_BGRA, 534 GL_UNSIGNED_BYTE, 0); 535 GLubyte* ptr = static_cast<GLubyte*>( 536 glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB)); 537 if (ptr == NULL) { 538 // If the buffer can't be mapped, assume that it's no longer valid and 539 // release it. 540 pixel_buffer_object_.Release(); 541 } else { 542 // Copy only from the dirty rects. Since the image obtained from OpenGL is 543 // upside-down we need to do some magic here to copy the correct rectangle. 544 const int y_offset = (frame.size().height() - 1) * frame.stride(); 545 for (DesktopRegion::Iterator i(region); 546 !i.IsAtEnd(); i.Advance()) { 547 DesktopRect copy_rect = i.rect(); 548 copy_rect.IntersectWith(clip_rect); 549 if (!copy_rect.is_empty()) { 550 CopyRect(ptr + y_offset, 551 -frame.stride(), 552 frame.data() + y_offset, 553 -frame.stride(), 554 DesktopFrame::kBytesPerPixel, 555 copy_rect); 556 } 557 } 558 } 559 if (!glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB)) { 560 // If glUnmapBuffer returns false, then the contents of the data store are 561 // undefined. This might be because the screen mode has changed, in which 562 // case it will be recreated in ScreenConfigurationChanged, but releasing 563 // the object here is the best option. Capturing will fall back on 564 // GlBlitSlow until such time as the pixel buffer object is recreated. 565 pixel_buffer_object_.Release(); 566 } 567 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); 568} 569 570void ScreenCapturerMac::GlBlitSlow(const DesktopFrame& frame) { 571 CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; 572 glReadBuffer(GL_FRONT); 573 glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); 574 glPixelStorei(GL_PACK_ALIGNMENT, 4); // Force 4-byte alignment. 575 glPixelStorei(GL_PACK_ROW_LENGTH, 0); 576 glPixelStorei(GL_PACK_SKIP_ROWS, 0); 577 glPixelStorei(GL_PACK_SKIP_PIXELS, 0); 578 // Read a block of pixels from the frame buffer. 579 glReadPixels(0, 0, frame.size().width(), frame.size().height(), 580 GL_BGRA, GL_UNSIGNED_BYTE, frame.data()); 581 glPopClientAttrib(); 582} 583 584void ScreenCapturerMac::CgBlitPreLion(const DesktopFrame& frame, 585 const DesktopRegion& region) { 586 // Copy the entire contents of the previous capture buffer, to capture over. 587 // TODO(wez): Get rid of this as per crbug.com/145064, or implement 588 // crbug.com/92354. 589 if (queue_.previous_frame()) { 590 memcpy(frame.data(), 591 queue_.previous_frame()->data(), 592 frame.stride() * frame.size().height()); 593 } 594 595 for (size_t i = 0; i < desktop_config_.displays.size(); ++i) { 596 const MacDisplayConfiguration& display_config = desktop_config_.displays[i]; 597 598 // Use deprecated APIs to determine the display buffer layout. 599 assert(cg_display_base_address_ && cg_display_bytes_per_row_ && 600 cg_display_bits_per_pixel_); 601 uint8_t* display_base_address = reinterpret_cast<uint8_t*>( 602 (*cg_display_base_address_)(display_config.id)); 603 assert(display_base_address); 604 int src_bytes_per_row = (*cg_display_bytes_per_row_)(display_config.id); 605 int src_bytes_per_pixel = 606 (*cg_display_bits_per_pixel_)(display_config.id) / 8; 607 608 // Determine the display's position relative to the desktop, in pixels. 609 DesktopRect display_bounds = display_config.pixel_bounds; 610 display_bounds.Translate(-desktop_config_.pixel_bounds.left(), 611 -desktop_config_.pixel_bounds.top()); 612 613 // Determine which parts of the blit region, if any, lay within the monitor. 614 DesktopRegion copy_region = region; 615 copy_region.IntersectWith(display_bounds); 616 if (copy_region.is_empty()) 617 continue; 618 619 // Translate the region to be copied into display-relative coordinates. 620 copy_region.Translate(-display_bounds.left(), -display_bounds.top()); 621 622 // Calculate where in the output buffer the display's origin is. 623 uint8_t* out_ptr = frame.data() + 624 (display_bounds.left() * src_bytes_per_pixel) + 625 (display_bounds.top() * frame.stride()); 626 627 // Copy the dirty region from the display buffer into our desktop buffer. 628 for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) { 629 CopyRect(display_base_address, 630 src_bytes_per_row, 631 out_ptr, 632 frame.stride(), 633 src_bytes_per_pixel, 634 i.rect()); 635 } 636 } 637} 638 639bool ScreenCapturerMac::CgBlitPostLion(const DesktopFrame& frame, 640 const DesktopRegion& region) { 641 // Copy the entire contents of the previous capture buffer, to capture over. 642 // TODO(wez): Get rid of this as per crbug.com/145064, or implement 643 // crbug.com/92354. 644 if (queue_.previous_frame()) { 645 memcpy(frame.data(), 646 queue_.previous_frame()->data(), 647 frame.stride() * frame.size().height()); 648 } 649 650 MacDisplayConfigurations displays_to_capture; 651 if (current_display_) { 652 // Capturing a single screen. Note that the screen id may change when 653 // screens are added or removed. 654 const MacDisplayConfiguration* config = 655 desktop_config_.FindDisplayConfigurationById(current_display_); 656 if (config) { 657 displays_to_capture.push_back(*config); 658 } else { 659 LOG(LS_ERROR) << "The selected screen cannot be found for capturing."; 660 return false; 661 } 662 } else { 663 // Capturing the whole desktop. 664 displays_to_capture = desktop_config_.displays; 665 } 666 667 // Create the window list once for all displays. 668 CFArrayRef window_list = CreateWindowListWithExclusion(excluded_window_); 669 670 for (size_t i = 0; i < displays_to_capture.size(); ++i) { 671 const MacDisplayConfiguration& display_config = displays_to_capture[i]; 672 673 // Capturing mixed-DPI on one surface is hard, so we only return displays 674 // that match the "primary" display's DPI. The primary display is always 675 // the first in the list. 676 if (i > 0 && display_config.dip_to_pixel_scale != 677 displays_to_capture[0].dip_to_pixel_scale) { 678 continue; 679 } 680 // Determine the display's position relative to the desktop, in pixels. 681 DesktopRect display_bounds = display_config.pixel_bounds; 682 display_bounds.Translate(-screen_pixel_bounds_.left(), 683 -screen_pixel_bounds_.top()); 684 685 // Determine which parts of the blit region, if any, lay within the monitor. 686 DesktopRegion copy_region = region; 687 copy_region.IntersectWith(display_bounds); 688 if (copy_region.is_empty()) 689 continue; 690 691 // Translate the region to be copied into display-relative coordinates. 692 copy_region.Translate(-display_bounds.left(), -display_bounds.top()); 693 694 DesktopRect excluded_window_bounds; 695 CGImageRef excluded_image = NULL; 696 CFDataRef excluded_window_region_data = NULL; 697 if (excluded_window_ && window_list) { 698 // Get the region of the excluded window relative the primary display. 699 excluded_window_bounds = GetExcludedWindowPixelBounds( 700 excluded_window_, display_config.dip_to_pixel_scale); 701 excluded_window_bounds.IntersectWith(display_config.pixel_bounds); 702 703 // Create the image under the excluded window first, because it's faster 704 // than captuing the whole display. 705 if (!excluded_window_bounds.is_empty()) { 706 excluded_image = CreateExcludedWindowRegionImage( 707 excluded_window_bounds, 708 display_config.dip_to_pixel_scale, 709 window_list, 710 &excluded_window_region_data); 711 } 712 } 713 714 // Create an image containing a snapshot of the display. 715 CGImageRef image = CGDisplayCreateImage(display_config.id); 716 if (image == NULL) 717 continue; 718 719 // Request access to the raw pixel data via the image's DataProvider. 720 CGDataProviderRef provider = CGImageGetDataProvider(image); 721 CFDataRef data = CGDataProviderCopyData(provider); 722 assert(data); 723 724 const uint8_t* display_base_address = CFDataGetBytePtr(data); 725 int src_bytes_per_row = CGImageGetBytesPerRow(image); 726 int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8; 727 728 // Calculate where in the output buffer the display's origin is. 729 uint8_t* out_ptr = frame.data() + 730 (display_bounds.left() * src_bytes_per_pixel) + 731 (display_bounds.top() * frame.stride()); 732 733 // Copy the dirty region from the display buffer into our desktop buffer. 734 for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) { 735 CopyRect(display_base_address, 736 src_bytes_per_row, 737 out_ptr, 738 frame.stride(), 739 src_bytes_per_pixel, 740 i.rect()); 741 } 742 743 // Copy the region of the excluded window to the frame. 744 if (excluded_image) { 745 assert(excluded_window_region_data); 746 display_base_address = CFDataGetBytePtr(excluded_window_region_data); 747 src_bytes_per_row = CGImageGetBytesPerRow(excluded_image); 748 749 // Translate the bounds relative to the desktop, because |frame| data 750 // starts from the desktop top-left corner. 751 DesktopRect window_bounds_relative_to_desktop(excluded_window_bounds); 752 window_bounds_relative_to_desktop.Translate( 753 -screen_pixel_bounds_.left(), -screen_pixel_bounds_.top()); 754 out_ptr = frame.data() + 755 (window_bounds_relative_to_desktop.left() * src_bytes_per_pixel) + 756 (window_bounds_relative_to_desktop.top() * frame.stride()); 757 758 CopyRect(display_base_address, 759 src_bytes_per_row, 760 out_ptr, 761 frame.stride(), 762 src_bytes_per_pixel, 763 DesktopRect::MakeSize(excluded_window_bounds.size())); 764 CFRelease(excluded_window_region_data); 765 CFRelease(excluded_image); 766 } 767 768 CFRelease(data); 769 CFRelease(image); 770 } 771 if (window_list) 772 CFRelease(window_list); 773 return true; 774} 775 776void ScreenCapturerMac::ScreenConfigurationChanged() { 777 if (current_display_) { 778 const MacDisplayConfiguration* config = 779 desktop_config_.FindDisplayConfigurationById(current_display_); 780 screen_pixel_bounds_ = config ? config->pixel_bounds : DesktopRect(); 781 dip_to_pixel_scale_ = config ? config->dip_to_pixel_scale : 1.0f; 782 } else { 783 screen_pixel_bounds_ = desktop_config_.pixel_bounds; 784 dip_to_pixel_scale_ = desktop_config_.dip_to_pixel_scale; 785 } 786 787 // Release existing buffers, which will be of the wrong size. 788 ReleaseBuffers(); 789 790 // Clear the dirty region, in case the display is down-sizing. 791 helper_.ClearInvalidRegion(); 792 793 // Re-mark the entire desktop as dirty. 794 helper_.InvalidateScreen(screen_pixel_bounds_.size()); 795 796 // Make sure the frame buffers will be reallocated. 797 queue_.Reset(); 798 799 // CgBlitPostLion uses CGDisplayCreateImage() to snapshot each display's 800 // contents. Although the API exists in OS 10.6, it crashes the caller if 801 // the machine has no monitor connected, so we fall back to depcreated APIs 802 // when running on 10.6. 803 if (rtc::GetOSVersionName() >= rtc::kMacOSLion) { 804 LOG(LS_INFO) << "Using CgBlitPostLion."; 805 // No need for any OpenGL support on Lion 806 return; 807 } 808 809 // Dynamically link to the deprecated pre-Lion capture APIs. 810 app_services_library_ = dlopen(kApplicationServicesLibraryName, 811 RTLD_LAZY); 812 if (!app_services_library_) { 813 LOG_F(LS_ERROR) << "Failed to open " << kApplicationServicesLibraryName; 814 abort(); 815 } 816 817 opengl_library_ = dlopen(kOpenGlLibraryName, RTLD_LAZY); 818 if (!opengl_library_) { 819 LOG_F(LS_ERROR) << "Failed to open " << kOpenGlLibraryName; 820 abort(); 821 } 822 823 cg_display_base_address_ = reinterpret_cast<CGDisplayBaseAddressFunc>( 824 dlsym(app_services_library_, "CGDisplayBaseAddress")); 825 cg_display_bytes_per_row_ = reinterpret_cast<CGDisplayBytesPerRowFunc>( 826 dlsym(app_services_library_, "CGDisplayBytesPerRow")); 827 cg_display_bits_per_pixel_ = reinterpret_cast<CGDisplayBitsPerPixelFunc>( 828 dlsym(app_services_library_, "CGDisplayBitsPerPixel")); 829 cgl_set_full_screen_ = reinterpret_cast<CGLSetFullScreenFunc>( 830 dlsym(opengl_library_, "CGLSetFullScreen")); 831 if (!(cg_display_base_address_ && cg_display_bytes_per_row_ && 832 cg_display_bits_per_pixel_ && cgl_set_full_screen_)) { 833 LOG_F(LS_ERROR); 834 abort(); 835 } 836 837 if (desktop_config_.displays.size() > 1) { 838 LOG(LS_INFO) << "Using CgBlitPreLion (Multi-monitor)."; 839 return; 840 } 841 842 CGDirectDisplayID mainDevice = CGMainDisplayID(); 843 if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) { 844 LOG(LS_INFO) << "Using CgBlitPreLion (OpenGL unavailable)."; 845 return; 846 } 847 848 LOG(LS_INFO) << "Using GlBlit"; 849 850 CGLPixelFormatAttribute attributes[] = { 851 // This function does an early return if GetOSVersionName() >= kMacOSLion, 852 // this code only runs on 10.6 and can be deleted once 10.6 support is 853 // dropped. So just keep using kCGLPFAFullScreen even though it was 854 // deprecated in 10.6 -- it's still functional there, and it's not used on 855 // newer OS X versions. 856#pragma clang diagnostic push 857#pragma clang diagnostic ignored "-Wdeprecated-declarations" 858 kCGLPFAFullScreen, 859#pragma clang diagnostic pop 860 kCGLPFADisplayMask, 861 (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(mainDevice), 862 (CGLPixelFormatAttribute)0 863 }; 864 CGLPixelFormatObj pixel_format = NULL; 865 GLint matching_pixel_format_count = 0; 866 CGLError err = CGLChoosePixelFormat(attributes, 867 &pixel_format, 868 &matching_pixel_format_count); 869 assert(err == kCGLNoError); 870 err = CGLCreateContext(pixel_format, NULL, &cgl_context_); 871 assert(err == kCGLNoError); 872 CGLDestroyPixelFormat(pixel_format); 873 (*cgl_set_full_screen_)(cgl_context_); 874 CGLSetCurrentContext(cgl_context_); 875 876 size_t buffer_size = screen_pixel_bounds_.width() * 877 screen_pixel_bounds_.height() * 878 sizeof(uint32_t); 879 pixel_buffer_object_.Init(cgl_context_, buffer_size); 880} 881 882bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() { 883 CGError err = CGRegisterScreenRefreshCallback( 884 ScreenCapturerMac::ScreenRefreshCallback, this); 885 if (err != kCGErrorSuccess) { 886 LOG(LS_ERROR) << "CGRegisterScreenRefreshCallback " << err; 887 return false; 888 } 889 890 err = CGScreenRegisterMoveCallback( 891 ScreenCapturerMac::ScreenUpdateMoveCallback, this); 892 if (err != kCGErrorSuccess) { 893 LOG(LS_ERROR) << "CGScreenRegisterMoveCallback " << err; 894 return false; 895 } 896 897 return true; 898} 899 900void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() { 901 CGUnregisterScreenRefreshCallback( 902 ScreenCapturerMac::ScreenRefreshCallback, this); 903 CGScreenUnregisterMoveCallback( 904 ScreenCapturerMac::ScreenUpdateMoveCallback, this); 905} 906 907void ScreenCapturerMac::ScreenRefresh(CGRectCount count, 908 const CGRect* rect_array) { 909 if (screen_pixel_bounds_.is_empty()) 910 return; 911 912 DesktopRegion region; 913 DesktopVector translate_vector = 914 DesktopVector().subtract(screen_pixel_bounds_.top_left()); 915 for (CGRectCount i = 0; i < count; ++i) { 916 // Convert from Density-Independent Pixel to physical pixel coordinates. 917 DesktopRect rect = ScaleAndRoundCGRect(rect_array[i], dip_to_pixel_scale_); 918 // Translate from local desktop to capturer framebuffer coordinates. 919 rect.Translate(translate_vector); 920 region.AddRect(rect); 921 } 922 923 helper_.InvalidateRegion(region); 924} 925 926void ScreenCapturerMac::ScreenUpdateMove(CGScreenUpdateMoveDelta delta, 927 size_t count, 928 const CGRect* rect_array) { 929 // Translate |rect_array| to identify the move's destination. 930 CGRect refresh_rects[count]; 931 for (CGRectCount i = 0; i < count; ++i) { 932 refresh_rects[i] = CGRectOffset(rect_array[i], delta.dX, delta.dY); 933 } 934 935 // Currently we just treat move events the same as refreshes. 936 ScreenRefresh(count, refresh_rects); 937} 938 939void ScreenCapturerMac::ScreenRefreshCallback(CGRectCount count, 940 const CGRect* rect_array, 941 void* user_parameter) { 942 ScreenCapturerMac* capturer = 943 reinterpret_cast<ScreenCapturerMac*>(user_parameter); 944 if (capturer->screen_pixel_bounds_.is_empty()) 945 capturer->ScreenConfigurationChanged(); 946 capturer->ScreenRefresh(count, rect_array); 947} 948 949void ScreenCapturerMac::ScreenUpdateMoveCallback( 950 CGScreenUpdateMoveDelta delta, 951 size_t count, 952 const CGRect* rect_array, 953 void* user_parameter) { 954 ScreenCapturerMac* capturer = 955 reinterpret_cast<ScreenCapturerMac*>(user_parameter); 956 capturer->ScreenUpdateMove(delta, count, rect_array); 957} 958 959DesktopFrame* ScreenCapturerMac::CreateFrame() { 960 rtc::scoped_ptr<DesktopFrame> frame( 961 new BasicDesktopFrame(screen_pixel_bounds_.size())); 962 963 frame->set_dpi(DesktopVector(kStandardDPI * dip_to_pixel_scale_, 964 kStandardDPI * dip_to_pixel_scale_)); 965 return frame.release(); 966} 967 968} // namespace 969 970// static 971ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { 972 if (!options.configuration_monitor()) 973 return NULL; 974 975 rtc::scoped_ptr<ScreenCapturerMac> capturer( 976 new ScreenCapturerMac(options.configuration_monitor())); 977 if (!capturer->Init()) 978 capturer.reset(); 979 return capturer.release(); 980} 981 982} // namespace webrtc 983