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 |