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 "remoting/capturer/video_frame_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 <set> | |
14 #include <stddef.h> | |
15 | |
16 #include "base/logging.h" | |
17 #include "base/file_path.h" | |
18 #include "base/mac/mac_util.h" | |
19 #include "base/mac/scoped_cftyperef.h" | |
20 #include "base/memory/scoped_ptr.h" | |
21 #include "base/scoped_native_library.h" | |
22 #include "base/synchronization/waitable_event.h" | |
23 #include "base/time.h" | |
24 #include "remoting/capturer/capture_data.h" | |
25 #include "remoting/capturer/mac/scoped_pixel_buffer_object.h" | |
26 #include "remoting/capturer/mouse_cursor_shape.h" | |
27 #include "remoting/capturer/video_frame.h" | |
28 #include "remoting/capturer/video_frame_capturer_helper.h" | |
29 #include "remoting/capturer/video_frame_queue.h" | |
30 | |
31 namespace remoting { | |
32 | |
33 namespace { | |
34 | |
35 // Definitions used to dynamic-link to deprecated OS 10.6 functions. | |
36 const char* kApplicationServicesLibraryName = | |
37 "/System/Library/Frameworks/ApplicationServices.framework/" | |
38 "ApplicationServices"; | |
39 typedef void* (*CGDisplayBaseAddressFunc)(CGDirectDisplayID); | |
40 typedef size_t (*CGDisplayBytesPerRowFunc)(CGDirectDisplayID); | |
41 typedef size_t (*CGDisplayBitsPerPixelFunc)(CGDirectDisplayID); | |
42 const char* kOpenGlLibraryName = | |
43 "/System/Library/Frameworks/OpenGL.framework/OpenGL"; | |
44 typedef CGLError (*CGLSetFullScreenFunc)(CGLContextObj); | |
45 | |
46 // skia/ext/skia_utils_mac.h only defines CGRectToSkRect(). | |
47 SkIRect CGRectToSkIRect(const CGRect& rect) { | |
48 SkIRect sk_rect = { | |
49 SkScalarRound(rect.origin.x), | |
50 SkScalarRound(rect.origin.y), | |
51 SkScalarRound(rect.origin.x + rect.size.width), | |
52 SkScalarRound(rect.origin.y + rect.size.height) | |
53 }; | |
54 return sk_rect; | |
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 SkIRect& 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 // The amount of time allowed for displays to reconfigure. | |
82 const int64 kDisplayConfigurationEventTimeoutInSeconds = 10; | |
83 | |
84 // A class representing a full-frame pixel buffer. | |
85 class VideoFrameMac : public VideoFrame { | |
86 public: | |
87 explicit VideoFrameMac(const SkISize& size); | |
88 virtual ~VideoFrameMac(); | |
89 | |
90 const SkIPoint& dpi() const { return dpi_; } | |
91 | |
92 private: | |
93 // Allocated pixel buffer. | |
94 scoped_array<uint8> data_; | |
95 | |
96 // DPI settings for this buffer. | |
97 SkIPoint dpi_; | |
98 | |
99 DISALLOW_COPY_AND_ASSIGN(VideoFrameMac); | |
100 }; | |
101 | |
102 // A class to perform video frame capturing for mac. | |
103 class VideoFrameCapturerMac : public VideoFrameCapturer { | |
104 public: | |
105 VideoFrameCapturerMac(); | |
106 virtual ~VideoFrameCapturerMac(); | |
107 | |
108 bool Init(); | |
109 | |
110 // Overridden from VideoFrameCapturer: | |
111 virtual void Start(Delegate* delegate) OVERRIDE; | |
112 virtual void Stop() OVERRIDE; | |
113 virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE; | |
114 virtual void CaptureFrame() OVERRIDE; | |
115 | |
116 private: | |
117 void CaptureCursor(); | |
118 | |
119 void GlBlitFast(const VideoFrame& buffer, const SkRegion& region); | |
120 void GlBlitSlow(const VideoFrame& buffer); | |
121 void CgBlitPreLion(const VideoFrame& buffer, const SkRegion& region); | |
122 void CgBlitPostLion(const VideoFrame& buffer, const SkRegion& region); | |
123 | |
124 // Called when the screen configuration is changed. | |
125 void ScreenConfigurationChanged(); | |
126 | |
127 void ScreenRefresh(CGRectCount count, const CGRect *rect_array); | |
128 void ScreenUpdateMove(CGScreenUpdateMoveDelta delta, | |
129 size_t count, | |
130 const CGRect *rect_array); | |
131 void DisplaysReconfigured(CGDirectDisplayID display, | |
132 CGDisplayChangeSummaryFlags flags); | |
133 static void ScreenRefreshCallback(CGRectCount count, | |
134 const CGRect *rect_array, | |
135 void *user_parameter); | |
136 static void ScreenUpdateMoveCallback(CGScreenUpdateMoveDelta delta, | |
137 size_t count, | |
138 const CGRect *rect_array, | |
139 void *user_parameter); | |
140 static void DisplaysReconfiguredCallback(CGDirectDisplayID display, | |
141 CGDisplayChangeSummaryFlags flags, | |
142 void *user_parameter); | |
143 | |
144 void ReleaseBuffers(); | |
145 | |
146 Delegate* delegate_; | |
147 | |
148 CGLContextObj cgl_context_; | |
149 ScopedPixelBufferObject pixel_buffer_object_; | |
150 | |
151 // Queue of the frames buffers. | |
152 VideoFrameQueue queue_; | |
153 | |
154 // Current display configuration. | |
155 std::vector<CGDirectDisplayID> display_ids_; | |
156 SkIRect desktop_bounds_; | |
157 | |
158 // A thread-safe list of invalid rectangles, and the size of the most | |
159 // recently captured screen. | |
160 VideoFrameCapturerHelper helper_; | |
161 | |
162 // Image of the last cursor that we sent to the client. | |
163 base::mac::ScopedCFTypeRef<CGImageRef> current_cursor_; | |
164 | |
165 // Contains an invalid region from the previous capture. | |
166 SkRegion last_invalid_region_; | |
167 | |
168 // Used to ensure that frame captures do not take place while displays | |
169 // are being reconfigured. | |
170 base::WaitableEvent display_configuration_capture_event_; | |
171 | |
172 // Records the Ids of attached displays which are being reconfigured. | |
173 // Accessed on the thread on which we are notified of display events. | |
174 std::set<CGDirectDisplayID> reconfiguring_displays_; | |
175 | |
176 // Power management assertion to prevent the screen from sleeping. | |
177 IOPMAssertionID power_assertion_id_display_; | |
178 | |
179 // Power management assertion to indicate that the user is active. | |
180 IOPMAssertionID power_assertion_id_user_; | |
181 | |
182 // Dynamically link to deprecated APIs for Mac OS X 10.6 support. | |
183 base::ScopedNativeLibrary app_services_library_; | |
184 CGDisplayBaseAddressFunc cg_display_base_address_; | |
185 CGDisplayBytesPerRowFunc cg_display_bytes_per_row_; | |
186 CGDisplayBitsPerPixelFunc cg_display_bits_per_pixel_; | |
187 base::ScopedNativeLibrary opengl_library_; | |
188 CGLSetFullScreenFunc cgl_set_full_screen_; | |
189 | |
190 DISALLOW_COPY_AND_ASSIGN(VideoFrameCapturerMac); | |
191 }; | |
192 | |
193 VideoFrameMac::VideoFrameMac(const SkISize& size) { | |
194 set_bytes_per_row(size.width() * sizeof(uint32_t)); | |
195 set_dimensions(size); | |
196 | |
197 size_t buffer_size = size.width() * size.height() * sizeof(uint32_t); | |
198 data_.reset(new uint8[buffer_size]); | |
199 set_pixels(data_.get()); | |
200 | |
201 // TODO(wez): Move the ugly DPI code into a helper. | |
202 NSScreen* screen = [NSScreen mainScreen]; | |
203 NSDictionary* attr = [screen deviceDescription]; | |
204 NSSize resolution = [[attr objectForKey: NSDeviceResolution] sizeValue]; | |
205 dpi_.set(resolution.width, resolution.height); | |
206 } | |
207 | |
208 VideoFrameMac::~VideoFrameMac() { | |
209 } | |
210 | |
211 VideoFrameCapturerMac::VideoFrameCapturerMac() | |
212 : delegate_(NULL), | |
213 cgl_context_(NULL), | |
214 display_configuration_capture_event_(false, true), | |
215 power_assertion_id_display_(kIOPMNullAssertionID), | |
216 power_assertion_id_user_(kIOPMNullAssertionID), | |
217 cg_display_base_address_(NULL), | |
218 cg_display_bytes_per_row_(NULL), | |
219 cg_display_bits_per_pixel_(NULL), | |
220 cgl_set_full_screen_(NULL) | |
221 { | |
222 } | |
223 | |
224 VideoFrameCapturerMac::~VideoFrameCapturerMac() { | |
225 ReleaseBuffers(); | |
226 CGUnregisterScreenRefreshCallback( | |
227 VideoFrameCapturerMac::ScreenRefreshCallback, this); | |
228 CGScreenUnregisterMoveCallback( | |
229 VideoFrameCapturerMac::ScreenUpdateMoveCallback, this); | |
230 CGError err = CGDisplayRemoveReconfigurationCallback( | |
231 VideoFrameCapturerMac::DisplaysReconfiguredCallback, this); | |
232 if (err != kCGErrorSuccess) { | |
233 LOG(ERROR) << "CGDisplayRemoveReconfigurationCallback " << err; | |
234 } | |
235 } | |
236 | |
237 bool VideoFrameCapturerMac::Init() { | |
238 CGError err = CGRegisterScreenRefreshCallback( | |
239 VideoFrameCapturerMac::ScreenRefreshCallback, this); | |
240 if (err != kCGErrorSuccess) { | |
241 LOG(ERROR) << "CGRegisterScreenRefreshCallback " << err; | |
242 return false; | |
243 } | |
244 | |
245 err = CGScreenRegisterMoveCallback( | |
246 VideoFrameCapturerMac::ScreenUpdateMoveCallback, this); | |
247 if (err != kCGErrorSuccess) { | |
248 LOG(ERROR) << "CGScreenRegisterMoveCallback " << err; | |
249 return false; | |
250 } | |
251 err = CGDisplayRegisterReconfigurationCallback( | |
252 VideoFrameCapturerMac::DisplaysReconfiguredCallback, this); | |
253 if (err != kCGErrorSuccess) { | |
254 LOG(ERROR) << "CGDisplayRegisterReconfigurationCallback " << err; | |
255 return false; | |
256 } | |
257 | |
258 ScreenConfigurationChanged(); | |
259 return true; | |
260 } | |
261 | |
262 void VideoFrameCapturerMac::ReleaseBuffers() { | |
263 if (cgl_context_) { | |
264 pixel_buffer_object_.Release(); | |
265 CGLDestroyContext(cgl_context_); | |
266 cgl_context_ = NULL; | |
267 } | |
268 // The buffers might be in use by the encoder, so don't delete them here. | |
269 // Instead, mark them as "needs update"; next time the buffers are used by | |
270 // the capturer, they will be recreated if necessary. | |
271 queue_.SetAllFramesNeedUpdate(); | |
272 } | |
273 | |
274 void VideoFrameCapturerMac::Start(Delegate* delegate) { | |
275 DCHECK(delegate_ == NULL); | |
276 | |
277 delegate_ = delegate; | |
278 | |
279 // Create power management assertions to wake the display and prevent it from | |
280 // going to sleep on user idle. | |
281 // TODO(jamiewalch): Use IOPMAssertionDeclareUserActivity on 10.7.3 and above | |
282 // instead of the following two assertions. | |
283 IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, | |
284 kIOPMAssertionLevelOn, | |
285 CFSTR("Chrome Remote Desktop connection active"), | |
286 &power_assertion_id_display_); | |
287 // This assertion ensures that the display is woken up if it already asleep | |
288 // (as used by Apple Remote Desktop). | |
289 IOPMAssertionCreateWithName(CFSTR("UserIsActive"), | |
290 kIOPMAssertionLevelOn, | |
291 CFSTR("Chrome Remote Desktop connection active"), | |
292 &power_assertion_id_user_); | |
293 } | |
294 | |
295 void VideoFrameCapturerMac::Stop() { | |
296 if (power_assertion_id_display_ != kIOPMNullAssertionID) { | |
297 IOPMAssertionRelease(power_assertion_id_display_); | |
298 power_assertion_id_display_ = kIOPMNullAssertionID; | |
299 } | |
300 if (power_assertion_id_user_ != kIOPMNullAssertionID) { | |
301 IOPMAssertionRelease(power_assertion_id_user_); | |
302 power_assertion_id_user_ = kIOPMNullAssertionID; | |
303 } | |
304 } | |
305 | |
306 void VideoFrameCapturerMac::InvalidateRegion(const SkRegion& invalid_region) { | |
307 helper_.InvalidateRegion(invalid_region); | |
308 } | |
309 | |
310 void VideoFrameCapturerMac::CaptureFrame() { | |
311 // Only allow captures when the display configuration is not occurring. | |
312 scoped_refptr<CaptureData> data; | |
313 | |
314 base::Time capture_start_time = base::Time::Now(); | |
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 SkRegion region; | |
325 helper_.SwapInvalidRegion(®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_needs_update()) { | |
331 scoped_ptr<VideoFrameMac> buffer(new VideoFrameMac( | |
332 SkISize::Make(desktop_bounds_.width(), desktop_bounds_.height()))); | |
333 queue_.ReplaceCurrentFrame(buffer.PassAs<VideoFrame>()); | |
334 } | |
335 | |
336 VideoFrame* current_buffer = queue_.current_frame(); | |
337 | |
338 bool flip = false; // GL capturers need flipping. | |
339 if (base::mac::IsOSLionOrLater()) { | |
340 // Lion requires us to use their new APIs for doing screen capture. These | |
341 // APIS currently crash on 10.6.8 if there is no monitor attached. | |
342 CgBlitPostLion(*current_buffer, region); | |
343 } else if (cgl_context_) { | |
344 flip = true; | |
345 if (pixel_buffer_object_.get() != 0) { | |
346 GlBlitFast(*current_buffer, region); | |
347 } else { | |
348 // See comment in ScopedPixelBufferObject::Init about why the slow | |
349 // path is always used on 10.5. | |
350 GlBlitSlow(*current_buffer); | |
351 } | |
352 } else { | |
353 CgBlitPreLion(*current_buffer, region); | |
354 } | |
355 | |
356 uint8* buffer = current_buffer->pixels(); | |
357 int stride = current_buffer->bytes_per_row(); | |
358 if (flip) { | |
359 stride = -stride; | |
360 buffer += (current_buffer->dimensions().height() - 1) * | |
361 current_buffer->bytes_per_row(); | |
362 } | |
363 | |
364 data = new CaptureData(buffer, stride, current_buffer->dimensions()); | |
365 data->set_dpi(static_cast<VideoFrameMac*>(current_buffer)->dpi()); | |
366 data->mutable_dirty_region() = region; | |
367 | |
368 helper_.set_size_most_recent(data->size()); | |
369 | |
370 // Signal that we are done capturing data from the display framebuffer, | |
371 // and accessing display structures. | |
372 display_configuration_capture_event_.Signal(); | |
373 | |
374 // Capture the current cursor shape and notify |delegate_| if it has changed. | |
375 CaptureCursor(); | |
376 | |
377 // Move the capture frame buffer queue on to the next buffer. | |
378 queue_.DoneWithCurrentFrame(); | |
379 | |
380 data->set_capture_time_ms( | |
381 (base::Time::Now() - capture_start_time).InMillisecondsRoundedUp()); | |
382 delegate_->OnCaptureCompleted(data); | |
383 } | |
384 | |
385 void VideoFrameCapturerMac::CaptureCursor() { | |
386 NSCursor* cursor = [NSCursor currentSystemCursor]; | |
387 if (cursor == nil) { | |
388 return; | |
389 } | |
390 | |
391 NSImage* nsimage = [cursor image]; | |
392 NSPoint hotspot = [cursor hotSpot]; | |
393 NSSize size = [nsimage size]; | |
394 CGImageRef image = [nsimage CGImageForProposedRect:NULL | |
395 context:nil | |
396 hints:nil]; | |
397 if (image == nil) { | |
398 return; | |
399 } | |
400 | |
401 if (CGImageGetBitsPerPixel(image) != 32 || | |
402 CGImageGetBytesPerRow(image) != (size.width * 4) || | |
403 CGImageGetBitsPerComponent(image) != 8) { | |
404 return; | |
405 } | |
406 | |
407 // Compare the current cursor with the last one we sent to the client | |
408 // and exit if the cursor is the same. | |
409 if (current_cursor_.get() != NULL) { | |
410 CGImageRef current = current_cursor_.get(); | |
411 if (CGImageGetWidth(image) == CGImageGetWidth(current) && | |
412 CGImageGetHeight(image) == CGImageGetHeight(current) && | |
413 CGImageGetBitsPerPixel(image) == CGImageGetBitsPerPixel(current) && | |
414 CGImageGetBytesPerRow(image) == CGImageGetBytesPerRow(current) && | |
415 CGImageGetBitsPerComponent(image) == | |
416 CGImageGetBitsPerComponent(current)) { | |
417 CGDataProviderRef provider_new = CGImageGetDataProvider(image); | |
418 base::mac::ScopedCFTypeRef<CFDataRef> data_ref_new( | |
419 CGDataProviderCopyData(provider_new)); | |
420 CGDataProviderRef provider_current = CGImageGetDataProvider(current); | |
421 base::mac::ScopedCFTypeRef<CFDataRef> data_ref_current( | |
422 CGDataProviderCopyData(provider_current)); | |
423 | |
424 if (data_ref_new.get() != NULL && data_ref_current.get() != NULL) { | |
425 int data_size = CFDataGetLength(data_ref_new); | |
426 CHECK(data_size == CFDataGetLength(data_ref_current)); | |
427 const uint8* data_new = CFDataGetBytePtr(data_ref_new); | |
428 const uint8* data_current = CFDataGetBytePtr(data_ref_current); | |
429 if (memcmp(data_new, data_current, data_size) == 0) { | |
430 return; | |
431 } | |
432 } | |
433 } | |
434 } | |
435 | |
436 // Record the last cursor image. | |
437 current_cursor_.reset(CGImageCreateCopy(image)); | |
438 | |
439 VLOG(3) << "Sending cursor: " << size.width << "x" << size.height; | |
440 | |
441 CGDataProviderRef provider = CGImageGetDataProvider(image); | |
442 base::mac::ScopedCFTypeRef<CFDataRef> image_data_ref( | |
443 CGDataProviderCopyData(provider)); | |
444 if (image_data_ref.get() == NULL) { | |
445 return; | |
446 } | |
447 const char* cursor_src_data = | |
448 reinterpret_cast<const char*>(CFDataGetBytePtr(image_data_ref)); | |
449 int data_size = CFDataGetLength(image_data_ref); | |
450 | |
451 // Create a MouseCursorShape that describes the cursor and pass it to | |
452 // the client. | |
453 scoped_ptr<MouseCursorShape> cursor_shape(new MouseCursorShape()); | |
454 cursor_shape->size.set(size.width, size.height); | |
455 cursor_shape->hotspot.set(hotspot.x, hotspot.y); | |
456 cursor_shape->data.assign(cursor_src_data, cursor_src_data + data_size); | |
457 | |
458 delegate_->OnCursorShapeChanged(cursor_shape.Pass()); | |
459 } | |
460 | |
461 void VideoFrameCapturerMac::GlBlitFast(const VideoFrame& buffer, | |
462 const SkRegion& region) { | |
463 const int buffer_height = buffer.dimensions().height(); | |
464 const int buffer_width = buffer.dimensions().width(); | |
465 | |
466 // Clip to the size of our current screen. | |
467 SkIRect clip_rect = SkIRect::MakeWH(buffer_width, buffer_height); | |
468 if (queue_.previous_frame()) { | |
469 // We are doing double buffer for the capture data so we just need to copy | |
470 // the invalid region from the previous capture in the current buffer. | |
471 // TODO(hclam): We can reduce the amount of copying here by subtracting | |
472 // |capturer_helper_|s region from |last_invalid_region_|. | |
473 // http://crbug.com/92354 | |
474 | |
475 // Since the image obtained from OpenGL is upside-down, need to do some | |
476 // magic here to copy the correct rectangle. | |
477 const int y_offset = (buffer_height - 1) * buffer.bytes_per_row(); | |
478 for(SkRegion::Iterator i(last_invalid_region_); !i.done(); i.next()) { | |
479 SkIRect copy_rect = i.rect(); | |
480 if (copy_rect.intersect(clip_rect)) { | |
481 CopyRect(queue_.previous_frame()->pixels() + y_offset, | |
482 -buffer.bytes_per_row(), | |
483 buffer.pixels() + y_offset, | |
484 -buffer.bytes_per_row(), | |
485 4, // Bytes for pixel for RGBA. | |
486 copy_rect); | |
487 } | |
488 } | |
489 } | |
490 last_invalid_region_ = region; | |
491 | |
492 CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; | |
493 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_.get()); | |
494 glReadPixels(0, 0, buffer_width, buffer_height, GL_BGRA, GL_UNSIGNED_BYTE, 0); | |
495 GLubyte* ptr = static_cast<GLubyte*>( | |
496 glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB)); | |
497 if (ptr == NULL) { | |
498 // If the buffer can't be mapped, assume that it's no longer valid and | |
499 // release it. | |
500 pixel_buffer_object_.Release(); | |
501 } else { | |
502 // Copy only from the dirty rects. Since the image obtained from OpenGL is | |
503 // upside-down we need to do some magic here to copy the correct rectangle. | |
504 const int y_offset = (buffer_height - 1) * buffer.bytes_per_row(); | |
505 for(SkRegion::Iterator i(region); !i.done(); i.next()) { | |
506 SkIRect copy_rect = i.rect(); | |
507 if (copy_rect.intersect(clip_rect)) { | |
508 CopyRect(ptr + y_offset, | |
509 -buffer.bytes_per_row(), | |
510 buffer.pixels() + y_offset, | |
511 -buffer.bytes_per_row(), | |
512 4, // Bytes for pixel for RGBA. | |
513 copy_rect); | |
514 } | |
515 } | |
516 } | |
517 if (!glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB)) { | |
518 // If glUnmapBuffer returns false, then the contents of the data store are | |
519 // undefined. This might be because the screen mode has changed, in which | |
520 // case it will be recreated in ScreenConfigurationChanged, but releasing | |
521 // the object here is the best option. Capturing will fall back on | |
522 // GlBlitSlow until such time as the pixel buffer object is recreated. | |
523 pixel_buffer_object_.Release(); | |
524 } | |
525 glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); | |
526 } | |
527 | |
528 void VideoFrameCapturerMac::GlBlitSlow(const VideoFrame& buffer) { | |
529 CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; | |
530 glReadBuffer(GL_FRONT); | |
531 glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); | |
532 glPixelStorei(GL_PACK_ALIGNMENT, 4); // Force 4-byte alignment. | |
533 glPixelStorei(GL_PACK_ROW_LENGTH, 0); | |
534 glPixelStorei(GL_PACK_SKIP_ROWS, 0); | |
535 glPixelStorei(GL_PACK_SKIP_PIXELS, 0); | |
536 // Read a block of pixels from the frame buffer. | |
537 glReadPixels(0, 0, buffer.dimensions().width(), buffer.dimensions().height(), | |
538 GL_BGRA, GL_UNSIGNED_BYTE, buffer.pixels()); | |
539 glPopClientAttrib(); | |
540 } | |
541 | |
542 void VideoFrameCapturerMac::CgBlitPreLion(const VideoFrame& buffer, | |
543 const SkRegion& region) { | |
544 const int buffer_height = buffer.dimensions().height(); | |
545 | |
546 // Copy the entire contents of the previous capture buffer, to capture over. | |
547 // TODO(wez): Get rid of this as per crbug.com/145064, or implement | |
548 // crbug.com/92354. | |
549 if (queue_.previous_frame()) { | |
550 memcpy(buffer.pixels(), | |
551 queue_.previous_frame()->pixels(), | |
552 buffer.bytes_per_row() * buffer_height); | |
553 } | |
554 | |
555 for (unsigned int d = 0; d < display_ids_.size(); ++d) { | |
556 // Use deprecated APIs to determine the display buffer layout. | |
557 DCHECK(cg_display_base_address_ && cg_display_bytes_per_row_ && | |
558 cg_display_bits_per_pixel_); | |
559 uint8* display_base_address = | |
560 reinterpret_cast<uint8*>((*cg_display_base_address_)(display_ids_[d])); | |
561 CHECK(display_base_address); | |
562 int src_bytes_per_row = (*cg_display_bytes_per_row_)(display_ids_[d]); | |
563 int src_bytes_per_pixel = | |
564 (*cg_display_bits_per_pixel_)(display_ids_[d]) / 8; | |
565 | |
566 // Determine the position of the display in the buffer. | |
567 SkIRect display_bounds = CGRectToSkIRect(CGDisplayBounds(display_ids_[d])); | |
568 display_bounds.offset(-desktop_bounds_.left(), -desktop_bounds_.top()); | |
569 | |
570 // Determine which parts of the blit region, if any, lay within the monitor. | |
571 SkRegion copy_region; | |
572 if (!copy_region.op(region, display_bounds, SkRegion::kIntersect_Op)) | |
573 continue; | |
574 | |
575 // Translate the region to be copied into display-relative coordinates. | |
576 copy_region.translate(-display_bounds.left(), -display_bounds.top()); | |
577 | |
578 // Calculate where in the output buffer the display's origin is. | |
579 uint8* out_ptr = buffer.pixels() + | |
580 (display_bounds.left() * src_bytes_per_pixel) + | |
581 (display_bounds.top() * buffer.bytes_per_row()); | |
582 | |
583 // Copy the dirty region from the display buffer into our desktop buffer. | |
584 for(SkRegion::Iterator i(copy_region); !i.done(); i.next()) { | |
585 CopyRect(display_base_address, | |
586 src_bytes_per_row, | |
587 out_ptr, | |
588 buffer.bytes_per_row(), | |
589 src_bytes_per_pixel, | |
590 i.rect()); | |
591 } | |
592 } | |
593 } | |
594 | |
595 void VideoFrameCapturerMac::CgBlitPostLion(const VideoFrame& buffer, | |
596 const SkRegion& region) { | |
597 const int buffer_height = buffer.dimensions().height(); | |
598 | |
599 // Copy the entire contents of the previous capture buffer, to capture over. | |
600 // TODO(wez): Get rid of this as per crbug.com/145064, or implement | |
601 // crbug.com/92354. | |
602 if (queue_.previous_frame()) { | |
603 memcpy(buffer.pixels(), | |
604 queue_.previous_frame()->pixels(), | |
605 buffer.bytes_per_row() * buffer_height); | |
606 } | |
607 | |
608 for (unsigned int d = 0; d < display_ids_.size(); ++d) { | |
609 // Determine the position of the display in the buffer. | |
610 SkIRect display_bounds = CGRectToSkIRect(CGDisplayBounds(display_ids_[d])); | |
611 display_bounds.offset(-desktop_bounds_.left(), -desktop_bounds_.top()); | |
612 | |
613 // Determine which parts of the blit region, if any, lay within the monitor. | |
614 SkRegion copy_region; | |
615 if (!copy_region.op(region, display_bounds, SkRegion::kIntersect_Op)) | |
616 continue; | |
617 | |
618 // Translate the region to be copied into display-relative coordinates. | |
619 copy_region.translate(-display_bounds.left(), -display_bounds.top()); | |
620 | |
621 // Create an image containing a snapshot of the display. | |
622 base::mac::ScopedCFTypeRef<CGImageRef> image( | |
623 CGDisplayCreateImage(display_ids_[d])); | |
624 if (image.get() == NULL) | |
625 continue; | |
626 | |
627 // Request access to the raw pixel data via the image's DataProvider. | |
628 CGDataProviderRef provider = CGImageGetDataProvider(image); | |
629 base::mac::ScopedCFTypeRef<CFDataRef> data( | |
630 CGDataProviderCopyData(provider)); | |
631 if (data.get() == NULL) | |
632 continue; | |
633 | |
634 const uint8* display_base_address = CFDataGetBytePtr(data); | |
635 int src_bytes_per_row = CGImageGetBytesPerRow(image); | |
636 int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8; | |
637 | |
638 // Calculate where in the output buffer the display's origin is. | |
639 uint8* out_ptr = buffer.pixels() + | |
640 (display_bounds.left() * src_bytes_per_pixel) + | |
641 (display_bounds.top() * buffer.bytes_per_row()); | |
642 | |
643 // Copy the dirty region from the display buffer into our desktop buffer. | |
644 for(SkRegion::Iterator i(copy_region); !i.done(); i.next()) { | |
645 CopyRect(display_base_address, | |
646 src_bytes_per_row, | |
647 out_ptr, | |
648 buffer.bytes_per_row(), | |
649 src_bytes_per_pixel, | |
650 i.rect()); | |
651 } | |
652 } | |
653 } | |
654 | |
655 void VideoFrameCapturerMac::ScreenConfigurationChanged() { | |
656 // Release existing buffers, which will be of the wrong size. | |
657 ReleaseBuffers(); | |
658 | |
659 // Clear the dirty region, in case the display is down-sizing. | |
660 helper_.ClearInvalidRegion(); | |
661 | |
662 // Fetch the list if active displays and calculate their bounds. | |
663 CGDisplayCount display_count; | |
664 CGError error = CGGetActiveDisplayList(0, NULL, &display_count); | |
665 CHECK_EQ(error, CGDisplayNoErr); | |
666 | |
667 display_ids_.resize(display_count); | |
668 error = CGGetActiveDisplayList(display_count, &display_ids_[0], | |
669 &display_count); | |
670 CHECK_EQ(error, CGDisplayNoErr); | |
671 CHECK_EQ(display_count, display_ids_.size()); | |
672 | |
673 desktop_bounds_ = SkIRect::MakeEmpty(); | |
674 for (unsigned int d = 0; d < display_count; ++d) { | |
675 CGRect display_bounds = CGDisplayBounds(display_ids_[d]); | |
676 desktop_bounds_.join(CGRectToSkIRect(display_bounds)); | |
677 } | |
678 | |
679 // Re-mark the entire desktop as dirty. | |
680 helper_.InvalidateScreen(SkISize::Make(desktop_bounds_.width(), | |
681 desktop_bounds_.height())); | |
682 | |
683 // Make sure the frame buffers will be reallocated. | |
684 queue_.SetAllFramesNeedUpdate(); | |
685 | |
686 // CgBlitPostLion uses CGDisplayCreateImage() to snapshot each display's | |
687 // contents. Although the API exists in OS 10.6, it crashes the caller if | |
688 // the machine has no monitor connected, so we fall back to depcreated APIs | |
689 // when running on 10.6. | |
690 if (base::mac::IsOSLionOrLater()) { | |
691 LOG(INFO) << "Using CgBlitPostLion."; | |
692 // No need for any OpenGL support on Lion | |
693 return; | |
694 } | |
695 | |
696 // Dynamically link to the deprecated pre-Lion capture APIs. | |
697 std::string app_services_library_error; | |
698 FilePath app_services_path(kApplicationServicesLibraryName); | |
699 app_services_library_.Reset( | |
700 base::LoadNativeLibrary(app_services_path, &app_services_library_error)); | |
701 CHECK(app_services_library_.is_valid()) << app_services_library_error; | |
702 | |
703 std::string opengl_library_error; | |
704 FilePath opengl_path(kOpenGlLibraryName); | |
705 opengl_library_.Reset( | |
706 base::LoadNativeLibrary(opengl_path, &opengl_library_error)); | |
707 CHECK(opengl_library_.is_valid()) << opengl_library_error; | |
708 | |
709 cg_display_base_address_ = reinterpret_cast<CGDisplayBaseAddressFunc>( | |
710 app_services_library_.GetFunctionPointer("CGDisplayBaseAddress")); | |
711 cg_display_bytes_per_row_ = reinterpret_cast<CGDisplayBytesPerRowFunc>( | |
712 app_services_library_.GetFunctionPointer("CGDisplayBytesPerRow")); | |
713 cg_display_bits_per_pixel_ = reinterpret_cast<CGDisplayBitsPerPixelFunc>( | |
714 app_services_library_.GetFunctionPointer("CGDisplayBitsPerPixel")); | |
715 cgl_set_full_screen_ = reinterpret_cast<CGLSetFullScreenFunc>( | |
716 opengl_library_.GetFunctionPointer("CGLSetFullScreen")); | |
717 CHECK(cg_display_base_address_ && cg_display_bytes_per_row_ && | |
718 cg_display_bits_per_pixel_ && cgl_set_full_screen_); | |
719 | |
720 if (display_ids_.size() > 1) { | |
721 LOG(INFO) << "Using CgBlitPreLion (Multi-monitor)."; | |
722 return; | |
723 } | |
724 | |
725 CGDirectDisplayID mainDevice = CGMainDisplayID(); | |
726 if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) { | |
727 LOG(INFO) << "Using CgBlitPreLion (OpenGL unavailable)."; | |
728 return; | |
729 } | |
730 | |
731 LOG(INFO) << "Using GlBlit"; | |
732 | |
733 CGLPixelFormatAttribute attributes[] = { | |
734 kCGLPFAFullScreen, | |
735 kCGLPFADisplayMask, | |
736 (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(mainDevice), | |
737 (CGLPixelFormatAttribute)0 | |
738 }; | |
739 CGLPixelFormatObj pixel_format = NULL; | |
740 GLint matching_pixel_format_count = 0; | |
741 CGLError err = CGLChoosePixelFormat(attributes, | |
742 &pixel_format, | |
743 &matching_pixel_format_count); | |
744 DCHECK_EQ(err, kCGLNoError); | |
745 err = CGLCreateContext(pixel_format, NULL, &cgl_context_); | |
746 DCHECK_EQ(err, kCGLNoError); | |
747 CGLDestroyPixelFormat(pixel_format); | |
748 (*cgl_set_full_screen_)(cgl_context_); | |
749 CGLSetCurrentContext(cgl_context_); | |
750 | |
751 size_t buffer_size = desktop_bounds_.width() * desktop_bounds_.height() * | |
752 sizeof(uint32_t); | |
753 pixel_buffer_object_.Init(cgl_context_, buffer_size); | |
754 } | |
755 | |
756 void VideoFrameCapturerMac::ScreenRefresh(CGRectCount count, | |
757 const CGRect* rect_array) { | |
758 if (desktop_bounds_.isEmpty()) { | |
759 return; | |
760 } | |
761 SkIRect skirect_array[count]; | |
762 for (CGRectCount i = 0; i < count; ++i) { | |
763 skirect_array[i] = CGRectToSkIRect(rect_array[i]); | |
764 skirect_array[i].offset(-desktop_bounds_.left(), -desktop_bounds_.top()); | |
765 } | |
766 SkRegion region; | |
767 region.setRects(skirect_array, count); | |
768 InvalidateRegion(region); | |
769 } | |
770 | |
771 void VideoFrameCapturerMac::ScreenUpdateMove(CGScreenUpdateMoveDelta delta, | |
772 size_t count, | |
773 const CGRect* rect_array) { | |
774 SkIRect skirect_array[count]; | |
775 for (CGRectCount i = 0; i < count; ++i) { | |
776 CGRect rect = rect_array[i]; | |
777 rect = CGRectOffset(rect, delta.dX, delta.dY); | |
778 skirect_array[i] = CGRectToSkIRect(rect); | |
779 skirect_array[i].offset(-desktop_bounds_.left(), -desktop_bounds_.top()); | |
780 } | |
781 SkRegion region; | |
782 region.setRects(skirect_array, count); | |
783 InvalidateRegion(region); | |
784 } | |
785 | |
786 void VideoFrameCapturerMac::DisplaysReconfigured( | |
787 CGDirectDisplayID display, | |
788 CGDisplayChangeSummaryFlags flags) { | |
789 if (flags & kCGDisplayBeginConfigurationFlag) { | |
790 if (reconfiguring_displays_.empty()) { | |
791 // If this is the first display to start reconfiguring then wait on | |
792 // |display_configuration_capture_event_| to block the capture thread | |
793 // from accessing display memory until the reconfiguration completes. | |
794 CHECK(display_configuration_capture_event_.TimedWait( | |
795 base::TimeDelta::FromSeconds( | |
796 kDisplayConfigurationEventTimeoutInSeconds))); | |
797 } | |
798 | |
799 reconfiguring_displays_.insert(display); | |
800 } else { | |
801 reconfiguring_displays_.erase(display); | |
802 | |
803 if (reconfiguring_displays_.empty()) { | |
804 // If no other displays are reconfiguring then refresh capturer data | |
805 // structures and un-block the capturer thread. | |
806 ScreenConfigurationChanged(); | |
807 display_configuration_capture_event_.Signal(); | |
808 } | |
809 } | |
810 } | |
811 | |
812 void VideoFrameCapturerMac::ScreenRefreshCallback(CGRectCount count, | |
813 const CGRect* rect_array, | |
814 void* user_parameter) { | |
815 VideoFrameCapturerMac* capturer = reinterpret_cast<VideoFrameCapturerMac*>( | |
816 user_parameter); | |
817 if (capturer->desktop_bounds_.isEmpty()) { | |
818 capturer->ScreenConfigurationChanged(); | |
819 } | |
820 capturer->ScreenRefresh(count, rect_array); | |
821 } | |
822 | |
823 void VideoFrameCapturerMac::ScreenUpdateMoveCallback( | |
824 CGScreenUpdateMoveDelta delta, | |
825 size_t count, | |
826 const CGRect* rect_array, | |
827 void* user_parameter) { | |
828 VideoFrameCapturerMac* capturer = reinterpret_cast<VideoFrameCapturerMac*>( | |
829 user_parameter); | |
830 capturer->ScreenUpdateMove(delta, count, rect_array); | |
831 } | |
832 | |
833 void VideoFrameCapturerMac::DisplaysReconfiguredCallback( | |
834 CGDirectDisplayID display, | |
835 CGDisplayChangeSummaryFlags flags, | |
836 void* user_parameter) { | |
837 VideoFrameCapturerMac* capturer = reinterpret_cast<VideoFrameCapturerMac*>( | |
838 user_parameter); | |
839 capturer->DisplaysReconfigured(display, flags); | |
840 } | |
841 | |
842 } // namespace | |
843 | |
844 // static | |
845 scoped_ptr<VideoFrameCapturer> VideoFrameCapturer::Create() { | |
846 scoped_ptr<VideoFrameCapturerMac> capturer(new VideoFrameCapturerMac()); | |
847 if (!capturer->Init()) | |
848 capturer.reset(); | |
849 return capturer.PassAs<VideoFrameCapturer>(); | |
850 } | |
851 | |
852 // static | |
853 scoped_ptr<VideoFrameCapturer> VideoFrameCapturer::CreateWithFactory( | |
854 SharedBufferFactory* shared_buffer_factory) { | |
855 NOTIMPLEMENTED(); | |
856 return scoped_ptr<VideoFrameCapturer>(); | |
857 } | |
858 | |
859 } // namespace remoting | |
OLD | NEW |