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