From 7ee0c16eddb5a9a9de8522784f914f0d46a7ea64 Mon Sep 17 00:00:00 2001 From: "jiayl@webrtc.org" Date: Wed, 26 Mar 2014 15:57:43 +0000 Subject: [PATCH] Makes ScreenCapturerMac exclude the window specified in DesktopCapturer::SetExcludedWindow. No behavior change for now since Chromium has not been updated to call SetExcludedWindow. BUG=2789 R=sergeyu@chromium.org Review URL: https://webrtc-codereview.appspot.com/10299004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5792 4adac7df-926f-26a2-2b94-8c16560cd09d --- .../desktop_and_cursor_composer.cc | 4 + .../desktop_and_cursor_composer.h | 1 + .../desktop_capture/desktop_capturer.h | 7 + .../desktop_capture/screen_capturer_mac.mm | 165 +++++++++++++++++- 4 files changed, 173 insertions(+), 4 deletions(-) diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc index 05e2a9b2b..2547ba37a 100644 --- a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc +++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc @@ -142,6 +142,10 @@ void DesktopAndCursorComposer::Capture(const DesktopRegion& region) { desktop_capturer_->Capture(region); } +void DesktopAndCursorComposer::SetExcludedWindow(WindowId window) { + desktop_capturer_->SetExcludedWindow(window); +} + SharedMemory* DesktopAndCursorComposer::CreateSharedMemory(size_t size) { return callback_->CreateSharedMemory(size); } diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h index 4f7c85bde..3fac0212d 100644 --- a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h +++ b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.h @@ -34,6 +34,7 @@ class DesktopAndCursorComposer : public DesktopCapturer, // DesktopCapturer interface. virtual void Start(DesktopCapturer::Callback* callback) OVERRIDE; virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetExcludedWindow(WindowId window) OVERRIDE; private: // DesktopCapturer::Callback interface. diff --git a/webrtc/modules/desktop_capture/desktop_capturer.h b/webrtc/modules/desktop_capture/desktop_capturer.h index bcb664ef8..7ad163649 100644 --- a/webrtc/modules/desktop_capture/desktop_capturer.h +++ b/webrtc/modules/desktop_capture/desktop_capturer.h @@ -13,6 +13,8 @@ #include +#include "webrtc/modules/desktop_capture/desktop_capture_types.h" + namespace webrtc { class DesktopFrame; @@ -52,6 +54,11 @@ class DesktopCapturer { // the top left corner of the capture target. Pending capture operations are // canceled when DesktopCapturer is deleted. virtual void Capture(const DesktopRegion& region) = 0; + + // Sets the window to be excluded from the captured image in the future + // Capture calls. Used to exclude the screenshare notification window for + // screen capturing. + virtual void SetExcludedWindow(WindowId window) {} }; } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_mac.mm b/webrtc/modules/desktop_capture/screen_capturer_mac.mm index 100309f89..2d5733906 100644 --- a/webrtc/modules/desktop_capture/screen_capturer_mac.mm +++ b/webrtc/modules/desktop_capture/screen_capturer_mac.mm @@ -63,7 +63,8 @@ DesktopRect ScaleAndRoundCGRect(const CGRect& rect, float scale) { static_cast(ceil((rect.origin.y + rect.size.height) * scale))); } -// Copy pixels in the |rect| from |src_place| to |dest_plane|. +// Copy pixels in the |rect| from |src_place| to |dest_plane|. |rect| should be +// relative to the origin of |src_plane| and |dest_plane|. void CopyRect(const uint8_t* src_plane, int src_plane_stride, uint8_t* dest_plane, @@ -87,6 +88,105 @@ void CopyRect(const uint8_t* src_plane, } } +// Returns an array of CGWindowID for all the on-screen windows except +// |window_to_exclude|, or NULL if the window is not found or it fails. The +// caller should release the returned CFArrayRef. +CFArrayRef CreateWindowListWithExclusion(CGWindowID window_to_exclude) { + if (!window_to_exclude) + return NULL; + + CFArrayRef all_windows = CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + if (!all_windows) + return NULL; + + CFMutableArrayRef returned_array = CFArrayCreateMutable( + NULL, CFArrayGetCount(all_windows), NULL); + + bool found = false; + for (CFIndex i = 0; i < CFArrayGetCount(all_windows); ++i) { + CFDictionaryRef window = reinterpret_cast( + CFArrayGetValueAtIndex(all_windows, i)); + + CFNumberRef id_ref = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowNumber)); + + CGWindowID id; + CFNumberGetValue(id_ref, kCFNumberIntType, &id); + if (id == window_to_exclude) { + found = true; + continue; + } + CFArrayAppendValue(returned_array, reinterpret_cast(id)); + } + CFRelease(all_windows); + + if (!found) { + CFRelease(returned_array); + returned_array = NULL; + } + return returned_array; +} + +// Returns the bounds of |window| in physical pixels, enlarged by a small amount +// on four edges to take account of the border/shadow effects. +DesktopRect GetExcludedWindowPixelBounds(CGWindowID window, + float dip_to_pixel_scale) { + // The amount of pixels to add to the actual window bounds to take into + // account of the border/shadow effects. + static const int kBorderEffectSize = 20; + CGRect rect; + CGWindowID ids[1]; + ids[0] = window; + + CFArrayRef window_id_array = + CFArrayCreate(NULL, reinterpret_cast(&ids), 1, NULL); + CFArrayRef window_array = + CGWindowListCreateDescriptionFromArray(window_id_array); + + if (CFArrayGetCount(window_array) > 0) { + CFDictionaryRef window = reinterpret_cast( + CFArrayGetValueAtIndex(window_array, 0)); + CFDictionaryRef bounds_ref = reinterpret_cast( + CFDictionaryGetValue(window, kCGWindowBounds)); + CGRectMakeWithDictionaryRepresentation(bounds_ref, &rect); + } + + CFRelease(window_id_array); + CFRelease(window_array); + + rect.origin.x -= kBorderEffectSize; + rect.origin.y -= kBorderEffectSize; + rect.size.width += kBorderEffectSize * 2; + rect.size.height += kBorderEffectSize * 2; + // |rect| is in DIP, so convert to physical pixels. + return ScaleAndRoundCGRect(rect, dip_to_pixel_scale); +} + +// Create an image of the given region using the given |window_list|. +// |pixel_bounds| should be in the primary display's coordinate in physical +// pixels. The caller should release the returned CGImageRef and CFDataRef. +CGImageRef CreateExcludedWindowRegionImage(const DesktopRect& pixel_bounds, + float dip_to_pixel_scale, + CFArrayRef window_list, + CFDataRef* data_ref) { + CGRect window_bounds; + // The origin is in DIP while the size is in physical pixels. That's what + // CGWindowListCreateImageFromArray expects. + window_bounds.origin.x = pixel_bounds.left() / dip_to_pixel_scale; + window_bounds.origin.y = pixel_bounds.top() / dip_to_pixel_scale; + window_bounds.size.width = pixel_bounds.width(); + window_bounds.size.height = pixel_bounds.height(); + + CGImageRef excluded_image = CGWindowListCreateImageFromArray( + window_bounds, window_list, kCGWindowImageDefault); + + CGDataProviderRef provider = CGImageGetDataProvider(excluded_image); + *data_ref = CGDataProviderCopyData(provider); + assert(*data_ref); + return excluded_image; +} + // A class to perform video frame capturing for mac. class ScreenCapturerMac : public ScreenCapturer { public: @@ -99,6 +199,7 @@ class ScreenCapturerMac : public ScreenCapturer { // Overridden from ScreenCapturer: virtual void Start(Callback* callback) OVERRIDE; virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetExcludedWindow(WindowId window) OVERRIDE; virtual void SetMouseShapeObserver( MouseShapeObserver* mouse_shape_observer) OVERRIDE; virtual bool GetScreenList(ScreenList* screens) OVERRIDE; @@ -186,6 +287,8 @@ class ScreenCapturerMac : public ScreenCapturer { void* opengl_library_; CGLSetFullScreenFunc cgl_set_full_screen_; + CGWindowID excluded_window_; + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac); }; @@ -227,7 +330,8 @@ ScreenCapturerMac::ScreenCapturerMac( cg_display_bytes_per_row_(NULL), cg_display_bits_per_pixel_(NULL), opengl_library_(NULL), - cgl_set_full_screen_(NULL) { + cgl_set_full_screen_(NULL), + excluded_window_(0) { } ScreenCapturerMac::~ScreenCapturerMac() { @@ -291,8 +395,7 @@ void ScreenCapturerMac::Start(Callback* callback) { &power_assertion_id_user_); } -void ScreenCapturerMac::Capture( - const DesktopRegion& region_to_capture) { +void ScreenCapturerMac::Capture(const DesktopRegion& region_to_capture) { TickTime capture_start_time = TickTime::Now(); queue_.MoveToNextFrame(); @@ -362,6 +465,10 @@ void ScreenCapturerMac::Capture( callback_->OnCaptureCompleted(new_frame); } +void ScreenCapturerMac::SetExcludedWindow(WindowId window) { + excluded_window_ = window; +} + void ScreenCapturerMac::SetMouseShapeObserver( MouseShapeObserver* mouse_shape_observer) { assert(!mouse_shape_observer_); @@ -631,6 +738,9 @@ bool ScreenCapturerMac::CgBlitPostLion(const DesktopFrame& frame, displays_to_capture = desktop_config_.displays; } + // Create the window list once for all displays. + CFArrayRef window_list = CreateWindowListWithExclusion(excluded_window_); + for (size_t i = 0; i < displays_to_capture.size(); ++i) { const MacDisplayConfiguration& display_config = displays_to_capture[i]; @@ -655,6 +765,26 @@ bool ScreenCapturerMac::CgBlitPostLion(const DesktopFrame& frame, // Translate the region to be copied into display-relative coordinates. copy_region.Translate(-display_bounds.left(), -display_bounds.top()); + DesktopRect excluded_window_bounds; + CGImageRef excluded_image = NULL; + CFDataRef excluded_window_region_data = NULL; + if (excluded_window_ && window_list) { + // Get the region of the excluded window relative the primary display. + excluded_window_bounds = GetExcludedWindowPixelBounds( + excluded_window_, display_config.dip_to_pixel_scale); + excluded_window_bounds.IntersectWith(display_config.pixel_bounds); + + // Create the image under the excluded window first, because it's faster + // than captuing the whole display. + if (!excluded_window_bounds.is_empty()) { + excluded_image = CreateExcludedWindowRegionImage( + excluded_window_bounds, + display_config.dip_to_pixel_scale, + window_list, + &excluded_window_region_data); + } + } + // Create an image containing a snapshot of the display. CGImageRef image = CGDisplayCreateImage(display_config.id); if (image == NULL) @@ -684,9 +814,36 @@ bool ScreenCapturerMac::CgBlitPostLion(const DesktopFrame& frame, i.rect()); } + // Copy the region of the excluded window to the frame. + if (excluded_image) { + assert(excluded_window_region_data); + display_base_address = CFDataGetBytePtr(excluded_window_region_data); + src_bytes_per_row = CGImageGetBytesPerRow(excluded_image); + + // Translate the bounds relative to the desktop, because |frame| data + // starts from the desktop top-left corner. + DesktopRect window_bounds_relative_to_desktop(excluded_window_bounds); + window_bounds_relative_to_desktop.Translate( + -screen_pixel_bounds_.left(), -screen_pixel_bounds_.top()); + out_ptr = frame.data() + + (window_bounds_relative_to_desktop.left() * src_bytes_per_pixel) + + (window_bounds_relative_to_desktop.top() * frame.stride()); + + CopyRect(display_base_address, + src_bytes_per_row, + out_ptr, + frame.stride(), + src_bytes_per_pixel, + DesktopRect::MakeSize(excluded_window_bounds.size())); + CFRelease(excluded_window_region_data); + CFRelease(excluded_image); + } + CFRelease(data); CFRelease(image); } + if (window_list) + CFRelease(window_list); return true; }