| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 "media/video/capture/screen/screen_capturer.h" | |
| 6 | |
| 7 #include <ApplicationServices/ApplicationServices.h> | |
| 8 #include <Cocoa/Cocoa.h> | |
| 9 #include <dlfcn.h> | |
| 10 #include <IOKit/pwr_mgt/IOPMLib.h> | |
| 11 #include <OpenGL/CGLMacro.h> | |
| 12 #include <OpenGL/OpenGL.h> | |
| 13 #include <sys/utsname.h> | |
| 14 #include <stddef.h> | |
| 15 #include <set> | |
| 16 | |
| 17 #include "base/logging.h" | |
| 18 #include "base/memory/scoped_ptr.h" | |
| 19 #include "base/synchronization/waitable_event.h" | |
| 20 #include "media/video/capture/screen/mac/desktop_configuration.h" | |
| 21 #include "media/video/capture/screen/mac/scoped_pixel_buffer_object.h" | |
| 22 #include "media/video/capture/screen/mouse_cursor_shape.h" | |
| 23 #include "media/video/capture/screen/screen_capture_frame_queue.h" | |
| 24 #include "media/video/capture/screen/screen_capturer_helper.h" | |
| 25 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" | |
| 26 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" | |
| 27 #include "third_party/webrtc/modules/desktop_capture/desktop_region.h" | |
| 28 | |
| 29 namespace media { | |
| 30 | |
| 31 namespace { | |
| 32 | |
| 33 // Definitions used to dynamic-link to deprecated OS 10.6 functions. | |
| 34 const char* kApplicationServicesLibraryName = | |
| 35 "/System/Library/Frameworks/ApplicationServices.framework/" | |
| 36 "ApplicationServices"; | |
| 37 typedef void* (*CGDisplayBaseAddressFunc)(CGDirectDisplayID); | |
| 38 typedef size_t (*CGDisplayBytesPerRowFunc)(CGDirectDisplayID); | |
| 39 typedef size_t (*CGDisplayBitsPerPixelFunc)(CGDirectDisplayID); | |
| 40 const char* kOpenGlLibraryName = | |
| 41 "/System/Library/Frameworks/OpenGL.framework/OpenGL"; | |
| 42 typedef CGLError (*CGLSetFullScreenFunc)(CGLContextObj); | |
| 43 | |
| 44 // Standard Mac displays have 72dpi, but we report 96dpi for | |
| 45 // consistency with Windows and Linux. | |
| 46 const int kStandardDPI = 96; | |
| 47 | |
| 48 // Scales all coordinates of a rect by a specified factor. | |
| 49 webrtc::DesktopRect ScaleAndRoundCGRect(const CGRect& rect, float scale) { | |
| 50 return webrtc::DesktopRect::MakeLTRB( | |
| 51 static_cast<int>(floor(rect.origin.x * scale)), | |
| 52 static_cast<int>(floor(rect.origin.y * scale)), | |
| 53 static_cast<int>(ceil((rect.origin.x + rect.size.width) * scale)), | |
| 54 static_cast<int>(ceil((rect.origin.y + rect.size.height) * scale))); | |
| 55 } | |
| 56 | |
| 57 // Copy pixels in the |rect| from |src_place| to |dest_plane|. | |
| 58 void CopyRect(const uint8* src_plane, | |
| 59 int src_plane_stride, | |
| 60 uint8* dest_plane, | |
| 61 int dest_plane_stride, | |
| 62 int bytes_per_pixel, | |
| 63 const webrtc::DesktopRect& rect) { | |
| 64 // Get the address of the starting point. | |
| 65 const int src_y_offset = src_plane_stride * rect.top(); | |
| 66 const int dest_y_offset = dest_plane_stride * rect.top(); | |
| 67 const int x_offset = bytes_per_pixel * rect.left(); | |
| 68 src_plane += src_y_offset + x_offset; | |
| 69 dest_plane += dest_y_offset + x_offset; | |
| 70 | |
| 71 // Copy pixels in the rectangle line by line. | |
| 72 const int bytes_per_line = bytes_per_pixel * rect.width(); | |
| 73 const int height = rect.height(); | |
| 74 for (int i = 0 ; i < height; ++i) { | |
| 75 memcpy(dest_plane, src_plane, bytes_per_line); | |
| 76 src_plane += src_plane_stride; | |
| 77 dest_plane += dest_plane_stride; | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 int GetDarwinVersion() { | |
| 82 struct utsname uname_info; | |
| 83 if (uname(&uname_info) != 0) { | |
| 84 LOG(ERROR) << "uname failed"; | |
| 85 return 0; | |
| 86 } | |
| 87 | |
| 88 if (strcmp(uname_info.sysname, "Darwin") != 0) | |
| 89 return 0; | |
| 90 | |
| 91 char* dot; | |
| 92 int result = strtol(uname_info.release, &dot, 10); | |
| 93 if (*dot != '.') { | |
| 94 LOG(ERROR) << "Failed to parse version"; | |
| 95 return 0; | |
| 96 } | |
| 97 | |
| 98 return result; | |
| 99 } | |
| 100 | |
| 101 bool IsOSLionOrLater() { | |
| 102 static int darwin_version = GetDarwinVersion(); | |
| 103 | |
| 104 // Verify that the version has been parsed correctly. | |
| 105 CHECK(darwin_version >= 6); | |
| 106 | |
| 107 // Darwin major version 11 corresponds to OSX 10.7. | |
| 108 return darwin_version >= 11; | |
| 109 } | |
| 110 | |
| 111 // The amount of time allowed for displays to reconfigure. | |
| 112 const int64 kDisplayConfigurationEventTimeoutInSeconds = 10; | |
| 113 | |
| 114 // A class to perform video frame capturing for mac. | |
| 115 class ScreenCapturerMac : public ScreenCapturer { | |
| 116 public: | |
| 117 ScreenCapturerMac(); | |
| 118 virtual ~ScreenCapturerMac(); | |
| 119 | |
| 120 bool Init(); | |
| 121 | |
| 122 // Overridden from ScreenCapturer: | |
| 123 virtual void Start(Callback* callback) OVERRIDE; | |
| 124 virtual void Capture(const webrtc::DesktopRegion& region) OVERRIDE; | |
| 125 virtual void SetMouseShapeObserver( | |
| 126 MouseShapeObserver* mouse_shape_observer) OVERRIDE; | |
| 127 | |
| 128 private: | |
| 129 void CaptureCursor(); | |
| 130 | |
| 131 void GlBlitFast(const webrtc::DesktopFrame& frame, | |
| 132 const webrtc::DesktopRegion& region); | |
| 133 void GlBlitSlow(const webrtc::DesktopFrame& frame); | |
| 134 void CgBlitPreLion(const webrtc::DesktopFrame& frame, | |
| 135 const webrtc::DesktopRegion& region); | |
| 136 void CgBlitPostLion(const webrtc::DesktopFrame& frame, | |
| 137 const webrtc::DesktopRegion& region); | |
| 138 | |
| 139 // Called when the screen configuration is changed. | |
| 140 void ScreenConfigurationChanged(); | |
| 141 | |
| 142 bool RegisterRefreshAndMoveHandlers(); | |
| 143 void UnregisterRefreshAndMoveHandlers(); | |
| 144 | |
| 145 void ScreenRefresh(CGRectCount count, const CGRect *rect_array); | |
| 146 void ScreenUpdateMove(CGScreenUpdateMoveDelta delta, | |
| 147 size_t count, | |
| 148 const CGRect *rect_array); | |
| 149 void DisplaysReconfigured(CGDirectDisplayID display, | |
| 150 CGDisplayChangeSummaryFlags flags); | |
| 151 static void ScreenRefreshCallback(CGRectCount count, | |
| 152 const CGRect *rect_array, | |
| 153 void *user_parameter); | |
| 154 static void ScreenUpdateMoveCallback(CGScreenUpdateMoveDelta delta, | |
| 155 size_t count, | |
| 156 const CGRect *rect_array, | |
| 157 void *user_parameter); | |
| 158 static void DisplaysReconfiguredCallback(CGDirectDisplayID display, | |
| 159 CGDisplayChangeSummaryFlags flags, | |
| 160 void *user_parameter); | |
| 161 | |
| 162 void ReleaseBuffers(); | |
| 163 | |
| 164 Callback* callback_; | |
| 165 MouseShapeObserver* mouse_shape_observer_; | |
| 166 | |
| 167 CGLContextObj cgl_context_; | |
| 168 ScopedPixelBufferObject pixel_buffer_object_; | |
| 169 | |
| 170 // Queue of the frames buffers. | |
| 171 ScreenCaptureFrameQueue queue_; | |
| 172 | |
| 173 // Current display configuration. | |
| 174 MacDesktopConfiguration desktop_config_; | |
| 175 | |
| 176 // A thread-safe list of invalid rectangles, and the size of the most | |
| 177 // recently captured screen. | |
| 178 ScreenCapturerHelper helper_; | |
| 179 | |
| 180 // The last cursor that we sent to the client. | |
| 181 MouseCursorShape last_cursor_; | |
| 182 | |
| 183 // Contains an invalid region from the previous capture. | |
| 184 webrtc::DesktopRegion last_invalid_region_; | |
| 185 | |
| 186 // Used to ensure that frame captures do not take place while displays | |
| 187 // are being reconfigured. | |
| 188 base::WaitableEvent display_configuration_capture_event_; | |
| 189 | |
| 190 // Records the Ids of attached displays which are being reconfigured. | |
| 191 // Accessed on the thread on which we are notified of display events. | |
| 192 std::set<CGDirectDisplayID> reconfiguring_displays_; | |
| 193 | |
| 194 // Power management assertion to prevent the screen from sleeping. | |
| 195 IOPMAssertionID power_assertion_id_display_; | |
| 196 | |
| 197 // Power management assertion to indicate that the user is active. | |
| 198 IOPMAssertionID power_assertion_id_user_; | |
| 199 | |
| 200 // Dynamically link to deprecated APIs for Mac OS X 10.6 support. | |
| 201 void* app_services_library_; | |
| 202 CGDisplayBaseAddressFunc cg_display_base_address_; | |
| 203 CGDisplayBytesPerRowFunc cg_display_bytes_per_row_; | |
| 204 CGDisplayBitsPerPixelFunc cg_display_bits_per_pixel_; | |
| 205 void* opengl_library_; | |
| 206 CGLSetFullScreenFunc cgl_set_full_screen_; | |
| 207 | |
| 208 DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac); | |
| 209 }; | |
| 210 | |
| 211 scoped_ptr<webrtc::DesktopFrame> CreateFrame( | |
| 212 const MacDesktopConfiguration& desktop_config) { | |
| 213 | |
| 214 webrtc::DesktopSize size(desktop_config.pixel_bounds.width(), | |
| 215 desktop_config.pixel_bounds.height()); | |
| 216 scoped_ptr<webrtc::DesktopFrame> frame(new webrtc::BasicDesktopFrame(size)); | |
| 217 | |
| 218 frame->set_dpi(webrtc::DesktopVector( | |
| 219 kStandardDPI * desktop_config.dip_to_pixel_scale, | |
| 220 kStandardDPI * desktop_config.dip_to_pixel_scale)); | |
| 221 return frame.Pass(); | |
| 222 } | |
| 223 | |
| 224 ScreenCapturerMac::ScreenCapturerMac() | |
| 225 : callback_(NULL), | |
| 226 mouse_shape_observer_(NULL), | |
| 227 cgl_context_(NULL), | |
| 228 display_configuration_capture_event_(false, true), | |
| 229 power_assertion_id_display_(kIOPMNullAssertionID), | |
| 230 power_assertion_id_user_(kIOPMNullAssertionID), | |
| 231 app_services_library_(NULL), | |
| 232 cg_display_base_address_(NULL), | |
| 233 cg_display_bytes_per_row_(NULL), | |
| 234 cg_display_bits_per_pixel_(NULL), | |
| 235 opengl_library_(NULL), | |
| 236 cgl_set_full_screen_(NULL) { | |
| 237 } | |
| 238 | |
| 239 ScreenCapturerMac::~ScreenCapturerMac() { | |
| 240 if (power_assertion_id_display_ != kIOPMNullAssertionID) { | |
| 241 IOPMAssertionRelease(power_assertion_id_display_); | |
| 242 power_assertion_id_display_ = kIOPMNullAssertionID; | |
| 243 } | |
| 244 if (power_assertion_id_user_ != kIOPMNullAssertionID) { | |
| 245 IOPMAssertionRelease(power_assertion_id_user_); | |
| 246 power_assertion_id_user_ = kIOPMNullAssertionID; | |
| 247 } | |
| 248 | |
| 249 ReleaseBuffers(); | |
| 250 UnregisterRefreshAndMoveHandlers(); | |
| 251 CGError err = CGDisplayRemoveReconfigurationCallback( | |
| 252 ScreenCapturerMac::DisplaysReconfiguredCallback, this); | |
| 253 if (err != kCGErrorSuccess) | |
| 254 LOG(ERROR) << "CGDisplayRemoveReconfigurationCallback " << err; | |
| 255 | |
| 256 dlclose(app_services_library_); | |
| 257 dlclose(opengl_library_); | |
| 258 } | |
| 259 | |
| 260 bool ScreenCapturerMac::Init() { | |
| 261 if (!RegisterRefreshAndMoveHandlers()) { | |
| 262 return false; | |
| 263 } | |
| 264 | |
| 265 CGError err = CGDisplayRegisterReconfigurationCallback( | |
| 266 ScreenCapturerMac::DisplaysReconfiguredCallback, this); | |
| 267 if (err != kCGErrorSuccess) { | |
| 268 LOG(ERROR) << "CGDisplayRegisterReconfigurationCallback " << err; | |
| 269 return false; | |
| 270 } | |
| 271 | |
| 272 ScreenConfigurationChanged(); | |
| 273 return true; | |
| 274 } | |
| 275 | |
| 276 void ScreenCapturerMac::ReleaseBuffers() { | |
| 277 if (cgl_context_) { | |
| 278 pixel_buffer_object_.Release(); | |
| 279 CGLDestroyContext(cgl_context_); | |
| 280 cgl_context_ = NULL; | |
| 281 } | |
| 282 // The buffers might be in use by the encoder, so don't delete them here. | |
| 283 // Instead, mark them as "needs update"; next time the buffers are used by | |
| 284 // the capturer, they will be recreated if necessary. | |
| 285 queue_.Reset(); | |
| 286 } | |
| 287 | |
| 288 void ScreenCapturerMac::Start(Callback* callback) { | |
| 289 DCHECK(!callback_); | |
| 290 DCHECK(callback); | |
| 291 | |
| 292 callback_ = callback; | |
| 293 | |
| 294 // Create power management assertions to wake the display and prevent it from | |
| 295 // going to sleep on user idle. | |
| 296 // TODO(jamiewalch): Use IOPMAssertionDeclareUserActivity on 10.7.3 and above | |
| 297 // instead of the following two assertions. | |
| 298 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, | |
| 299 kIOPMAssertionLevelOn, | |
| 300 CFSTR("Chrome Remote Desktop connection active"), | |
| 301 &power_assertion_id_display_); | |
| 302 // This assertion ensures that the display is woken up if it already asleep | |
| 303 // (as used by Apple Remote Desktop). | |
| 304 IOPMAssertionCreateWithName(CFSTR("UserIsActive"), | |
| 305 kIOPMAssertionLevelOn, | |
| 306 CFSTR("Chrome Remote Desktop connection active"), | |
| 307 &power_assertion_id_user_); | |
| 308 } | |
| 309 | |
| 310 void ScreenCapturerMac::Capture( | |
| 311 const webrtc::DesktopRegion& region_to_capture) { | |
| 312 base::Time capture_start_time = base::Time::Now(); | |
| 313 | |
| 314 queue_.MoveToNextFrame(); | |
| 315 | |
| 316 // Wait until the display configuration is stable. If one or more displays | |
| 317 // are reconfiguring then |display_configuration_capture_event_| will not be | |
| 318 // set until the reconfiguration completes. | |
| 319 // TODO(wez): Replace this with an early-exit (See crbug.com/104542). | |
| 320 CHECK(display_configuration_capture_event_.TimedWait( | |
| 321 base::TimeDelta::FromSeconds( | |
| 322 kDisplayConfigurationEventTimeoutInSeconds))); | |
| 323 | |
| 324 webrtc::DesktopRegion region; | |
| 325 helper_.TakeInvalidRegion(®ion); | |
| 326 | |
| 327 // If the current buffer is from an older generation then allocate a new one. | |
| 328 // Note that we can't reallocate other buffers at this point, since the caller | |
| 329 // may still be reading from them. | |
| 330 if (!queue_.current_frame()) | |
| 331 queue_.ReplaceCurrentFrame(CreateFrame(desktop_config_)); | |
| 332 | |
| 333 webrtc::DesktopFrame* current_frame = queue_.current_frame(); | |
| 334 | |
| 335 bool flip = false; // GL capturers need flipping. | |
| 336 if (IsOSLionOrLater()) { | |
| 337 // Lion requires us to use their new APIs for doing screen capture. These | |
| 338 // APIS currently crash on 10.6.8 if there is no monitor attached. | |
| 339 CgBlitPostLion(*current_frame, region); | |
| 340 } else if (cgl_context_) { | |
| 341 flip = true; | |
| 342 if (pixel_buffer_object_.get() != 0) { | |
| 343 GlBlitFast(*current_frame, region); | |
| 344 } else { | |
| 345 // See comment in ScopedPixelBufferObject::Init about why the slow | |
| 346 // path is always used on 10.5. | |
| 347 GlBlitSlow(*current_frame); | |
| 348 } | |
| 349 } else { | |
| 350 CgBlitPreLion(*current_frame, region); | |
| 351 } | |
| 352 | |
| 353 uint8* buffer = current_frame->data(); | |
| 354 int stride = current_frame->stride(); | |
| 355 if (flip) { | |
| 356 stride = -stride; | |
| 357 buffer += (current_frame->size().height() - 1) * current_frame->stride(); | |
| 358 } | |
| 359 | |
| 360 webrtc::DesktopFrame* new_frame = queue_.current_frame()->Share(); | |
| 361 *new_frame->mutable_updated_region() = region; | |
| 362 | |
| 363 helper_.set_size_most_recent(new_frame->size()); | |
| 364 | |
| 365 // Signal that we are done capturing data from the display framebuffer, | |
| 366 // and accessing display structures. | |
| 367 display_configuration_capture_event_.Signal(); | |
| 368 | |
| 369 // Capture the current cursor shape and notify |callback_| if it has changed. | |
| 370 CaptureCursor(); | |
| 371 | |
| 372 new_frame->set_capture_time_ms( | |
| 373 (base::Time::Now() - capture_start_time).InMillisecondsRoundedUp()); | |
| 374 callback_->OnCaptureCompleted(new_frame); | |
| 375 } | |
| 376 | |
| 377 void ScreenCapturerMac::SetMouseShapeObserver( | |
| 378 MouseShapeObserver* mouse_shape_observer) { | |
| 379 DCHECK(!mouse_shape_observer_); | |
| 380 DCHECK(mouse_shape_observer); | |
| 381 mouse_shape_observer_ = mouse_shape_observer; | |
| 382 } | |
| 383 | |
| 384 void ScreenCapturerMac::CaptureCursor() { | |
| 385 if (!mouse_shape_observer_) | |
| 386 return; | |
| 387 | |
| 388 NSCursor* cursor = [NSCursor currentSystemCursor]; | |
| 389 if (cursor == nil) | |
| 390 return; | |
| 391 | |
| 392 NSImage* nsimage = [cursor image]; | |
| 393 NSPoint hotspot = [cursor hotSpot]; | |
| 394 NSSize size = [nsimage size]; | |
| 395 CGImageRef image = [nsimage CGImageForProposedRect:NULL | |
| 396 context:nil | |
| 397 hints:nil]; | |
| 398 if (image == nil) | |
| 399 return; | |
| 400 | |
| 401 if (CGImageGetBitsPerPixel(image) != 32 || | |
| 402 CGImageGetBytesPerRow(image) != (size.width * 4) || | |
| 403 CGImageGetBitsPerComponent(image) != 8) { | |
| 404 return; | |
| 405 } | |
| 406 | |
| 407 CGDataProviderRef provider = CGImageGetDataProvider(image); | |
| 408 CFDataRef image_data_ref = CGDataProviderCopyData(provider); | |
| 409 if (image_data_ref == NULL) | |
| 410 return; | |
| 411 | |
| 412 const char* cursor_src_data = | |
| 413 reinterpret_cast<const char*>(CFDataGetBytePtr(image_data_ref)); | |
| 414 int data_size = CFDataGetLength(image_data_ref); | |
| 415 | |
| 416 // Create a MouseCursorShape that describes the cursor and pass it to | |
| 417 // the client. | |
| 418 scoped_ptr<MouseCursorShape> cursor_shape(new MouseCursorShape()); | |
| 419 cursor_shape->size.set(size.width, size.height); | |
| 420 cursor_shape->hotspot.set(hotspot.x, hotspot.y); | |
| 421 cursor_shape->data.assign(cursor_src_data, cursor_src_data + data_size); | |
| 422 | |
| 423 CFRelease(image_data_ref); | |
| 424 | |
| 425 // Compare the current cursor with the last one we sent to the client. If | |
| 426 // they're the same, then don't bother sending the cursor again. | |
| 427 if (last_cursor_.size.equals(cursor_shape->size) && | |
| 428 last_cursor_.hotspot.equals(cursor_shape->hotspot) && | |
| 429 last_cursor_.data == cursor_shape->data) { | |
| 430 return; | |
| 431 } | |
| 432 | |
| 433 // Record the last cursor image that we sent to the client. | |
| 434 last_cursor_ = *cursor_shape; | |
| 435 | |
| 436 VLOG(3) << "Sending cursor: " << size.width << "x" << size.height; | |
| 437 mouse_shape_observer_->OnCursorShapeChanged(cursor_shape.Pass()); | |
| 438 } | |
| 439 | |
| 440 void ScreenCapturerMac::GlBlitFast(const webrtc::DesktopFrame& frame, | |
| 441 const webrtc::DesktopRegion& region) { | |
| 442 // Clip to the size of our current screen. | |
| 443 webrtc::DesktopRect clip_rect = webrtc::DesktopRect::MakeSize(frame.size()); | |
| 444 if (queue_.previous_frame()) { | |
| 445 // We are doing double buffer for the capture data so we just need to copy | |
| 446 // the invalid region from the previous capture in the current buffer. | |
| 447 // TODO(hclam): We can reduce the amount of copying here by subtracting | |
| 448 // |capturer_helper_|s region from |last_invalid_region_|. | |
| 449 // http://crbug.com/92354 | |
| 450 | |
| 451 // Since the image obtained from OpenGL is upside-down, need to do some | |
| 452 // magic here to copy the correct rectangle. | |
| 453 const int y_offset = (frame.size().width() - 1) * frame.stride(); | |
| 454 for (webrtc::DesktopRegion::Iterator i(last_invalid_region_); | |
| 455 !i.IsAtEnd(); i.Advance()) { | |
| 456 webrtc::DesktopRect copy_rect = i.rect(); | |
| 457 copy_rect.IntersectWith(clip_rect); | |
| 458 if (!copy_rect.is_empty()) { | |
| 459 CopyRect(queue_.previous_frame()->data() + y_offset, | |
| 460 -frame.stride(), | |
| 461 frame.data() + y_offset, | |
| 462 -frame.stride(), | |
| 463 webrtc::DesktopFrame::kBytesPerPixel, | |
| 464 copy_rect); | |
| 465 } | |
| 466 } | |
| 467 } | |
| 468 last_invalid_region_ = region; | |
| 469 | |
| 470 CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; | |
| 471 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_.get()); | |
| 472 glReadPixels(0, 0, frame.size().height(), frame.size().width(), GL_BGRA, | |
| 473 GL_UNSIGNED_BYTE, 0); | |
| 474 GLubyte* ptr = static_cast<GLubyte*>( | |
| 475 glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB)); | |
| 476 if (ptr == NULL) { | |
| 477 // If the buffer can't be mapped, assume that it's no longer valid and | |
| 478 // release it. | |
| 479 pixel_buffer_object_.Release(); | |
| 480 } else { | |
| 481 // Copy only from the dirty rects. Since the image obtained from OpenGL is | |
| 482 // upside-down we need to do some magic here to copy the correct rectangle. | |
| 483 const int y_offset = (frame.size().height() - 1) * frame.stride(); | |
| 484 for (webrtc::DesktopRegion::Iterator i(region); | |
| 485 !i.IsAtEnd(); i.Advance()) { | |
| 486 webrtc::DesktopRect copy_rect = i.rect(); | |
| 487 copy_rect.IntersectWith(clip_rect); | |
| 488 if (!copy_rect.is_empty()) { | |
| 489 CopyRect(ptr + y_offset, | |
| 490 -frame.stride(), | |
| 491 frame.data() + y_offset, | |
| 492 -frame.stride(), | |
| 493 webrtc::DesktopFrame::kBytesPerPixel, | |
| 494 copy_rect); | |
| 495 } | |
| 496 } | |
| 497 } | |
| 498 if (!glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB)) { | |
| 499 // If glUnmapBuffer returns false, then the contents of the data store are | |
| 500 // undefined. This might be because the screen mode has changed, in which | |
| 501 // case it will be recreated in ScreenConfigurationChanged, but releasing | |
| 502 // the object here is the best option. Capturing will fall back on | |
| 503 // GlBlitSlow until such time as the pixel buffer object is recreated. | |
| 504 pixel_buffer_object_.Release(); | |
| 505 } | |
| 506 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); | |
| 507 } | |
| 508 | |
| 509 void ScreenCapturerMac::GlBlitSlow(const webrtc::DesktopFrame& frame) { | |
| 510 CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; | |
| 511 glReadBuffer(GL_FRONT); | |
| 512 glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); | |
| 513 glPixelStorei(GL_PACK_ALIGNMENT, 4); // Force 4-byte alignment. | |
| 514 glPixelStorei(GL_PACK_ROW_LENGTH, 0); | |
| 515 glPixelStorei(GL_PACK_SKIP_ROWS, 0); | |
| 516 glPixelStorei(GL_PACK_SKIP_PIXELS, 0); | |
| 517 // Read a block of pixels from the frame buffer. | |
| 518 glReadPixels(0, 0, frame.size().width(), frame.size().height(), | |
| 519 GL_BGRA, GL_UNSIGNED_BYTE, frame.data()); | |
| 520 glPopClientAttrib(); | |
| 521 } | |
| 522 | |
| 523 void ScreenCapturerMac::CgBlitPreLion(const webrtc::DesktopFrame& frame, | |
| 524 const webrtc::DesktopRegion& region) { | |
| 525 // Copy the entire contents of the previous capture buffer, to capture over. | |
| 526 // TODO(wez): Get rid of this as per crbug.com/145064, or implement | |
| 527 // crbug.com/92354. | |
| 528 if (queue_.previous_frame()) { | |
| 529 memcpy(frame.data(), | |
| 530 queue_.previous_frame()->data(), | |
| 531 frame.stride() * frame.size().height()); | |
| 532 } | |
| 533 | |
| 534 for (size_t i = 0; i < desktop_config_.displays.size(); ++i) { | |
| 535 const MacDisplayConfiguration& display_config = desktop_config_.displays[i]; | |
| 536 | |
| 537 // Use deprecated APIs to determine the display buffer layout. | |
| 538 DCHECK(cg_display_base_address_ && cg_display_bytes_per_row_ && | |
| 539 cg_display_bits_per_pixel_); | |
| 540 uint8* display_base_address = | |
| 541 reinterpret_cast<uint8*>((*cg_display_base_address_)(display_config.id)); | |
| 542 CHECK(display_base_address); | |
| 543 int src_bytes_per_row = (*cg_display_bytes_per_row_)(display_config.id); | |
| 544 int src_bytes_per_pixel = | |
| 545 (*cg_display_bits_per_pixel_)(display_config.id) / 8; | |
| 546 | |
| 547 // Determine the display's position relative to the desktop, in pixels. | |
| 548 webrtc::DesktopRect display_bounds = display_config.pixel_bounds; | |
| 549 display_bounds.Translate(-desktop_config_.pixel_bounds.left(), | |
| 550 -desktop_config_.pixel_bounds.top()); | |
| 551 | |
| 552 // Determine which parts of the blit region, if any, lay within the monitor. | |
| 553 webrtc::DesktopRegion copy_region = region; | |
| 554 copy_region.IntersectWith(display_bounds); | |
| 555 if (copy_region.is_empty()) | |
| 556 continue; | |
| 557 | |
| 558 // Translate the region to be copied into display-relative coordinates. | |
| 559 copy_region.Translate(-display_bounds.left(), -display_bounds.top()); | |
| 560 | |
| 561 // Calculate where in the output buffer the display's origin is. | |
| 562 uint8* out_ptr = frame.data() + | |
| 563 (display_bounds.left() * src_bytes_per_pixel) + | |
| 564 (display_bounds.top() * frame.stride()); | |
| 565 | |
| 566 // Copy the dirty region from the display buffer into our desktop buffer. | |
| 567 for (webrtc::DesktopRegion::Iterator i(copy_region); | |
| 568 !i.IsAtEnd(); i.Advance()) { | |
| 569 CopyRect(display_base_address, | |
| 570 src_bytes_per_row, | |
| 571 out_ptr, | |
| 572 frame.stride(), | |
| 573 src_bytes_per_pixel, | |
| 574 i.rect()); | |
| 575 } | |
| 576 } | |
| 577 } | |
| 578 | |
| 579 void ScreenCapturerMac::CgBlitPostLion(const webrtc::DesktopFrame& frame, | |
| 580 const webrtc::DesktopRegion& region) { | |
| 581 // Copy the entire contents of the previous capture buffer, to capture over. | |
| 582 // TODO(wez): Get rid of this as per crbug.com/145064, or implement | |
| 583 // crbug.com/92354. | |
| 584 if (queue_.previous_frame()) { | |
| 585 memcpy(frame.data(), | |
| 586 queue_.previous_frame()->data(), | |
| 587 frame.stride() * frame.size().height()); | |
| 588 } | |
| 589 | |
| 590 for (size_t i = 0; i < desktop_config_.displays.size(); ++i) { | |
| 591 const MacDisplayConfiguration& display_config = desktop_config_.displays[i]; | |
| 592 | |
| 593 // Determine the display's position relative to the desktop, in pixels. | |
| 594 webrtc::DesktopRect display_bounds = display_config.pixel_bounds; | |
| 595 display_bounds.Translate(-desktop_config_.pixel_bounds.left(), | |
| 596 -desktop_config_.pixel_bounds.top()); | |
| 597 | |
| 598 // Determine which parts of the blit region, if any, lay within the monitor. | |
| 599 webrtc::DesktopRegion copy_region = region; | |
| 600 copy_region.IntersectWith(display_bounds); | |
| 601 if (copy_region.is_empty()) | |
| 602 continue; | |
| 603 | |
| 604 // Translate the region to be copied into display-relative coordinates. | |
| 605 copy_region.Translate(-display_bounds.left(), -display_bounds.top()); | |
| 606 | |
| 607 // Create an image containing a snapshot of the display. | |
| 608 CGImageRef image = CGDisplayCreateImage(display_config.id); | |
| 609 if (image == NULL) | |
| 610 continue; | |
| 611 | |
| 612 // Request access to the raw pixel data via the image's DataProvider. | |
| 613 CGDataProviderRef provider = CGImageGetDataProvider(image); | |
| 614 CFDataRef data = CGDataProviderCopyData(provider); | |
| 615 CHECK(data); | |
| 616 | |
| 617 const uint8* display_base_address = CFDataGetBytePtr(data); | |
| 618 int src_bytes_per_row = CGImageGetBytesPerRow(image); | |
| 619 int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8; | |
| 620 | |
| 621 // Calculate where in the output buffer the display's origin is. | |
| 622 uint8* out_ptr = frame.data() + | |
| 623 (display_bounds.left() * src_bytes_per_pixel) + | |
| 624 (display_bounds.top() * frame.stride()); | |
| 625 | |
| 626 // Copy the dirty region from the display buffer into our desktop buffer. | |
| 627 for (webrtc::DesktopRegion::Iterator i(copy_region); | |
| 628 !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 CFRelease(data); | |
| 638 CFRelease(image); | |
| 639 } | |
| 640 } | |
| 641 | |
| 642 void ScreenCapturerMac::ScreenConfigurationChanged() { | |
| 643 // Release existing buffers, which will be of the wrong size. | |
| 644 ReleaseBuffers(); | |
| 645 | |
| 646 // Clear the dirty region, in case the display is down-sizing. | |
| 647 helper_.ClearInvalidRegion(); | |
| 648 | |
| 649 // Refresh the cached desktop configuration. | |
| 650 desktop_config_ = MacDesktopConfiguration::GetCurrent( | |
| 651 MacDesktopConfiguration::TopLeftOrigin); | |
| 652 | |
| 653 // Re-mark the entire desktop as dirty. | |
| 654 helper_.InvalidateScreen( | |
| 655 webrtc::DesktopSize(desktop_config_.pixel_bounds.width(), | |
| 656 desktop_config_.pixel_bounds.height())); | |
| 657 | |
| 658 // Make sure the frame buffers will be reallocated. | |
| 659 queue_.Reset(); | |
| 660 | |
| 661 // CgBlitPostLion uses CGDisplayCreateImage() to snapshot each display's | |
| 662 // contents. Although the API exists in OS 10.6, it crashes the caller if | |
| 663 // the machine has no monitor connected, so we fall back to depcreated APIs | |
| 664 // when running on 10.6. | |
| 665 if (IsOSLionOrLater()) { | |
| 666 LOG(INFO) << "Using CgBlitPostLion."; | |
| 667 // No need for any OpenGL support on Lion | |
| 668 return; | |
| 669 } | |
| 670 | |
| 671 // Dynamically link to the deprecated pre-Lion capture APIs. | |
| 672 app_services_library_ = dlopen(kApplicationServicesLibraryName, | |
| 673 RTLD_LAZY); | |
| 674 CHECK(app_services_library_) | |
| 675 << "Failed to open " << kApplicationServicesLibraryName; | |
| 676 | |
| 677 opengl_library_ = dlopen(kOpenGlLibraryName, RTLD_LAZY); | |
| 678 CHECK(opengl_library_) << "Failed to open " << kOpenGlLibraryName; | |
| 679 | |
| 680 cg_display_base_address_ = reinterpret_cast<CGDisplayBaseAddressFunc>( | |
| 681 dlsym(app_services_library_, "CGDisplayBaseAddress")); | |
| 682 cg_display_bytes_per_row_ = reinterpret_cast<CGDisplayBytesPerRowFunc>( | |
| 683 dlsym(app_services_library_, "CGDisplayBytesPerRow")); | |
| 684 cg_display_bits_per_pixel_ = reinterpret_cast<CGDisplayBitsPerPixelFunc>( | |
| 685 dlsym(app_services_library_, "CGDisplayBitsPerPixel")); | |
| 686 cgl_set_full_screen_ = reinterpret_cast<CGLSetFullScreenFunc>( | |
| 687 dlsym(opengl_library_, "CGLSetFullScreen")); | |
| 688 CHECK(cg_display_base_address_ && cg_display_bytes_per_row_ && | |
| 689 cg_display_bits_per_pixel_ && cgl_set_full_screen_); | |
| 690 | |
| 691 if (desktop_config_.displays.size() > 1) { | |
| 692 LOG(INFO) << "Using CgBlitPreLion (Multi-monitor)."; | |
| 693 return; | |
| 694 } | |
| 695 | |
| 696 CGDirectDisplayID mainDevice = CGMainDisplayID(); | |
| 697 if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) { | |
| 698 LOG(INFO) << "Using CgBlitPreLion (OpenGL unavailable)."; | |
| 699 return; | |
| 700 } | |
| 701 | |
| 702 LOG(INFO) << "Using GlBlit"; | |
| 703 | |
| 704 CGLPixelFormatAttribute attributes[] = { | |
| 705 kCGLPFAFullScreen, | |
| 706 kCGLPFADisplayMask, | |
| 707 (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(mainDevice), | |
| 708 (CGLPixelFormatAttribute)0 | |
| 709 }; | |
| 710 CGLPixelFormatObj pixel_format = NULL; | |
| 711 GLint matching_pixel_format_count = 0; | |
| 712 CGLError err = CGLChoosePixelFormat(attributes, | |
| 713 &pixel_format, | |
| 714 &matching_pixel_format_count); | |
| 715 DCHECK_EQ(err, kCGLNoError); | |
| 716 err = CGLCreateContext(pixel_format, NULL, &cgl_context_); | |
| 717 DCHECK_EQ(err, kCGLNoError); | |
| 718 CGLDestroyPixelFormat(pixel_format); | |
| 719 (*cgl_set_full_screen_)(cgl_context_); | |
| 720 CGLSetCurrentContext(cgl_context_); | |
| 721 | |
| 722 size_t buffer_size = desktop_config_.pixel_bounds.width() * | |
| 723 desktop_config_.pixel_bounds.height() * | |
| 724 sizeof(uint32_t); | |
| 725 pixel_buffer_object_.Init(cgl_context_, buffer_size); | |
| 726 } | |
| 727 | |
| 728 bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() { | |
| 729 CGError err = CGRegisterScreenRefreshCallback( | |
| 730 ScreenCapturerMac::ScreenRefreshCallback, this); | |
| 731 if (err != kCGErrorSuccess) { | |
| 732 LOG(ERROR) << "CGRegisterScreenRefreshCallback " << err; | |
| 733 return false; | |
| 734 } | |
| 735 | |
| 736 err = CGScreenRegisterMoveCallback( | |
| 737 ScreenCapturerMac::ScreenUpdateMoveCallback, this); | |
| 738 if (err != kCGErrorSuccess) { | |
| 739 LOG(ERROR) << "CGScreenRegisterMoveCallback " << err; | |
| 740 return false; | |
| 741 } | |
| 742 | |
| 743 return true; | |
| 744 } | |
| 745 | |
| 746 void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() { | |
| 747 CGUnregisterScreenRefreshCallback( | |
| 748 ScreenCapturerMac::ScreenRefreshCallback, this); | |
| 749 CGScreenUnregisterMoveCallback( | |
| 750 ScreenCapturerMac::ScreenUpdateMoveCallback, this); | |
| 751 } | |
| 752 | |
| 753 void ScreenCapturerMac::ScreenRefresh(CGRectCount count, | |
| 754 const CGRect* rect_array) { | |
| 755 if (desktop_config_.pixel_bounds.is_empty()) | |
| 756 return; | |
| 757 | |
| 758 webrtc::DesktopRegion region; | |
| 759 | |
| 760 for (CGRectCount i = 0; i < count; ++i) { | |
| 761 // Convert from Density-Independent Pixel to physical pixel coordinates. | |
| 762 webrtc::DesktopRect rect = | |
| 763 ScaleAndRoundCGRect(rect_array[i], desktop_config_.dip_to_pixel_scale); | |
| 764 | |
| 765 // Translate from local desktop to capturer framebuffer coordinates. | |
| 766 rect.Translate(-desktop_config_.pixel_bounds.left(), | |
| 767 -desktop_config_.pixel_bounds.top()); | |
| 768 | |
| 769 region.AddRect(rect); | |
| 770 } | |
| 771 | |
| 772 helper_.InvalidateRegion(region); | |
| 773 } | |
| 774 | |
| 775 void ScreenCapturerMac::ScreenUpdateMove(CGScreenUpdateMoveDelta delta, | |
| 776 size_t count, | |
| 777 const CGRect* rect_array) { | |
| 778 // Translate |rect_array| to identify the move's destination. | |
| 779 CGRect refresh_rects[count]; | |
| 780 for (CGRectCount i = 0; i < count; ++i) { | |
| 781 refresh_rects[i] = CGRectOffset(rect_array[i], delta.dX, delta.dY); | |
| 782 } | |
| 783 | |
| 784 // Currently we just treat move events the same as refreshes. | |
| 785 ScreenRefresh(count, refresh_rects); | |
| 786 } | |
| 787 | |
| 788 void ScreenCapturerMac::DisplaysReconfigured( | |
| 789 CGDirectDisplayID display, | |
| 790 CGDisplayChangeSummaryFlags flags) { | |
| 791 if (flags & kCGDisplayBeginConfigurationFlag) { | |
| 792 if (reconfiguring_displays_.empty()) { | |
| 793 // If this is the first display to start reconfiguring then wait on | |
| 794 // |display_configuration_capture_event_| to block the capture thread | |
| 795 // from accessing display memory until the reconfiguration completes. | |
| 796 CHECK(display_configuration_capture_event_.TimedWait( | |
| 797 base::TimeDelta::FromSeconds( | |
| 798 kDisplayConfigurationEventTimeoutInSeconds))); | |
| 799 } | |
| 800 | |
| 801 reconfiguring_displays_.insert(display); | |
| 802 } else { | |
| 803 reconfiguring_displays_.erase(display); | |
| 804 | |
| 805 if (reconfiguring_displays_.empty()) { | |
| 806 // If no other displays are reconfiguring then refresh capturer data | |
| 807 // structures and un-block the capturer thread. Occasionally, the | |
| 808 // refresh and move handlers are lost when the screen mode changes, | |
| 809 // so re-register them here (the same does not appear to be true for | |
| 810 // the reconfiguration handler itself). | |
| 811 UnregisterRefreshAndMoveHandlers(); | |
| 812 RegisterRefreshAndMoveHandlers(); | |
| 813 ScreenConfigurationChanged(); | |
| 814 display_configuration_capture_event_.Signal(); | |
| 815 } | |
| 816 } | |
| 817 } | |
| 818 | |
| 819 void ScreenCapturerMac::ScreenRefreshCallback(CGRectCount count, | |
| 820 const CGRect* rect_array, | |
| 821 void* user_parameter) { | |
| 822 ScreenCapturerMac* capturer = reinterpret_cast<ScreenCapturerMac*>( | |
| 823 user_parameter); | |
| 824 if (capturer->desktop_config_.pixel_bounds.is_empty()) { | |
| 825 capturer->ScreenConfigurationChanged(); | |
| 826 } | |
| 827 capturer->ScreenRefresh(count, rect_array); | |
| 828 } | |
| 829 | |
| 830 void ScreenCapturerMac::ScreenUpdateMoveCallback( | |
| 831 CGScreenUpdateMoveDelta delta, | |
| 832 size_t count, | |
| 833 const CGRect* rect_array, | |
| 834 void* user_parameter) { | |
| 835 ScreenCapturerMac* capturer = reinterpret_cast<ScreenCapturerMac*>( | |
| 836 user_parameter); | |
| 837 capturer->ScreenUpdateMove(delta, count, rect_array); | |
| 838 } | |
| 839 | |
| 840 void ScreenCapturerMac::DisplaysReconfiguredCallback( | |
| 841 CGDirectDisplayID display, | |
| 842 CGDisplayChangeSummaryFlags flags, | |
| 843 void* user_parameter) { | |
| 844 ScreenCapturerMac* capturer = reinterpret_cast<ScreenCapturerMac*>( | |
| 845 user_parameter); | |
| 846 capturer->DisplaysReconfigured(display, flags); | |
| 847 } | |
| 848 | |
| 849 } // namespace | |
| 850 | |
| 851 // static | |
| 852 scoped_ptr<ScreenCapturer> ScreenCapturer::Create() { | |
| 853 scoped_ptr<ScreenCapturerMac> capturer(new ScreenCapturerMac()); | |
| 854 if (!capturer->Init()) | |
| 855 capturer.reset(); | |
| 856 return capturer.PassAs<ScreenCapturer>(); | |
| 857 } | |
| 858 | |
| 859 } // namespace media | |
| OLD | NEW |