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