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
diff --git a/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc b/webrtc/modules/desktop_capture/desktop_and_cursor_composer.cc
index 05e2a9b..2547ba3 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 @@
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 4f7c85b..3fac021 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 @@
// 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 bcb664e..7ad1636 100644
--- a/webrtc/modules/desktop_capture/desktop_capturer.h
+++ b/webrtc/modules/desktop_capture/desktop_capturer.h
@@ -13,6 +13,8 @@
#include <stddef.h>
+#include "webrtc/modules/desktop_capture/desktop_capture_types.h"
+
namespace webrtc {
class DesktopFrame;
@@ -52,6 +54,11 @@
// 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 100309f..2d57339 100644
--- a/webrtc/modules/desktop_capture/screen_capturer_mac.mm
+++ b/webrtc/modules/desktop_capture/screen_capturer_mac.mm
@@ -63,7 +63,8 @@
static_cast<int>(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 @@
}
}
+// 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<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(all_windows, i));
+
+ CFNumberRef id_ref = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowNumber));
+
+ CGWindowID id;
+ CFNumberGetValue(id_ref, kCFNumberIntType, &id);
+ if (id == window_to_exclude) {
+ found = true;
+ continue;
+ }
+ CFArrayAppendValue(returned_array, reinterpret_cast<void *>(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<const void **>(&ids), 1, NULL);
+ CFArrayRef window_array =
+ CGWindowListCreateDescriptionFromArray(window_id_array);
+
+ if (CFArrayGetCount(window_array) > 0) {
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>(
+ 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 @@
// 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 @@
void* opengl_library_;
CGLSetFullScreenFunc cgl_set_full_screen_;
+ CGWindowID excluded_window_;
+
DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac);
};
@@ -227,7 +330,8 @@
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 @@
&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 @@
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 @@
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 @@
// 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 @@
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;
}