diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index a9fdac6380..5aee3363bc 100644 --- a/webrtc/modules/desktop_capture/desktop_capture.gypi +++ b/webrtc/modules/desktop_capture/desktop_capture.gypi @@ -11,42 +11,119 @@ 'conditions': [ # Desktop capturer is supported only on Windows, OSX and Linux. ['OS=="win" or OS=="mac" or OS=="linux"', { - 'desktop_capture_enabled%': 1, + 'desktop_capture_supported%': 1, }, { - 'desktop_capture_enabled%': 0, + 'desktop_capture_supported%': 0, }], ], }, + 'targets': [ + { + 'target_name': 'desktop_capture', + 'type': 'static_library', + 'dependencies': [ + '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', + ], + 'sources': [ + "desktop_capturer.h", + "desktop_frame.cc", + "desktop_frame.h", + "desktop_frame_win.cc", + "desktop_frame_win.h", + "desktop_geometry.cc", + "desktop_geometry.h", + "desktop_region.cc", + "desktop_region.h", + "differ.cc", + "differ.h", + "differ_block.cc", + "differ_block.h", + "mac/desktop_configuration.h", + "mac/desktop_configuration.mm", + "mac/scoped_pixel_buffer_object.cc", + "mac/scoped_pixel_buffer_object.h", + "mouse_cursor_shape.h", + "screen_capture_frame_queue.cc", + "screen_capture_frame_queue.h", + "screen_capturer.h", + "screen_capturer_fake.cc", + "screen_capturer_fake.h", + "screen_capturer_helper.cc", + "screen_capturer_helper.h", + "screen_capturer_mac.mm", + "screen_capturer_win.cc", + "screen_capturer_x11.cc", + "shared_desktop_frame.cc", + "shared_desktop_frame.h", + "shared_memory.cc", + "shared_memory.h", + "win/desktop.cc", + "win/desktop.h", + "win/scoped_thread_desktop.cc", + "win/scoped_thread_desktop.h", + "window_capturer.h", + "window_capturer_linux.cc", + "window_capturer_mac.cc", + "window_capturer_win.cc", + "x11/x_server_pixel_buffer.cc", + "x11/x_server_pixel_buffer.h", + ], + 'conditions': [ + ['OS!="ios" and (target_arch=="ia32" or target_arch=="x64")', { + 'dependencies': [ + 'desktop_capture_differ_sse2', + ], + }], + ['use_x11 == 1', { + 'link_settings': { + 'libraries': [ + '-lX11', + '-lXdamage', + '-lXext', + '-lXfixes', + ], + }, + }], + ['OS!="win" and OS!="mac" and use_x11==0', { + 'sources': [ + "screen_capturer_null.cc", + ], + }], + ['OS=="mac"', { + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/AppKit.framework', + '$(SDKROOT)/System/Library/Frameworks/IOKit.framework', + '$(SDKROOT)/System/Library/Frameworks/OpenGL.framework', + ], + }, + }], + ], + }, + ], # targets 'conditions': [ - ['desktop_capture_enabled==1', { + ['OS!="ios" and (target_arch=="ia32" or target_arch=="x64")', { 'targets': [ { - 'target_name': 'desktop_capture', + # Have to be compiled as a separate target because it needs to be + # compiled with SSE2 enabled. + 'target_name': 'desktop_capture_differ_sse2', 'type': 'static_library', - 'dependencies': [ - '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', - ], 'sources': [ - "desktop_capturer.h", - "desktop_frame.cc", - "desktop_frame.h", - "desktop_frame_win.cc", - "desktop_frame_win.h", - "desktop_geometry.cc", - "desktop_geometry.h", - "desktop_region.cc", - "desktop_region.h", - "shared_memory.cc", - "shared_memory.h", - "window_capturer.h", - "window_capturer_linux.cc", - "window_capturer_mac.cc", - "window_capturer_win.cc", + "differ_block_sse2.cc", + "differ_block_sse2.h", + ], + 'conditions': [ + [ 'os_posix == 1 and OS != "mac"', { + 'cflags': [ + '-msse2', + ], + }], ], }, ], # targets - }], # desktop_capture_enabled==1 - ['desktop_capture_enabled==1 and include_tests==1', { + }], + ['include_tests==1', { 'targets': [ { 'target_name': 'desktop_capture_unittests', @@ -56,14 +133,34 @@ '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', '<(webrtc_root)/test/test.gyp:test_support', '<(webrtc_root)/test/test.gyp:test_support_main', + '<(DEPTH)/testing/gmock.gyp:gmock', '<(DEPTH)/testing/gtest.gyp:gtest', ], 'sources': [ "desktop_region_unittest.cc", + "differ_block_unittest.cc", + "differ_unittest.cc", + "screen_capturer_helper_unittest.cc", + "screen_capturer_mac_unittest.cc", + "screen_capturer_mock_objects.h", + "screen_capturer_unittest.cc", "window_capturer_unittest.cc", ], + 'conditions': [ + # Run screen/window capturer tests only on platforms where they are + # supported. + ['desktop_capture_supported==1', { + 'sources!': [ + "screen_capturer_helper_unittest.cc", + "screen_capturer_mac_unittest.cc", + "screen_capturer_mock_objects.h", + "screen_capturer_unittest.cc", + "window_capturer_unittest.cc", + ], + }], + ], }, - ], # targets - }], # desktop_capture_enabled==1 && include_tests==1 + ], # targets + }], # include_tests==1 ], } diff --git a/webrtc/modules/desktop_capture/desktop_capturer.h b/webrtc/modules/desktop_capture/desktop_capturer.h index 21a4210943..bcb664ef85 100644 --- a/webrtc/modules/desktop_capture/desktop_capturer.h +++ b/webrtc/modules/desktop_capture/desktop_capturer.h @@ -11,6 +11,8 @@ #ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_ #define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_ +#include + namespace webrtc { class DesktopFrame; diff --git a/webrtc/modules/desktop_capture/differ.cc b/webrtc/modules/desktop_capture/differ.cc new file mode 100644 index 0000000000..5a347a7dd6 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ.cc @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/differ.h" + +#include "string.h" + +#include "webrtc/modules/desktop_capture/differ_block.h" +#include "webrtc/system_wrappers/interface/logging.h" + +namespace webrtc { + +Differ::Differ(int width, int height, int bpp, int stride) { + // Dimensions of screen. + width_ = width; + height_ = height; + bytes_per_pixel_ = bpp; + bytes_per_row_ = stride; + + // Calc number of blocks (full and partial) required to cover entire image. + // One additional row/column is added as a boundary on the right & bottom. + diff_info_width_ = ((width_ + kBlockSize - 1) / kBlockSize) + 1; + diff_info_height_ = ((height_ + kBlockSize - 1) / kBlockSize) + 1; + diff_info_size_ = diff_info_width_ * diff_info_height_ * sizeof(DiffInfo); + diff_info_.reset(new DiffInfo[diff_info_size_]); +} + +Differ::~Differ() {} + +void Differ::CalcDirtyRegion(const void* prev_buffer, const void* curr_buffer, + DesktopRegion* region) { + // Identify all the blocks that contain changed pixels. + MarkDirtyBlocks(prev_buffer, curr_buffer); + + // Now that we've identified the blocks that have changed, merge adjacent + // blocks to minimize the number of rects that we return. + MergeBlocks(region); +} + +void Differ::MarkDirtyBlocks(const void* prev_buffer, const void* curr_buffer) { + memset(diff_info_.get(), 0, diff_info_size_); + + // Calc number of full blocks. + int x_full_blocks = width_ / kBlockSize; + int y_full_blocks = height_ / kBlockSize; + + // Calc size of partial blocks which may be present on right and bottom edge. + int partial_column_width = width_ - (x_full_blocks * kBlockSize); + int partial_row_height = height_ - (y_full_blocks * kBlockSize); + + // Offset from the start of one block-column to the next. + int block_x_offset = bytes_per_pixel_ * kBlockSize; + // Offset from the start of one block-row to the next. + int block_y_stride = (width_ * bytes_per_pixel_) * kBlockSize; + // Offset from the start of one diff_info row to the next. + int diff_info_stride = diff_info_width_ * sizeof(DiffInfo); + + const uint8_t* prev_block_row_start = + static_cast(prev_buffer); + const uint8_t* curr_block_row_start = + static_cast(curr_buffer); + DiffInfo* diff_info_row_start = static_cast(diff_info_.get()); + + for (int y = 0; y < y_full_blocks; y++) { + const uint8_t* prev_block = prev_block_row_start; + const uint8_t* curr_block = curr_block_row_start; + DiffInfo* diff_info = diff_info_row_start; + + for (int x = 0; x < x_full_blocks; x++) { + // Mark this block as being modified so that it gets incorporated into + // a dirty rect. + *diff_info = BlockDifference(prev_block, curr_block, bytes_per_row_); + prev_block += block_x_offset; + curr_block += block_x_offset; + diff_info += sizeof(DiffInfo); + } + + // If there is a partial column at the end, handle it. + // This condition should rarely, if ever, occur. + if (partial_column_width != 0) { + *diff_info = DiffPartialBlock(prev_block, curr_block, bytes_per_row_, + partial_column_width, kBlockSize); + diff_info += sizeof(DiffInfo); + } + + // Update pointers for next row. + prev_block_row_start += block_y_stride; + curr_block_row_start += block_y_stride; + diff_info_row_start += diff_info_stride; + } + + // If the screen height is not a multiple of the block size, then this + // handles the last partial row. This situation is far more common than the + // 'partial column' case. + if (partial_row_height != 0) { + const uint8_t* prev_block = prev_block_row_start; + const uint8_t* curr_block = curr_block_row_start; + DiffInfo* diff_info = diff_info_row_start; + for (int x = 0; x < x_full_blocks; x++) { + *diff_info = DiffPartialBlock(prev_block, curr_block, + bytes_per_row_, + kBlockSize, partial_row_height); + prev_block += block_x_offset; + curr_block += block_x_offset; + diff_info += sizeof(DiffInfo); + } + if (partial_column_width != 0) { + *diff_info = DiffPartialBlock(prev_block, curr_block, bytes_per_row_, + partial_column_width, partial_row_height); + diff_info += sizeof(DiffInfo); + } + } +} + +DiffInfo Differ::DiffPartialBlock(const uint8_t* prev_buffer, + const uint8_t* curr_buffer, + int stride, int width, int height) { + int width_bytes = width * bytes_per_pixel_; + for (int y = 0; y < height; y++) { + if (memcmp(prev_buffer, curr_buffer, width_bytes) != 0) + return 1; + prev_buffer += bytes_per_row_; + curr_buffer += bytes_per_row_; + } + return 0; +} + +void Differ::MergeBlocks(DesktopRegion* region) { + region->Clear(); + + uint8_t* diff_info_row_start = static_cast(diff_info_.get()); + int diff_info_stride = diff_info_width_ * sizeof(DiffInfo); + + for (int y = 0; y < diff_info_height_; y++) { + uint8_t* diff_info = diff_info_row_start; + for (int x = 0; x < diff_info_width_; x++) { + if (*diff_info != 0) { + // We've found a modified block. Look at blocks to the right and below + // to group this block with as many others as we can. + int left = x * kBlockSize; + int top = y * kBlockSize; + int width = 1; + int height = 1; + *diff_info = 0; + + // Group with blocks to the right. + // We can keep looking until we find an unchanged block because we + // have a boundary block which is never marked as having diffs. + uint8_t* right = diff_info + 1; + while (*right) { + *right++ = 0; + width++; + } + + // Group with blocks below. + // The entire width of blocks that we matched above much match for + // each row that we add. + uint8_t* bottom = diff_info; + bool found_new_row; + do { + found_new_row = true; + bottom += diff_info_stride; + right = bottom; + for (int x2 = 0; x2 < width; x2++) { + if (*right++ == 0) { + found_new_row = false; + } + } + + if (found_new_row) { + height++; + + // We need to go back and erase the diff markers so that we don't + // try to add these blocks a second time. + right = bottom; + for (int x2 = 0; x2 < width; x2++) { + *right++ = 0; + } + } + } while (found_new_row); + + // Add rect to list of dirty rects. + width *= kBlockSize; + if (left + width > width_) { + width = width_ - left; + } + height *= kBlockSize; + if (top + height > height_) { + height = height_ - top; + } + region->AddRect(DesktopRect::MakeXYWH(left, top, width, height)); + } + + // Increment to next block in this row. + diff_info++; + } + + // Go to start of next row. + diff_info_row_start += diff_info_stride; + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/differ.h b/webrtc/modules/desktop_capture/differ.h new file mode 100644 index 0000000000..8edce80b4e --- /dev/null +++ b/webrtc/modules/desktop_capture/differ.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_ + +#include + +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +typedef uint8_t DiffInfo; + +// TODO(sergeyu): Simplify differ now that we are working with DesktopRegion. +// diff_info_ should no longer be needed, as we can put our data directly into +// the region that we are calculating. +// http://crbug.com/92379 +// TODO(sergeyu): Rename this class to something more sensible, e.g. +// ScreenCaptureFrameDifferencer. +class Differ { + public: + // Create a differ that operates on bitmaps with the specified width, height + // and bytes_per_pixel. + Differ(int width, int height, int bytes_per_pixel, int stride); + ~Differ(); + + int width() { return width_; } + int height() { return height_; } + int bytes_per_pixel() { return bytes_per_pixel_; } + int bytes_per_row() { return bytes_per_row_; } + + // Given the previous and current screen buffer, calculate the dirty region + // that encloses all of the changed pixels in the new screen. + void CalcDirtyRegion(const void* prev_buffer, const void* curr_buffer, + DesktopRegion* region); + + private: + // Allow tests to access our private parts. + friend class DifferTest; + + // Identify all of the blocks that contain changed pixels. + void MarkDirtyBlocks(const void* prev_buffer, const void* curr_buffer); + + // After the dirty blocks have been identified, this routine merges adjacent + // blocks into a region. + // The goal is to minimize the region that covers the dirty blocks. + void MergeBlocks(DesktopRegion* region); + + // Check for diffs in upper-left portion of the block. The size of the portion + // to check is specified by the |width| and |height| values. + // Note that if we force the capturer to always return images whose width and + // height are multiples of kBlockSize, then this will never be called. + DiffInfo DiffPartialBlock(const uint8_t* prev_buffer, + const uint8_t* curr_buffer, + int stride, + int width, int height); + + // Dimensions of screen. + int width_; + int height_; + + // Number of bytes for each pixel in source and dest bitmap. + // (Yes, they must match.) + int bytes_per_pixel_; + + // Number of bytes in each row of the image (AKA: stride). + int bytes_per_row_; + + // Diff information for each block in the image. + scoped_array diff_info_; + + // Dimensions and total size of diff info array. + int diff_info_width_; + int diff_info_height_; + int diff_info_size_; + + DISALLOW_COPY_AND_ASSIGN(Differ); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_H_ diff --git a/webrtc/modules/desktop_capture/differ_block.cc b/webrtc/modules/desktop_capture/differ_block.cc new file mode 100644 index 0000000000..3d1b229541 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block.cc @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/differ_block.h" + +#include + +#include "build/build_config.h" +#include "webrtc/modules/desktop_capture/differ_block_sse2.h" +#include "webrtc/system_wrappers/interface/cpu_features_wrapper.h" + +namespace webrtc { + +int BlockDifference_C(const uint8_t* image1, + const uint8_t* image2, + int stride) { + int width_bytes = kBlockSize * kBytesPerPixel; + + for (int y = 0; y < kBlockSize; y++) { + if (memcmp(image1, image2, width_bytes) != 0) + return 1; + image1 += stride; + image2 += stride; + } + return 0; +} + +int BlockDifference(const uint8_t* image1, const uint8_t* image2, int stride) { + static int (*diff_proc)(const uint8_t*, const uint8_t*, int) = NULL; + + if (!diff_proc) { +#if defined(ARCH_CPU_ARM_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY) + // For ARM and MIPS processors, always use C version. + // TODO(hclam): Implement a NEON version. + diff_proc = &BlockDifference_C; +#else + bool have_sse2 = WebRtc_GetCPUInfo(kSSE2) != 0; + // For x86 processors, check if SSE2 is supported. + if (have_sse2 && kBlockSize == 32) { + diff_proc = &BlockDifference_SSE2_W32; + } else if (have_sse2 && kBlockSize == 16) { + diff_proc = &BlockDifference_SSE2_W16; + } else { + diff_proc = &BlockDifference_C; + } +#endif + } + + return diff_proc(image1, image2, stride); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/differ_block.h b/webrtc/modules/desktop_capture/differ_block.h new file mode 100644 index 0000000000..2b43f4ed06 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_ + +#include "webrtc/typedefs.h" + +namespace webrtc { + +// Size (in pixels) of each square block used for diffing. This must be a +// multiple of sizeof(uint64)/8. +const int kBlockSize = 32; + +// Format: BGRA 32 bit. +const int kBytesPerPixel = 4; + +// Low level functions to compare 2 blocks of pixels. Zero means the blocks +// are identical. One - the blocks are different. +int BlockDifference(const uint8_t* image1, const uint8_t* image2, int stride); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_ diff --git a/webrtc/modules/desktop_capture/differ_block_sse2.cc b/webrtc/modules/desktop_capture/differ_block_sse2.cc new file mode 100644 index 0000000000..7f31bd3a84 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block_sse2.cc @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/differ_block_sse2.h" + +#if defined(_MSC_VER) +#include +#else +#include +#include +#endif + +#include "webrtc/modules/desktop_capture/differ_block.h" + +namespace webrtc { + +extern int BlockDifference_SSE2_W16(const uint8_t* image1, + const uint8_t* image2, + int stride) { + __m128i acc = _mm_setzero_si128(); + __m128i v0; + __m128i v1; + __m128i sad; + for (int y = 0; y < kBlockSize; ++y) { + const __m128i* i1 = reinterpret_cast(image1); + const __m128i* i2 = reinterpret_cast(image2); + v0 = _mm_loadu_si128(i1); + v1 = _mm_loadu_si128(i2); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 1); + v1 = _mm_loadu_si128(i2 + 1); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 2); + v1 = _mm_loadu_si128(i2 + 2); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 3); + v1 = _mm_loadu_si128(i2 + 3); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + + // This essential means sad = acc >> 64. We only care about the lower 16 + // bits. + sad = _mm_shuffle_epi32(acc, 0xEE); + sad = _mm_adds_epu16(sad, acc); + int diff = _mm_cvtsi128_si32(sad); + if (diff) + return 1; + image1 += stride; + image2 += stride; + } + return 0; +} + +extern int BlockDifference_SSE2_W32(const uint8_t* image1, + const uint8_t* image2, + int stride) { + __m128i acc = _mm_setzero_si128(); + __m128i v0; + __m128i v1; + __m128i sad; + for (int y = 0; y < kBlockSize; ++y) { + const __m128i* i1 = reinterpret_cast(image1); + const __m128i* i2 = reinterpret_cast(image2); + v0 = _mm_loadu_si128(i1); + v1 = _mm_loadu_si128(i2); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 1); + v1 = _mm_loadu_si128(i2 + 1); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 2); + v1 = _mm_loadu_si128(i2 + 2); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 3); + v1 = _mm_loadu_si128(i2 + 3); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 4); + v1 = _mm_loadu_si128(i2 + 4); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 5); + v1 = _mm_loadu_si128(i2 + 5); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 6); + v1 = _mm_loadu_si128(i2 + 6); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + v0 = _mm_loadu_si128(i1 + 7); + v1 = _mm_loadu_si128(i2 + 7); + sad = _mm_sad_epu8(v0, v1); + acc = _mm_adds_epu16(acc, sad); + + // This essential means sad = acc >> 64. We only care about the lower 16 + // bits. + sad = _mm_shuffle_epi32(acc, 0xEE); + sad = _mm_adds_epu16(sad, acc); + int diff = _mm_cvtsi128_si32(sad); + if (diff) + return 1; + image1 += stride; + image2 += stride; + } + return 0; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/differ_block_sse2.h b/webrtc/modules/desktop_capture/differ_block_sse2.h new file mode 100644 index 0000000000..081e6fa235 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block_sse2.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This header file is used only differ_block.h. It defines the SSE2 rountines +// for finding block difference. + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_ + +#include + +namespace webrtc { + +// Find block difference of dimension 16x16. +extern int BlockDifference_SSE2_W16(const uint8_t* image1, + const uint8_t* image2, + int stride); + +// Find block difference of dimension 32x32. +extern int BlockDifference_SSE2_W32(const uint8_t* image1, + const uint8_t* image2, + int stride); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_SSE2_H_ diff --git a/webrtc/modules/desktop_capture/differ_block_unittest.cc b/webrtc/modules/desktop_capture/differ_block_unittest.cc new file mode 100644 index 0000000000..4f79eed983 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_block_unittest.cc @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "testing/gmock/include/gmock/gmock.h" +#include "webrtc/modules/desktop_capture/differ_block.h" +#include "webrtc/system_wrappers/interface/ref_count.h" + +namespace webrtc { + +// Run 900 times to mimic 1280x720. +// TODO(fbarchard): Remove benchmark once performance is non-issue. +static const int kTimesToRun = 900; + +static void GenerateData(uint8_t* data, int size) { + for (int i = 0; i < size; ++i) { + data[i] = i; + } +} + +// Memory buffer large enough for 2 blocks aligned to 16 bytes. +static const int kSizeOfBlock = kBlockSize * kBlockSize * kBytesPerPixel; +uint8_t block_buffer[kSizeOfBlock * 2 + 16]; + +void PrepareBuffers(uint8_t* &block1, uint8_t* &block2) { + block1 = reinterpret_cast + ((reinterpret_cast(&block_buffer[0]) + 15) & ~15); + GenerateData(block1, kSizeOfBlock); + block2 = block1 + kSizeOfBlock; + memcpy(block2, block1, kSizeOfBlock); +} + +TEST(BlockDifferenceTestSame, BlockDifference) { + uint8_t* block1; + uint8_t* block2; + PrepareBuffers(block1, block2); + + // These blocks should match. + for (int i = 0; i < kTimesToRun; ++i) { + int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel); + EXPECT_EQ(0, result); + } +} + +TEST(BlockDifferenceTestLast, BlockDifference) { + uint8_t* block1; + uint8_t* block2; + PrepareBuffers(block1, block2); + block2[kSizeOfBlock-2] += 1; + + for (int i = 0; i < kTimesToRun; ++i) { + int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel); + EXPECT_EQ(1, result); + } +} + +TEST(BlockDifferenceTestMid, BlockDifference) { + uint8_t* block1; + uint8_t* block2; + PrepareBuffers(block1, block2); + block2[kSizeOfBlock/2+1] += 1; + + for (int i = 0; i < kTimesToRun; ++i) { + int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel); + EXPECT_EQ(1, result); + } +} + +TEST(BlockDifferenceTestFirst, BlockDifference) { + uint8_t* block1; + uint8_t* block2; + PrepareBuffers(block1, block2); + block2[0] += 1; + + for (int i = 0; i < kTimesToRun; ++i) { + int result = BlockDifference(block1, block2, kBlockSize * kBytesPerPixel); + EXPECT_EQ(1, result); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/differ_unittest.cc b/webrtc/modules/desktop_capture/differ_unittest.cc new file mode 100644 index 0000000000..40fde4dbc4 --- /dev/null +++ b/webrtc/modules/desktop_capture/differ_unittest.cc @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "testing/gmock/include/gmock/gmock.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/differ_block.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +// 96x96 screen gives a 4x4 grid of blocks. +const int kScreenWidth= 96; +const int kScreenHeight = 96; + +// To test partial blocks, we need a width and height that are not multiples +// of 16 (or 32, depending on current block size). +const int kPartialScreenWidth = 70; +const int kPartialScreenHeight = 70; + +class DifferTest : public testing::Test { + public: + DifferTest() { + } + + protected: + void InitDiffer(int width, int height) { + width_ = width; + height_ = height; + bytes_per_pixel_ = kBytesPerPixel; + stride_ = (kBytesPerPixel * width); + buffer_size_ = width_ * height_ * bytes_per_pixel_; + + differ_.reset(new Differ(width_, height_, bytes_per_pixel_, stride_)); + + prev_.reset(new uint8_t[buffer_size_]); + memset(prev_.get(), 0, buffer_size_); + + curr_.reset(new uint8_t[buffer_size_]); + memset(curr_.get(), 0, buffer_size_); + } + + void ClearBuffer(uint8_t* buffer) { + memset(buffer, 0, buffer_size_); + } + + // Here in DifferTest so that tests can access private methods of Differ. + void MarkDirtyBlocks(const void* prev_buffer, const void* curr_buffer) { + differ_->MarkDirtyBlocks(prev_buffer, curr_buffer); + } + + void MergeBlocks(DesktopRegion* dirty) { + differ_->MergeBlocks(dirty); + } + + // Convenience method to count rectangles in a region. + int RegionRectCount(const DesktopRegion& region) { + int count = 0; + for (DesktopRegion::Iterator iter(region); + !iter.IsAtEnd(); iter.Advance()) { + ++count; + } + return count; + } + + // Convenience wrapper for Differ's DiffBlock that calculates the appropriate + // offset to the start of the desired block. + DiffInfo DiffBlock(int block_x, int block_y) { + // Offset from upper-left of buffer to upper-left of requested block. + int block_offset = ((block_y * stride_) + (block_x * bytes_per_pixel_)) + * kBlockSize; + return BlockDifference(prev_.get() + block_offset, + curr_.get() + block_offset, + stride_); + } + + // Write the pixel |value| into the specified block in the |buffer|. + // This is a convenience wrapper around WritePixel(). + void WriteBlockPixel(uint8_t* buffer, int block_x, int block_y, + int pixel_x, int pixel_y, uint32_t value) { + WritePixel(buffer, (block_x * kBlockSize) + pixel_x, + (block_y * kBlockSize) + pixel_y, value); + } + + // Write the test pixel |value| into the |buffer| at the specified |x|,|y| + // location. + // Only the low-order bytes from |value| are written (assuming little-endian). + // So, for |value| = 0xaabbccdd: + // If bytes_per_pixel = 4, then ddccbbaa will be written as the pixel value. + // If = 3, ddccbb + // If = 2, ddcc + // If = 1, dd + void WritePixel(uint8_t* buffer, int x, int y, uint32_t value) { + uint8_t* pixel = reinterpret_cast(&value); + buffer += (y * stride_) + (x * bytes_per_pixel_); + for (int b = bytes_per_pixel_ - 1; b >= 0; b--) { + *buffer++ = pixel[b]; + } + } + + // DiffInfo utility routines. + // These are here so that we don't have to make each DifferText_Xxx_Test + // class a friend class to Differ. + + // Clear out the entire |diff_info_| buffer. + void ClearDiffInfo() { + memset(differ_->diff_info_.get(), 0, differ_->diff_info_size_); + } + + // Get the value in the |diff_info_| array at (x,y). + DiffInfo GetDiffInfo(int x, int y) { + DiffInfo* diff_info = differ_->diff_info_.get(); + return diff_info[(y * GetDiffInfoWidth()) + x]; + } + + // Width of |diff_info_| array. + int GetDiffInfoWidth() { + return differ_->diff_info_width_; + } + + // Height of |diff_info_| array. + int GetDiffInfoHeight() { + return differ_->diff_info_height_; + } + + // Size of |diff_info_| array. + int GetDiffInfoSize() { + return differ_->diff_info_size_; + } + + void SetDiffInfo(int x, int y, const DiffInfo& value) { + DiffInfo* diff_info = differ_->diff_info_.get(); + diff_info[(y * GetDiffInfoWidth()) + x] = value; + } + + // Mark the range of blocks specified. + void MarkBlocks(int x_origin, int y_origin, int width, int height) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + SetDiffInfo(x_origin + x, y_origin + y, 1); + } + } + } + + // Verify that |region| contains a rectangle defined by |x|, |y|, |width| and + // |height|. + // |x|, |y|, |width| and |height| are specified in block (not pixel) units. + bool CheckDirtyRegionContainsRect(const DesktopRegion& region, + int x, int y, + int width, int height) { + DesktopRect r = + DesktopRect::MakeXYWH(x * kBlockSize, y * kBlockSize, + width * kBlockSize, height * kBlockSize); + for (DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) { + if (i.rect().equals(r)) + return true; + } + return false; + } + + // Mark the range of blocks specified and then verify that they are + // merged correctly. + // Only one rectangular region of blocks can be checked with this routine. + bool MarkBlocksAndCheckMerge(int x_origin, int y_origin, + int width, int height) { + ClearDiffInfo(); + MarkBlocks(x_origin, y_origin, width, height); + + DesktopRegion dirty; + MergeBlocks(&dirty); + + + DesktopRect expected_rect = DesktopRect::MakeXYWH( + x_origin * kBlockSize, y_origin * kBlockSize, + width * kBlockSize, height * kBlockSize); + + // Verify that the region contains expected_rect and it's the only + // rectangle. + DesktopRegion::Iterator it(dirty); + return !it.IsAtEnd() && expected_rect.equals(it.rect()) && + (it.Advance(), it.IsAtEnd()); + } + + // The differ class we're testing. + scoped_ptr differ_; + + // Screen/buffer info. + int width_; + int height_; + int bytes_per_pixel_; + int stride_; + + // Size of each screen buffer. + int buffer_size_; + + // Previous and current screen buffers. + scoped_array prev_; + scoped_array curr_; + + private: + DISALLOW_COPY_AND_ASSIGN(DifferTest); +}; + +TEST_F(DifferTest, Setup) { + InitDiffer(kScreenWidth, kScreenHeight); + // 96x96 pixels results in 3x3 array. Add 1 to each dimension as boundary. + // +---+---+---+---+ + // | o | o | o | _ | + // +---+---+---+---+ o = blocks mapped to screen pixels + // | o | o | o | _ | + // +---+---+---+---+ _ = boundary blocks + // | o | o | o | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + EXPECT_EQ(4, GetDiffInfoWidth()); + EXPECT_EQ(4, GetDiffInfoHeight()); + EXPECT_EQ(16, GetDiffInfoSize()); +} + +TEST_F(DifferTest, MarkDirtyBlocks_All) { + InitDiffer(kScreenWidth, kScreenHeight); + ClearDiffInfo(); + + // Update a pixel in each block. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + WriteBlockPixel(curr_.get(), x, y, 10, 10, 0xff00ff); + } + } + + MarkDirtyBlocks(prev_.get(), curr_.get()); + + // Make sure each block is marked as dirty. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + EXPECT_EQ(1, GetDiffInfo(x, y)) + << "when x = " << x << ", and y = " << y; + } + } +} + +TEST_F(DifferTest, MarkDirtyBlocks_Sampling) { + InitDiffer(kScreenWidth, kScreenHeight); + ClearDiffInfo(); + + // Update some pixels in image. + WriteBlockPixel(curr_.get(), 1, 0, 10, 10, 0xff00ff); + WriteBlockPixel(curr_.get(), 2, 1, 10, 10, 0xff00ff); + WriteBlockPixel(curr_.get(), 0, 2, 10, 10, 0xff00ff); + + MarkDirtyBlocks(prev_.get(), curr_.get()); + + // Make sure corresponding blocks are updated. + EXPECT_EQ(0, GetDiffInfo(0, 0)); + EXPECT_EQ(0, GetDiffInfo(0, 1)); + EXPECT_EQ(1, GetDiffInfo(0, 2)); + EXPECT_EQ(1, GetDiffInfo(1, 0)); + EXPECT_EQ(0, GetDiffInfo(1, 1)); + EXPECT_EQ(0, GetDiffInfo(1, 2)); + EXPECT_EQ(0, GetDiffInfo(2, 0)); + EXPECT_EQ(1, GetDiffInfo(2, 1)); + EXPECT_EQ(0, GetDiffInfo(2, 2)); +} + +TEST_F(DifferTest, DiffBlock) { + InitDiffer(kScreenWidth, kScreenHeight); + + // Verify no differences at start. + EXPECT_EQ(0, DiffBlock(0, 0)); + EXPECT_EQ(0, DiffBlock(1, 1)); + + // Write new data into the 4 corners of the middle block and verify that + // neighboring blocks are not affected. + int max = kBlockSize - 1; + WriteBlockPixel(curr_.get(), 1, 1, 0, 0, 0xffffff); + WriteBlockPixel(curr_.get(), 1, 1, 0, max, 0xffffff); + WriteBlockPixel(curr_.get(), 1, 1, max, 0, 0xffffff); + WriteBlockPixel(curr_.get(), 1, 1, max, max, 0xffffff); + EXPECT_EQ(0, DiffBlock(0, 0)); + EXPECT_EQ(0, DiffBlock(0, 1)); + EXPECT_EQ(0, DiffBlock(0, 2)); + EXPECT_EQ(0, DiffBlock(1, 0)); + EXPECT_EQ(1, DiffBlock(1, 1)); // Only this block should change. + EXPECT_EQ(0, DiffBlock(1, 2)); + EXPECT_EQ(0, DiffBlock(2, 0)); + EXPECT_EQ(0, DiffBlock(2, 1)); + EXPECT_EQ(0, DiffBlock(2, 2)); +} + +TEST_F(DifferTest, Partial_Setup) { + InitDiffer(kPartialScreenWidth, kPartialScreenHeight); + // 70x70 pixels results in 3x3 array: 2x2 full blocks + partials around + // the edge. One more is added to each dimension as a boundary. + // +---+---+---+---+ + // | o | o | + | _ | + // +---+---+---+---+ o = blocks mapped to screen pixels + // | o | o | + | _ | + // +---+---+---+---+ + = partial blocks (top/left mapped to screen pixels) + // | + | + | + | _ | + // +---+---+---+---+ _ = boundary blocks + // | _ | _ | _ | _ | + // +---+---+---+---+ + EXPECT_EQ(4, GetDiffInfoWidth()); + EXPECT_EQ(4, GetDiffInfoHeight()); + EXPECT_EQ(16, GetDiffInfoSize()); +} + +TEST_F(DifferTest, Partial_FirstPixel) { + InitDiffer(kPartialScreenWidth, kPartialScreenHeight); + ClearDiffInfo(); + + // Update the first pixel in each block. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + WriteBlockPixel(curr_.get(), x, y, 0, 0, 0xff00ff); + } + } + + MarkDirtyBlocks(prev_.get(), curr_.get()); + + // Make sure each block is marked as dirty. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + EXPECT_EQ(1, GetDiffInfo(x, y)) + << "when x = " << x << ", and y = " << y; + } + } +} + +TEST_F(DifferTest, Partial_BorderPixel) { + InitDiffer(kPartialScreenWidth, kPartialScreenHeight); + ClearDiffInfo(); + + // Update the right/bottom border pixels. + for (int y = 0; y < height_; y++) { + WritePixel(curr_.get(), width_ - 1, y, 0xff00ff); + } + for (int x = 0; x < width_; x++) { + WritePixel(curr_.get(), x, height_ - 1, 0xff00ff); + } + + MarkDirtyBlocks(prev_.get(), curr_.get()); + + // Make sure last (partial) block in each row/column is marked as dirty. + int x_last = GetDiffInfoWidth() - 2; + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + EXPECT_EQ(1, GetDiffInfo(x_last, y)) + << "when x = " << x_last << ", and y = " << y; + } + int y_last = GetDiffInfoHeight() - 2; + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + EXPECT_EQ(1, GetDiffInfo(x, y_last)) + << "when x = " << x << ", and y = " << y_last; + } + // All other blocks are clean. + for (int y = 0; y < GetDiffInfoHeight() - 2; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 2; x++) { + EXPECT_EQ(0, GetDiffInfo(x, y)) << "when x = " << x << ", and y = " << y; + } + } +} + +TEST_F(DifferTest, MergeBlocks_Empty) { + InitDiffer(kScreenWidth, kScreenHeight); + + // No blocks marked: + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + + DesktopRegion dirty; + MergeBlocks(&dirty); + + EXPECT_TRUE(dirty.is_empty()); +} + +TEST_F(DifferTest, MergeBlocks_SingleBlock) { + InitDiffer(kScreenWidth, kScreenHeight); + // Mark a single block and make sure that there is a single merged + // rect with the correct bounds. + for (int y = 0; y < GetDiffInfoHeight() - 1; y++) { + for (int x = 0; x < GetDiffInfoWidth() - 1; x++) { + ASSERT_TRUE(MarkBlocksAndCheckMerge(x, y, 1, 1)) << "x: " << x + << "y: " << y; + } + } +} + +TEST_F(DifferTest, MergeBlocks_BlockRow) { + InitDiffer(kScreenWidth, kScreenHeight); + + // +---+---+---+---+ + // | X | X | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 1)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 1)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 2, 2, 1)); +} + +TEST_F(DifferTest, MergeBlocks_BlockColumn) { + InitDiffer(kScreenWidth, kScreenHeight); + + // +---+---+---+---+ + // | X | | | _ | + // +---+---+---+---+ + // | X | | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 1, 2)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | X | | _ | + // +---+---+---+---+ + // | | X | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 1, 2)); + + // +---+---+---+---+ + // | | | X | _ | + // +---+---+---+---+ + // | | | X | _ | + // +---+---+---+---+ + // | | | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(2, 0, 1, 3)); +} + +TEST_F(DifferTest, MergeBlocks_BlockRect) { + InitDiffer(kScreenWidth, kScreenHeight); + + // +---+---+---+---+ + // | X | X | | _ | + // +---+---+---+---+ + // | X | X | | _ | + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 2, 2)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 1, 2, 2)); + + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(1, 0, 2, 3)); + + // +---+---+---+---+ + // | | | | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 1, 3, 2)); + + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | X | X | X | _ | + // +---+---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ASSERT_TRUE(MarkBlocksAndCheckMerge(0, 0, 3, 3)); +} + +// This tests marked regions that require more than 1 single dirty rect. +// The exact rects returned depend on the current implementation, so these +// may need to be updated if we modify how we merge blocks. +TEST_F(DifferTest, MergeBlocks_MultiRect) { + InitDiffer(kScreenWidth, kScreenHeight); + DesktopRegion dirty; + + // +---+---+---+---+ +---+---+---+ + // | | X | | _ | | | 0 | | + // +---+---+---+---+ +---+---+---+ + // | X | | | _ | | 1 | | | + // +---+---+---+---+ => +---+---+---+ + // | | | X | _ | | | | 2 | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(1, 0, 1, 1); + MarkBlocks(0, 1, 1, 1); + MarkBlocks(2, 2, 1, 1); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(3, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 0, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 2, 1, 1)); + + // +---+---+---+---+ +---+---+---+ + // | | | X | _ | | | | 0 | + // +---+---+---+---+ +---+---+---+ + // | X | X | X | _ | | 1 1 1 | + // +---+---+---+---+ => + + + // | X | X | X | _ | | 1 1 1 | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(2, 0, 1, 1); + MarkBlocks(0, 1, 3, 2); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(2, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 0, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 3, 2)); + + // +---+---+---+---+ +---+---+---+ + // | | | | _ | | | | | + // +---+---+---+---+ +---+---+---+ + // | X | | X | _ | | 0 | | 1 | + // +---+---+---+---+ => +---+---+---+ + // | X | X | X | _ | | 2 2 2 | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(0, 1, 1, 1); + MarkBlocks(2, 1, 1, 1); + MarkBlocks(0, 2, 3, 1); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(3, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1)); + + // +---+---+---+---+ +---+---+---+ + // | X | X | X | _ | | 0 0 0 | + // +---+---+---+---+ +---+---+---+ + // | X | | X | _ | | 1 | | 2 | + // +---+---+---+---+ => +---+---+---+ + // | X | X | X | _ | | 3 3 3 | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(0, 0, 3, 1); + MarkBlocks(0, 1, 1, 1); + MarkBlocks(2, 1, 1, 1); + MarkBlocks(0, 2, 3, 1); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(4, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 3, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 2, 1, 1, 1)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 2, 3, 1)); + + // +---+---+---+---+ +---+---+---+ + // | X | X | | _ | | 0 0 | | + // +---+---+---+---+ + +---+ + // | X | X | | _ | | 0 0 | | + // +---+---+---+---+ => +---+---+---+ + // | | X | | _ | | | 1 | | + // +---+---+---+---+ +---+---+---+ + // | _ | _ | _ | _ | + // +---+---+---+---+ + ClearDiffInfo(); + MarkBlocks(0, 0, 2, 2); + MarkBlocks(1, 2, 1, 1); + + dirty.Clear(); + MergeBlocks(&dirty); + + ASSERT_EQ(2, RegionRectCount(dirty)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 2, 2)); + ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 2, 1, 1)); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration.h b/webrtc/modules/desktop_capture/mac/desktop_configuration.h new file mode 100644 index 0000000000..433040a04e --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/desktop_configuration.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ + +#include +#include +#include + +#include "webrtc/typedefs.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// Describes the configuration of a specific display. +struct MacDisplayConfiguration { + MacDisplayConfiguration(); + + // Cocoa identifier for this display. + CGDirectDisplayID id; + + // Bounds of this display in Density-Independent Pixels (DIPs). + DesktopRect bounds; + + // Bounds of this display in physical pixels. + DesktopRect pixel_bounds; + + // Scale factor from DIPs to physical pixels. + float dip_to_pixel_scale; +}; + +typedef std::vector MacDisplayConfigurations; + +// Describes the configuration of the whole desktop. +struct MacDesktopConfiguration { + // Used to request bottom-up or top-down coordinates. + enum Origin { BottomLeftOrigin, TopLeftOrigin }; + + MacDesktopConfiguration(); + ~MacDesktopConfiguration(); + + // Returns the desktop & display configurations in Cocoa-style "bottom-up" + // (the origin is the bottom-left of the primary monitor, and coordinates + // increase as you move up the screen) or Carbon-style "top-down" coordinates. + static MacDesktopConfiguration GetCurrent(Origin origin); + + // Bounds of the desktop in Density-Independent Pixels (DIPs). + DesktopRect bounds; + + // Bounds of the desktop in physical pixels. + DesktopRect pixel_bounds; + + // Scale factor from DIPs to physical pixels. + float dip_to_pixel_scale; + + // Configurations of the displays making up the desktop area. + MacDisplayConfigurations displays; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_ diff --git a/webrtc/modules/desktop_capture/mac/desktop_configuration.mm b/webrtc/modules/desktop_capture/mac/desktop_configuration.mm new file mode 100644 index 0000000000..e9b9863a8d --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/desktop_configuration.mm @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" + +#include +#include +#include + +#include "webrtc/system_wrappers/interface/logging.h" + +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 + +@interface NSScreen (LionAPI) +- (CGFloat)backingScaleFactor; +- (NSRect)convertRectToBacking:(NSRect)aRect; +@end + +#endif // 10.7 + +namespace webrtc { + +namespace { + +DesktopRect NSRectToDesktopRect(const NSRect& ns_rect) { + return DesktopRect::MakeLTRB( + static_cast(floor(ns_rect.origin.x)), + static_cast(floor(ns_rect.origin.y)), + static_cast(ceil(ns_rect.origin.x + ns_rect.size.width)), + static_cast(ceil(ns_rect.origin.y + ns_rect.size.height))); +} + +DesktopRect JoinRects(const DesktopRect& a, + const DesktopRect& b) { + return DesktopRect::MakeLTRB( + std::min(a.left(), b.left()), + std::min(a.top(), b.top()), + std::max(a.right(), b.right()), + std::max(a.bottom(), b.bottom())); +} + +// Inverts the position of |rect| from bottom-up coordinates to top-down, +// relative to |bounds|. +void InvertRectYOrigin(const DesktopRect& bounds, + DesktopRect* rect) { + assert(bounds.top() == 0); + *rect = DesktopRect::MakeXYWH( + rect->left(), bounds.bottom() - rect->bottom(), + rect->width(), rect->height()); +} + +MacDisplayConfiguration GetConfigurationForScreen(NSScreen* screen) { + MacDisplayConfiguration display_config; + + // Fetch the NSScreenNumber, which is also the CGDirectDisplayID. + NSDictionary* device_description = [screen deviceDescription]; + display_config.id = static_cast( + [[device_description objectForKey:@"NSScreenNumber"] intValue]); + + // Determine the display's logical & physical dimensions. + NSRect ns_bounds = [screen frame]; + display_config.bounds = NSRectToDesktopRect(ns_bounds); + + // If the host is running Mac OS X 10.7+ or later, query the scaling factor + // between logical and physical (aka "backing") pixels, otherwise assume 1:1. + if ([screen respondsToSelector:@selector(backingScaleFactor)] && + [screen respondsToSelector:@selector(convertRectToBacking:)]) { + display_config.dip_to_pixel_scale = [screen backingScaleFactor]; + NSRect ns_pixel_bounds = [screen convertRectToBacking: ns_bounds]; + display_config.pixel_bounds = NSRectToDesktopRect(ns_pixel_bounds); + } else { + display_config.pixel_bounds = display_config.bounds; + } + + return display_config; +} + +} // namespace + +MacDisplayConfiguration::MacDisplayConfiguration() + : id(0), + dip_to_pixel_scale(1.0f) { +} + +MacDesktopConfiguration::MacDesktopConfiguration() + : dip_to_pixel_scale(1.0f) { +} + +MacDesktopConfiguration::~MacDesktopConfiguration() { +} + +// static +MacDesktopConfiguration MacDesktopConfiguration::GetCurrent(Origin origin) { + MacDesktopConfiguration desktop_config; + + NSArray* screens = [NSScreen screens]; + assert(screens); + + // Iterator over the monitors, adding the primary monitor and monitors whose + // DPI match that of the primary monitor. + for (NSUInteger i = 0; i < [screens count]; ++i) { + MacDisplayConfiguration display_config = + GetConfigurationForScreen([screens objectAtIndex: i]); + + // Handling mixed-DPI is hard, so we only return displays that match the + // "primary" display's DPI. The primary display is always the first in the + // list returned by [NSScreen screens]. + if (i == 0) { + desktop_config.dip_to_pixel_scale = display_config.dip_to_pixel_scale; + } else if (desktop_config.dip_to_pixel_scale != + display_config.dip_to_pixel_scale) { + continue; + } + + // Cocoa uses bottom-up coordinates, so if the caller wants top-down then + // we need to invert the positions of secondary monitors relative to the + // primary one (the primary monitor's position is (0,0) in both systems). + if (i > 0 && origin == TopLeftOrigin) { + InvertRectYOrigin(desktop_config.displays[0].bounds, + &display_config.bounds); + InvertRectYOrigin(desktop_config.displays[0].pixel_bounds, + &display_config.pixel_bounds); + } + + // Add the display to the configuration. + desktop_config.displays.push_back(display_config); + + // Update the desktop bounds to account for this display. + desktop_config.bounds = + JoinRects(desktop_config.bounds, display_config.bounds); + desktop_config.pixel_bounds = + JoinRects(desktop_config.pixel_bounds, display_config.pixel_bounds); + } + + return desktop_config; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc new file mode 100644 index 0000000000..f83d76e1a0 --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.cc @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h" + +#include + +namespace webrtc { + +ScopedPixelBufferObject::ScopedPixelBufferObject() + : cgl_context_(NULL), + pixel_buffer_object_(0) { +} + +ScopedPixelBufferObject::~ScopedPixelBufferObject() { + Release(); +} + +bool ScopedPixelBufferObject::Init(CGLContextObj cgl_context, + int size_in_bytes) { + cgl_context_ = cgl_context; + CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; + glGenBuffersARB(1, &pixel_buffer_object_); + if (glGetError() == GL_NO_ERROR) { + glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_); + glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, size_in_bytes, NULL, + GL_STREAM_READ_ARB); + glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); + if (glGetError() != GL_NO_ERROR) { + Release(); + } + } else { + cgl_context_ = NULL; + pixel_buffer_object_ = 0; + } + return pixel_buffer_object_ != 0; +} + +void ScopedPixelBufferObject::Release() { + if (pixel_buffer_object_) { + CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; + glDeleteBuffersARB(1, &pixel_buffer_object_); + cgl_context_ = NULL; + pixel_buffer_object_ = 0; + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h new file mode 100644 index 0000000000..b4f6ad13a3 --- /dev/null +++ b/webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_ + +#include +#include + +#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class ScopedPixelBufferObject { + public: + ScopedPixelBufferObject(); + ~ScopedPixelBufferObject(); + + bool Init(CGLContextObj cgl_context, int size_in_bytes); + void Release(); + + GLuint get() const { return pixel_buffer_object_; } + + private: + CGLContextObj cgl_context_; + GLuint pixel_buffer_object_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPixelBufferObject); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCOPED_PIXEL_BUFFER_OBJECT_H_ diff --git a/webrtc/modules/desktop_capture/mouse_cursor_shape.h b/webrtc/modules/desktop_capture/mouse_cursor_shape.h new file mode 100644 index 0000000000..36ab120e0f --- /dev/null +++ b/webrtc/modules/desktop_capture/mouse_cursor_shape.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_ + +#include + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +// Type used to return mouse cursor shape from video capturers. +struct MouseCursorShape { + // Size of the cursor in screen pixels. + DesktopSize size; + + // Coordinates of the cursor hotspot relative to upper-left corner. + DesktopVector hotspot; + + // Cursor pixmap data in 32-bit BGRA format. + std::string data; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_MOUSE_CURSOR_SHAPE_H_ diff --git a/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc b/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc new file mode 100644 index 0000000000..b045f05267 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capture_frame_queue.cc @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" + +#include + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/shared_desktop_frame.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +ScreenCaptureFrameQueue::ScreenCaptureFrameQueue() : current_(0) {} + +ScreenCaptureFrameQueue::~ScreenCaptureFrameQueue() {} + +void ScreenCaptureFrameQueue::MoveToNextFrame() { + current_ = (current_ + 1) % kQueueLength; + + // Verify that the frame is not shared, i.e. that consumer has released it + // before attempting to capture again. + assert(!frames_[current_].get() || !frames_[current_]->IsShared()); +} + +void ScreenCaptureFrameQueue::ReplaceCurrentFrame(DesktopFrame* frame) { + frames_[current_].reset(SharedDesktopFrame::Wrap(frame)); +} + +void ScreenCaptureFrameQueue::Reset() { + for (int i = 0; i < kQueueLength; ++i) + frames_[i].reset(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capture_frame_queue.h b/webrtc/modules/desktop_capture/screen_capture_frame_queue.h new file mode 100644 index 0000000000..69f1bdfc1b --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capture_frame_queue.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ + +#include "webrtc/modules/desktop_capture/shared_desktop_frame.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/typedefs.h" + +namespace webrtc { +class DesktopFrame; +} // namespace webrtc + +namespace webrtc { + +// Represents a queue of reusable video frames. Provides access to the 'current' +// frame - the frame that the caller is working with at the moment, and to the +// 'previous' frame - the predecessor of the current frame swapped by +// MoveToNextFrame() call, if any. +// +// The caller is expected to (re)allocate frames if current_frame() returns +// NULL. The caller can mark all frames in the queue for reallocation (when, +// say, frame dimensions change). The queue records which frames need updating +// which the caller can query. +// +// Frame consumer is expected to never hold more than kQueueLength frames +// created by this function and it should release the earliest one before trying +// to capture a new frame (i.e. before MoveToNextFrame() is called). +class ScreenCaptureFrameQueue { + public: + ScreenCaptureFrameQueue(); + ~ScreenCaptureFrameQueue(); + + // Moves to the next frame in the queue, moving the 'current' frame to become + // the 'previous' one. + void MoveToNextFrame(); + + // Replaces the current frame with a new one allocated by the caller. The + // existing frame (if any) is destroyed. Takes ownership of |frame|. + void ReplaceCurrentFrame(DesktopFrame* frame); + + // Marks all frames obsolete and resets the previous frame pointer. No + // frames are freed though as the caller can still access them. + void Reset(); + + SharedDesktopFrame* current_frame() const { + return frames_[current_].get(); + } + + SharedDesktopFrame* previous_frame() const { + return frames_[(current_ + kQueueLength - 1) % kQueueLength].get(); + } + + private: + // Index of the current frame. + int current_; + + static const int kQueueLength = 2; + scoped_ptr frames_[kQueueLength]; + + DISALLOW_COPY_AND_ASSIGN(ScreenCaptureFrameQueue); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURE_FRAME_QUEUE_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer.h b/webrtc/modules/desktop_capture/screen_capturer.h new file mode 100644 index 0000000000..17101c5221 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_ + +#include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +struct MouseCursorShape; + +// Class used to capture video frames asynchronously. +// +// The full capture sequence is as follows: +// +// (1) Start +// This is when pre-capture steps are executed, such as flagging the +// display to prevent it from sleeping during a session. +// +// (2) CaptureFrame +// This is where the bits for the invalid rects are packaged up and sent +// to the encoder. +// A screen capture is performed if needed. For example, Windows requires +// a capture to calculate the diff from the previous screen, whereas the +// Mac version does not. +// +// Implementation has to ensure the following guarantees: +// 1. Double buffering +// Since data can be read while another capture action is happening. +class ScreenCapturer : public DesktopCapturer { + public: + // Provides callbacks used by the capturer to pass captured video frames and + // mouse cursor shapes to the processing pipeline. + // + // TODO(sergeyu): Move cursor shape capturing to a separate class because it's + // unrelated. + class MouseShapeObserver { + public: + // Called when the cursor shape has changed. Must take ownership of + // |cursor_shape|. + virtual void OnCursorShapeChanged(MouseCursorShape* cursor_shape) = 0; + + protected: + virtual ~MouseShapeObserver() {} + }; + + virtual ~ScreenCapturer() {} + + // Creates platform-specific capturer. + static ScreenCapturer* Create(); + +#if defined(WEBRTC_LINUX) + // Creates platform-specific capturer and instructs it whether it should use + // X DAMAGE support. + static ScreenCapturer* CreateWithXDamage(bool use_x_damage); +#elif defined(WEBRTC_WIN) + // Creates Windows-specific capturer and instructs it whether or not to + // disable desktop compositing. + static ScreenCapturer* CreateWithDisableAero(bool disable_aero); +#endif // defined(WEBRTC_WIN) + + // Called at the beginning of a capturing session. |mouse_shape_observer| must + // remain valid until the capturer is destroyed. + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) = 0; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer_fake.cc b/webrtc/modules/desktop_capture/screen_capturer_fake.cc new file mode 100644 index 0000000000..654fbe0c1d --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_fake.cc @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer_fake.h" + +#include + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/system_wrappers/interface/compile_assert.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +namespace webrtc { + +// ScreenCapturerFake generates a white picture of size kWidth x kHeight +// with a rectangle of size kBoxWidth x kBoxHeight. The rectangle moves kSpeed +// pixels per frame along both axes, and bounces off the sides of the screen. +static const int kWidth = ScreenCapturerFake::kWidth; +static const int kHeight = ScreenCapturerFake::kHeight; +static const int kBoxWidth = 140; +static const int kBoxHeight = 140; +static const int kSpeed = 20; + +ScreenCapturerFake::ScreenCapturerFake() + : callback_(NULL), + mouse_shape_observer_(NULL), + bytes_per_row_(0), + box_pos_x_(0), + box_pos_y_(0), + box_speed_x_(kSpeed), + box_speed_y_(kSpeed) { + + COMPILE_ASSERT(kBoxWidth < kWidth && kBoxHeight < kHeight); + COMPILE_ASSERT((kBoxWidth % kSpeed == 0) && (kWidth % kSpeed == 0) && + (kBoxHeight % kSpeed == 0) && (kHeight % kSpeed == 0)); + + ScreenConfigurationChanged(); +} + +ScreenCapturerFake::~ScreenCapturerFake() { +} + +void ScreenCapturerFake::Start(Callback* callback) { + assert(!callback_); + assert(callback); + callback_ = callback; +} + +void ScreenCapturerFake::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + if (!queue_.current_frame()) { + int buffer_size = size_.height() * bytes_per_row_; + SharedMemory* shared_memory = + callback_->CreateSharedMemory(buffer_size); + scoped_ptr frame; + DesktopSize frame_size(size_.width(), size_.height()); + if (shared_memory) { + frame.reset(new SharedMemoryDesktopFrame( + frame_size, bytes_per_row_, shared_memory)); + } else { + frame.reset(new BasicDesktopFrame(frame_size)); + } + queue_.ReplaceCurrentFrame(frame.release()); + } + + assert(queue_.current_frame()); + GenerateImage(); + + queue_.current_frame()->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(size_)); + queue_.current_frame()->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + + callback_->OnCaptureCompleted(queue_.current_frame()->Share()); +} + +void ScreenCapturerFake::SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) { + assert(!mouse_shape_observer_); + assert(mouse_shape_observer); + mouse_shape_observer_ = mouse_shape_observer; +} + +void ScreenCapturerFake::GenerateImage() { + DesktopFrame* frame = queue_.current_frame(); + + const int kBytesPerPixel = DesktopFrame::kBytesPerPixel; + + memset(frame->data(), 0xff, + size_.width() * size_.height() * kBytesPerPixel); + + uint8_t* row = frame->data() + + (box_pos_y_ * size_.width() + box_pos_x_) * kBytesPerPixel; + + box_pos_x_ += box_speed_x_; + if (box_pos_x_ + kBoxWidth >= size_.width() || box_pos_x_ == 0) + box_speed_x_ = -box_speed_x_; + + box_pos_y_ += box_speed_y_; + if (box_pos_y_ + kBoxHeight >= size_.height() || box_pos_y_ == 0) + box_speed_y_ = -box_speed_y_; + + // Draw rectangle with the following colors in its corners: + // cyan....yellow + // .............. + // blue.......red + for (int y = 0; y < kBoxHeight; ++y) { + for (int x = 0; x < kBoxWidth; ++x) { + int r = x * 255 / kBoxWidth; + int g = y * 255 / kBoxHeight; + int b = 255 - (x * 255 / kBoxWidth); + row[x * kBytesPerPixel] = r; + row[x * kBytesPerPixel + 1] = g; + row[x * kBytesPerPixel + 2] = b; + row[x * kBytesPerPixel + 3] = 0xff; + } + row += bytes_per_row_; + } +} + +void ScreenCapturerFake::ScreenConfigurationChanged() { + size_.set(kWidth, kHeight); + queue_.Reset(); + bytes_per_row_ = size_.width() * DesktopFrame::kBytesPerPixel; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_fake.h b/webrtc/modules/desktop_capture/screen_capturer_fake.h new file mode 100644 index 0000000000..92712cf376 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_fake.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FAKE_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FAKE_H_ + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +// A ScreenCapturerFake generates artificial image for testing purpose. +// +// ScreenCapturerFake is double-buffered as required by ScreenCapturer. +class ScreenCapturerFake : public ScreenCapturer { + public: + // ScreenCapturerFake generates a picture of size kWidth x kHeight. + static const int kWidth = 800; + static const int kHeight = 600; + + ScreenCapturerFake(); + virtual ~ScreenCapturerFake(); + + // DesktopCapturer interface. + virtual void Start(Callback* callback) OVERRIDE; + virtual void Capture(const DesktopRegion& rect) OVERRIDE; + + // ScreenCapturer interface. + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) OVERRIDE; + + private: + // Generates an image in the front buffer. + void GenerateImage(); + + // Called when the screen configuration is changed. + void ScreenConfigurationChanged(); + + Callback* callback_; + MouseShapeObserver* mouse_shape_observer_; + + DesktopSize size_; + int bytes_per_row_; + int box_pos_x_; + int box_pos_y_; + int box_speed_x_; + int box_speed_y_; + + ScreenCaptureFrameQueue queue_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerFake); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FAKE_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper.cc b/webrtc/modules/desktop_capture/screen_capturer_helper.cc new file mode 100644 index 0000000000..75af043c84 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_helper.cc @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" + +#include + +#include "webrtc/system_wrappers/interface/logging.h" + +namespace webrtc { + +ScreenCapturerHelper::ScreenCapturerHelper() + : invalid_region_lock_(RWLockWrapper::CreateRWLock()), + log_grid_size_(0) { +} + +ScreenCapturerHelper::~ScreenCapturerHelper() { +} + +void ScreenCapturerHelper::ClearInvalidRegion() { + WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_); + invalid_region_.Clear(); +} + +void ScreenCapturerHelper::InvalidateRegion( + const DesktopRegion& invalid_region) { + WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_); + invalid_region_.AddRegion(invalid_region); +} + +void ScreenCapturerHelper::InvalidateScreen(const DesktopSize& size) { + WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_); + invalid_region_.AddRect(DesktopRect::MakeSize(size)); +} + +void ScreenCapturerHelper::TakeInvalidRegion( + DesktopRegion* invalid_region) { + invalid_region->Clear(); + + { + WriteLockScoped scoped_invalid_region_lock(*invalid_region_lock_); + invalid_region->Swap(&invalid_region_); + } + + if (log_grid_size_ > 0) { + DesktopRegion expanded_region; + ExpandToGrid(*invalid_region, log_grid_size_, &expanded_region); + expanded_region.Swap(invalid_region); + + invalid_region->IntersectWith(DesktopRect::MakeSize(size_most_recent_)); + } +} + +void ScreenCapturerHelper::SetLogGridSize(int log_grid_size) { + log_grid_size_ = log_grid_size; +} + +const DesktopSize& ScreenCapturerHelper::size_most_recent() const { + return size_most_recent_; +} + +void ScreenCapturerHelper::set_size_most_recent( + const DesktopSize& size) { + size_most_recent_ = size; +} + +// Returns the largest multiple of |n| that is <= |x|. +// |n| must be a power of 2. |nMask| is ~(|n| - 1). +static int DownToMultiple(int x, int nMask) { + return (x & nMask); +} + +// Returns the smallest multiple of |n| that is >= |x|. +// |n| must be a power of 2. |nMask| is ~(|n| - 1). +static int UpToMultiple(int x, int n, int nMask) { + return ((x + n - 1) & nMask); +} + +void ScreenCapturerHelper::ExpandToGrid(const DesktopRegion& region, + int log_grid_size, + DesktopRegion* result) { + assert(log_grid_size >= 1); + int grid_size = 1 << log_grid_size; + int grid_size_mask = ~(grid_size - 1); + + result->Clear(); + for (DesktopRegion::Iterator it(region); !it.IsAtEnd(); it.Advance()) { + int left = DownToMultiple(it.rect().left(), grid_size_mask); + int right = UpToMultiple(it.rect().right(), grid_size, grid_size_mask); + int top = DownToMultiple(it.rect().top(), grid_size_mask); + int bottom = UpToMultiple(it.rect().bottom(), grid_size, grid_size_mask); + result->AddRect(DesktopRect::MakeLTRB(left, top, right, bottom)); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper.h b/webrtc/modules/desktop_capture/screen_capturer_helper.h new file mode 100644 index 0000000000..07eda6950a --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_helper.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +// ScreenCapturerHelper is intended to be used by an implementation of the +// ScreenCapturer interface. It maintains a thread-safe invalid region, and +// the size of the most recently captured screen, on behalf of the +// ScreenCapturer that owns it. +class ScreenCapturerHelper { + public: + ScreenCapturerHelper(); + ~ScreenCapturerHelper(); + + // Clear out the invalid region. + void ClearInvalidRegion(); + + // Invalidate the specified region. + void InvalidateRegion(const DesktopRegion& invalid_region); + + // Invalidate the entire screen, of a given size. + void InvalidateScreen(const DesktopSize& size); + + // Copies current invalid region to |invalid_region| clears invalid region + // storage for the next frame. + void TakeInvalidRegion(DesktopRegion* invalid_region); + + // Access the size of the most recently captured screen. + const DesktopSize& size_most_recent() const; + void set_size_most_recent(const DesktopSize& size); + + // Lossy compression can result in color values leaking between pixels in one + // block. If part of a block changes, then unchanged parts of that block can + // be changed in the compressed output. So we need to re-render an entire + // block whenever part of the block changes. + // + // If |log_grid_size| is >= 1, then this function makes TakeInvalidRegion() + // produce an invalid region expanded so that its vertices lie on a grid of + // size 2 ^ |log_grid_size|. The expanded region is then clipped to the size + // of the most recently captured screen, as previously set by + // set_size_most_recent(). + // If |log_grid_size| is <= 0, then the invalid region is not expanded. + void SetLogGridSize(int log_grid_size); + + // Expands a region so that its vertices all lie on a grid. + // The grid size must be >= 2, so |log_grid_size| must be >= 1. + static void ExpandToGrid(const DesktopRegion& region, + int log_grid_size, + DesktopRegion* result); + + private: + // A region that has been manually invalidated (through InvalidateRegion). + // These will be returned as dirty_region in the capture data during the next + // capture. + DesktopRegion invalid_region_; + + // A lock protecting |invalid_region_| across threads. + scoped_ptr invalid_region_lock_; + + // The size of the most recently captured screen. + DesktopSize size_most_recent_; + + // The log (base 2) of the size of the grid to which the invalid region is + // expanded. + // If the value is <= 0, then the invalid region is not expanded to a grid. + int log_grid_size_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerHelper); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_HELPER_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc new file mode 100644 index 0000000000..f7f138615b --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_helper_unittest.cc @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class ScreenCapturerHelperTest : public testing::Test { + protected: + ScreenCapturerHelper capturer_helper_; +}; + +TEST_F(ScreenCapturerHelperTest, ClearInvalidRegion) { + DesktopRegion region(DesktopRect::MakeXYWH(1, 2, 3, 4)); + capturer_helper_.InvalidateRegion(region); + capturer_helper_.ClearInvalidRegion(); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(region.is_empty()); +} + +TEST_F(ScreenCapturerHelperTest, InvalidateRegion) { + DesktopRegion region; + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(region.is_empty()); + + region.SetRect(DesktopRect::MakeXYWH(1, 2, 3, 4)); + capturer_helper_.InvalidateRegion(region); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(1, 2, 3, 4)).Equals(region)); + + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(1, 2, 3, 4))); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(4, 2, 3, 4))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(1, 2, 6, 4)).Equals(region)); +} + +TEST_F(ScreenCapturerHelperTest, InvalidateScreen) { + DesktopRegion region; + capturer_helper_.InvalidateScreen(DesktopSize(12, 34)); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeWH(12, 34)).Equals(region)); +} + +TEST_F(ScreenCapturerHelperTest, SizeMostRecent) { + EXPECT_TRUE(capturer_helper_.size_most_recent().is_empty()); + capturer_helper_.set_size_most_recent(DesktopSize(12, 34)); + EXPECT_TRUE( + DesktopSize(12, 34).equals(capturer_helper_.size_most_recent())); +} + +TEST_F(ScreenCapturerHelperTest, SetLogGridSize) { + capturer_helper_.set_size_most_recent(DesktopSize(10, 10)); + + DesktopRegion region; + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion().Equals(region)); + + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region)); + + capturer_helper_.SetLogGridSize(-1); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region)); + + capturer_helper_.SetLogGridSize(0); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region)); + + capturer_helper_.SetLogGridSize(1); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(6, 6, 2, 2)).Equals(region)); + + capturer_helper_.SetLogGridSize(2); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(4, 4, 4, 4)).Equals(region)); + + capturer_helper_.SetLogGridSize(0); + capturer_helper_.InvalidateRegion( + DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1))); + capturer_helper_.TakeInvalidRegion(®ion); + EXPECT_TRUE(DesktopRegion(DesktopRect::MakeXYWH(7, 7, 1, 1)).Equals(region)); +} + +void TestExpandRegionToGrid(const DesktopRegion& region, int log_grid_size, + const DesktopRegion& expanded_region_expected) { + DesktopRegion expanded_region1; + ScreenCapturerHelper::ExpandToGrid(region, log_grid_size, &expanded_region1); + EXPECT_TRUE(expanded_region_expected.Equals(expanded_region1)); + + DesktopRegion expanded_region2; + ScreenCapturerHelper::ExpandToGrid(expanded_region1, log_grid_size, + &expanded_region2); + EXPECT_TRUE(expanded_region1.Equals(expanded_region2)); +} + +void TestExpandRectToGrid(int l, int t, int r, int b, int log_grid_size, + int lExpanded, int tExpanded, + int rExpanded, int bExpanded) { + TestExpandRegionToGrid(DesktopRegion(DesktopRect::MakeLTRB(l, t, r, b)), + log_grid_size, + DesktopRegion(DesktopRect::MakeLTRB( + lExpanded, tExpanded, rExpanded, bExpanded))); +} + +TEST_F(ScreenCapturerHelperTest, ExpandToGrid) { + const int kLogGridSize = 4; + const int kGridSize = 1 << kLogGridSize; + for (int i = -2; i <= 2; i++) { + int x = i * kGridSize; + for (int j = -2; j <= 2; j++) { + int y = j * kGridSize; + TestExpandRectToGrid(x + 0, y + 0, x + 1, y + 1, kLogGridSize, + x + 0, y + 0, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x + 0, y + kGridSize - 1, x + 1, y + kGridSize, + kLogGridSize, + x + 0, y + 0, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x + kGridSize - 1, y + kGridSize - 1, + x + kGridSize, y + kGridSize, kLogGridSize, + x + 0, y + 0, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x + kGridSize - 1, y + 0, + x + kGridSize, y + 1, kLogGridSize, + x + 0, y + 0, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x - 1, y + 0, x + 1, y + 1, kLogGridSize, + x - kGridSize, y + 0, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x - 1, y - 1, x + 1, y + 0, kLogGridSize, + x - kGridSize, y - kGridSize, x + kGridSize, y); + TestExpandRectToGrid(x + 0, y - 1, x + 1, y + 1, kLogGridSize, + x, y - kGridSize, x + kGridSize, y + kGridSize); + TestExpandRectToGrid(x - 1, y - 1, x + 0, y + 1, kLogGridSize, + x - kGridSize, y - kGridSize, x, y + kGridSize); + + // Construct a region consisting of 3 pixels and verify that it's expanded + // properly to 3 squares that are kGridSize by kGridSize. + for (int q = 0; q < 4; ++q) { + DesktopRegion region; + DesktopRegion expanded_region_expected; + + if (q != 0) { + region.AddRect(DesktopRect::MakeXYWH(x - 1, y - 1, 1, 1)); + expanded_region_expected.AddRect(DesktopRect::MakeXYWH( + x - kGridSize, y - kGridSize, kGridSize, kGridSize)); + } + if (q != 1) { + region.AddRect(DesktopRect::MakeXYWH(x, y - 1, 1, 1)); + expanded_region_expected.AddRect(DesktopRect::MakeXYWH( + x, y - kGridSize, kGridSize, kGridSize)); + } + if (q != 2) { + region.AddRect(DesktopRect::MakeXYWH(x - 1, y, 1, 1)); + expanded_region_expected.AddRect(DesktopRect::MakeXYWH( + x - kGridSize, y, kGridSize, kGridSize)); + } + if (q != 3) { + region.AddRect(DesktopRect::MakeXYWH(x, y, 1, 1)); + expanded_region_expected.AddRect(DesktopRect::MakeXYWH( + x, y, kGridSize, kGridSize)); + } + + TestExpandRegionToGrid(region, kLogGridSize, expanded_region_expected); + } + } + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_mac.mm b/webrtc/modules/desktop_capture/screen_capturer_mac.mm new file mode 100644 index 0000000000..90e9df21e3 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_mac.mm @@ -0,0 +1,879 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/modules/desktop_capture/mac/scoped_pixel_buffer_object.h" +#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/system_wrappers/interface/event_wrapper.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +namespace webrtc { + +namespace { + +// Definitions used to dynamic-link to deprecated OS 10.6 functions. +const char* kApplicationServicesLibraryName = + "/System/Library/Frameworks/ApplicationServices.framework/" + "ApplicationServices"; +typedef void* (*CGDisplayBaseAddressFunc)(CGDirectDisplayID); +typedef size_t (*CGDisplayBytesPerRowFunc)(CGDirectDisplayID); +typedef size_t (*CGDisplayBitsPerPixelFunc)(CGDirectDisplayID); +const char* kOpenGlLibraryName = + "/System/Library/Frameworks/OpenGL.framework/OpenGL"; +typedef CGLError (*CGLSetFullScreenFunc)(CGLContextObj); + +// Standard Mac displays have 72dpi, but we report 96dpi for +// consistency with Windows and Linux. +const int kStandardDPI = 96; + +// Scales all coordinates of a rect by a specified factor. +DesktopRect ScaleAndRoundCGRect(const CGRect& rect, float scale) { + return DesktopRect::MakeLTRB( + static_cast(floor(rect.origin.x * scale)), + static_cast(floor(rect.origin.y * scale)), + static_cast(ceil((rect.origin.x + rect.size.width) * scale)), + static_cast(ceil((rect.origin.y + rect.size.height) * scale))); +} + +// Copy pixels in the |rect| from |src_place| to |dest_plane|. +void CopyRect(const uint8_t* src_plane, + int src_plane_stride, + uint8_t* dest_plane, + int dest_plane_stride, + int bytes_per_pixel, + const DesktopRect& rect) { + // Get the address of the starting point. + const int src_y_offset = src_plane_stride * rect.top(); + const int dest_y_offset = dest_plane_stride * rect.top(); + const int x_offset = bytes_per_pixel * rect.left(); + src_plane += src_y_offset + x_offset; + dest_plane += dest_y_offset + x_offset; + + // Copy pixels in the rectangle line by line. + const int bytes_per_line = bytes_per_pixel * rect.width(); + const int height = rect.height(); + for (int i = 0 ; i < height; ++i) { + memcpy(dest_plane, src_plane, bytes_per_line); + src_plane += src_plane_stride; + dest_plane += dest_plane_stride; + } +} + +int GetDarwinVersion() { + struct utsname uname_info; + if (uname(&uname_info) != 0) { + LOG(LS_ERROR) << "uname failed"; + return 0; + } + + if (strcmp(uname_info.sysname, "Darwin") != 0) + return 0; + + char* dot; + int result = strtol(uname_info.release, &dot, 10); + if (*dot != '.') { + LOG(LS_ERROR) << "Failed to parse version"; + return 0; + } + + return result; +} + +bool IsOSLionOrLater() { + static int darwin_version = GetDarwinVersion(); + + // Verify that the version has been parsed correctly. + if (darwin_version < 6) { + LOG_F(LS_ERROR) << "Invalid Darwin version: " << darwin_version; + abort(); + } + + // Darwin major version 11 corresponds to OSX 10.7. + return darwin_version >= 11; +} + +// The amount of time allowed for displays to reconfigure. +const int64_t kDisplayConfigurationEventTimeoutMs = 10 * 1000; + +// A class to perform video frame capturing for mac. +class ScreenCapturerMac : public ScreenCapturer { + public: + ScreenCapturerMac(); + virtual ~ScreenCapturerMac(); + + bool Init(); + + // Overridden from ScreenCapturer: + virtual void Start(Callback* callback) OVERRIDE; + virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) OVERRIDE; + + private: + void CaptureCursor(); + + void GlBlitFast(const DesktopFrame& frame, + const DesktopRegion& region); + void GlBlitSlow(const DesktopFrame& frame); + void CgBlitPreLion(const DesktopFrame& frame, + const DesktopRegion& region); + void CgBlitPostLion(const DesktopFrame& frame, + const DesktopRegion& region); + + // Called when the screen configuration is changed. + void ScreenConfigurationChanged(); + + bool RegisterRefreshAndMoveHandlers(); + void UnregisterRefreshAndMoveHandlers(); + + void ScreenRefresh(CGRectCount count, const CGRect *rect_array); + void ScreenUpdateMove(CGScreenUpdateMoveDelta delta, + size_t count, + const CGRect *rect_array); + void DisplaysReconfigured(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags); + static void ScreenRefreshCallback(CGRectCount count, + const CGRect *rect_array, + void *user_parameter); + static void ScreenUpdateMoveCallback(CGScreenUpdateMoveDelta delta, + size_t count, + const CGRect *rect_array, + void *user_parameter); + static void DisplaysReconfiguredCallback(CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void *user_parameter); + + void ReleaseBuffers(); + + Callback* callback_; + MouseShapeObserver* mouse_shape_observer_; + + CGLContextObj cgl_context_; + ScopedPixelBufferObject pixel_buffer_object_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Current display configuration. + MacDesktopConfiguration desktop_config_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // The last cursor that we sent to the client. + MouseCursorShape last_cursor_; + + // Contains an invalid region from the previous capture. + DesktopRegion last_invalid_region_; + + // Used to ensure that frame captures do not take place while displays + // are being reconfigured. + scoped_ptr display_configuration_capture_event_; + + // Records the Ids of attached displays which are being reconfigured. + // Accessed on the thread on which we are notified of display events. + std::set reconfiguring_displays_; + + // Power management assertion to prevent the screen from sleeping. + IOPMAssertionID power_assertion_id_display_; + + // Power management assertion to indicate that the user is active. + IOPMAssertionID power_assertion_id_user_; + + // Dynamically link to deprecated APIs for Mac OS X 10.6 support. + void* app_services_library_; + CGDisplayBaseAddressFunc cg_display_base_address_; + CGDisplayBytesPerRowFunc cg_display_bytes_per_row_; + CGDisplayBitsPerPixelFunc cg_display_bits_per_pixel_; + void* opengl_library_; + CGLSetFullScreenFunc cgl_set_full_screen_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac); +}; + +DesktopFrame* CreateFrame( + const MacDesktopConfiguration& desktop_config) { + + DesktopSize size(desktop_config.pixel_bounds.width(), + desktop_config.pixel_bounds.height()); + scoped_ptr frame(new BasicDesktopFrame(size)); + + frame->set_dpi(DesktopVector( + kStandardDPI * desktop_config.dip_to_pixel_scale, + kStandardDPI * desktop_config.dip_to_pixel_scale)); + return frame.release(); +} + +ScreenCapturerMac::ScreenCapturerMac() + : callback_(NULL), + mouse_shape_observer_(NULL), + cgl_context_(NULL), + display_configuration_capture_event_(EventWrapper::Create()), + power_assertion_id_display_(kIOPMNullAssertionID), + power_assertion_id_user_(kIOPMNullAssertionID), + app_services_library_(NULL), + cg_display_base_address_(NULL), + cg_display_bytes_per_row_(NULL), + cg_display_bits_per_pixel_(NULL), + opengl_library_(NULL), + cgl_set_full_screen_(NULL) { + display_configuration_capture_event_->Set(); +} + +ScreenCapturerMac::~ScreenCapturerMac() { + if (power_assertion_id_display_ != kIOPMNullAssertionID) { + IOPMAssertionRelease(power_assertion_id_display_); + power_assertion_id_display_ = kIOPMNullAssertionID; + } + if (power_assertion_id_user_ != kIOPMNullAssertionID) { + IOPMAssertionRelease(power_assertion_id_user_); + power_assertion_id_user_ = kIOPMNullAssertionID; + } + + ReleaseBuffers(); + UnregisterRefreshAndMoveHandlers(); + CGError err = CGDisplayRemoveReconfigurationCallback( + ScreenCapturerMac::DisplaysReconfiguredCallback, this); + if (err != kCGErrorSuccess) + LOG(LS_ERROR) << "CGDisplayRemoveReconfigurationCallback " << err; + + dlclose(app_services_library_); + dlclose(opengl_library_); +} + +bool ScreenCapturerMac::Init() { + if (!RegisterRefreshAndMoveHandlers()) { + return false; + } + + CGError err = CGDisplayRegisterReconfigurationCallback( + ScreenCapturerMac::DisplaysReconfiguredCallback, this); + if (err != kCGErrorSuccess) { + LOG(LS_ERROR) << "CGDisplayRegisterReconfigurationCallback " << err; + return false; + } + + ScreenConfigurationChanged(); + return true; +} + +void ScreenCapturerMac::ReleaseBuffers() { + if (cgl_context_) { + pixel_buffer_object_.Release(); + CGLDestroyContext(cgl_context_); + cgl_context_ = NULL; + } + // The buffers might be in use by the encoder, so don't delete them here. + // Instead, mark them as "needs update"; next time the buffers are used by + // the capturer, they will be recreated if necessary. + queue_.Reset(); +} + +void ScreenCapturerMac::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; + + // Create power management assertions to wake the display and prevent it from + // going to sleep on user idle. + // TODO(jamiewalch): Use IOPMAssertionDeclareUserActivity on 10.7.3 and above + // instead of the following two assertions. + IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, + kIOPMAssertionLevelOn, + CFSTR("Chrome Remote Desktop connection active"), + &power_assertion_id_display_); + // This assertion ensures that the display is woken up if it already asleep + // (as used by Apple Remote Desktop). + IOPMAssertionCreateWithName(CFSTR("UserIsActive"), + kIOPMAssertionLevelOn, + CFSTR("Chrome Remote Desktop connection active"), + &power_assertion_id_user_); +} + +void ScreenCapturerMac::Capture( + const DesktopRegion& region_to_capture) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Wait until the display configuration is stable. If one or more displays + // are reconfiguring then |display_configuration_capture_event_| will not be + // set until the reconfiguration completes. + // TODO(wez): Replace this with an early-exit (See crbug.com/104542). + if (!display_configuration_capture_event_->Wait( + kDisplayConfigurationEventTimeoutMs)) { + LOG_F(LS_ERROR) << "Event wait timed out."; + abort(); + } + + DesktopRegion region; + helper_.TakeInvalidRegion(®ion); + + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame()) + queue_.ReplaceCurrentFrame(CreateFrame(desktop_config_)); + + DesktopFrame* current_frame = queue_.current_frame(); + + bool flip = false; // GL capturers need flipping. + if (IsOSLionOrLater()) { + // Lion requires us to use their new APIs for doing screen capture. These + // APIS currently crash on 10.6.8 if there is no monitor attached. + CgBlitPostLion(*current_frame, region); + } else if (cgl_context_) { + flip = true; + if (pixel_buffer_object_.get() != 0) { + GlBlitFast(*current_frame, region); + } else { + // See comment in ScopedPixelBufferObject::Init about why the slow + // path is always used on 10.5. + GlBlitSlow(*current_frame); + } + } else { + CgBlitPreLion(*current_frame, region); + } + + uint8_t* buffer = current_frame->data(); + int stride = current_frame->stride(); + if (flip) { + stride = -stride; + buffer += (current_frame->size().height() - 1) * current_frame->stride(); + } + + DesktopFrame* new_frame = queue_.current_frame()->Share(); + *new_frame->mutable_updated_region() = region; + + helper_.set_size_most_recent(new_frame->size()); + + // Signal that we are done capturing data from the display framebuffer, + // and accessing display structures. + display_configuration_capture_event_->Set(); + + // Capture the current cursor shape and notify |callback_| if it has changed. + CaptureCursor(); + + new_frame->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(new_frame); +} + +void ScreenCapturerMac::SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) { + assert(!mouse_shape_observer_); + assert(mouse_shape_observer); + mouse_shape_observer_ = mouse_shape_observer; +} + +void ScreenCapturerMac::CaptureCursor() { + if (!mouse_shape_observer_) + return; + + NSCursor* cursor = [NSCursor currentSystemCursor]; + if (cursor == nil) + return; + + NSImage* nsimage = [cursor image]; + NSPoint hotspot = [cursor hotSpot]; + NSSize size = [nsimage size]; + CGImageRef image = [nsimage CGImageForProposedRect:NULL + context:nil + hints:nil]; + if (image == nil) + return; + + if (CGImageGetBitsPerPixel(image) != 32 || + CGImageGetBytesPerRow(image) != (size.width * 4) || + CGImageGetBitsPerComponent(image) != 8) { + return; + } + + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef image_data_ref = CGDataProviderCopyData(provider); + if (image_data_ref == NULL) + return; + + const char* cursor_src_data = + reinterpret_cast(CFDataGetBytePtr(image_data_ref)); + int data_size = CFDataGetLength(image_data_ref); + + // Create a MouseCursorShape that describes the cursor and pass it to + // the client. + scoped_ptr cursor_shape(new MouseCursorShape()); + cursor_shape->size.set(size.width, size.height); + cursor_shape->hotspot.set(hotspot.x, hotspot.y); + cursor_shape->data.assign(cursor_src_data, cursor_src_data + data_size); + + CFRelease(image_data_ref); + + // Compare the current cursor with the last one we sent to the client. If + // they're the same, then don't bother sending the cursor again. + if (last_cursor_.size.equals(cursor_shape->size) && + last_cursor_.hotspot.equals(cursor_shape->hotspot) && + last_cursor_.data == cursor_shape->data) { + return; + } + + // Record the last cursor image that we sent to the client. + last_cursor_ = *cursor_shape; + + mouse_shape_observer_->OnCursorShapeChanged(cursor_shape.release()); +} + +void ScreenCapturerMac::GlBlitFast(const DesktopFrame& frame, + const DesktopRegion& region) { + // Clip to the size of our current screen. + DesktopRect clip_rect = DesktopRect::MakeSize(frame.size()); + if (queue_.previous_frame()) { + // We are doing double buffer for the capture data so we just need to copy + // the invalid region from the previous capture in the current buffer. + // TODO(hclam): We can reduce the amount of copying here by subtracting + // |capturer_helper_|s region from |last_invalid_region_|. + // http://crbug.com/92354 + + // Since the image obtained from OpenGL is upside-down, need to do some + // magic here to copy the correct rectangle. + const int y_offset = (frame.size().width() - 1) * frame.stride(); + for (DesktopRegion::Iterator i(last_invalid_region_); + !i.IsAtEnd(); i.Advance()) { + DesktopRect copy_rect = i.rect(); + copy_rect.IntersectWith(clip_rect); + if (!copy_rect.is_empty()) { + CopyRect(queue_.previous_frame()->data() + y_offset, + -frame.stride(), + frame.data() + y_offset, + -frame.stride(), + DesktopFrame::kBytesPerPixel, + copy_rect); + } + } + } + last_invalid_region_ = region; + + CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; + glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, pixel_buffer_object_.get()); + glReadPixels(0, 0, frame.size().height(), frame.size().width(), GL_BGRA, + GL_UNSIGNED_BYTE, 0); + GLubyte* ptr = static_cast( + glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB)); + if (ptr == NULL) { + // If the buffer can't be mapped, assume that it's no longer valid and + // release it. + pixel_buffer_object_.Release(); + } else { + // Copy only from the dirty rects. Since the image obtained from OpenGL is + // upside-down we need to do some magic here to copy the correct rectangle. + const int y_offset = (frame.size().height() - 1) * frame.stride(); + for (DesktopRegion::Iterator i(region); + !i.IsAtEnd(); i.Advance()) { + DesktopRect copy_rect = i.rect(); + copy_rect.IntersectWith(clip_rect); + if (!copy_rect.is_empty()) { + CopyRect(ptr + y_offset, + -frame.stride(), + frame.data() + y_offset, + -frame.stride(), + DesktopFrame::kBytesPerPixel, + copy_rect); + } + } + } + if (!glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB)) { + // If glUnmapBuffer returns false, then the contents of the data store are + // undefined. This might be because the screen mode has changed, in which + // case it will be recreated in ScreenConfigurationChanged, but releasing + // the object here is the best option. Capturing will fall back on + // GlBlitSlow until such time as the pixel buffer object is recreated. + pixel_buffer_object_.Release(); + } + glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0); +} + +void ScreenCapturerMac::GlBlitSlow(const DesktopFrame& frame) { + CGLContextObj CGL_MACRO_CONTEXT = cgl_context_; + glReadBuffer(GL_FRONT); + glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); + glPixelStorei(GL_PACK_ALIGNMENT, 4); // Force 4-byte alignment. + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glPixelStorei(GL_PACK_SKIP_ROWS, 0); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + // Read a block of pixels from the frame buffer. + glReadPixels(0, 0, frame.size().width(), frame.size().height(), + GL_BGRA, GL_UNSIGNED_BYTE, frame.data()); + glPopClientAttrib(); +} + +void ScreenCapturerMac::CgBlitPreLion(const DesktopFrame& frame, + const DesktopRegion& region) { + // Copy the entire contents of the previous capture buffer, to capture over. + // TODO(wez): Get rid of this as per crbug.com/145064, or implement + // crbug.com/92354. + if (queue_.previous_frame()) { + memcpy(frame.data(), + queue_.previous_frame()->data(), + frame.stride() * frame.size().height()); + } + + for (size_t i = 0; i < desktop_config_.displays.size(); ++i) { + const MacDisplayConfiguration& display_config = desktop_config_.displays[i]; + + // Use deprecated APIs to determine the display buffer layout. + assert(cg_display_base_address_ && cg_display_bytes_per_row_ && + cg_display_bits_per_pixel_); + uint8_t* display_base_address = reinterpret_cast( + (*cg_display_base_address_)(display_config.id)); + assert(display_base_address); + int src_bytes_per_row = (*cg_display_bytes_per_row_)(display_config.id); + int src_bytes_per_pixel = + (*cg_display_bits_per_pixel_)(display_config.id) / 8; + + // Determine the display's position relative to the desktop, in pixels. + DesktopRect display_bounds = display_config.pixel_bounds; + display_bounds.Translate(-desktop_config_.pixel_bounds.left(), + -desktop_config_.pixel_bounds.top()); + + // Determine which parts of the blit region, if any, lay within the monitor. + DesktopRegion copy_region = region; + copy_region.IntersectWith(display_bounds); + if (copy_region.is_empty()) + continue; + + // Translate the region to be copied into display-relative coordinates. + copy_region.Translate(-display_bounds.left(), -display_bounds.top()); + + // Calculate where in the output buffer the display's origin is. + uint8_t* out_ptr = frame.data() + + (display_bounds.left() * src_bytes_per_pixel) + + (display_bounds.top() * frame.stride()); + + // Copy the dirty region from the display buffer into our desktop buffer. + for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) { + CopyRect(display_base_address, + src_bytes_per_row, + out_ptr, + frame.stride(), + src_bytes_per_pixel, + i.rect()); + } + } +} + +void ScreenCapturerMac::CgBlitPostLion(const DesktopFrame& frame, + const DesktopRegion& region) { + // Copy the entire contents of the previous capture buffer, to capture over. + // TODO(wez): Get rid of this as per crbug.com/145064, or implement + // crbug.com/92354. + if (queue_.previous_frame()) { + memcpy(frame.data(), + queue_.previous_frame()->data(), + frame.stride() * frame.size().height()); + } + + for (size_t i = 0; i < desktop_config_.displays.size(); ++i) { + const MacDisplayConfiguration& display_config = desktop_config_.displays[i]; + + // Determine the display's position relative to the desktop, in pixels. + DesktopRect display_bounds = display_config.pixel_bounds; + display_bounds.Translate(-desktop_config_.pixel_bounds.left(), + -desktop_config_.pixel_bounds.top()); + + // Determine which parts of the blit region, if any, lay within the monitor. + DesktopRegion copy_region = region; + copy_region.IntersectWith(display_bounds); + if (copy_region.is_empty()) + continue; + + // Translate the region to be copied into display-relative coordinates. + copy_region.Translate(-display_bounds.left(), -display_bounds.top()); + + // Create an image containing a snapshot of the display. + CGImageRef image = CGDisplayCreateImage(display_config.id); + if (image == NULL) + continue; + + // Request access to the raw pixel data via the image's DataProvider. + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef data = CGDataProviderCopyData(provider); + assert(data); + + const uint8_t* display_base_address = CFDataGetBytePtr(data); + int src_bytes_per_row = CGImageGetBytesPerRow(image); + int src_bytes_per_pixel = CGImageGetBitsPerPixel(image) / 8; + + // Calculate where in the output buffer the display's origin is. + uint8_t* out_ptr = frame.data() + + (display_bounds.left() * src_bytes_per_pixel) + + (display_bounds.top() * frame.stride()); + + // Copy the dirty region from the display buffer into our desktop buffer. + for (DesktopRegion::Iterator i(copy_region); !i.IsAtEnd(); i.Advance()) { + CopyRect(display_base_address, + src_bytes_per_row, + out_ptr, + frame.stride(), + src_bytes_per_pixel, + i.rect()); + } + + CFRelease(data); + CFRelease(image); + } +} + +void ScreenCapturerMac::ScreenConfigurationChanged() { + // Release existing buffers, which will be of the wrong size. + ReleaseBuffers(); + + // Clear the dirty region, in case the display is down-sizing. + helper_.ClearInvalidRegion(); + + // Refresh the cached desktop configuration. + desktop_config_ = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::TopLeftOrigin); + + // Re-mark the entire desktop as dirty. + helper_.InvalidateScreen( + DesktopSize(desktop_config_.pixel_bounds.width(), + desktop_config_.pixel_bounds.height())); + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + // CgBlitPostLion uses CGDisplayCreateImage() to snapshot each display's + // contents. Although the API exists in OS 10.6, it crashes the caller if + // the machine has no monitor connected, so we fall back to depcreated APIs + // when running on 10.6. + if (IsOSLionOrLater()) { + LOG(LS_INFO) << "Using CgBlitPostLion."; + // No need for any OpenGL support on Lion + return; + } + + // Dynamically link to the deprecated pre-Lion capture APIs. + app_services_library_ = dlopen(kApplicationServicesLibraryName, + RTLD_LAZY); + if (!app_services_library_) { + LOG_F(LS_ERROR) << "Failed to open " << kApplicationServicesLibraryName; + abort(); + } + + opengl_library_ = dlopen(kOpenGlLibraryName, RTLD_LAZY); + if (!opengl_library_) { + LOG_F(LS_ERROR) << "Failed to open " << kOpenGlLibraryName; + abort(); + } + + cg_display_base_address_ = reinterpret_cast( + dlsym(app_services_library_, "CGDisplayBaseAddress")); + cg_display_bytes_per_row_ = reinterpret_cast( + dlsym(app_services_library_, "CGDisplayBytesPerRow")); + cg_display_bits_per_pixel_ = reinterpret_cast( + dlsym(app_services_library_, "CGDisplayBitsPerPixel")); + cgl_set_full_screen_ = reinterpret_cast( + dlsym(opengl_library_, "CGLSetFullScreen")); + if (!(cg_display_base_address_ && cg_display_bytes_per_row_ && + cg_display_bits_per_pixel_ && cgl_set_full_screen_)) { + LOG_F(LS_ERROR); + abort(); + } + + if (desktop_config_.displays.size() > 1) { + LOG(LS_INFO) << "Using CgBlitPreLion (Multi-monitor)."; + return; + } + + CGDirectDisplayID mainDevice = CGMainDisplayID(); + if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) { + LOG(LS_INFO) << "Using CgBlitPreLion (OpenGL unavailable)."; + return; + } + + LOG(LS_INFO) << "Using GlBlit"; + + CGLPixelFormatAttribute attributes[] = { + kCGLPFAFullScreen, + kCGLPFADisplayMask, + (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(mainDevice), + (CGLPixelFormatAttribute)0 + }; + CGLPixelFormatObj pixel_format = NULL; + GLint matching_pixel_format_count = 0; + CGLError err = CGLChoosePixelFormat(attributes, + &pixel_format, + &matching_pixel_format_count); + assert(err == kCGLNoError); + err = CGLCreateContext(pixel_format, NULL, &cgl_context_); + assert(err == kCGLNoError); + CGLDestroyPixelFormat(pixel_format); + (*cgl_set_full_screen_)(cgl_context_); + CGLSetCurrentContext(cgl_context_); + + size_t buffer_size = desktop_config_.pixel_bounds.width() * + desktop_config_.pixel_bounds.height() * + sizeof(uint32_t); + pixel_buffer_object_.Init(cgl_context_, buffer_size); +} + +bool ScreenCapturerMac::RegisterRefreshAndMoveHandlers() { + CGError err = CGRegisterScreenRefreshCallback( + ScreenCapturerMac::ScreenRefreshCallback, this); + if (err != kCGErrorSuccess) { + LOG(LS_ERROR) << "CGRegisterScreenRefreshCallback " << err; + return false; + } + + err = CGScreenRegisterMoveCallback( + ScreenCapturerMac::ScreenUpdateMoveCallback, this); + if (err != kCGErrorSuccess) { + LOG(LS_ERROR) << "CGScreenRegisterMoveCallback " << err; + return false; + } + + return true; +} + +void ScreenCapturerMac::UnregisterRefreshAndMoveHandlers() { + CGUnregisterScreenRefreshCallback( + ScreenCapturerMac::ScreenRefreshCallback, this); + CGScreenUnregisterMoveCallback( + ScreenCapturerMac::ScreenUpdateMoveCallback, this); +} + +void ScreenCapturerMac::ScreenRefresh(CGRectCount count, + const CGRect* rect_array) { + if (desktop_config_.pixel_bounds.is_empty()) + return; + + DesktopRegion region; + + for (CGRectCount i = 0; i < count; ++i) { + // Convert from Density-Independent Pixel to physical pixel coordinates. + DesktopRect rect = + ScaleAndRoundCGRect(rect_array[i], desktop_config_.dip_to_pixel_scale); + + // Translate from local desktop to capturer framebuffer coordinates. + rect.Translate(-desktop_config_.pixel_bounds.left(), + -desktop_config_.pixel_bounds.top()); + + region.AddRect(rect); + } + + helper_.InvalidateRegion(region); +} + +void ScreenCapturerMac::ScreenUpdateMove(CGScreenUpdateMoveDelta delta, + size_t count, + const CGRect* rect_array) { + // Translate |rect_array| to identify the move's destination. + CGRect refresh_rects[count]; + for (CGRectCount i = 0; i < count; ++i) { + refresh_rects[i] = CGRectOffset(rect_array[i], delta.dX, delta.dY); + } + + // Currently we just treat move events the same as refreshes. + ScreenRefresh(count, refresh_rects); +} + +void ScreenCapturerMac::DisplaysReconfigured( + CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags) { + if (flags & kCGDisplayBeginConfigurationFlag) { + if (reconfiguring_displays_.empty()) { + // If this is the first display to start reconfiguring then wait on + // |display_configuration_capture_event_| to block the capture thread + // from accessing display memory until the reconfiguration completes. + if (!display_configuration_capture_event_->Wait( + kDisplayConfigurationEventTimeoutMs)) { + LOG_F(LS_ERROR) << "Event wait timed out."; + abort(); + } + } + + reconfiguring_displays_.insert(display); + } else { + reconfiguring_displays_.erase(display); + + if (reconfiguring_displays_.empty()) { + // If no other displays are reconfiguring then refresh capturer data + // structures and un-block the capturer thread. Occasionally, the + // refresh and move handlers are lost when the screen mode changes, + // so re-register them here (the same does not appear to be true for + // the reconfiguration handler itself). + UnregisterRefreshAndMoveHandlers(); + RegisterRefreshAndMoveHandlers(); + ScreenConfigurationChanged(); + display_configuration_capture_event_->Set(); + } + } +} + +void ScreenCapturerMac::ScreenRefreshCallback(CGRectCount count, + const CGRect* rect_array, + void* user_parameter) { + ScreenCapturerMac* capturer = + reinterpret_cast(user_parameter); + if (capturer->desktop_config_.pixel_bounds.is_empty()) + capturer->ScreenConfigurationChanged(); + capturer->ScreenRefresh(count, rect_array); +} + +void ScreenCapturerMac::ScreenUpdateMoveCallback( + CGScreenUpdateMoveDelta delta, + size_t count, + const CGRect* rect_array, + void* user_parameter) { + ScreenCapturerMac* capturer = + reinterpret_cast(user_parameter); + capturer->ScreenUpdateMove(delta, count, rect_array); +} + +void ScreenCapturerMac::DisplaysReconfiguredCallback( + CGDirectDisplayID display, + CGDisplayChangeSummaryFlags flags, + void* user_parameter) { + ScreenCapturerMac* capturer = + reinterpret_cast(user_parameter); + capturer->DisplaysReconfigured(display, flags); +} + +} // namespace + +// static +ScreenCapturer* ScreenCapturer::Create() { + scoped_ptr capturer(new ScreenCapturerMac()); + if (!capturer->Init()) + capturer.reset(); + return capturer.release(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc new file mode 100644 index 0000000000..a51a4792ce --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_mac_unittest.cc @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +#include + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" +#include "webrtc/modules/desktop_capture/screen_capturer_mock_objects.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; + +namespace webrtc { + +class ScreenCapturerMacTest : public testing::Test { + public: + // Verifies that the whole screen is initially dirty. + void CaptureDoneCallback1(DesktopFrame* frame); + + // Verifies that a rectangle explicitly marked as dirty is propagated + // correctly. + void CaptureDoneCallback2(DesktopFrame* frame); + + protected: + virtual void SetUp() OVERRIDE { + capturer_.reset(ScreenCapturer::Create()); + } + + scoped_ptr capturer_; + MockScreenCapturerCallback callback_; +}; + +void ScreenCapturerMacTest::CaptureDoneCallback1( + DesktopFrame* frame) { + scoped_ptr owned_frame(frame); + + MacDesktopConfiguration config = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::BottomLeftOrigin); + + // Verify that the region contains full frame. + DesktopRegion::Iterator it(frame->updated_region()); + EXPECT_TRUE(!it.IsAtEnd() && it.rect().equals(config.pixel_bounds)); +} + +void ScreenCapturerMacTest::CaptureDoneCallback2( + DesktopFrame* frame) { + scoped_ptr owned_frame(frame); + + MacDesktopConfiguration config = MacDesktopConfiguration::GetCurrent( + MacDesktopConfiguration::BottomLeftOrigin); + int width = config.pixel_bounds.width(); + int height = config.pixel_bounds.height(); + + EXPECT_EQ(width, frame->size().width()); + EXPECT_EQ(height, frame->size().height()); + EXPECT_TRUE(frame->data() != NULL); + // Depending on the capture method, the screen may be flipped or not, so + // the stride may be positive or negative. + EXPECT_EQ(static_cast(sizeof(uint32_t) * width), + abs(frame->stride())); +} + +TEST_F(ScreenCapturerMacTest, Capture) { + EXPECT_CALL(callback_, OnCaptureCompleted(_)) + .Times(2) + .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback1)) + .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback2)); + + EXPECT_CALL(callback_, CreateSharedMemory(_)) + .Times(AnyNumber()) + .WillRepeatedly(Return(static_cast(NULL))); + + SCOPED_TRACE(""); + capturer_->Start(&callback_); + + // Check that we get an initial full-screen updated. + capturer_->Capture(DesktopRegion()); + + // Check that subsequent dirty rects are propagated correctly. + capturer_->Capture(DesktopRegion()); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h b/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h new file mode 100644 index 0000000000..17673b5cc0 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +namespace webrtc { + +class MockScreenCapturer : public ScreenCapturer { + public: + MockScreenCapturer() {} + virtual ~MockScreenCapturer() {} + + MOCK_METHOD1(Start, void(Callback* callback)); + MOCK_METHOD1(Capture, void(const DesktopRegion& region)); + MOCK_METHOD1(SetMouseShapeObserver, void( + MouseShapeObserver* mouse_shape_observer)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockScreenCapturer); +}; + +class MockScreenCapturerCallback : public ScreenCapturer::Callback { + public: + MockScreenCapturerCallback() {} + virtual ~MockScreenCapturerCallback() {} + + MOCK_METHOD1(CreateSharedMemory, SharedMemory*(size_t)); + MOCK_METHOD1(OnCaptureCompleted, void(DesktopFrame*)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockScreenCapturerCallback); +}; + +class MockMouseShapeObserver : public ScreenCapturer::MouseShapeObserver { + public: + MockMouseShapeObserver() {} + virtual ~MockMouseShapeObserver() {} + + void OnCursorShapeChanged(MouseCursorShape* cursor_shape) OVERRIDE { + OnCursorShapeChangedPtr(cursor_shape); + delete cursor_shape; + } + + MOCK_METHOD1(OnCursorShapeChangedPtr, + void(MouseCursorShape* cursor_shape)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockMouseShapeObserver); +}; + + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_MOCK_OBJECTS_H_ diff --git a/webrtc/modules/desktop_capture/screen_capturer_null.cc b/webrtc/modules/desktop_capture/screen_capturer_null.cc new file mode 100644 index 0000000000..a94d6035e4 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_null.cc @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +namespace webrtc { + +// static +ScreenCapturer* ScreenCapturer::Create() { + return NULL; +} + +#if defined(OS_LINUX) +// static +ScreenCapturer* ScreenCapturer::CreateWithXDamage(bool use_x_damage) { + return NULL; +} +#elif defined(OS_WIN) +// static +ScreenCapturer* ScreenCapturer::CreateWithDisableAero(bool disable_aero) { + return NULL; +} +#endif // defined(OS_WIN) + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc new file mode 100644 index 0000000000..d5ba213ef8 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/screen_capturer_mock_objects.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::SaveArg; + +const int kTestSharedMemoryId = 123; + +namespace webrtc { + +class ScreenCapturerTest : public testing::Test { + public: + SharedMemory* CreateSharedMemory(size_t size); + + protected: + scoped_ptr capturer_; + MockMouseShapeObserver mouse_observer_; + MockScreenCapturerCallback callback_; +}; + +class FakeSharedMemory : public SharedMemory { + public: + FakeSharedMemory(char* buffer, size_t size) + : SharedMemory(buffer, size, 0, kTestSharedMemoryId), + buffer_(buffer) { + } + virtual ~FakeSharedMemory() { + delete[] buffer_; + } + private: + char* buffer_; + DISALLOW_COPY_AND_ASSIGN(FakeSharedMemory); +}; + +SharedMemory* ScreenCapturerTest::CreateSharedMemory(size_t size) { + return new FakeSharedMemory(new char[size], size); +} + +TEST_F(ScreenCapturerTest, StartCapturer) { + capturer_.reset(ScreenCapturer::Create()); + capturer_->SetMouseShapeObserver(&mouse_observer_); + capturer_->Start(&callback_); +} + +TEST_F(ScreenCapturerTest, Capture) { + // Assume that Start() treats the screen as invalid initially. + DesktopFrame* frame = NULL; + EXPECT_CALL(callback_, OnCaptureCompleted(_)) + .WillOnce(SaveArg<0>(&frame)); + EXPECT_CALL(mouse_observer_, OnCursorShapeChangedPtr(_)) + .Times(AnyNumber()); + + EXPECT_CALL(callback_, CreateSharedMemory(_)) + .Times(AnyNumber()) + .WillRepeatedly(Return(static_cast(NULL))); + + capturer_.reset(ScreenCapturer::Create()); + capturer_->Start(&callback_); + capturer_->Capture(DesktopRegion()); + + ASSERT_TRUE(frame); + EXPECT_GT(frame->size().width(), 0); + EXPECT_GT(frame->size().height(), 0); + EXPECT_GE(frame->stride(), + frame->size().width() * DesktopFrame::kBytesPerPixel); + EXPECT_TRUE(frame->shared_memory() == NULL); + + // Verify that the region contains whole screen. + EXPECT_FALSE(frame->updated_region().is_empty()); + DesktopRegion::Iterator it(frame->updated_region()); + ASSERT_TRUE(!it.IsAtEnd()); + EXPECT_TRUE(it.rect().equals(DesktopRect::MakeSize(frame->size()))); + it.Advance(); + EXPECT_TRUE(it.IsAtEnd()); + + delete frame; +} + +#if defined(OS_WIN) + +TEST_F(ScreenCapturerTest, UseSharedBuffers) { + DesktopFrame* frame = NULL; + EXPECT_CALL(callback_, OnCaptureCompleted(_)) + .WillOnce(SaveArg<0>(&frame)); + EXPECT_CALL(mouse_observer_, OnCursorShapeChangedPtr(_)) + .Times(AnyNumber()); + + EXPECT_CALL(callback_, CreateSharedMemory(_)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &ScreenCapturerTest::CreateSharedMemory)); + + capturer_.reset(ScreenCapturer::Create()); + capturer_->Start(&callback_); + capturer_->Capture(DesktopRegion()); + + ASSERT_TRUE(frame); + ASSERT_TRUE(frame->shared_memory()); + EXPECT_EQ(frame->shared_memory()->id(), kTestSharedMemoryId); + + delete frame; +} + +#endif // defined(OS_WIN) + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_win.cc b/webrtc/modules/desktop_capture/screen_capturer_win.cc new file mode 100644 index 0000000000..629537f871 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_win.cc @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +#include + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_frame_win.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/win/desktop.h" +#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +namespace webrtc { + +namespace { + +// Constants from dwmapi.h. +const UINT DWM_EC_DISABLECOMPOSITION = 0; +const UINT DWM_EC_ENABLECOMPOSITION = 1; + +typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT); + +const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll"; + +// Pixel colors used when generating cursor outlines. +const uint32_t kPixelBgraBlack = 0xff000000; +const uint32_t kPixelBgraWhite = 0xffffffff; +const uint32_t kPixelBgraTransparent = 0x00000000; + +uint8_t AlphaMul(uint8_t v, uint8_t alpha) { + return (static_cast(v) * alpha) >> 8; +} + +// ScreenCapturerWin captures 32bit RGB using GDI. +// +// ScreenCapturerWin is double-buffered as required by ScreenCapturer. +class ScreenCapturerWin : public ScreenCapturer { + public: + ScreenCapturerWin(bool disable_aero); + virtual ~ScreenCapturerWin(); + + // Overridden from ScreenCapturer: + virtual void Start(Callback* callback) OVERRIDE; + virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) OVERRIDE; + + private: + // Make sure that the device contexts match the screen configuration. + void PrepareCaptureResources(); + + // Captures the current screen contents into the current buffer. + void CaptureImage(); + + // Expand the cursor shape to add a white outline for visibility against + // dark backgrounds. + void AddCursorOutline(int width, int height, uint32_t* dst); + + // Capture the current cursor shape. + void CaptureCursor(); + + Callback* callback_; + MouseShapeObserver* mouse_shape_observer_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Snapshot of the last cursor bitmap we sent to the client. This is used + // to diff against the current cursor so we only send a cursor-change + // message when the shape has changed. + MouseCursorShape last_cursor_; + + ScopedThreadDesktop desktop_; + + // GDI resources used for screen capture. + HDC desktop_dc_; + HDC memory_dc_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Rectangle describing the bounds of the desktop device context. + DesktopRect desktop_dc_rect_; + + // Class to calculate the difference between two screen bitmaps. + scoped_ptr differ_; + + HMODULE dwmapi_library_; + DwmEnableCompositionFunc composition_func_; + + // Used to suppress duplicate logging of SetThreadExecutionState errors. + bool set_thread_execution_state_failed_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWin); +}; + +ScreenCapturerWin::ScreenCapturerWin(bool disable_aero) + : callback_(NULL), + mouse_shape_observer_(NULL), + desktop_dc_(NULL), + memory_dc_(NULL), + dwmapi_library_(NULL), + composition_func_(NULL), + set_thread_execution_state_failed_(false) { + if (disable_aero) { + // Load dwmapi.dll dynamically since it is not available on XP. + if (!dwmapi_library_) + dwmapi_library_ = LoadLibrary(kDwmapiLibraryName); + + if (dwmapi_library_) { + composition_func_ = reinterpret_cast( + GetProcAddress(dwmapi_library_, "DwmEnableComposition")); + } + } +} + +ScreenCapturerWin::~ScreenCapturerWin() { + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); + if (memory_dc_) + DeleteDC(memory_dc_); + + // Restore Aero. + if (composition_func_) + (*composition_func_)(DWM_EC_ENABLECOMPOSITION); + + if (dwmapi_library_) + FreeLibrary(dwmapi_library_); +} + +void ScreenCapturerWin::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Request that the system not power-down the system, or the display hardware. + if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { + if (!set_thread_execution_state_failed_) { + set_thread_execution_state_failed_ = true; + LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " + << GetLastError(); + } + } + + // Make sure the GDI capture resources are up-to-date. + PrepareCaptureResources(); + + // Copy screen bits to the current buffer. + CaptureImage(); + + const DesktopFrame* current_frame = queue_.current_frame(); + const DesktopFrame* last_frame = queue_.previous_frame(); + if (last_frame) { + // Make sure the differencer is set up correctly for these previous and + // current screens. + if (!differ_.get() || + (differ_->width() != current_frame->size().width()) || + (differ_->height() != current_frame->size().height()) || + (differ_->bytes_per_row() != current_frame->stride())) { + differ_.reset(new Differ(current_frame->size().width(), + current_frame->size().height(), + DesktopFrame::kBytesPerPixel, + current_frame->stride())); + } + + // Calculate difference between the two last captured frames. + DesktopRegion region; + differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(), + ®ion); + helper_.InvalidateRegion(region); + } else { + // No previous frame is available. Invalidate the whole screen. + helper_.InvalidateScreen(current_frame->size()); + } + + helper_.set_size_most_recent(current_frame->size()); + + // Emit the current frame. + DesktopFrame* frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector( + GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->Clear(); + helper_.TakeInvalidRegion(frame->mutable_updated_region()); + frame->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(frame); + + // Check for cursor shape update. + CaptureCursor(); +} + +void ScreenCapturerWin::SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) { + assert(!mouse_shape_observer_); + assert(mouse_shape_observer); + + mouse_shape_observer_ = mouse_shape_observer; +} + +void ScreenCapturerWin::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; + + // Vote to disable Aero composited desktop effects while capturing. Windows + // will restore Aero automatically if the process exits. This has no effect + // under Windows 8 or higher. See crbug.com/124018. + if (composition_func_) + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); +} + +void ScreenCapturerWin::PrepareCaptureResources() { + // Switch to the desktop receiving user input if different from the current + // one. + scoped_ptr input_desktop(Desktop::GetInputDesktop()); + if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { + // Release GDI resources otherwise SetThreadDesktop will fail. + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = NULL; + } + + // If SetThreadDesktop() fails, the thread is still assigned a desktop. + // So we can continue capture screen bits, just from the wrong desktop. + desktop_.SetThreadDesktop(input_desktop.release()); + + // Re-assert our vote to disable Aero. + // See crbug.com/124018 and crbug.com/129906. + if (composition_func_ != NULL) { + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); + } + } + + // If the display bounds have changed then recreate GDI resources. + // TODO(wez): Also check for pixel format changes. + DesktopRect screen_rect(DesktopRect::MakeXYWH( + GetSystemMetrics(SM_XVIRTUALSCREEN), + GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CYVIRTUALSCREEN))); + if (!screen_rect.equals(desktop_dc_rect_)) { + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = NULL; + } + desktop_dc_rect_ = DesktopRect(); + } + + if (desktop_dc_ == NULL) { + assert(memory_dc_ == NULL); + + // Create GDI device contexts to capture from the desktop into memory. + desktop_dc_ = GetDC(NULL); + if (!desktop_dc_) + abort(); + memory_dc_ = CreateCompatibleDC(desktop_dc_); + if (!memory_dc_) + abort(); + desktop_dc_rect_ = screen_rect; + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + helper_.ClearInvalidRegion(); + } +} + +void ScreenCapturerWin::CaptureImage() { + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame()) { + assert(desktop_dc_ != NULL); + assert(memory_dc_ != NULL); + + DesktopSize size = DesktopSize( + desktop_dc_rect_.width(), desktop_dc_rect_.height()); + + size_t buffer_size = size.width() * size.height() * + DesktopFrame::kBytesPerPixel; + SharedMemory* shared_memory = + callback_->CreateSharedMemory(buffer_size); + scoped_ptr buffer( + DesktopFrameWin::Create(size, shared_memory, desktop_dc_)); + queue_.ReplaceCurrentFrame(buffer.release()); + } + + // Select the target bitmap into the memory dc and copy the rect from desktop + // to memory. + DesktopFrameWin* current = static_cast( + queue_.current_frame()->GetUnderlyingFrame()); + HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap()); + if (previous_object != NULL) { + BitBlt(memory_dc_, + 0, 0, desktop_dc_rect_.width(), desktop_dc_rect_.height(), + desktop_dc_, + desktop_dc_rect_.left(), desktop_dc_rect_.top(), + SRCCOPY | CAPTUREBLT); + + // Select back the previously selected object to that the device contect + // could be destroyed independently of the bitmap if needed. + SelectObject(memory_dc_, previous_object); + } +} + +void ScreenCapturerWin::AddCursorOutline(int width, + int height, + uint32_t* dst) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // If this is a transparent pixel (bgr == 0 and alpha = 0), check the + // neighbor pixels to see if this should be changed to an outline pixel. + if (*dst == kPixelBgraTransparent) { + // Change to white pixel if any neighbors (top, bottom, left, right) + // are black. + if ((y > 0 && dst[-width] == kPixelBgraBlack) || + (y < height - 1 && dst[width] == kPixelBgraBlack) || + (x > 0 && dst[-1] == kPixelBgraBlack) || + (x < width - 1 && dst[1] == kPixelBgraBlack)) { + *dst = kPixelBgraWhite; + } + } + dst++; + } + } +} + +void ScreenCapturerWin::CaptureCursor() { + CURSORINFO cursor_info; + cursor_info.cbSize = sizeof(CURSORINFO); + if (!GetCursorInfo(&cursor_info)) { + LOG_F(LS_INFO) << "Unable to get cursor info. Error = " << GetLastError(); + return; + } + + // Note that this does not need to be freed. + HCURSOR hcursor = cursor_info.hCursor; + ICONINFO iinfo; + if (!GetIconInfo(hcursor, &iinfo)) { + LOG_F(LS_INFO) << "Unable to get cursor icon info. Error = " + << GetLastError(); + return; + } + int hotspot_x = iinfo.xHotspot; + int hotspot_y = iinfo.yHotspot; + + // Get the cursor bitmap. + HBITMAP hbitmap; + BITMAP bitmap; + bool color_bitmap; + if (iinfo.hbmColor) { + // Color cursor bitmap. + color_bitmap = true; + hbitmap = reinterpret_cast( + CopyImage(iinfo.hbmColor, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION)); + if (!hbitmap) { + LOG_F(LS_INFO) << "Unable to copy color cursor image. Error = " + << GetLastError(); + return; + } + + // Free the color and mask bitmaps since we only need our copy. + DeleteObject(iinfo.hbmColor); + DeleteObject(iinfo.hbmMask); + } else { + // Black and white (xor) cursor. + color_bitmap = false; + hbitmap = iinfo.hbmMask; + } + + if (!GetObject(hbitmap, sizeof(BITMAP), &bitmap)) { + LOG_F(LS_INFO) << "Unable to get cursor bitmap. Error = " << GetLastError(); + DeleteObject(hbitmap); + return; + } + + int width = bitmap.bmWidth; + int height = bitmap.bmHeight; + // For non-color cursors, the mask contains both an AND and an XOR mask and + // the height includes both. Thus, the width is correct, but we need to + // divide by 2 to get the correct mask height. + if (!color_bitmap) { + height /= 2; + } + int data_size = height * width * DesktopFrame::kBytesPerPixel; + + scoped_ptr cursor(new MouseCursorShape()); + cursor->data.resize(data_size); + uint8_t* cursor_dst_data = + reinterpret_cast(&*(cursor->data.begin())); + + // Copy/convert cursor bitmap into format needed by chromotocol. + int row_bytes = bitmap.bmWidthBytes; + if (color_bitmap) { + if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 32) { + LOG_F(LS_INFO) << "Unsupported color cursor format. Error = " + << GetLastError(); + DeleteObject(hbitmap); + return; + } + + // Copy across colour cursor imagery. + // MouseCursorShape stores imagery top-down, and premultiplied + // by the alpha channel, whereas windows stores them bottom-up + // and not premultiplied. + uint8_t* cursor_src_data = reinterpret_cast(bitmap.bmBits); + uint8_t* src = cursor_src_data + ((height - 1) * row_bytes); + uint8_t* dst = cursor_dst_data; + for (int row = 0; row < height; ++row) { + for (int column = 0; column < width; ++column) { + dst[0] = AlphaMul(src[0], src[3]); + dst[1] = AlphaMul(src[1], src[3]); + dst[2] = AlphaMul(src[2], src[3]); + dst[3] = src[3]; + dst += DesktopFrame::kBytesPerPixel; + src += DesktopFrame::kBytesPerPixel; + } + src -= row_bytes + (width * DesktopFrame::kBytesPerPixel); + } + } else { + if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 1) { + LOG(LS_VERBOSE) << "Unsupported cursor mask format. Error = " + << GetLastError(); + DeleteObject(hbitmap); + return; + } + + // x2 because there are 2 masks in the bitmap: AND and XOR. + int mask_bytes = height * row_bytes * 2; + scoped_array mask(new uint8_t[mask_bytes]); + if (!GetBitmapBits(hbitmap, mask_bytes, mask.get())) { + LOG(LS_VERBOSE) << "Unable to get cursor mask bits. Error = " + << GetLastError(); + DeleteObject(hbitmap); + return; + } + uint8_t* and_mask = mask.get(); + uint8_t* xor_mask = mask.get() + height * row_bytes; + uint8_t* dst = cursor_dst_data; + bool add_outline = false; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int byte = y * row_bytes + x / 8; + int bit = 7 - x % 8; + int and_bit = and_mask[byte] & (1 << bit); + int xor_bit = xor_mask[byte] & (1 << bit); + + // The two cursor masks combine as follows: + // AND XOR Windows Result Our result RGB Alpha + // 0 0 Black Black 00 ff + // 0 1 White White ff ff + // 1 0 Screen Transparent 00 00 + // 1 1 Reverse-screen Black 00 ff + // Since we don't support XOR cursors, we replace the "Reverse Screen" + // with black. In this case, we also add an outline around the cursor + // so that it is visible against a dark background. + int rgb = (!and_bit && xor_bit) ? 0xff : 0x00; + int alpha = (and_bit && !xor_bit) ? 0x00 : 0xff; + *dst++ = rgb; + *dst++ = rgb; + *dst++ = rgb; + *dst++ = alpha; + if (and_bit && xor_bit) { + add_outline = true; + } + } + } + if (add_outline) { + AddCursorOutline(width, height, + reinterpret_cast(cursor_dst_data)); + } + } + + DeleteObject(hbitmap); + + cursor->size.set(width, height); + cursor->hotspot.set(hotspot_x, hotspot_y); + + // Compare the current cursor with the last one we sent to the client. If + // they're the same, then don't bother sending the cursor again. + if (last_cursor_.size.equals(cursor->size) && + last_cursor_.hotspot.equals(cursor->hotspot) && + last_cursor_.data == cursor->data) { + return; + } + + LOG(LS_VERBOSE) << "Sending updated cursor: " << width << "x" << height; + + // Record the last cursor image that we sent to the client. + last_cursor_ = *cursor; + + if (mouse_shape_observer_) + mouse_shape_observer_->OnCursorShapeChanged(cursor.release()); +} + +} // namespace + +// static +ScreenCapturer* ScreenCapturer::Create() { + return CreateWithDisableAero(true); +} + +// static +ScreenCapturer* ScreenCapturer::CreateWithDisableAero(bool disable_aero) { + return new ScreenCapturerWin(disable_aero); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_x11.cc b/webrtc/modules/desktop_capture/screen_capturer_x11.cc new file mode 100644 index 0000000000..f08f499dd1 --- /dev/null +++ b/webrtc/modules/desktop_capture/screen_capturer_x11.cc @@ -0,0 +1,628 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +#include +#include + +#include +#include +#include +#include + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +// TODO(sergeyu): Move this to a header where it can be shared. +#if defined(NDEBUG) +#define DCHECK(condition) (void)(condition) +#else // NDEBUG +#define DCHECK(condition) if (!(condition)) {abort();} +#endif + +namespace webrtc { + +namespace { + +// A class to perform video frame capturing for Linux. +class ScreenCapturerLinux : public ScreenCapturer { + public: + ScreenCapturerLinux(); + virtual ~ScreenCapturerLinux(); + + // TODO(ajwong): Do we really want this to be synchronous? + bool Init(bool use_x_damage); + + // DesktopCapturer interface. + virtual void Start(Callback* delegate) OVERRIDE; + virtual void Capture(const DesktopRegion& region) OVERRIDE; + + // ScreenCapturer interface. + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) OVERRIDE; + + private: + void InitXDamage(); + + // Read and handle all currently-pending XEvents. + // In the DAMAGE case, process the XDamage events and store the resulting + // damage rectangles in the ScreenCapturerHelper. + // In all cases, call ScreenConfigurationChanged() in response to any + // ConfigNotify events. + void ProcessPendingXEvents(); + + // Capture the cursor image and notify the delegate if it was captured. + void CaptureCursor(); + + // Capture screen pixels to the current buffer in the queue. In the DAMAGE + // case, the ScreenCapturerHelper already holds the list of invalid rectangles + // from ProcessPendingXEvents(). In the non-DAMAGE case, this captures the + // whole screen, then calculates some invalid rectangles that include any + // differences between this and the previous capture. + DesktopFrame* CaptureScreen(); + + // Called when the screen configuration is changed. |root_window_size| + // specifies the most recent size of the root window. + void ScreenConfigurationChanged(const DesktopSize& root_window_size); + + // Synchronize the current buffer with |last_buffer_|, by copying pixels from + // the area of |last_invalid_rects|. + // Note this only works on the assumption that kNumBuffers == 2, as + // |last_invalid_rects| holds the differences from the previous buffer and + // the one prior to that (which will then be the current buffer). + void SynchronizeFrame(); + + void DeinitXlib(); + + // Capture a rectangle from |x_server_pixel_buffer_|, and copy the data into + // |frame|. + void CaptureRect(const DesktopRect& rect, + DesktopFrame* frame); + + // We expose two forms of blitting to handle variations in the pixel format. + // In FastBlit, the operation is effectively a memcpy. + void FastBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame); + void SlowBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame); + + // Returns the number of bits |mask| has to be shifted left so its last + // (most-significant) bit set becomes the most-significant bit of the word. + // When |mask| is 0 the function returns 31. + static uint32_t GetRgbShift(uint32_t mask); + + Callback* callback_; + MouseShapeObserver* mouse_shape_observer_; + + // X11 graphics context. + Display* display_; + GC gc_; + Window root_window_; + + // Last known dimensions of the root window. + DesktopSize root_window_size_; + + // XFixes. + bool has_xfixes_; + int xfixes_event_base_; + int xfixes_error_base_; + + // XDamage information. + bool use_damage_; + Damage damage_handle_; + int damage_event_base_; + int damage_error_base_; + XserverRegion damage_region_; + + // Access to the X Server's pixel buffer. + XServerPixelBuffer x_server_pixel_buffer_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Invalid region from the previous capture. This is used to synchronize the + // current with the last buffer used. + DesktopRegion last_invalid_region_; + + // |Differ| for use when polling for changes. + scoped_ptr differ_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux); +}; + +ScreenCapturerLinux::ScreenCapturerLinux() + : callback_(NULL), + mouse_shape_observer_(NULL), + display_(NULL), + gc_(NULL), + root_window_(BadValue), + has_xfixes_(false), + xfixes_event_base_(-1), + xfixes_error_base_(-1), + use_damage_(false), + damage_handle_(0), + damage_event_base_(-1), + damage_error_base_(-1), + damage_region_(0) { + helper_.SetLogGridSize(4); +} + +ScreenCapturerLinux::~ScreenCapturerLinux() { + DeinitXlib(); +} + +bool ScreenCapturerLinux::Init(bool use_x_damage) { + use_x_damage = true; + + // TODO(ajwong): We should specify the display string we are attaching to + // in the constructor. + display_ = XOpenDisplay(NULL); + if (!display_) { + LOG(LS_ERROR) << "Unable to open display"; + return false; + } + + root_window_ = RootWindow(display_, DefaultScreen(display_)); + if (root_window_ == BadValue) { + LOG(LS_ERROR) << "Unable to get the root window"; + DeinitXlib(); + return false; + } + + gc_ = XCreateGC(display_, root_window_, 0, NULL); + if (gc_ == NULL) { + LOG(LS_ERROR) << "Unable to get graphics context"; + DeinitXlib(); + return false; + } + + // Check for XFixes extension. This is required for cursor shape + // notifications, and for our use of XDamage. + if (XFixesQueryExtension(display_, &xfixes_event_base_, + &xfixes_error_base_)) { + has_xfixes_ = true; + } else { + LOG(LS_INFO) << "X server does not support XFixes."; + } + + // Register for changes to the dimensions of the root window. + XSelectInput(display_, root_window_, StructureNotifyMask); + + root_window_size_ = XServerPixelBuffer::GetRootWindowSize(display_); + x_server_pixel_buffer_.Init(display_, root_window_size_); + + if (has_xfixes_) { + // Register for changes to the cursor shape. + XFixesSelectCursorInput(display_, root_window_, + XFixesDisplayCursorNotifyMask); + } + + if (use_x_damage) { + InitXDamage(); + } + + return true; +} + +void ScreenCapturerLinux::InitXDamage() { + // Our use of XDamage requires XFixes. + if (!has_xfixes_) { + return; + } + + // Check for XDamage extension. + if (!XDamageQueryExtension(display_, &damage_event_base_, + &damage_error_base_)) { + LOG(LS_INFO) << "X server does not support XDamage."; + return; + } + + // TODO(lambroslambrou): Disable DAMAGE in situations where it is known + // to fail, such as when Desktop Effects are enabled, with graphics + // drivers (nVidia, ATI) that fail to report DAMAGE notifications + // properly. + + // Request notifications every time the screen becomes damaged. + damage_handle_ = XDamageCreate(display_, root_window_, + XDamageReportNonEmpty); + if (!damage_handle_) { + LOG(LS_ERROR) << "Unable to initialize XDamage."; + return; + } + + // Create an XFixes server-side region to collate damage into. + damage_region_ = XFixesCreateRegion(display_, 0, 0); + if (!damage_region_) { + XDamageDestroy(display_, damage_handle_); + LOG(LS_ERROR) << "Unable to create XFixes region."; + return; + } + + use_damage_ = true; + LOG(LS_INFO) << "Using XDamage extension."; +} + +void ScreenCapturerLinux::Start(Callback* callback) { + DCHECK(!callback_); + DCHECK(callback); + + callback_ = callback; +} + +void ScreenCapturerLinux::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Process XEvents for XDamage and cursor shape tracking. + ProcessPendingXEvents(); + + // If the current frame is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame()) { + scoped_ptr frame( + new BasicDesktopFrame(root_window_size_)); + queue_.ReplaceCurrentFrame(frame.release()); + } + + // Refresh the Differ helper used by CaptureFrame(), if needed. + DesktopFrame* frame = queue_.current_frame(); + if (!use_damage_ && ( + !differ_.get() || + (differ_->width() != frame->size().width()) || + (differ_->height() != frame->size().height()) || + (differ_->bytes_per_row() != frame->stride()))) { + differ_.reset(new Differ(frame->size().width(), frame->size().height(), + DesktopFrame::kBytesPerPixel, + frame->stride())); + } + + DesktopFrame* result = CaptureScreen(); + last_invalid_region_ = result->updated_region(); + result->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(result); +} + +void ScreenCapturerLinux::SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) { + DCHECK(!mouse_shape_observer_); + DCHECK(mouse_shape_observer); + + mouse_shape_observer_ = mouse_shape_observer; +} + +void ScreenCapturerLinux::ProcessPendingXEvents() { + // Find the number of events that are outstanding "now." We don't just loop + // on XPending because we want to guarantee this terminates. + int events_to_process = XPending(display_); + XEvent e; + + for (int i = 0; i < events_to_process; i++) { + XNextEvent(display_, &e); + if (use_damage_ && (e.type == damage_event_base_ + XDamageNotify)) { + XDamageNotifyEvent* event = reinterpret_cast(&e); + DCHECK(event->level == XDamageReportNonEmpty); + } else if (e.type == ConfigureNotify) { + const XConfigureEvent& event = e.xconfigure; + ScreenConfigurationChanged( + DesktopSize(event.width, event.height)); + } else if (has_xfixes_ && + e.type == xfixes_event_base_ + XFixesCursorNotify) { + XFixesCursorNotifyEvent* cne; + cne = reinterpret_cast(&e); + if (cne->subtype == XFixesDisplayCursorNotify) { + CaptureCursor(); + } + } else { + LOG(LS_WARNING) << "Got unknown event type: " << e.type; + } + } +} + +void ScreenCapturerLinux::CaptureCursor() { + DCHECK(has_xfixes_); + + XFixesCursorImage* img = XFixesGetCursorImage(display_); + if (!img) { + return; + } + + scoped_ptr cursor(new MouseCursorShape()); + cursor->size = DesktopSize(img->width, img->height); + cursor->hotspot = DesktopVector(img->xhot, img->yhot); + + int total_bytes = cursor->size.width ()* cursor->size.height() * + DesktopFrame::kBytesPerPixel; + cursor->data.resize(total_bytes); + + // Xlib stores 32-bit data in longs, even if longs are 64-bits long. + unsigned long* src = img->pixels; + uint32_t* dst = reinterpret_cast(&*(cursor->data.begin())); + uint32_t* dst_end = dst + (img->width * img->height); + while (dst < dst_end) { + *dst++ = static_cast(*src++); + } + XFree(img); + + if (mouse_shape_observer_) + mouse_shape_observer_->OnCursorShapeChanged(cursor.release()); +} + +DesktopFrame* ScreenCapturerLinux::CaptureScreen() { + DesktopFrame* frame = queue_.current_frame()->Share(); + + // Pass the screen size to the helper, so it can clip the invalid region if it + // expands that region to a grid. + helper_.set_size_most_recent(frame->size()); + + // In the DAMAGE case, ensure the frame is up-to-date with the previous frame + // if any. If there isn't a previous frame, that means a screen-resolution + // change occurred, and |invalid_rects| will be updated to include the whole + // screen. + if (use_damage_ && queue_.previous_frame()) + SynchronizeFrame(); + + DesktopRegion* updated_region = frame->mutable_updated_region(); + + x_server_pixel_buffer_.Synchronize(); + if (use_damage_ && queue_.previous_frame()) { + // Atomically fetch and clear the damage region. + XDamageSubtract(display_, damage_handle_, None, damage_region_); + int rects_num = 0; + XRectangle bounds; + XRectangle* rects = XFixesFetchRegionAndBounds(display_, damage_region_, + &rects_num, &bounds); + for (int i = 0; i < rects_num; ++i) { + updated_region->AddRect(DesktopRect::MakeXYWH( + rects[i].x, rects[i].y, rects[i].width, rects[i].height)); + } + XFree(rects); + helper_.InvalidateRegion(*updated_region); + + // Capture the damaged portions of the desktop. + helper_.TakeInvalidRegion(updated_region); + + // Clip the damaged portions to the current screen size, just in case some + // spurious XDamage notifications were received for a previous (larger) + // screen size. + updated_region->IntersectWith( + DesktopRect::MakeSize(root_window_size_)); + + for (DesktopRegion::Iterator it(*updated_region); + !it.IsAtEnd(); it.Advance()) { + CaptureRect(it.rect(), frame); + } + } else { + // Doing full-screen polling, or this is the first capture after a + // screen-resolution change. In either case, need a full-screen capture. + DesktopRect screen_rect = + DesktopRect::MakeSize(frame->size()); + CaptureRect(screen_rect, frame); + + if (queue_.previous_frame()) { + // Full-screen polling, so calculate the invalid rects here, based on the + // changed pixels between current and previous buffers. + DCHECK(differ_.get() != NULL); + DCHECK(queue_.previous_frame()->data()); + differ_->CalcDirtyRegion(queue_.previous_frame()->data(), + frame->data(), updated_region); + } else { + // No previous buffer, so always invalidate the whole screen, whether + // or not DAMAGE is being used. DAMAGE doesn't necessarily send a + // full-screen notification after a screen-resolution change, so + // this is done here. + updated_region->SetRect(screen_rect); + } + } + + return frame; +} + +void ScreenCapturerLinux::ScreenConfigurationChanged( + const DesktopSize& root_window_size) { + root_window_size_ = root_window_size; + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + helper_.ClearInvalidRegion(); + x_server_pixel_buffer_.Init(display_, root_window_size_); +} + +void ScreenCapturerLinux::SynchronizeFrame() { + // Synchronize the current buffer with the previous one since we do not + // capture the entire desktop. Note that encoder may be reading from the + // previous buffer at this time so thread access complaints are false + // positives. + + // TODO(hclam): We can reduce the amount of copying here by subtracting + // |capturer_helper_|s region from |last_invalid_region_|. + // http://crbug.com/92354 + DCHECK(queue_.previous_frame()); + + DesktopFrame* current = queue_.current_frame(); + DesktopFrame* last = queue_.previous_frame(); + DCHECK(current != last); + for (DesktopRegion::Iterator it(last_invalid_region_); + !it.IsAtEnd(); it.Advance()) { + const DesktopRect& r = it.rect(); + int offset = r.top() * current->stride() + + r.left() * DesktopFrame::kBytesPerPixel; + for (int i = 0; i < r.height(); ++i) { + memcpy(current->data() + offset, last->data() + offset, + r.width() * DesktopFrame::kBytesPerPixel); + offset += current->size().width() * DesktopFrame::kBytesPerPixel; + } + } +} + +void ScreenCapturerLinux::DeinitXlib() { + if (gc_) { + XFreeGC(display_, gc_); + gc_ = NULL; + } + + x_server_pixel_buffer_.Release(); + + if (display_) { + if (damage_handle_) + XDamageDestroy(display_, damage_handle_); + if (damage_region_) + XFixesDestroyRegion(display_, damage_region_); + XCloseDisplay(display_); + display_ = NULL; + damage_handle_ = 0; + damage_region_ = 0; + } +} + +void ScreenCapturerLinux::CaptureRect(const DesktopRect& rect, + DesktopFrame* frame) { + uint8_t* image = x_server_pixel_buffer_.CaptureRect(rect); + int depth = x_server_pixel_buffer_.GetDepth(); + if ((depth == 24 || depth == 32) && + x_server_pixel_buffer_.GetBitsPerPixel() == 32 && + x_server_pixel_buffer_.GetRedMask() == 0xff0000 && + x_server_pixel_buffer_.GetGreenMask() == 0xff00 && + x_server_pixel_buffer_.GetBlueMask() == 0xff) { + FastBlit(image, rect, frame); + } else { + SlowBlit(image, rect, frame); + } +} + +void ScreenCapturerLinux::FastBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame) { + uint8_t* src_pos = image; + int src_stride = x_server_pixel_buffer_.GetStride(); + int dst_x = rect.left(), dst_y = rect.top(); + + uint8_t* dst_pos = frame->data() + frame->stride() * dst_y; + dst_pos += dst_x * DesktopFrame::kBytesPerPixel; + + int height = rect.height(); + int row_bytes = rect.width() * DesktopFrame::kBytesPerPixel; + for (int y = 0; y < height; ++y) { + memcpy(dst_pos, src_pos, row_bytes); + src_pos += src_stride; + dst_pos += frame->stride(); + } +} + +void ScreenCapturerLinux::SlowBlit(uint8_t* image, + const DesktopRect& rect, + DesktopFrame* frame) { + int src_stride = x_server_pixel_buffer_.GetStride(); + int dst_x = rect.left(), dst_y = rect.top(); + int width = rect.width(), height = rect.height(); + + uint32_t red_mask = x_server_pixel_buffer_.GetRedMask(); + uint32_t green_mask = x_server_pixel_buffer_.GetGreenMask(); + uint32_t blue_mask = x_server_pixel_buffer_.GetBlueMask(); + + uint32_t red_shift = GetRgbShift(red_mask); + uint32_t green_shift = GetRgbShift(green_mask); + uint32_t blue_shift = GetRgbShift(blue_mask); + + unsigned int bits_per_pixel = x_server_pixel_buffer_.GetBitsPerPixel(); + + uint8_t* dst_pos = frame->data() + frame->stride() * dst_y; + uint8_t* src_pos = image; + dst_pos += dst_x * DesktopFrame::kBytesPerPixel; + // TODO(hclam): Optimize, perhaps using MMX code or by converting to + // YUV directly + for (int y = 0; y < height; y++) { + uint32_t* dst_pos_32 = reinterpret_cast(dst_pos); + uint32_t* src_pos_32 = reinterpret_cast(src_pos); + uint16_t* src_pos_16 = reinterpret_cast(src_pos); + for (int x = 0; x < width; x++) { + // Dereference through an appropriately-aligned pointer. + uint32_t pixel; + if (bits_per_pixel == 32) { + pixel = src_pos_32[x]; + } else if (bits_per_pixel == 16) { + pixel = src_pos_16[x]; + } else { + pixel = src_pos[x]; + } + uint32_t r = (pixel & red_mask) << red_shift; + uint32_t g = (pixel & green_mask) << green_shift; + uint32_t b = (pixel & blue_mask) << blue_shift; + + // Write as 32-bit RGB. + dst_pos_32[x] = ((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) | + ((b >> 24) & 0xff); + } + dst_pos += frame->stride(); + src_pos += src_stride; + } +} + +// static +uint32_t ScreenCapturerLinux::GetRgbShift(uint32_t mask) { + int shift = 0; + if ((mask & 0xffff0000u) == 0) { + mask <<= 16; + shift += 16; + } + if ((mask & 0xff000000u) == 0) { + mask <<= 8; + shift += 8; + } + if ((mask & 0xf0000000u) == 0) { + mask <<= 4; + shift += 4; + } + if ((mask & 0xc0000000u) == 0) { + mask <<= 2; + shift += 2; + } + if ((mask & 0x80000000u) == 0) + shift += 1; + + return shift; +} + +} // namespace + +// static +ScreenCapturer* ScreenCapturer::Create() { + scoped_ptr capturer(new ScreenCapturerLinux()); + if (!capturer->Init(false)) + capturer.reset(); + return capturer.release(); +} + +// static +ScreenCapturer* ScreenCapturer::CreateWithXDamage(bool use_x_damage) { + scoped_ptr capturer(new ScreenCapturerLinux()); + if (!capturer->Init(use_x_damage)) + capturer.reset(); + return capturer.release(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/shared_desktop_frame.cc b/webrtc/modules/desktop_capture/shared_desktop_frame.cc new file mode 100644 index 0000000000..29a5919abc --- /dev/null +++ b/webrtc/modules/desktop_capture/shared_desktop_frame.cc @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/shared_desktop_frame.h" + +#include "webrtc/system_wrappers/interface/atomic32.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class SharedDesktopFrame::Core { + public: + Core(DesktopFrame* frame) : frame_(frame) {} + + DesktopFrame* frame() { return frame_.get(); } + + bool HasOneRef() { return ref_count_.Value() == 1; } + + virtual int32_t AddRef() { + return ++ref_count_; + } + + virtual int32_t Release() { + int32_t ref_count; + ref_count = --ref_count_; + if (ref_count == 0) + delete this; + return ref_count; + } + + private: + virtual ~Core() {} + + Atomic32 ref_count_; + scoped_ptr frame_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +SharedDesktopFrame::~SharedDesktopFrame() {} + +// static +SharedDesktopFrame* SharedDesktopFrame::Wrap( + DesktopFrame* desktop_frame) { + scoped_refptr core(new Core(desktop_frame)); + return new SharedDesktopFrame(core); +} + +DesktopFrame* SharedDesktopFrame::GetUnderlyingFrame() { + return core_->frame(); +} + +SharedDesktopFrame* SharedDesktopFrame::Share() { + SharedDesktopFrame* result = new SharedDesktopFrame(core_); + result->set_dpi(dpi()); + result->set_capture_time_ms(capture_time_ms()); + *result->mutable_updated_region() = updated_region(); + return result; +} + +bool SharedDesktopFrame::IsShared() { + return !core_->HasOneRef(); +} + +SharedDesktopFrame::SharedDesktopFrame(scoped_refptr core) + : DesktopFrame(core->frame()->size(), core->frame()->stride(), + core->frame()->data(), core->frame()->shared_memory()), + core_(core) { +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/shared_desktop_frame.h b/webrtc/modules/desktop_capture/shared_desktop_frame.h new file mode 100644 index 0000000000..d77cb15b4d --- /dev/null +++ b/webrtc/modules/desktop_capture/shared_desktop_frame.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/system_wrappers/interface/scoped_refptr.h" + +namespace webrtc { + +// SharedDesktopFrame is a DesktopFrame that may have multiple instances all +// sharing the same buffer. +class SharedDesktopFrame : public DesktopFrame { + public: + virtual ~SharedDesktopFrame(); + + static SharedDesktopFrame* Wrap(DesktopFrame* desktop_frame); + + // Returns the underlying instance of DesktopFrame. + DesktopFrame* GetUnderlyingFrame(); + + // Creates a clone of this object. + SharedDesktopFrame* Share(); + + // Checks if the frame is currently shared. If it returns false it's + // guaranteed that there are no clones of the object. + bool IsShared(); + + private: + class Core; + + SharedDesktopFrame(scoped_refptr core); + + scoped_refptr core_; + + DISALLOW_COPY_AND_ASSIGN(SharedDesktopFrame); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_SHARED_DESKTOP_FRAME_H_ diff --git a/webrtc/modules/desktop_capture/win/desktop.cc b/webrtc/modules/desktop_capture/win/desktop.cc new file mode 100644 index 0000000000..e665751636 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/desktop.cc @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/desktop.h" + +#include + +#include "webrtc/system_wrappers/interface/logging.h" + +namespace webrtc { + +Desktop::Desktop(HDESK desktop, bool own) : desktop_(desktop), own_(own) { +} + +Desktop::~Desktop() { + if (own_ && desktop_ != NULL) { + if (!::CloseDesktop(desktop_)) { + LOG(LS_ERROR) << "Failed to close the owned desktop handle: " + << GetLastError(); + } + } +} + +bool Desktop::GetName(std::wstring* desktop_name_out) const { + if (desktop_ == NULL) + return false; + + DWORD length = 0; + int rv = GetUserObjectInformationW(desktop_, UOI_NAME, NULL, 0, &length); + if (rv || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + abort(); + + length /= sizeof(WCHAR); + std::vector buffer(length); + if (!GetUserObjectInformationW(desktop_, UOI_NAME, &buffer[0], + length * sizeof(WCHAR), &length)) { + LOG(LS_ERROR) << "Failed to query the desktop name: " << GetLastError(); + return false; + } + + desktop_name_out->assign(&buffer[0], length / sizeof(WCHAR)); + return true; +} + +bool Desktop::IsSame(const Desktop& other) const { + std::wstring name; + if (!GetName(&name)) + return false; + + std::wstring other_name; + if (!other.GetName(&other_name)) + return false; + + return name == other_name; +} + +bool Desktop::SetThreadDesktop() const { + if (!::SetThreadDesktop(desktop_)) { + LOG(LS_ERROR) << "Failed to assign the desktop to the current thread: " + << GetLastError(); + return false; + } + + return true; +} + +Desktop* Desktop::GetDesktop(const WCHAR* desktop_name) { + ACCESS_MASK desired_access = + DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | + DESKTOP_HOOKCONTROL | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE; + HDESK desktop = OpenDesktop(desktop_name, 0, FALSE, desired_access); + if (desktop == NULL) { + LOG(LS_ERROR) << "Failed to open the desktop '" << desktop_name << "': " + << GetLastError(); + return NULL; + } + + return new Desktop(desktop, true); +} + +Desktop* Desktop::GetInputDesktop() { + HDESK desktop = OpenInputDesktop( + 0, FALSE, GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE); + if (desktop == NULL) + return NULL; + + return new Desktop(desktop, true); +} + +Desktop* Desktop::GetThreadDesktop() { + HDESK desktop = ::GetThreadDesktop(GetCurrentThreadId()); + if (desktop == NULL) { + LOG(LS_ERROR) << "Failed to retrieve the handle of the desktop assigned to " + "the current thread: " + << GetLastError(); + return NULL; + } + + return new Desktop(desktop, false); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/desktop.h b/webrtc/modules/desktop_capture/win/desktop.h new file mode 100644 index 0000000000..bdc490c728 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/desktop.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ + +#include +#include + +#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class Desktop { + public: + ~Desktop(); + + // Returns the name of the desktop represented by the object. Return false if + // quering the name failed for any reason. + bool GetName(std::wstring* desktop_name_out) const; + + // Returns true if |other| has the same name as this desktop. Returns false + // in any other case including failing Win32 APIs and uninitialized desktop + // handles. + bool IsSame(const Desktop& other) const; + + // Assigns the desktop to the current thread. Returns false is the operation + // failed for any reason. + bool SetThreadDesktop() const; + + // Returns the desktop by its name or NULL if an error occurs. + static Desktop* GetDesktop(const wchar_t* desktop_name); + + // Returns the desktop currently receiving user input or NULL if an error + // occurs. + static Desktop* GetInputDesktop(); + + // Returns the desktop currently assigned to the calling thread or NULL if + // an error occurs. + static Desktop* GetThreadDesktop(); + + private: + Desktop(HDESK desktop, bool own); + + // The desktop handle. + HDESK desktop_; + + // True if |desktop_| must be closed on teardown. + bool own_; + + DISALLOW_COPY_AND_ASSIGN(Desktop); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DESKTOP_H_ diff --git a/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc new file mode 100644 index 0000000000..09648d7d32 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.cc @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" + +#include "webrtc/system_wrappers/interface/logging.h" + +#include "webrtc/modules/desktop_capture/win/desktop.h" + +namespace webrtc { + +ScopedThreadDesktop::ScopedThreadDesktop() + : initial_(Desktop::GetThreadDesktop()) { +} + +ScopedThreadDesktop::~ScopedThreadDesktop() { + Revert(); +} + +bool ScopedThreadDesktop::IsSame(const Desktop& desktop) { + if (assigned_.get() != NULL) { + return assigned_->IsSame(desktop); + } else { + return initial_->IsSame(desktop); + } +} + +void ScopedThreadDesktop::Revert() { + if (assigned_.get() != NULL) { + initial_->SetThreadDesktop(); + assigned_.reset(); + } +} + +bool ScopedThreadDesktop::SetThreadDesktop(Desktop* desktop) { + Revert(); + + scoped_ptr scoped_desktop(desktop); + + if (initial_->IsSame(*desktop)) + return true; + + if (!desktop->SetThreadDesktop()) + return false; + + assigned_.reset(scoped_desktop.release()); + return true; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h new file mode 100644 index 0000000000..39514237ea --- /dev/null +++ b/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ + +#include + +#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class Desktop; + +class ScopedThreadDesktop { + public: + ScopedThreadDesktop(); + ~ScopedThreadDesktop(); + + // Returns true if |desktop| has the same desktop name as the currently + // assigned desktop (if assigned) or as the initial desktop (if not assigned). + // Returns false in any other case including failing Win32 APIs and + // uninitialized desktop handles. + bool IsSame(const Desktop& desktop); + + // Reverts the calling thread to use the initial desktop. + void Revert(); + + // Assigns |desktop| to be the calling thread. Returns true if the thread has + // been switched to |desktop| successfully. Takes ownership of |desktop|. + bool SetThreadDesktop(Desktop* desktop); + + private: + // The desktop handle assigned to the calling thread by Set + scoped_ptr assigned_; + + // The desktop handle assigned to the calling thread at creation. + scoped_ptr initial_; + + DISALLOW_COPY_AND_ASSIGN(ScopedThreadDesktop); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_THREAD_DESKTOP_H_ diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc new file mode 100644 index 0000000000..2f2a80fa5d --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.cc @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h" + +#include +#include + +#include "webrtc/system_wrappers/interface/logging.h" + +#if defined(TOOLKIT_GTK) +#include +#else // !defined(TOOLKIT_GTK) +#include +#endif // !defined(TOOLKIT_GTK) + +namespace { + +#if defined(TOOLKIT_GTK) +// GDK sets error handler for Xlib errors, so we need to use it to +// trap X errors when this code is compiled with GTK. +void EnableXServerErrorTrap() { + gdk_error_trap_push(); +} + +int GetLastXServerError() { + return gdk_error_trap_pop(); +} + +#else // !defined(TOOLKIT_GTK) + +static bool g_xserver_error_trap_enabled = false; +static int g_last_xserver_error_code = 0; + +int XServerErrorHandler(Display* display, XErrorEvent* error_event) { + assert(g_xserver_error_trap_enabled); + g_last_xserver_error_code = error_event->error_code; + return 0; +} + +void EnableXServerErrorTrap() { + assert(!g_xserver_error_trap_enabled); + XSetErrorHandler(&XServerErrorHandler); + g_xserver_error_trap_enabled = true; + g_last_xserver_error_code = 0; +} + +int GetLastXServerError() { + assert(g_xserver_error_trap_enabled); + XSetErrorHandler(NULL); + g_xserver_error_trap_enabled = false; + return g_last_xserver_error_code; +} + +#endif // !defined(TOOLKIT_GTK) + +} // namespace + +namespace webrtc { + +XServerPixelBuffer::XServerPixelBuffer() + : display_(NULL), root_window_(0), + x_image_(NULL), + shm_segment_info_(NULL), shm_pixmap_(0), shm_gc_(NULL) { +} + +XServerPixelBuffer::~XServerPixelBuffer() { + Release(); +} + +void XServerPixelBuffer::Release() { + if (x_image_) { + XDestroyImage(x_image_); + x_image_ = NULL; + } + if (shm_pixmap_) { + XFreePixmap(display_, shm_pixmap_); + shm_pixmap_ = 0; + } + if (shm_gc_) { + XFreeGC(display_, shm_gc_); + shm_gc_ = NULL; + } + if (shm_segment_info_) { + if (shm_segment_info_->shmaddr != reinterpret_cast(-1)) + shmdt(shm_segment_info_->shmaddr); + if (shm_segment_info_->shmid != -1) + shmctl(shm_segment_info_->shmid, IPC_RMID, 0); + delete shm_segment_info_; + shm_segment_info_ = NULL; + } +} + +void XServerPixelBuffer::Init(Display* display, + const DesktopSize& screen_size) { + Release(); + display_ = display; + root_window_size_ = screen_size; + int default_screen = DefaultScreen(display_); + root_window_ = RootWindow(display_, default_screen); + InitShm(default_screen); +} + +// static +DesktopSize XServerPixelBuffer::GetRootWindowSize(Display* display) { + XWindowAttributes root_attr; + XGetWindowAttributes(display, DefaultRootWindow(display), &root_attr); + return DesktopSize(root_attr.width, root_attr.height); +} + +void XServerPixelBuffer::InitShm(int screen) { + Visual* default_visual = DefaultVisual(display_, screen); + int default_depth = DefaultDepth(display_, screen); + + int major, minor; + Bool havePixmaps; + if (!XShmQueryVersion(display_, &major, &minor, &havePixmaps)) + // Shared memory not supported. CaptureRect will use the XImage API instead. + return; + + bool using_shm = false; + shm_segment_info_ = new XShmSegmentInfo; + shm_segment_info_->shmid = -1; + shm_segment_info_->shmaddr = reinterpret_cast(-1); + shm_segment_info_->readOnly = False; + x_image_ = XShmCreateImage(display_, default_visual, default_depth, ZPixmap, + 0, shm_segment_info_, root_window_size_.width(), + root_window_size_.height()); + if (x_image_) { + shm_segment_info_->shmid = shmget( + IPC_PRIVATE, x_image_->bytes_per_line * x_image_->height, + IPC_CREAT | 0600); + if (shm_segment_info_->shmid != -1) { + shm_segment_info_->shmaddr = x_image_->data = + reinterpret_cast(shmat(shm_segment_info_->shmid, 0, 0)); + if (x_image_->data != reinterpret_cast(-1)) { + EnableXServerErrorTrap(); + using_shm = XShmAttach(display_, shm_segment_info_); + XSync(display_, False); + if (GetLastXServerError() != 0) + using_shm = false; + if (using_shm) { + LOG(LS_VERBOSE) << "Using X shared memory segment " + << shm_segment_info_->shmid; + } + } + } else { + LOG(LS_WARNING) << "Failed to get shared memory segment. " + "Performance may be degraded."; + } + } + + if (!using_shm) { + LOG(LS_WARNING) << "Not using shared memory. Performance may be degraded."; + Release(); + return; + } + + if (havePixmaps) + havePixmaps = InitPixmaps(default_depth); + + shmctl(shm_segment_info_->shmid, IPC_RMID, 0); + shm_segment_info_->shmid = -1; + + LOG(LS_VERBOSE) << "Using X shared memory extension v" + << major << "." << minor + << " with" << (havePixmaps ? "" : "out") << " pixmaps."; +} + +bool XServerPixelBuffer::InitPixmaps(int depth) { + if (XShmPixmapFormat(display_) != ZPixmap) + return false; + + EnableXServerErrorTrap(); + shm_pixmap_ = XShmCreatePixmap(display_, root_window_, + shm_segment_info_->shmaddr, + shm_segment_info_, + root_window_size_.width(), + root_window_size_.height(), depth); + XSync(display_, False); + if (GetLastXServerError() != 0) { + // |shm_pixmap_| is not not valid because the request was not processed + // by the X Server, so zero it. + shm_pixmap_ = 0; + return false; + } + + EnableXServerErrorTrap(); + XGCValues shm_gc_values; + shm_gc_values.subwindow_mode = IncludeInferiors; + shm_gc_values.graphics_exposures = False; + shm_gc_ = XCreateGC(display_, root_window_, + GCSubwindowMode | GCGraphicsExposures, + &shm_gc_values); + XSync(display_, False); + if (GetLastXServerError() != 0) { + XFreePixmap(display_, shm_pixmap_); + shm_pixmap_ = 0; + shm_gc_ = 0; // See shm_pixmap_ comment above. + return false; + } + + return true; +} + +void XServerPixelBuffer::Synchronize() { + if (shm_segment_info_ && !shm_pixmap_) { + // XShmGetImage can fail if the display is being reconfigured. + EnableXServerErrorTrap(); + XShmGetImage(display_, root_window_, x_image_, 0, 0, AllPlanes); + GetLastXServerError(); + } +} + +uint8_t* XServerPixelBuffer::CaptureRect(const DesktopRect& rect) { + assert(rect.right() <= root_window_size_.width()); + assert(rect.bottom() <= root_window_size_.height()); + + if (shm_segment_info_) { + if (shm_pixmap_) { + XCopyArea(display_, root_window_, shm_pixmap_, shm_gc_, + rect.left(), rect.top(), rect.width(), rect.height(), + rect.left(), rect.top()); + XSync(display_, False); + } + return reinterpret_cast(x_image_->data) + + rect.top() * x_image_->bytes_per_line + + rect.left() * x_image_->bits_per_pixel / 8; + } else { + if (x_image_) + XDestroyImage(x_image_); + x_image_ = XGetImage(display_, root_window_, rect.left(), rect.top(), + rect.width(), rect.height(), AllPlanes, ZPixmap); + return reinterpret_cast(x_image_->data); + } +} + +int XServerPixelBuffer::GetStride() const { + return x_image_->bytes_per_line; +} + +int XServerPixelBuffer::GetDepth() const { + return x_image_->depth; +} + +int XServerPixelBuffer::GetBitsPerPixel() const { + return x_image_->bits_per_pixel; +} + +int XServerPixelBuffer::GetRedMask() const { + return x_image_->red_mask; +} + +int XServerPixelBuffer::GetBlueMask() const { + return x_image_->blue_mask; +} + +int XServerPixelBuffer::GetGreenMask() const { + return x_image_->green_mask; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h new file mode 100644 index 0000000000..8759be329b --- /dev/null +++ b/webrtc/modules/desktop_capture/x11/x_server_pixel_buffer.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Don't include this file in any .h files because it pulls in some X headers. + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_ + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +#include +#include + +namespace webrtc { + +// A class to allow the X server's pixel buffer to be accessed as efficiently +// as possible. +class XServerPixelBuffer { + public: + XServerPixelBuffer(); + ~XServerPixelBuffer(); + + void Release(); + + // Allocate (or reallocate) the pixel buffer with the given size, which is + // assumed to be the current size of the root window. + // |screen_size| should either come from GetRootWindowSize(), or + // from a recent ConfigureNotify event on the root window. + void Init(Display* display, const DesktopSize& screen_size); + + // Request the current size of the root window from the X Server. + static DesktopSize GetRootWindowSize(Display* display); + + // If shared memory is being used without pixmaps, synchronize this pixel + // buffer with the root window contents (otherwise, this is a no-op). + // This is to avoid doing a full-screen capture for each individual + // rectangle in the capture list, when it only needs to be done once at the + // beginning. + void Synchronize(); + + // Capture the specified rectangle and return a pointer to its top-left pixel + // or NULL if capture fails. The returned pointer remains valid until the next + // call to CaptureRect. + // In the case where the full-screen data is captured by Synchronize(), this + // simply returns the pointer without doing any more work. + // The caller must ensure that |rect| is no larger than the screen size + // supplied to Init(). + uint8_t* CaptureRect(const DesktopRect& rect); + + // Return information about the most recent capture. This is only guaranteed + // to be valid between CaptureRect calls. + int GetStride() const; + int GetDepth() const; + int GetBitsPerPixel() const; + int GetRedMask() const; + int GetBlueMask() const; + int GetGreenMask() const; + + private: + void InitShm(int screen); + bool InitPixmaps(int depth); + + Display* display_; + Window root_window_; + DesktopSize root_window_size_; + XImage* x_image_; + XShmSegmentInfo* shm_segment_info_; + Pixmap shm_pixmap_; + GC shm_gc_; + + DISALLOW_COPY_AND_ASSIGN(XServerPixelBuffer); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_X11_X_SERVER_PIXEL_BUFFER_H_