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