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