summaryrefslogtreecommitdiffstats
path: root/media/video
diff options
context:
space:
mode:
authorsergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-28 23:54:03 +0000
committersergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-28 23:54:03 +0000
commit497d8a9857e7383f979b89694aff8dd403c176fe (patch)
tree2f07bb42d4a7a8e94e959c90dacbfa62eed1debd /media/video
parentbac4ae80a786d68bfc18ca46f0adfcc4a1c43b0f (diff)
downloadchromium_src-497d8a9857e7383f979b89694aff8dd403c176fe.zip
chromium_src-497d8a9857e7383f979b89694aff8dd403c176fe.tar.gz
chromium_src-497d8a9857e7383f979b89694aff8dd403c176fe.tar.bz2
Move screen capturers from remoting/capturer to media/video/capturer/screen
Screen capturers will be used in content, and so they have to be moved to avoid dependency on remoting/capturer in content. Beside moving the files this CL also renames classes as follows: remoting::VideoFrameCapturer -> media::ScreenCapturer remoting::VideoFrame -> media::ScreenCaptureFrame remoting::CaptureData -> media::ScreenCaptureData BUG=134249 Review URL: https://codereview.chromium.org/12047101 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@179218 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/video')
-rw-r--r--media/video/capture/screen/DEPS3
-rw-r--r--media/video/capture/screen/OWNERS3
-rw-r--r--media/video/capture/screen/differ.cc211
-rw-r--r--media/video/capture/screen/differ.h85
-rw-r--r--media/video/capture/screen/differ_block.cc49
-rw-r--r--media/video/capture/screen/differ_block.h28
-rw-r--r--media/video/capture/screen/differ_block_sse2.cc112
-rw-r--r--media/video/capture/screen/differ_block_sse2.h25
-rw-r--r--media/video/capture/screen/differ_block_unittest.cc81
-rw-r--r--media/video/capture/screen/differ_unittest.cc639
-rw-r--r--media/video/capture/screen/linux/x_server_pixel_buffer.cc275
-rw-r--r--media/video/capture/screen/linux/x_server_pixel_buffer.h82
-rw-r--r--media/video/capture/screen/mac/desktop_configuration.h63
-rw-r--r--media/video/capture/screen/mac/desktop_configuration.mm106
-rw-r--r--media/video/capture/screen/mac/scoped_pixel_buffer_object.cc47
-rw-r--r--media/video/capture/screen/mac/scoped_pixel_buffer_object.h33
-rw-r--r--media/video/capture/screen/mouse_cursor_shape.cc17
-rw-r--r--media/video/capture/screen/mouse_cursor_shape.h33
-rw-r--r--media/video/capture/screen/screen_capture_data.cc22
-rw-r--r--media/video/capture/screen/screen_capture_data.h93
-rw-r--r--media/video/capture/screen/screen_capture_frame.cc18
-rw-r--r--media/video/capture/screen/screen_capture_frame.h64
-rw-r--r--media/video/capture/screen/screen_capture_frame_queue.cc39
-rw-r--r--media/video/capture/screen/screen_capture_frame_queue.h69
-rw-r--r--media/video/capture/screen/screen_capturer.h116
-rw-r--r--media/video/capture/screen/screen_capturer_fake.cc118
-rw-r--r--media/video/capture/screen/screen_capturer_fake.h63
-rw-r--r--media/video/capture/screen/screen_capturer_helper.cc118
-rw-r--r--media/video/capture/screen/screen_capturer_helper.h84
-rw-r--r--media/video/capture/screen/screen_capturer_helper_unittest.cc215
-rw-r--r--media/video/capture/screen/screen_capturer_linux.cc617
-rw-r--r--media/video/capture/screen/screen_capturer_mac.mm871
-rw-r--r--media/video/capture/screen/screen_capturer_mac_unittest.cc121
-rw-r--r--media/video/capture/screen/screen_capturer_mock_objects.cc30
-rw-r--r--media/video/capture/screen/screen_capturer_mock_objects.h46
-rw-r--r--media/video/capture/screen/screen_capturer_unittest.cc106
-rw-r--r--media/video/capture/screen/screen_capturer_win.cc599
-rw-r--r--media/video/capture/screen/shared_buffer.cc42
-rw-r--r--media/video/capture/screen/shared_buffer.h75
-rw-r--r--media/video/capture/screen/shared_buffer_factory.h29
-rw-r--r--media/video/capture/screen/shared_buffer_unittest.cc55
-rw-r--r--media/video/capture/screen/win/desktop.cc104
-rw-r--r--media/video/capture/screen/win/desktop.h59
-rw-r--r--media/video/capture/screen/win/scoped_thread_desktop.cc49
-rw-r--r--media/video/capture/screen/win/scoped_thread_desktop.h48
45 files changed, 5762 insertions, 0 deletions
diff --git a/media/video/capture/screen/DEPS b/media/video/capture/screen/DEPS
new file mode 100644
index 0000000..2a3980b
--- /dev/null
+++ b/media/video/capture/screen/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+skia",
+]
diff --git a/media/video/capture/screen/OWNERS b/media/video/capture/screen/OWNERS
new file mode 100644
index 0000000..3276530
--- /dev/null
+++ b/media/video/capture/screen/OWNERS
@@ -0,0 +1,3 @@
+alexeypa@chromium.org
+sergeyu@chromium.org
+wez@chromium.org
diff --git a/media/video/capture/screen/differ.cc b/media/video/capture/screen/differ.cc
new file mode 100644
index 0000000..3296903
--- /dev/null
+++ b/media/video/capture/screen/differ.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/differ.h"
+
+#include "base/logging.h"
+#include "media/video/capture/screen/differ_block.h"
+
+namespace media {
+
+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,
+ SkRegion* region) {
+ if (!region) {
+ return;
+ }
+ region->setEmpty();
+
+ if (!prev_buffer || !curr_buffer) {
+ return;
+ }
+
+ // 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* prev_block_row_start = static_cast<const uint8*>(prev_buffer);
+ const uint8* curr_block_row_start = static_cast<const uint8*>(curr_buffer);
+ DiffInfo* diff_info_row_start = static_cast<DiffInfo*>(diff_info_.get());
+
+ for (int y = 0; y < y_full_blocks; y++) {
+ const uint8* prev_block = prev_block_row_start;
+ const uint8* 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* prev_block = prev_block_row_start;
+ const uint8* 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* prev_buffer,
+ const uint8* 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(SkRegion* region) {
+ DCHECK(region);
+ region->setEmpty();
+
+ uint8* diff_info_row_start = static_cast<uint8*>(diff_info_.get());
+ int diff_info_stride = diff_info_width_ * sizeof(DiffInfo);
+
+ for (int y = 0; y < diff_info_height_; y++) {
+ uint8* 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* 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* 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->op(SkIRect::MakeXYWH(left, top, width, height),
+ SkRegion::kUnion_Op);
+ }
+
+ // Increment to next block in this row.
+ diff_info++;
+ }
+
+ // Go to start of next row.
+ diff_info_row_start += diff_info_stride;
+ }
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/differ.h b/media/video/capture/screen/differ.h
new file mode 100644
index 0000000..898c27d
--- /dev/null
+++ b/media/video/capture/screen/differ.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+#include "third_party/skia/include/core/SkRegion.h"
+
+namespace media {
+
+typedef uint8 DiffInfo;
+
+// TODO: Simplify differ now that we are working with SkRegions.
+// 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 MEDIA_EXPORT 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,
+ SkRegion* 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(SkRegion* 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* prev_buffer, const uint8* 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<DiffInfo> 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 media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_H_
diff --git a/media/video/capture/screen/differ_block.cc b/media/video/capture/screen/differ_block.cc
new file mode 100644
index 0000000..4a1b44d
--- /dev/null
+++ b/media/video/capture/screen/differ_block.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/differ_block.h"
+
+#include "base/cpu.h"
+#include "build/build_config.h"
+#include "media/video/capture/screen/differ_block_sse2.h"
+
+namespace media {
+
+int BlockDifference_C(const uint8* image1, const uint8* 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* image1, const uint8* image2, int stride) {
+ static int (*diff_proc)(const uint8*, const uint8*, int) = NULL;
+
+ if (!diff_proc) {
+#if defined(ARCH_CPU_ARM_FAMILY)
+ // For ARM processors, always use C version.
+ // TODO(hclam): Implement a NEON version.
+ diff_proc = &BlockDifference_C;
+#else
+ base::CPU cpu;
+ // For x86 processors, check if SSE2 is supported.
+ if (cpu.has_sse2() && kBlockSize == 32) {
+ diff_proc = &BlockDifference_SSE2_W32;
+ } else if (cpu.has_sse2() && kBlockSize == 16) {
+ diff_proc = &BlockDifference_SSE2_W16;
+ } else {
+ diff_proc = &BlockDifference_C;
+ }
+#endif
+ }
+
+ return diff_proc(image1, image2, stride);
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/differ_block.h b/media/video/capture/screen/differ_block.h
new file mode 100644
index 0000000..8b211e2
--- /dev/null
+++ b/media/video/capture/screen/differ_block.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_BLOCK_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_BLOCK_H_
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// 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.
+MEDIA_EXPORT int BlockDifference(const uint8* image1,
+ const uint8* image2,
+ int stride);
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_BLOCK_H_
diff --git a/media/video/capture/screen/differ_block_sse2.cc b/media/video/capture/screen/differ_block_sse2.cc
new file mode 100644
index 0000000..abe7ced
--- /dev/null
+++ b/media/video/capture/screen/differ_block_sse2.cc
@@ -0,0 +1,112 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/differ_block_sse2.h"
+
+#if defined(_MSC_VER)
+#include <intrin.h>
+#else
+#include <mmintrin.h>
+#include <emmintrin.h>
+#endif
+
+#include "media/video/capture/screen/differ_block.h"
+
+namespace media {
+
+extern int BlockDifference_SSE2_W16(const uint8* image1, const uint8* 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<const __m128i*>(image1);
+ const __m128i* i2 = reinterpret_cast<const __m128i*>(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* image1, const uint8* 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<const __m128i*>(image1);
+ const __m128i* i2 = reinterpret_cast<const __m128i*>(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 media
diff --git a/media/video/capture/screen/differ_block_sse2.h b/media/video/capture/screen/differ_block_sse2.h
new file mode 100644
index 0000000..08d4d47
--- /dev/null
+++ b/media/video/capture/screen/differ_block_sse2.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This header file is used only differ_block.h. It defines the SSE2 rountines
+// for finding block difference.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_BLOCK_SSE2_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_BLOCK_SSE2_H_
+
+#include "base/basictypes.h"
+
+namespace media {
+
+// Find block difference of dimension 16x16.
+extern int BlockDifference_SSE2_W16(const uint8* image1, const uint8* image2,
+ int stride);
+
+// Find block difference of dimension 32x32.
+extern int BlockDifference_SSE2_W32(const uint8* image1, const uint8* image2,
+ int stride);
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_DIFFER_BLOCK_SSE2_H_
diff --git a/media/video/capture/screen/differ_block_unittest.cc b/media/video/capture/screen/differ_block_unittest.cc
new file mode 100644
index 0000000..45694a0
--- /dev/null
+++ b/media/video/capture/screen/differ_block_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/ref_counted.h"
+#include "media/video/capture/screen/differ_block.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+// Run 900 times to mimic 1280x720.
+// TODO(fbarchard): Remove benchmark once performance is non-issue.
+static const int kTimesToRun = 900;
+
+static void GenerateData(uint8* 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 block_buffer[kSizeOfBlock * 2 + 16];
+
+void PrepareBuffers(uint8* &block1, uint8* &block2) {
+ block1 = reinterpret_cast<uint8*>
+ ((reinterpret_cast<uintptr_t>(&block_buffer[0]) + 15) & ~15);
+ GenerateData(block1, kSizeOfBlock);
+ block2 = block1 + kSizeOfBlock;
+ memcpy(block2, block1, kSizeOfBlock);
+}
+
+TEST(BlockDifferenceTestSame, BlockDifference) {
+ uint8* block1;
+ uint8* 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* block1;
+ uint8* 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* block1;
+ uint8* 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* block1;
+ uint8* 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 media
diff --git a/media/video/capture/screen/differ_unittest.cc b/media/video/capture/screen/differ_unittest.cc
new file mode 100644
index 0000000..9b9b20f
--- /dev/null
+++ b/media/video/capture/screen/differ_unittest.cc
@@ -0,0 +1,639 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "media/video/capture/screen/differ.h"
+#include "media/video/capture/screen/differ_block.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+// 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[buffer_size_]);
+ memset(prev_.get(), 0, buffer_size_);
+
+ curr_.reset(new uint8[buffer_size_]);
+ memset(curr_.get(), 0, buffer_size_);
+ }
+
+ void ClearBuffer(uint8* 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(SkRegion* dirty) {
+ differ_->MergeBlocks(dirty);
+ }
+
+ // Convenience method to count rectangles in a region.
+ int RegionRectCount(const SkRegion& region) {
+ int count = 0;
+ for(SkRegion::Iterator iter(region); !iter.done(); iter.next()) {
+ ++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* buffer, int block_x, int block_y,
+ int pixel_x, int pixel_y, uint32 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* buffer, int x, int y, uint32 value) {
+ uint8* pixel = reinterpret_cast<uint8*>(&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 SkRegion& region, int x, int y,
+ int width, int height) {
+ SkIRect r = SkIRect::MakeXYWH(x * kBlockSize, y * kBlockSize,
+ width * kBlockSize, height * kBlockSize);
+ bool found = false;
+ for (SkRegion::Iterator i(region); !found && !i.done(); i.next()) {
+ found = (i.rect() == r);
+ }
+ return found;
+ }
+
+ // 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);
+
+ SkRegion dirty;
+ MergeBlocks(&dirty);
+
+ bool is_good = dirty.isRect();
+ if (is_good) {
+ is_good = CheckDirtyRegionContainsRect(dirty, x_origin, y_origin,
+ width, height);
+ }
+ return is_good;
+ }
+
+ // The differ class we're testing.
+ scoped_ptr<Differ> 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<uint8> prev_;
+ scoped_array<uint8> 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();
+
+ SkRegion dirty;
+ MergeBlocks(&dirty);
+
+ EXPECT_TRUE(dirty.isEmpty());
+}
+
+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);
+ SkRegion dirty;
+
+ // +---+---+---+---+ +---+---+---+
+ // | | X | | _ | | | 0 | |
+ // +---+---+---+---+ +---+---+---+
+ // | X | | | _ | | 1 | | |
+ // +---+---+---+---+ => +---+---+---+
+ // | | | X | _ | | | | 2 |
+ // +---+---+---+---+ +---+---+---+
+ // | _ | _ | _ | _ |
+ // +---+---+---+---+
+ ClearDiffInfo();
+ MarkBlocks(1, 0, 1, 1);
+ MarkBlocks(0, 1, 1, 1);
+ MarkBlocks(2, 2, 1, 1);
+
+ dirty.setEmpty();
+ 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.setEmpty();
+ 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.setEmpty();
+ 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.setEmpty();
+ 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.setEmpty();
+ MergeBlocks(&dirty);
+
+ ASSERT_EQ(2, RegionRectCount(dirty));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 0, 0, 2, 2));
+ ASSERT_TRUE(CheckDirtyRegionContainsRect(dirty, 1, 2, 1, 1));
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/linux/x_server_pixel_buffer.cc b/media/video/capture/screen/linux/x_server_pixel_buffer.cc
new file mode 100644
index 0000000..99d454c
--- /dev/null
+++ b/media/video/capture/screen/linux/x_server_pixel_buffer.cc
@@ -0,0 +1,275 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/linux/x_server_pixel_buffer.h"
+
+#include <sys/shm.h>
+
+#include "base/logging.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gdk/gdk.h>
+#else // !defined(TOOLKIT_GTK)
+#include <X11/Xlib.h>
+#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) {
+ DCHECK(g_xserver_error_trap_enabled);
+ g_last_xserver_error_code = error_event->error_code;
+ return 0;
+}
+
+void EnableXServerErrorTrap() {
+ DCHECK(!g_xserver_error_trap_enabled);
+ XSetErrorHandler(&XServerErrorHandler);
+ g_xserver_error_trap_enabled = true;
+ g_last_xserver_error_code = 0;
+}
+
+int GetLastXServerError() {
+ DCHECK(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 media {
+
+XServerPixelBuffer::XServerPixelBuffer()
+ : display_(NULL), root_window_(0),
+ root_window_size_(SkISize::Make(0, 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<char*>(-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 SkISize& 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
+SkISize XServerPixelBuffer::GetRootWindowSize(Display* display) {
+ XWindowAttributes root_attr;
+ XGetWindowAttributes(display, DefaultRootWindow(display), &root_attr);
+ return SkISize::Make(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<char*>(-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<char*>(shmat(shm_segment_info_->shmid, 0, 0));
+ if (x_image_->data != reinterpret_cast<char*>(-1)) {
+ EnableXServerErrorTrap();
+ using_shm = XShmAttach(display_, shm_segment_info_);
+ XSync(display_, False);
+ if (GetLastXServerError() != 0)
+ using_shm = false;
+ if (using_shm) {
+ VLOG(1) << "Using X shared memory segment "
+ << shm_segment_info_->shmid;
+ }
+ }
+ } else {
+ LOG(WARNING) << "Failed to get shared memory segment. "
+ "Performance may be degraded.";
+ }
+ }
+
+ if (!using_shm) {
+ LOG(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;
+
+ VLOG(1) << "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* XServerPixelBuffer::CaptureRect(const SkIRect& rect) {
+ DCHECK(SkIRect::MakeSize(root_window_size_).contains(rect));
+ if (shm_segment_info_) {
+ if (shm_pixmap_) {
+ XCopyArea(display_, root_window_, shm_pixmap_, shm_gc_,
+ rect.fLeft, rect.fTop, rect.width(), rect.height(),
+ rect.fLeft, rect.fTop);
+ XSync(display_, False);
+ }
+ return reinterpret_cast<uint8*>(x_image_->data) +
+ rect.fTop * x_image_->bytes_per_line +
+ rect.fLeft * x_image_->bits_per_pixel / 8;
+ } else {
+ if (x_image_)
+ XDestroyImage(x_image_);
+ x_image_ = XGetImage(display_, root_window_, rect.fLeft, rect.fTop,
+ rect.width(), rect.height(), AllPlanes, ZPixmap);
+ return reinterpret_cast<uint8*>(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;
+}
+
+int XServerPixelBuffer::GetRedShift() const {
+ return ffs(x_image_->red_mask) - 1;
+}
+
+int XServerPixelBuffer::GetBlueShift() const {
+ return ffs(x_image_->blue_mask) - 1;
+}
+
+int XServerPixelBuffer::GetGreenShift() const {
+ return ffs(x_image_->green_mask) - 1;
+}
+
+bool XServerPixelBuffer::IsRgb() const {
+ return GetRedShift() == 16 && GetGreenShift() == 8 && GetBlueShift() == 0;
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/linux/x_server_pixel_buffer.h b/media/video/capture/screen/linux/x_server_pixel_buffer.h
new file mode 100644
index 0000000..e4d2f4b
--- /dev/null
+++ b/media/video/capture/screen/linux/x_server_pixel_buffer.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Don't include this file in any .h files because it pulls in some X headers.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_LINUX_X_SERVER_PIXEL_BUFFER_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_LINUX_X_SERVER_PIXEL_BUFFER_H_
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkRect.h"
+
+#include <X11/Xutil.h>
+#include <X11/extensions/XShm.h>
+
+namespace media {
+
+// 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 SkISize& screen_size);
+
+ // Request the current size of the root window from the X Server.
+ static SkISize 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* CaptureRect(const SkIRect& 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;
+ int GetRedShift() const;
+ int GetBlueShift() const;
+ int GetGreenShift() const;
+ bool IsRgb() const;
+
+ private:
+ void InitShm(int screen);
+ bool InitPixmaps(int depth);
+
+ Display* display_;
+ Window root_window_;
+ SkISize root_window_size_;
+ XImage* x_image_;
+ XShmSegmentInfo* shm_segment_info_;
+ Pixmap shm_pixmap_;
+ GC shm_gc_;
+
+ DISALLOW_COPY_AND_ASSIGN(XServerPixelBuffer);
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_LINUX_X_SERVER_PIXEL_BUFFER_H_
diff --git a/media/video/capture/screen/mac/desktop_configuration.h b/media/video/capture/screen/mac/desktop_configuration.h
new file mode 100644
index 0000000..998f918
--- /dev/null
+++ b/media/video/capture/screen/mac/desktop_configuration.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_MAC_DESKTOP_CONFIGURATION_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_MAC_DESKTOP_CONFIGURATION_H_
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <Carbon/Carbon.h>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkPoint.h"
+#include "third_party/skia/include/core/SkRect.h"
+
+namespace media {
+
+// Describes the configuration of a specific display.
+struct MacDisplayConfiguration {
+ MacDisplayConfiguration();
+
+ // Returns the current configuration of the specified display.
+ static MacDisplayConfiguration ForDisplay(CGDirectDisplayID display_id);
+
+ // Cocoa identifier for this display.
+ CGDirectDisplayID id;
+
+ // Bounds of this display in Density-Independent Pixels (DIPs).
+ SkIRect bounds;
+
+ // Bounds of this display in physical pixels.
+ SkIRect pixel_bounds;
+
+ // Scale factor from DIPs to physical pixels.
+ float dip_to_pixel_scale;
+};
+
+typedef std::vector<MacDisplayConfiguration> MacDisplayConfigurations;
+
+// Describes the configuration of the whole desktop.
+struct MacDesktopConfiguration {
+ MacDesktopConfiguration();
+ ~MacDesktopConfiguration();
+
+ // Returns the current configuration of the desktop.
+ static MacDesktopConfiguration GetCurrent();
+
+ // Bounds of the desktop in Density-Independent Pixels (DIPs).
+ SkIRect bounds;
+
+ // Bounds of the desktop in physical pixels.
+ SkIRect 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 media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_MAC_DESKTOP_CONFIGURATION_H_
diff --git a/media/video/capture/screen/mac/desktop_configuration.mm b/media/video/capture/screen/mac/desktop_configuration.mm
new file mode 100644
index 0000000..064c4ef
--- /dev/null
+++ b/media/video/capture/screen/mac/desktop_configuration.mm
@@ -0,0 +1,106 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/mac/desktop_configuration.h"
+
+#include <Cocoa/Cocoa.h>
+
+#include "base/logging.h"
+#include "skia/ext/skia_utils_mac.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 media {
+
+MacDisplayConfiguration::MacDisplayConfiguration()
+ : id(0),
+ bounds(SkIRect::MakeEmpty()),
+ pixel_bounds(SkIRect::MakeEmpty()),
+ dip_to_pixel_scale(1.0f) {
+}
+
+static SkIRect NSRectToSkIRect(const NSRect& ns_rect) {
+ SkIRect result;
+ gfx::CGRectToSkRect(NSRectToCGRect(ns_rect)).roundOut(&result);
+ return result;
+}
+
+static 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<CGDirectDisplayID>(
+ [[device_description objectForKey:@"NSScreenNumber"] intValue]);
+
+ // Determine the display's logical & physical dimensions.
+ NSRect ns_bounds = [screen frame];
+ display_config.bounds = NSRectToSkIRect(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 = NSRectToSkIRect(ns_pixel_bounds);
+ } else {
+ display_config.pixel_bounds = display_config.bounds;
+ }
+
+ return display_config;
+}
+
+MacDesktopConfiguration::MacDesktopConfiguration()
+ : bounds(SkIRect::MakeEmpty()),
+ pixel_bounds(SkIRect::MakeEmpty()),
+ dip_to_pixel_scale(1.0f) {
+}
+
+MacDesktopConfiguration::~MacDesktopConfiguration() {
+}
+
+// static
+MacDesktopConfiguration MacDesktopConfiguration::GetCurrent() {
+ MacDesktopConfiguration desktop_config;
+
+ NSArray* screens = [NSScreen screens];
+ CHECK(screens != NULL);
+
+ // 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;
+ }
+
+ // 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.join(display_config.bounds);
+ desktop_config.pixel_bounds.join(display_config.pixel_bounds);
+ }
+
+ return desktop_config;
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/mac/scoped_pixel_buffer_object.cc b/media/video/capture/screen/mac/scoped_pixel_buffer_object.cc
new file mode 100644
index 0000000..29adf6f
--- /dev/null
+++ b/media/video/capture/screen/mac/scoped_pixel_buffer_object.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/mac/scoped_pixel_buffer_object.h"
+
+namespace media {
+
+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 media
diff --git a/media/video/capture/screen/mac/scoped_pixel_buffer_object.h b/media/video/capture/screen/mac/scoped_pixel_buffer_object.h
new file mode 100644
index 0000000..0c06278
--- /dev/null
+++ b/media/video/capture/screen/mac/scoped_pixel_buffer_object.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SCOPED_PIXEL_BUFFER_OBJECT_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SCOPED_PIXEL_BUFFER_OBJECT_H_
+
+#include <OpenGL/CGLMacro.h>
+#include <OpenGL/OpenGL.h>
+#include "base/basictypes.h"
+
+namespace media {
+
+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 media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SCOPED_PIXEL_BUFFER_OBJECT_H_
diff --git a/media/video/capture/screen/mouse_cursor_shape.cc b/media/video/capture/screen/mouse_cursor_shape.cc
new file mode 100644
index 0000000..f57f7fb
--- /dev/null
+++ b/media/video/capture/screen/mouse_cursor_shape.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/mouse_cursor_shape.h"
+
+namespace media {
+
+MouseCursorShape::MouseCursorShape()
+ : size(SkISize::Make(0, 0)),
+ hotspot(SkIPoint::Make(0, 0)) {
+}
+
+MouseCursorShape::~MouseCursorShape() {
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/mouse_cursor_shape.h b/media/video/capture/screen/mouse_cursor_shape.h
new file mode 100644
index 0000000..e2d57f1
--- /dev/null
+++ b/media/video/capture/screen/mouse_cursor_shape.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_MOUSE_CURSOR_SHAPE_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_MOUSE_CURSOR_SHAPE_H_
+
+#include <string>
+
+#include "media/base/media_export.h"
+#include "third_party/skia/include/core/SkPoint.h"
+#include "third_party/skia/include/core/SkSize.h"
+
+namespace media {
+
+// Type used to return mouse cursor shape from video capturers.
+struct MEDIA_EXPORT MouseCursorShape {
+ MouseCursorShape();
+ ~MouseCursorShape();
+
+ // Size of the cursor in screen pixels.
+ SkISize size;
+
+ // Coordinates of the cursor hotspot relative to upper-left corner.
+ SkIPoint hotspot;
+
+ // Cursor pixmap data in 32-bit BGRA format.
+ std::string data;
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_MOUSE_CURSOR_SHAPE_H_
diff --git a/media/video/capture/screen/screen_capture_data.cc b/media/video/capture/screen/screen_capture_data.cc
new file mode 100644
index 0000000..2869401
--- /dev/null
+++ b/media/video/capture/screen/screen_capture_data.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capture_data.h"
+
+namespace media {
+
+ScreenCaptureData::ScreenCaptureData(uint8* data,
+ int stride,
+ const SkISize& size)
+ : data_(data),
+ stride_(stride),
+ size_(size),
+ capture_time_ms_(0),
+ client_sequence_number_(0),
+ dpi_(SkIPoint::Make(0, 0)) {
+}
+
+ScreenCaptureData::~ScreenCaptureData() {}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capture_data.h b/media/video/capture/screen/screen_capture_data.h
new file mode 100644
index 0000000..c5e4a09
--- /dev/null
+++ b/media/video/capture/screen/screen_capture_data.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_CAPTURE_DATA_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_CAPTURE_DATA_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/media_export.h"
+#include "media/video/capture/screen/shared_buffer.h"
+#include "third_party/skia/include/core/SkRegion.h"
+
+namespace media {
+
+class SharedBuffer;
+
+// Stores the data and information of a capture to pass off to the
+// encoding thread.
+class MEDIA_EXPORT ScreenCaptureData
+ : public base::RefCountedThreadSafe<ScreenCaptureData> {
+ public:
+ // 32 bit RGB is 4 bytes per pixel.
+ static const int kBytesPerPixel = 4;
+
+ ScreenCaptureData(uint8* data, int stride, const SkISize& size);
+
+ // Data buffer.
+ uint8* data() const { return data_; }
+
+ // Distance in bytes between neighboring lines in the data buffer.
+ int stride() const { return stride_; }
+
+ // Gets the dirty region from the previous capture.
+ const SkRegion& dirty_region() const { return dirty_region_; }
+
+ // Returns the size of the image captured.
+ SkISize size() const { return size_; }
+
+ SkRegion& mutable_dirty_region() { return dirty_region_; }
+
+ // Returns the time spent on capturing.
+ int capture_time_ms() const { return capture_time_ms_; }
+
+ // Sets the time spent on capturing.
+ void set_capture_time_ms(int capture_time_ms) {
+ capture_time_ms_ = capture_time_ms;
+ }
+
+ int64 client_sequence_number() const { return client_sequence_number_; }
+
+ void set_client_sequence_number(int64 client_sequence_number) {
+ client_sequence_number_ = client_sequence_number;
+ }
+
+ SkIPoint dpi() const { return dpi_; }
+
+ void set_dpi(const SkIPoint& dpi) { dpi_ = dpi; }
+
+ // Returns the shared memory buffer pointed to by |data|.
+ scoped_refptr<SharedBuffer> shared_buffer() const { return shared_buffer_; }
+
+ // Sets the shared memory buffer pointed to by |data|.
+ void set_shared_buffer(scoped_refptr<SharedBuffer> shared_buffer) {
+ shared_buffer_ = shared_buffer;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<ScreenCaptureData>;
+ virtual ~ScreenCaptureData();
+
+ uint8* data_;
+ int stride_;
+ SkRegion dirty_region_;
+ SkISize size_;
+
+ // Time spent in capture. Unit is in milliseconds.
+ int capture_time_ms_;
+
+ // Sequence number supplied by client for performance tracking.
+ int64 client_sequence_number_;
+
+ // DPI for this frame.
+ SkIPoint dpi_;
+
+ scoped_refptr<SharedBuffer> shared_buffer_;
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_CAPTURE_DATA_H_
diff --git a/media/video/capture/screen/screen_capture_frame.cc b/media/video/capture/screen/screen_capture_frame.cc
new file mode 100644
index 0000000..4da28eb
--- /dev/null
+++ b/media/video/capture/screen/screen_capture_frame.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capture_frame.h"
+
+namespace media {
+
+ScreenCaptureFrame::~ScreenCaptureFrame() {
+}
+
+ScreenCaptureFrame::ScreenCaptureFrame()
+ : bytes_per_row_(0),
+ dimensions_(SkISize::Make(0, 0)),
+ pixels_(NULL) {
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capture_frame.h b/media/video/capture/screen/screen_capture_frame.h
new file mode 100644
index 0000000..090501e
--- /dev/null
+++ b/media/video/capture/screen/screen_capture_frame.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURE_FRAME_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURE_FRAME_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/media_export.h"
+#include "media/video/capture/screen/shared_buffer.h"
+#include "third_party/skia/include/core/SkSize.h"
+#include "third_party/skia/include/core/SkTypes.h"
+
+namespace media {
+
+// Represents a video frame.
+class MEDIA_EXPORT ScreenCaptureFrame {
+ public:
+ virtual ~ScreenCaptureFrame();
+
+ int bytes_per_row() const { return bytes_per_row_; }
+ const SkISize& dimensions() const { return dimensions_; }
+ uint8* pixels() const { return pixels_; }
+ const scoped_refptr<SharedBuffer>& shared_buffer() const {
+ return shared_buffer_;
+ }
+
+ protected:
+ // Initializes an empty video frame. Derived classes are expected to allocate
+ // memory for the frame in a platform-specific way and set the properties of
+ // the allocated frame.
+ ScreenCaptureFrame();
+
+ void set_bytes_per_row(int bytes_per_row) {
+ bytes_per_row_ = bytes_per_row;
+ }
+
+ void set_dimensions(const SkISize& dimensions) { dimensions_ = dimensions; }
+ void set_pixels(uint8* ptr) { pixels_ = ptr; }
+ void set_shared_buffer(scoped_refptr<SharedBuffer> shared_buffer) {
+ shared_buffer_ = shared_buffer;
+ }
+
+ private:
+ // Bytes per row of pixels including necessary padding.
+ int bytes_per_row_;
+
+ // Dimensions of the buffer in pixels.
+ SkISize dimensions_;
+
+ // Points to the pixel buffer.
+ uint8* pixels_;
+
+ // Points to an optional shared memory buffer that backs up |pixels_| buffer.
+ scoped_refptr<SharedBuffer> shared_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureFrame);
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURE_FRAME_H_
diff --git a/media/video/capture/screen/screen_capture_frame_queue.cc b/media/video/capture/screen/screen_capture_frame_queue.cc
new file mode 100644
index 0000000..610a062
--- /dev/null
+++ b/media/video/capture/screen/screen_capture_frame_queue.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capture_frame_queue.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "media/video/capture/screen/screen_capture_frame.h"
+
+namespace media {
+
+ScreenCaptureFrameQueue::ScreenCaptureFrameQueue()
+ : current_(0),
+ previous_(NULL) {
+ SetAllFramesNeedUpdate();
+}
+
+ScreenCaptureFrameQueue::~ScreenCaptureFrameQueue() {
+}
+
+void ScreenCaptureFrameQueue::DoneWithCurrentFrame() {
+ previous_ = current_frame();
+ current_ = (current_ + 1) % kQueueLength;
+}
+
+void ScreenCaptureFrameQueue::ReplaceCurrentFrame(
+ scoped_ptr<ScreenCaptureFrame> frame) {
+ frames_[current_] = frame.Pass();
+ needs_update_[current_] = false;
+}
+
+void ScreenCaptureFrameQueue::SetAllFramesNeedUpdate() {
+ std::fill(needs_update_, needs_update_ + arraysize(needs_update_), true);
+ previous_ = NULL;
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capture_frame_queue.h b/media/video/capture/screen/screen_capture_frame_queue.h
new file mode 100644
index 0000000..d36f15a
--- /dev/null
+++ b/media/video/capture/screen/screen_capture_frame_queue.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURE_FRAME_QUEUE_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURE_FRAME_QUEUE_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace media {
+
+class ScreenCaptureFrame;
+
+// 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
+// DoneWithCurrentFrame() call, if any.
+//
+// The caller is expected to (re)allocate frames if current_frame_needs_update()
+// is set. 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.
+class ScreenCaptureFrameQueue {
+ public:
+ ScreenCaptureFrameQueue();
+ ~ScreenCaptureFrameQueue();
+
+ // Moves to the next frame in the queue, moving the 'current' frame to become
+ // the 'previous' one.
+ void DoneWithCurrentFrame();
+
+ // Replaces the current frame with a new one allocated by the caller.
+ // The existing frame (if any) is destroyed.
+ void ReplaceCurrentFrame(scoped_ptr<ScreenCaptureFrame> frame);
+
+ // Marks all frames obsolete and resets the previous frame pointer. No
+ // frames are freed though as the caller can still access them.
+ void SetAllFramesNeedUpdate();
+
+ ScreenCaptureFrame* current_frame() const {
+ return frames_[current_].get();
+ }
+
+ bool current_frame_needs_update() const {
+ return !current_frame() || needs_update_[current_];
+ }
+
+ ScreenCaptureFrame* previous_frame() const { return previous_; }
+
+ private:
+ // Index of the current frame.
+ int current_;
+
+ static const int kQueueLength = 2;
+ scoped_ptr<ScreenCaptureFrame> frames_[kQueueLength];
+
+ // True if the corresponding frame needs to be re-allocated.
+ bool needs_update_[kQueueLength];
+
+ // Points to the previous frame if any.
+ ScreenCaptureFrame* previous_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureFrameQueue);
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURE_FRAME_QUEUE_H_
diff --git a/media/video/capture/screen/screen_capturer.h b/media/video/capture/screen/screen_capturer.h
new file mode 100644
index 0000000..0d34871
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/shared_memory.h"
+#include "media/base/media_export.h"
+#include "third_party/skia/include/core/SkRegion.h"
+
+namespace media {
+
+class ScreenCaptureData;
+struct MouseCursorShape;
+class SharedBufferFactory;
+
+// 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) InvalidateRegion
+// This is an optional step where regions of the screen are marked as
+// invalid. Some platforms (Windows, for now) won't use this and will
+// instead calculate the diff-regions later (in step (2). Other
+// platforms (Mac) will use this to mark all the changed regions of the
+// screen. Some limited rect-merging (e.g., to eliminate exact
+// duplicates) may be done here.
+//
+// (3) 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.
+//
+// (4) Stop
+// This is when post-capture steps are executed, such as releasing the
+// assertion that prevents the display from sleeping.
+//
+// Implementation has to ensure the following guarantees:
+// 1. Double buffering
+// Since data can be read while another capture action is happening.
+class MEDIA_EXPORT ScreenCapturer {
+ public:
+ // Provides callbacks used by the capturer to pass captured video frames and
+ // mouse cursor shapes to the processing pipeline.
+ class Delegate {
+ public:
+ // Called when a video frame has been captured. |capture_data| describes
+ // a captured frame.
+ virtual void OnCaptureCompleted(
+ scoped_refptr<ScreenCaptureData> capture_data) = 0;
+
+ // Called when the cursor shape has changed.
+ // TODO(sergeyu): Move cursor shape capturing to a separate class.
+ virtual void OnCursorShapeChanged(
+ scoped_ptr<MouseCursorShape> cursor_shape) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ virtual ~ScreenCapturer() {}
+
+ // Create platform-specific capturer.
+ static scoped_ptr<ScreenCapturer> Create();
+
+ // Create platform-specific capturer that uses shared memory buffers.
+ static scoped_ptr<ScreenCapturer> CreateWithFactory(
+ SharedBufferFactory* shared_buffer_factory);
+
+#if defined(OS_LINUX)
+ // Set whether the ScreenCapturer should try to use X DAMAGE support if it
+ // is available. This needs to be called before the ScreenCapturer is
+ // created.
+ // This is used by the Virtual Me2Me host, since the XDamage extension is
+ // known to work reliably in this case.
+
+ // TODO(lambroslambrou): This currently sets a global flag, referenced during
+ // ScreenCapturer::Create(). This is a temporary solution, until the
+ // DesktopEnvironment class is refactored to allow applications to control
+ // the creation of various stubs (including the ScreenCapturer) - see
+ // http://crbug.com/104544
+ static void EnableXDamage(bool enable);
+#endif // defined(OS_LINUX)
+
+ // Called at the beginning of a capturing session. |delegate| must remain
+ // valid until Stop() is called.
+ virtual void Start(Delegate* delegate) = 0;
+
+ // Called at the end of a capturing session.
+ virtual void Stop() = 0;
+
+ // Invalidates the specified region.
+ virtual void InvalidateRegion(const SkRegion& invalid_region) = 0;
+
+ // Captures the screen data associated with each of the accumulated
+ // dirty region. When the capture is complete, the delegate is notified even
+ // if the dirty region is empty.
+ //
+ // It is OK to call this method while another thread is reading
+ // data of the previous capture. There can be at most one concurrent read
+ // going on when this method is called.
+ virtual void CaptureFrame() = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_H_
diff --git a/media/video/capture/screen/screen_capturer_fake.cc b/media/video/capture/screen/screen_capturer_fake.cc
new file mode 100644
index 0000000..d069d54
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_fake.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer_fake.h"
+
+#include "base/time.h"
+#include "media/video/capture/screen/screen_capture_data.h"
+
+namespace media {
+
+// 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;
+
+COMPILE_ASSERT(kBoxWidth < kWidth && kBoxHeight < kHeight, bad_box_size);
+COMPILE_ASSERT((kBoxWidth % kSpeed == 0) && (kWidth % kSpeed == 0) &&
+ (kBoxHeight % kSpeed == 0) && (kHeight % kSpeed == 0),
+ sizes_must_be_multiple_of_kSpeed);
+
+ScreenCapturerFake::ScreenCapturerFake()
+ : size_(SkISize::Make(0, 0)),
+ bytes_per_row_(0),
+ box_pos_x_(0),
+ box_pos_y_(0),
+ box_speed_x_(kSpeed),
+ box_speed_y_(kSpeed),
+ current_buffer_(0) {
+ ScreenConfigurationChanged();
+}
+
+ScreenCapturerFake::~ScreenCapturerFake() {
+}
+
+void ScreenCapturerFake::Start(Delegate* delegate) {
+ delegate_ = delegate;
+}
+
+void ScreenCapturerFake::Stop() {
+}
+
+void ScreenCapturerFake::InvalidateRegion(const SkRegion& invalid_region) {
+ helper_.InvalidateRegion(invalid_region);
+}
+
+void ScreenCapturerFake::CaptureFrame() {
+ base::Time capture_start_time = base::Time::Now();
+
+ GenerateImage();
+ helper_.InvalidateScreen(size_);
+
+ SkRegion invalid_region;
+ helper_.SwapInvalidRegion(&invalid_region);
+
+ current_buffer_ = (current_buffer_ + 1) % kNumBuffers;
+
+ scoped_refptr<ScreenCaptureData> capture_data(new ScreenCaptureData(
+ buffers_[current_buffer_].get(), bytes_per_row_, size_));
+ capture_data->mutable_dirty_region() = invalid_region;
+
+ helper_.set_size_most_recent(capture_data->size());
+
+ capture_data->set_capture_time_ms(
+ (base::Time::Now() - capture_start_time).InMillisecondsRoundedUp());
+ delegate_->OnCaptureCompleted(capture_data);
+}
+
+void ScreenCapturerFake::GenerateImage() {
+ memset(buffers_[current_buffer_].get(), 0xff,
+ size_.width() * size_.height() * ScreenCaptureData::kBytesPerPixel);
+
+ uint8* row = buffers_[current_buffer_].get() +
+ (box_pos_y_ * size_.width() + box_pos_x_) *
+ ScreenCaptureData::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 * ScreenCaptureData::kBytesPerPixel] = r;
+ row[x * ScreenCaptureData::kBytesPerPixel + 1] = g;
+ row[x * ScreenCaptureData::kBytesPerPixel + 2] = b;
+ row[x * ScreenCaptureData::kBytesPerPixel + 3] = 0xff;
+ }
+ row += bytes_per_row_;
+ }
+}
+
+void ScreenCapturerFake::ScreenConfigurationChanged() {
+ size_ = SkISize::Make(kWidth, kHeight);
+ bytes_per_row_ = size_.width() * ScreenCaptureData::kBytesPerPixel;
+
+ // Create memory for the buffers.
+ int buffer_size = size_.height() * bytes_per_row_;
+ for (int i = 0; i < kNumBuffers; i++) {
+ buffers_[i].reset(new uint8[buffer_size]);
+ }
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capturer_fake.h b/media/video/capture/screen/screen_capturer_fake.h
new file mode 100644
index 0000000..11daad4
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_fake.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_FAKE_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_FAKE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+#include "media/video/capture/screen/screen_capturer.h"
+#include "media/video/capture/screen/screen_capturer_helper.h"
+
+namespace media {
+
+// A ScreenCapturerFake generates artificial image for testing purpose.
+//
+// ScreenCapturerFake is double-buffered as required by ScreenCapturer.
+class MEDIA_EXPORT 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();
+
+ // Overridden from ScreenCapturer:
+ virtual void Start(Delegate* delegate) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE;
+ virtual void CaptureFrame() OVERRIDE;
+
+ private:
+ // Generates an image in the front buffer.
+ void GenerateImage();
+
+ // Called when the screen configuration is changed.
+ void ScreenConfigurationChanged();
+
+ Delegate* delegate_;
+
+ SkISize size_;
+ int bytes_per_row_;
+ int box_pos_x_;
+ int box_pos_y_;
+ int box_speed_x_;
+ int box_speed_y_;
+
+ ScreenCapturerHelper helper_;
+
+ // We have two buffers for the screen images as required by Capturer.
+ static const int kNumBuffers = 2;
+ scoped_array<uint8> buffers_[kNumBuffers];
+
+ // The current buffer with valid data for reading.
+ int current_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCapturerFake);
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_FAKE_H_
diff --git a/media/video/capture/screen/screen_capturer_helper.cc b/media/video/capture/screen/screen_capturer_helper.cc
new file mode 100644
index 0000000..afc8208
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_helper.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer_helper.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace media {
+
+ScreenCapturerHelper::ScreenCapturerHelper() :
+ size_most_recent_(SkISize::Make(0, 0)),
+ log_grid_size_(0) {
+}
+
+ScreenCapturerHelper::~ScreenCapturerHelper() {
+}
+
+void ScreenCapturerHelper::ClearInvalidRegion() {
+ base::AutoLock auto_invalid_region_lock(invalid_region_lock_);
+ invalid_region_.setEmpty();
+}
+
+void ScreenCapturerHelper::InvalidateRegion(
+ const SkRegion& invalid_region) {
+ base::AutoLock auto_invalid_region_lock(invalid_region_lock_);
+ invalid_region_.op(invalid_region, SkRegion::kUnion_Op);
+}
+
+void ScreenCapturerHelper::InvalidateScreen(const SkISize& size) {
+ base::AutoLock auto_invalid_region_lock(invalid_region_lock_);
+ invalid_region_.op(SkIRect::MakeWH(size.width(), size.height()),
+ SkRegion::kUnion_Op);
+}
+
+void ScreenCapturerHelper::InvalidateFullScreen() {
+ if (!size_most_recent_.isZero())
+ InvalidateScreen(size_most_recent_);
+}
+
+void ScreenCapturerHelper::SwapInvalidRegion(SkRegion* invalid_region) {
+ {
+ base::AutoLock auto_invalid_region_lock(invalid_region_lock_);
+ invalid_region->swap(invalid_region_);
+ }
+ if (log_grid_size_ > 0) {
+ scoped_ptr<SkRegion> expanded_region(
+ ExpandToGrid(*invalid_region, log_grid_size_));
+ invalid_region->swap(*expanded_region);
+ invalid_region->op(SkRegion(SkIRect::MakeSize(size_most_recent_)),
+ SkRegion::kIntersect_Op);
+ }
+}
+
+void ScreenCapturerHelper::SetLogGridSize(int log_grid_size) {
+ log_grid_size_ = log_grid_size;
+}
+
+const SkISize& ScreenCapturerHelper::size_most_recent() const {
+ return size_most_recent_;
+}
+
+void ScreenCapturerHelper::set_size_most_recent(const SkISize& 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);
+}
+
+scoped_ptr<SkRegion> ScreenCapturerHelper::ExpandToGrid(
+ const SkRegion& region,
+ int log_grid_size) {
+ DCHECK(log_grid_size >= 1);
+ int grid_size = 1 << log_grid_size;
+ int grid_size_mask = ~(grid_size - 1);
+ // Count the rects in the region.
+ int rectNum = 0;
+ SkRegion::Iterator iter(region);
+ while (!iter.done()) {
+ iter.next();
+ ++rectNum;
+ }
+ // Expand each rect.
+ scoped_array<SkIRect> rects(new SkIRect[rectNum]);
+ iter.rewind();
+ int rectI = 0;
+ while (!iter.done()) {
+ SkIRect rect = iter.rect();
+ iter.next();
+ int left = std::min(rect.left(), rect.right());
+ int right = std::max(rect.left(), rect.right());
+ int top = std::min(rect.top(), rect.bottom());
+ int bottom = std::max(rect.top(), rect.bottom());
+ left = DownToMultiple(left, grid_size_mask);
+ right = UpToMultiple(right, grid_size, grid_size_mask);
+ top = DownToMultiple(top, grid_size_mask);
+ bottom = UpToMultiple(bottom, grid_size, grid_size_mask);
+ rects[rectI++] = SkIRect::MakeLTRB(left, top, right, bottom);
+ }
+ // Make the union of the expanded rects.
+ scoped_ptr<SkRegion> regionNew(new SkRegion());
+ regionNew->setRects(rects.get(), rectNum);
+ return regionNew.Pass();
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capturer_helper.h b/media/video/capture/screen/screen_capturer_helper.h
new file mode 100644
index 0000000..7b5e27e
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_helper.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_HELPER_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_HELPER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "media/base/media_export.h"
+#include "third_party/skia/include/core/SkRegion.h"
+
+namespace media {
+
+// 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 MEDIA_EXPORT ScreenCapturerHelper {
+ public:
+ ScreenCapturerHelper();
+ ~ScreenCapturerHelper();
+
+ // Clear out the invalid region.
+ void ClearInvalidRegion();
+
+ // Invalidate the specified region.
+ void InvalidateRegion(const SkRegion& invalid_region);
+
+ // Invalidate the entire screen, of a given size.
+ void InvalidateScreen(const SkISize& size);
+
+ // Invalidate the entire screen, using the size of the most recently
+ // captured screen.
+ void InvalidateFullScreen();
+
+ // Swap the given region with the stored invalid region.
+ void SwapInvalidRegion(SkRegion* invalid_region);
+
+ // Access the size of the most recently captured screen.
+ const SkISize& size_most_recent() const;
+ void set_size_most_recent(const SkISize& 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 SwapInvalidRegion
+ // 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 scoped_ptr<SkRegion> ExpandToGrid(const SkRegion& region,
+ int log_grid_size);
+
+ 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.
+ SkRegion invalid_region_;
+
+ // A lock protecting |invalid_region_| across threads.
+ base::Lock invalid_region_lock_;
+
+ // The size of the most recently captured screen.
+ SkISize 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 media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_HELPER_H_
diff --git a/media/video/capture/screen/screen_capturer_helper_unittest.cc b/media/video/capture/screen/screen_capturer_helper_unittest.cc
new file mode 100644
index 0000000..dc22231
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_helper_unittest.cc
@@ -0,0 +1,215 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer_helper.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class ScreenCapturerHelperTest : public testing::Test {
+ protected:
+ ScreenCapturerHelper capturer_helper_;
+};
+
+bool Equals(const SkRegion& region1, const SkRegion& region2) {
+ SkRegion::Iterator iter1(region1);
+ SkRegion::Iterator iter2(region2);
+ while (!iter1.done()) {
+ SkIRect rect1 = iter1.rect();
+ iter1.next();
+ if (iter2.done()) {
+ return false;
+ }
+ SkIRect rect2 = iter2.rect();
+ iter2.next();
+ if (rect1 != rect2) {
+ return false;
+ }
+ }
+ if (!iter2.done()) {
+ return false;
+ }
+ return true;
+}
+
+TEST_F(ScreenCapturerHelperTest, ClearInvalidRegion) {
+ SkRegion region;
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(1, 2, 3, 4)));
+ capturer_helper_.ClearInvalidRegion();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(region.isEmpty());
+}
+
+TEST_F(ScreenCapturerHelperTest, InvalidateRegion) {
+ SkRegion region;
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeEmpty()), region));
+
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(1, 2, 3, 4)));
+ region.setEmpty();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeXYWH(1, 2, 3, 4)), region));
+
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(1, 2, 3, 4)));
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(4, 2, 3, 4)));
+ region.setEmpty();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeXYWH(1, 2, 6, 4)), region));
+}
+
+TEST_F(ScreenCapturerHelperTest, InvalidateScreen) {
+ SkRegion region;
+ capturer_helper_.InvalidateScreen(SkISize::Make(12, 34));
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeWH(12, 34)), region));
+}
+
+TEST_F(ScreenCapturerHelperTest, InvalidateFullScreen) {
+ SkRegion region;
+ capturer_helper_.set_size_most_recent(SkISize::Make(12, 34));
+ capturer_helper_.InvalidateFullScreen();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeWH(12, 34)), region));
+}
+
+TEST_F(ScreenCapturerHelperTest, SizeMostRecent) {
+ ASSERT_EQ(SkISize::Make(0, 0), capturer_helper_.size_most_recent());
+ capturer_helper_.set_size_most_recent(SkISize::Make(12, 34));
+ ASSERT_EQ(SkISize::Make(12, 34), capturer_helper_.size_most_recent());
+}
+
+TEST_F(ScreenCapturerHelperTest, SetLogGridSize) {
+ capturer_helper_.set_size_most_recent(SkISize::Make(10, 10));
+
+ SkRegion region;
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeEmpty()), region));
+
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)));
+ region.setEmpty();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)), region));
+
+ capturer_helper_.SetLogGridSize(-1);
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)));
+ region.setEmpty();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)), region));
+
+ capturer_helper_.SetLogGridSize(0);
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)));
+ region.setEmpty();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)), region));
+
+ capturer_helper_.SetLogGridSize(1);
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)));
+ region.setEmpty();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeXYWH(6, 6, 2, 2)), region));
+
+ capturer_helper_.SetLogGridSize(2);
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)));
+ region.setEmpty();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeXYWH(4, 4, 4, 4)), region));
+
+ capturer_helper_.SetLogGridSize(0);
+ capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)));
+ region.setEmpty();
+ capturer_helper_.SwapInvalidRegion(&region);
+ ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1)), region));
+}
+
+void TestExpandRegionToGrid(const SkRegion& region, int log_grid_size,
+ const SkRegion& expandedRegionExpected) {
+ scoped_ptr<SkRegion> expandedRegion1(
+ ScreenCapturerHelper::ExpandToGrid(region, log_grid_size));
+ ASSERT_TRUE(Equals(expandedRegionExpected, *expandedRegion1));
+ scoped_ptr<SkRegion> expandedRegion2(
+ ScreenCapturerHelper::ExpandToGrid(*expandedRegion1, log_grid_size));
+ ASSERT_TRUE(Equals(*expandedRegion1, *expandedRegion2));
+}
+
+void TestExpandRectToGrid(int l, int t, int r, int b, int log_grid_size,
+ int lExpanded, int tExpanded,
+ int rExpanded, int bExpanded) {
+ TestExpandRegionToGrid(SkRegion(SkIRect::MakeLTRB(l, t, r, b)), log_grid_size,
+ SkRegion(SkIRect::MakeLTRB(lExpanded, tExpanded,
+ rExpanded, bExpanded)));
+}
+
+TEST_F(ScreenCapturerHelperTest, ExpandToGrid) {
+ const int LOG_GRID_SIZE = 4;
+ const int GRID_SIZE = 1 << LOG_GRID_SIZE;
+ for (int i = -2; i <= 2; i++) {
+ int x = i * GRID_SIZE;
+ for (int j = -2; j <= 2; j++) {
+ int y = j * GRID_SIZE;
+ TestExpandRectToGrid(x + 0, y + 0, x + 1, y + 1, LOG_GRID_SIZE,
+ x + 0, y + 0, x + GRID_SIZE, y + GRID_SIZE);
+ TestExpandRectToGrid(x + 0, y + GRID_SIZE - 1, x + 1, y + GRID_SIZE,
+ LOG_GRID_SIZE,
+ x + 0, y + 0, x + GRID_SIZE, y + GRID_SIZE);
+ TestExpandRectToGrid(x + GRID_SIZE - 1, y + GRID_SIZE - 1,
+ x + GRID_SIZE, y + GRID_SIZE, LOG_GRID_SIZE,
+ x + 0, y + 0, x + GRID_SIZE, y + GRID_SIZE);
+ TestExpandRectToGrid(x + GRID_SIZE - 1, y + 0,
+ x + GRID_SIZE, y + 1, LOG_GRID_SIZE,
+ x + 0, y + 0, x + GRID_SIZE, y + GRID_SIZE);
+ TestExpandRectToGrid(x - 1, y + 0, x + 1, y + 1, LOG_GRID_SIZE,
+ x - GRID_SIZE, y + 0, x + GRID_SIZE, y + GRID_SIZE);
+ TestExpandRectToGrid(x - 1, y - 1, x + 1, y + 0, LOG_GRID_SIZE,
+ x - GRID_SIZE, y - GRID_SIZE, x + GRID_SIZE, y);
+ TestExpandRectToGrid(x + 0, y - 1, x + 1, y + 1, LOG_GRID_SIZE,
+ x, y - GRID_SIZE, x + GRID_SIZE, y + GRID_SIZE);
+ TestExpandRectToGrid(x - 1, y - 1, x + 0, y + 1, LOG_GRID_SIZE,
+ x - GRID_SIZE, y - GRID_SIZE, x, y + GRID_SIZE);
+
+ SkRegion region(SkIRect::MakeLTRB(x - 1, y - 1, x + 1, y + 1));
+ region.op(SkIRect::MakeLTRB(x - 1, y - 1, x + 0, y + 0),
+ SkRegion::kDifference_Op);
+ SkRegion expandedRegionExpected(SkIRect::MakeLTRB(
+ x - GRID_SIZE, y - GRID_SIZE, x + GRID_SIZE, y + GRID_SIZE));
+ expandedRegionExpected.op(
+ SkIRect::MakeLTRB(x - GRID_SIZE, y - GRID_SIZE, x + 0, y + 0),
+ SkRegion::kDifference_Op);
+ TestExpandRegionToGrid(region, LOG_GRID_SIZE, expandedRegionExpected);
+
+ region.setRect(SkIRect::MakeLTRB(x - 1, y - 1, x + 1, y + 1));
+ region.op(SkIRect::MakeLTRB(x - 1, y + 0, x + 0, y + 1),
+ SkRegion::kDifference_Op);
+ expandedRegionExpected.setRect(SkIRect::MakeLTRB(
+ x - GRID_SIZE, y - GRID_SIZE, x + GRID_SIZE, y + GRID_SIZE));
+ expandedRegionExpected.op(
+ SkIRect::MakeLTRB(x - GRID_SIZE, y + 0, x + 0, y + GRID_SIZE),
+ SkRegion::kDifference_Op);
+ TestExpandRegionToGrid(region, LOG_GRID_SIZE, expandedRegionExpected);
+
+ region.setRect(SkIRect::MakeLTRB(x - 1, y - 1, x + 1, y + 1));
+ region.op(SkIRect::MakeLTRB(x + 0, y + 0, x + 1, y + 1),
+ SkRegion::kDifference_Op);
+ expandedRegionExpected.setRect(SkIRect::MakeLTRB(
+ x - GRID_SIZE, y - GRID_SIZE, x + GRID_SIZE, y + GRID_SIZE));
+ expandedRegionExpected.op(
+ SkIRect::MakeLTRB(x + 0, y + 0, x + GRID_SIZE, y + GRID_SIZE),
+ SkRegion::kDifference_Op);
+ TestExpandRegionToGrid(region, LOG_GRID_SIZE, expandedRegionExpected);
+
+ region.setRect(SkIRect::MakeLTRB(x - 1, y - 1, x + 1, y + 1));
+ region.op(SkIRect::MakeLTRB(x + 0, y - 1, x + 1, y + 0),
+ SkRegion::kDifference_Op);
+ expandedRegionExpected.setRect(SkIRect::MakeLTRB(
+ x - GRID_SIZE, y - GRID_SIZE, x + GRID_SIZE, y + GRID_SIZE));
+ expandedRegionExpected.op(
+ SkIRect::MakeLTRB(x + 0, y - GRID_SIZE, x + GRID_SIZE, y + 0),
+ SkRegion::kDifference_Op);
+ TestExpandRegionToGrid(region, LOG_GRID_SIZE, expandedRegionExpected);
+ }
+ }
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capturer_linux.cc b/media/video/capture/screen/screen_capturer_linux.cc
new file mode 100644
index 0000000..774d62e
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_linux.cc
@@ -0,0 +1,617 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xdamage.h>
+#include <X11/extensions/Xfixes.h>
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/time.h"
+#include "media/video/capture/screen/differ.h"
+#include "media/video/capture/screen/linux/x_server_pixel_buffer.h"
+#include "media/video/capture/screen/mouse_cursor_shape.h"
+#include "media/video/capture/screen/screen_capture_data.h"
+#include "media/video/capture/screen/screen_capture_frame.h"
+#include "media/video/capture/screen/screen_capture_frame_queue.h"
+#include "media/video/capture/screen/screen_capturer_helper.h"
+
+namespace media {
+
+namespace {
+
+// Default to false, since many systems have broken XDamage support - see
+// http://crbug.com/73423.
+static bool g_should_use_x_damage = false;
+
+static bool ShouldUseXDamage() {
+ return g_should_use_x_damage;
+}
+
+// A class representing a full-frame pixel buffer.
+class ScreenCaptureFrameLinux : public ScreenCaptureFrame {
+ public:
+ explicit ScreenCaptureFrameLinux(const SkISize& window_size);
+ virtual ~ScreenCaptureFrameLinux();
+
+ private:
+ // Allocated pixel buffer.
+ scoped_array<uint8> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureFrameLinux);
+};
+
+// A class to perform video frame capturing for Linux.
+class ScreenCapturerLinux : public ScreenCapturer {
+ public:
+ ScreenCapturerLinux();
+ virtual ~ScreenCapturerLinux();
+
+ bool Init(); // TODO(ajwong): Do we really want this to be synchronous?
+
+ // Capturer interface.
+ virtual void Start(Delegate* delegate) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE;
+ virtual void CaptureFrame() 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, and return the data in a new ScreenCaptureData
+ // object, to be freed by the caller. 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.
+ scoped_refptr<ScreenCaptureData> CaptureScreen();
+
+ // Called when the screen configuration is changed. |root_window_size|
+ // specifies the most recent size of the root window.
+ void ScreenConfigurationChanged(const SkISize& 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
+ // |capture_data|.
+ void CaptureRect(const SkIRect& rect, ScreenCaptureData* capture_data);
+
+ // We expose two forms of blitting to handle variations in the pixel format.
+ // In FastBlit, the operation is effectively a memcpy.
+ void FastBlit(uint8* image,
+ const SkIRect& rect,
+ ScreenCaptureData* capture_data);
+ void SlowBlit(uint8* image,
+ const SkIRect& rect,
+ ScreenCaptureData* capture_data);
+
+ Delegate* delegate_;
+
+ // X11 graphics context.
+ Display* display_;
+ GC gc_;
+ Window root_window_;
+
+ // Last known dimensions of the root window.
+ SkISize 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.
+ SkRegion last_invalid_region_;
+
+ // |Differ| for use when polling for changes.
+ scoped_ptr<Differ> differ_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCapturerLinux);
+};
+
+ScreenCaptureFrameLinux::ScreenCaptureFrameLinux(const SkISize& window_size) {
+ set_bytes_per_row(window_size.width() * ScreenCaptureData::kBytesPerPixel);
+ set_dimensions(window_size);
+
+ size_t buffer_size = bytes_per_row() * window_size.height();
+ data_.reset(new uint8[buffer_size]);
+ set_pixels(data_.get());
+}
+
+ScreenCaptureFrameLinux::~ScreenCaptureFrameLinux() {
+}
+
+ScreenCapturerLinux::ScreenCapturerLinux()
+ : delegate_(NULL),
+ display_(NULL),
+ gc_(NULL),
+ root_window_(BadValue),
+ root_window_size_(SkISize::Make(0, 0)),
+ 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() {
+ // TODO(ajwong): We should specify the display string we are attaching to
+ // in the constructor.
+ display_ = XOpenDisplay(NULL);
+ if (!display_) {
+ LOG(ERROR) << "Unable to open display";
+ return false;
+ }
+
+ root_window_ = RootWindow(display_, DefaultScreen(display_));
+ if (root_window_ == BadValue) {
+ LOG(ERROR) << "Unable to get the root window";
+ DeinitXlib();
+ return false;
+ }
+
+ gc_ = XCreateGC(display_, root_window_, 0, NULL);
+ if (gc_ == NULL) {
+ LOG(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(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 (ShouldUseXDamage()) {
+ 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(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(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(ERROR) << "Unable to create XFixes region.";
+ return;
+ }
+
+ use_damage_ = true;
+ LOG(INFO) << "Using XDamage extension.";
+}
+
+void ScreenCapturerLinux::Start(Delegate* delegate) {
+ DCHECK(delegate_ == NULL);
+
+ delegate_ = delegate;
+}
+
+void ScreenCapturerLinux::Stop() {
+}
+
+void ScreenCapturerLinux::InvalidateRegion(const SkRegion& invalid_region) {
+ helper_.InvalidateRegion(invalid_region);
+}
+
+void ScreenCapturerLinux::CaptureFrame() {
+ base::Time capture_start_time = base::Time::Now();
+
+ // Process XEvents for XDamage and cursor shape tracking.
+ ProcessPendingXEvents();
+
+ // 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_needs_update()) {
+ scoped_ptr<ScreenCaptureFrameLinux> buffer(new ScreenCaptureFrameLinux(
+ root_window_size_));
+ queue_.ReplaceCurrentFrame(buffer.PassAs<ScreenCaptureFrame>());
+ }
+
+ // Refresh the Differ helper used by CaptureFrame(), if needed.
+ const ScreenCaptureFrame* current_buffer = queue_.current_frame();
+ if (!use_damage_ && (
+ !differ_.get() ||
+ (differ_->width() != current_buffer->dimensions().width()) ||
+ (differ_->height() != current_buffer->dimensions().height()) ||
+ (differ_->bytes_per_row() != current_buffer->bytes_per_row()))) {
+ differ_.reset(new Differ(current_buffer->dimensions().width(),
+ current_buffer->dimensions().height(),
+ ScreenCaptureData::kBytesPerPixel,
+ current_buffer->bytes_per_row()));
+ }
+
+ scoped_refptr<ScreenCaptureData> capture_data(CaptureScreen());
+
+ // Swap the current & previous buffers ready for the next capture.
+ last_invalid_region_ = capture_data->dirty_region();
+
+ queue_.DoneWithCurrentFrame();
+
+ capture_data->set_capture_time_ms(
+ (base::Time::Now() - capture_start_time).InMillisecondsRoundedUp());
+ delegate_->OnCaptureCompleted(capture_data);
+}
+
+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<XDamageNotifyEvent*>(&e);
+ DCHECK(event->level == XDamageReportNonEmpty);
+ } else if (e.type == ConfigureNotify) {
+ const XConfigureEvent& event = e.xconfigure;
+ ScreenConfigurationChanged(SkISize::Make(event.width, event.height));
+ } else if (has_xfixes_ &&
+ e.type == xfixes_event_base_ + XFixesCursorNotify) {
+ XFixesCursorNotifyEvent* cne;
+ cne = reinterpret_cast<XFixesCursorNotifyEvent*>(&e);
+ if (cne->subtype == XFixesDisplayCursorNotify) {
+ CaptureCursor();
+ }
+ } else {
+ LOG(WARNING) << "Got unknown event type: " << e.type;
+ }
+ }
+}
+
+void ScreenCapturerLinux::CaptureCursor() {
+ DCHECK(has_xfixes_);
+
+ XFixesCursorImage* img = XFixesGetCursorImage(display_);
+ if (!img) {
+ return;
+ }
+
+ scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape());
+ cursor->size.set(img->width, img->height);
+ cursor->hotspot.set(img->xhot, img->yhot);
+
+ int total_bytes = cursor->size.width() * cursor->size.height() *
+ ScreenCaptureData::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* dst = reinterpret_cast<uint32*>(string_as_array(&cursor->data));
+ uint32* dst_end = dst + (img->width * img->height);
+ while (dst < dst_end) {
+ *dst++ = static_cast<uint32>(*src++);
+ }
+ XFree(img);
+
+ delegate_->OnCursorShapeChanged(cursor.Pass());
+}
+
+scoped_refptr<ScreenCaptureData> ScreenCapturerLinux::CaptureScreen() {
+ ScreenCaptureFrame* frame = queue_.current_frame();
+ scoped_refptr<ScreenCaptureData> capture_data(new ScreenCaptureData(
+ frame->pixels(), frame->bytes_per_row(), frame->dimensions()));
+
+ // 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(capture_data->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();
+
+ SkRegion invalid_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 nRects = 0;
+ XRectangle bounds;
+ XRectangle* rects = XFixesFetchRegionAndBounds(display_, damage_region_,
+ &nRects, &bounds);
+ for (int i=0; i<nRects; ++i) {
+ invalid_region.op(SkIRect::MakeXYWH(rects[i].x, rects[i].y,
+ rects[i].width, rects[i].height),
+ SkRegion::kUnion_Op);
+ }
+ XFree(rects);
+ helper_.InvalidateRegion(invalid_region);
+
+ // Capture the damaged portions of the desktop.
+ helper_.SwapInvalidRegion(&invalid_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.
+ invalid_region.op(SkIRect::MakeSize(root_window_size_),
+ SkRegion::kIntersect_Op);
+ for (SkRegion::Iterator it(invalid_region); !it.done(); it.next()) {
+ CaptureRect(it.rect(), capture_data);
+ }
+ } else {
+ // Doing full-screen polling, or this is the first capture after a
+ // screen-resolution change. In either case, need a full-screen capture.
+ SkIRect screen_rect = SkIRect::MakeWH(frame->dimensions().width(),
+ frame->dimensions().height());
+ CaptureRect(screen_rect, capture_data);
+
+ 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_ != NULL);
+ differ_->CalcDirtyRegion(queue_.previous_frame()->pixels(),
+ frame->pixels(), &invalid_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.
+ invalid_region.op(screen_rect, SkRegion::kUnion_Op);
+ }
+ }
+
+ capture_data->mutable_dirty_region() = invalid_region;
+ return capture_data;
+}
+
+void ScreenCapturerLinux::ScreenConfigurationChanged(
+ const SkISize& root_window_size) {
+ root_window_size_ = root_window_size;
+
+ // Make sure the frame buffers will be reallocated.
+ queue_.SetAllFramesNeedUpdate();
+
+ 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());
+
+ ScreenCaptureFrame* current = queue_.current_frame();
+ ScreenCaptureFrame* last = queue_.previous_frame();
+ DCHECK_NE(current, last);
+ for (SkRegion::Iterator it(last_invalid_region_); !it.done(); it.next()) {
+ const SkIRect& r = it.rect();
+ int offset = r.fTop * current->bytes_per_row() +
+ r.fLeft * ScreenCaptureData::kBytesPerPixel;
+ for (int i = 0; i < r.height(); ++i) {
+ memcpy(current->pixels() + offset, last->pixels() + offset,
+ r.width() * ScreenCaptureData::kBytesPerPixel);
+ offset += current->dimensions().width() *
+ ScreenCaptureData::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 SkIRect& rect,
+ ScreenCaptureData* capture_data) {
+ uint8* image = x_server_pixel_buffer_.CaptureRect(rect);
+ int depth = x_server_pixel_buffer_.GetDepth();
+ int bpp = x_server_pixel_buffer_.GetBitsPerPixel();
+ bool is_rgb = x_server_pixel_buffer_.IsRgb();
+ if ((depth == 24 || depth == 32) && bpp == 32 && is_rgb) {
+ DVLOG(3) << "Fast blitting";
+ FastBlit(image, rect, capture_data);
+ } else {
+ DVLOG(3) << "Slow blitting";
+ SlowBlit(image, rect, capture_data);
+ }
+}
+
+void ScreenCapturerLinux::FastBlit(uint8* image, const SkIRect& rect,
+ ScreenCaptureData* capture_data) {
+ uint8* src_pos = image;
+ int src_stride = x_server_pixel_buffer_.GetStride();
+ int dst_x = rect.fLeft, dst_y = rect.fTop;
+
+ uint8* dst_pos = capture_data->data() + capture_data->stride() * dst_y;
+ dst_pos += dst_x * ScreenCaptureData::kBytesPerPixel;
+
+ int height = rect.height();
+ int row_bytes = rect.width() * ScreenCaptureData::kBytesPerPixel;
+ for (int y = 0; y < height; ++y) {
+ memcpy(dst_pos, src_pos, row_bytes);
+ src_pos += src_stride;
+ dst_pos += capture_data->stride();
+ }
+}
+
+void ScreenCapturerLinux::SlowBlit(uint8* image, const SkIRect& rect,
+ ScreenCaptureData* capture_data) {
+ int src_stride = x_server_pixel_buffer_.GetStride();
+ int dst_x = rect.fLeft, dst_y = rect.fTop;
+ int width = rect.width(), height = rect.height();
+
+ unsigned int red_mask = x_server_pixel_buffer_.GetRedMask();
+ unsigned int blue_mask = x_server_pixel_buffer_.GetBlueMask();
+ unsigned int green_mask = x_server_pixel_buffer_.GetGreenMask();
+ unsigned int red_shift = x_server_pixel_buffer_.GetRedShift();
+ unsigned int blue_shift = x_server_pixel_buffer_.GetBlueShift();
+ unsigned int green_shift = x_server_pixel_buffer_.GetGreenShift();
+
+ unsigned int max_red = red_mask >> red_shift;
+ unsigned int max_blue = blue_mask >> blue_shift;
+ unsigned int max_green = green_mask >> green_shift;
+
+ unsigned int bits_per_pixel = x_server_pixel_buffer_.GetBitsPerPixel();
+
+ uint8* dst_pos = capture_data->data() + capture_data->stride() * dst_y;
+ uint8* src_pos = image;
+ dst_pos += dst_x * ScreenCaptureData::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<uint32_t*>(dst_pos);
+ uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
+ uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(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) * 255) / max_red;
+ uint32_t b = (((pixel & blue_mask) >> blue_shift) * 255) / max_blue;
+ uint32_t g = (((pixel & green_mask) >> green_shift) * 255) / max_green;
+ // Write as 32-bit RGB.
+ dst_pos_32[x] = r << 16 | g << 8 | b;
+ }
+ dst_pos += capture_data->stride();
+ src_pos += src_stride;
+ }
+}
+
+} // namespace
+
+// static
+scoped_ptr<ScreenCapturer> ScreenCapturer::Create() {
+ scoped_ptr<ScreenCapturerLinux> capturer(new ScreenCapturerLinux());
+ if (!capturer->Init())
+ capturer.reset();
+ return capturer.PassAs<ScreenCapturer>();
+}
+
+// static
+scoped_ptr<ScreenCapturer> ScreenCapturer::CreateWithFactory(
+ SharedBufferFactory* shared_buffer_factory) {
+ NOTIMPLEMENTED();
+ return scoped_ptr<ScreenCapturer>();
+}
+
+// static
+void ScreenCapturer::EnableXDamage(bool enable) {
+ g_should_use_x_damage = enable;
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capturer_mac.mm b/media/video/capture/screen/screen_capturer_mac.mm
new file mode 100644
index 0000000..1baa090
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_mac.mm
@@ -0,0 +1,871 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <Cocoa/Cocoa.h>
+#include <dlfcn.h>
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include <OpenGL/CGLMacro.h>
+#include <OpenGL/OpenGL.h>
+#include <set>
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/file_path.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_native_library.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/time.h"
+#include "media/video/capture/screen/mac/desktop_configuration.h"
+#include "media/video/capture/screen/mac/scoped_pixel_buffer_object.h"
+#include "media/video/capture/screen/mouse_cursor_shape.h"
+#include "media/video/capture/screen/screen_capture_data.h"
+#include "media/video/capture/screen/screen_capture_frame.h"
+#include "media/video/capture/screen/screen_capture_frame_queue.h"
+#include "media/video/capture/screen/screen_capturer_helper.h"
+#include "skia/ext/skia_utils_mac.h"
+
+namespace media {
+
+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 DPI for Mac OS X displays with 1:1 backing scale.
+const int kStandardDPI = 72;
+
+// skia/ext/skia_utils_mac.h only defines CGRectToSkRect().
+SkIRect CGRectToSkIRect(const CGRect& rect) {
+ SkIRect result;
+ gfx::CGRectToSkRect(rect).round(&result);
+ return result;
+}
+
+// Scales all coordinates of an SkRect by a specified factor.
+SkRect ScaleSkRect(const SkRect& rect, float scale) {
+ SkRect result = {
+ rect.left() * scale, rect.top() * scale,
+ rect.right() * scale, rect.bottom() * scale
+ };
+ return result;
+}
+
+// Copy pixels in the |rect| from |src_place| to |dest_plane|.
+void CopyRect(const uint8* src_plane,
+ int src_plane_stride,
+ uint8* dest_plane,
+ int dest_plane_stride,
+ int bytes_per_pixel,
+ const SkIRect& 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;
+ }
+}
+
+// The amount of time allowed for displays to reconfigure.
+const int64 kDisplayConfigurationEventTimeoutInSeconds = 10;
+
+// A class representing a full-frame pixel buffer.
+class ScreenCaptureFrameMac : public ScreenCaptureFrame {
+ public:
+ explicit ScreenCaptureFrameMac(const MacDesktopConfiguration& desktop_config);
+ virtual ~ScreenCaptureFrameMac();
+
+ const SkIPoint& dpi() const { return dpi_; }
+
+ private:
+ // Allocated pixel buffer.
+ scoped_array<uint8> data_;
+
+ // DPI settings for this buffer.
+ SkIPoint dpi_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureFrameMac);
+};
+
+// 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(Delegate* delegate) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE;
+ virtual void CaptureFrame() OVERRIDE;
+
+ private:
+ void CaptureCursor();
+
+ void GlBlitFast(const ScreenCaptureFrame& buffer, const SkRegion& region);
+ void GlBlitSlow(const ScreenCaptureFrame& buffer);
+ void CgBlitPreLion(const ScreenCaptureFrame& buffer, const SkRegion& region);
+ void CgBlitPostLion(const ScreenCaptureFrame& buffer, const SkRegion& region);
+
+ // Called when the screen configuration is changed.
+ void ScreenConfigurationChanged();
+
+ 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();
+
+ Delegate* delegate_;
+
+ 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_;
+
+ // Image of the last cursor that we sent to the client.
+ base::mac::ScopedCFTypeRef<CGImageRef> current_cursor_;
+
+ // Contains an invalid region from the previous capture.
+ SkRegion last_invalid_region_;
+
+ // Used to ensure that frame captures do not take place while displays
+ // are being reconfigured.
+ base::WaitableEvent 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<CGDirectDisplayID> 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.
+ base::ScopedNativeLibrary app_services_library_;
+ CGDisplayBaseAddressFunc cg_display_base_address_;
+ CGDisplayBytesPerRowFunc cg_display_bytes_per_row_;
+ CGDisplayBitsPerPixelFunc cg_display_bits_per_pixel_;
+ base::ScopedNativeLibrary opengl_library_;
+ CGLSetFullScreenFunc cgl_set_full_screen_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCapturerMac);
+};
+
+ScreenCaptureFrameMac::ScreenCaptureFrameMac(
+ const MacDesktopConfiguration& desktop_config) {
+ SkISize size = SkISize::Make(desktop_config.pixel_bounds.width(),
+ desktop_config.pixel_bounds.height());
+ set_bytes_per_row(size.width() * sizeof(uint32_t));
+ set_dimensions(size);
+
+ size_t buffer_size = size.width() * size.height() * sizeof(uint32_t);
+ data_.reset(new uint8[buffer_size]);
+ set_pixels(data_.get());
+
+ dpi_ = SkIPoint::Make(kStandardDPI * desktop_config.dip_to_pixel_scale,
+ kStandardDPI * desktop_config.dip_to_pixel_scale);
+}
+
+ScreenCaptureFrameMac::~ScreenCaptureFrameMac() {
+}
+
+ScreenCapturerMac::ScreenCapturerMac()
+ : delegate_(NULL),
+ cgl_context_(NULL),
+ display_configuration_capture_event_(false, true),
+ power_assertion_id_display_(kIOPMNullAssertionID),
+ power_assertion_id_user_(kIOPMNullAssertionID),
+ cg_display_base_address_(NULL),
+ cg_display_bytes_per_row_(NULL),
+ cg_display_bits_per_pixel_(NULL),
+ cgl_set_full_screen_(NULL)
+{
+}
+
+ScreenCapturerMac::~ScreenCapturerMac() {
+ ReleaseBuffers();
+ CGUnregisterScreenRefreshCallback(
+ ScreenCapturerMac::ScreenRefreshCallback, this);
+ CGScreenUnregisterMoveCallback(
+ ScreenCapturerMac::ScreenUpdateMoveCallback, this);
+ CGError err = CGDisplayRemoveReconfigurationCallback(
+ ScreenCapturerMac::DisplaysReconfiguredCallback, this);
+ if (err != kCGErrorSuccess) {
+ LOG(ERROR) << "CGDisplayRemoveReconfigurationCallback " << err;
+ }
+}
+
+bool ScreenCapturerMac::Init() {
+ CGError err = CGRegisterScreenRefreshCallback(
+ ScreenCapturerMac::ScreenRefreshCallback, this);
+ if (err != kCGErrorSuccess) {
+ LOG(ERROR) << "CGRegisterScreenRefreshCallback " << err;
+ return false;
+ }
+
+ err = CGScreenRegisterMoveCallback(
+ ScreenCapturerMac::ScreenUpdateMoveCallback, this);
+ if (err != kCGErrorSuccess) {
+ LOG(ERROR) << "CGScreenRegisterMoveCallback " << err;
+ return false;
+ }
+ err = CGDisplayRegisterReconfigurationCallback(
+ ScreenCapturerMac::DisplaysReconfiguredCallback, this);
+ if (err != kCGErrorSuccess) {
+ LOG(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_.SetAllFramesNeedUpdate();
+}
+
+void ScreenCapturerMac::Start(Delegate* delegate) {
+ DCHECK(delegate_ == NULL);
+
+ delegate_ = delegate;
+
+ // 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::Stop() {
+ 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;
+ }
+}
+
+void ScreenCapturerMac::InvalidateRegion(const SkRegion& invalid_region) {
+ helper_.InvalidateRegion(invalid_region);
+}
+
+void ScreenCapturerMac::CaptureFrame() {
+ // Only allow captures when the display configuration is not occurring.
+ scoped_refptr<ScreenCaptureData> data;
+
+ base::Time capture_start_time = base::Time::Now();
+
+ // 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).
+ CHECK(display_configuration_capture_event_.TimedWait(
+ base::TimeDelta::FromSeconds(
+ kDisplayConfigurationEventTimeoutInSeconds)));
+
+ SkRegion region;
+ helper_.SwapInvalidRegion(&region);
+
+ // 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_needs_update()) {
+ scoped_ptr<ScreenCaptureFrameMac> buffer(
+ new ScreenCaptureFrameMac(desktop_config_));
+ queue_.ReplaceCurrentFrame(buffer.PassAs<ScreenCaptureFrame>());
+ }
+
+ ScreenCaptureFrame* current_buffer = queue_.current_frame();
+
+ bool flip = false; // GL capturers need flipping.
+ if (base::mac::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_buffer, region);
+ } else if (cgl_context_) {
+ flip = true;
+ if (pixel_buffer_object_.get() != 0) {
+ GlBlitFast(*current_buffer, region);
+ } else {
+ // See comment in ScopedPixelBufferObject::Init about why the slow
+ // path is always used on 10.5.
+ GlBlitSlow(*current_buffer);
+ }
+ } else {
+ CgBlitPreLion(*current_buffer, region);
+ }
+
+ uint8* buffer = current_buffer->pixels();
+ int stride = current_buffer->bytes_per_row();
+ if (flip) {
+ stride = -stride;
+ buffer += (current_buffer->dimensions().height() - 1) *
+ current_buffer->bytes_per_row();
+ }
+
+ data = new ScreenCaptureData(buffer, stride, current_buffer->dimensions());
+ data->set_dpi(static_cast<ScreenCaptureFrameMac*>(current_buffer)->dpi());
+ data->mutable_dirty_region() = region;
+
+ helper_.set_size_most_recent(data->size());
+
+ // Signal that we are done capturing data from the display framebuffer,
+ // and accessing display structures.
+ display_configuration_capture_event_.Signal();
+
+ // Capture the current cursor shape and notify |delegate_| if it has changed.
+ CaptureCursor();
+
+ // Move the capture frame buffer queue on to the next buffer.
+ queue_.DoneWithCurrentFrame();
+
+ data->set_capture_time_ms(
+ (base::Time::Now() - capture_start_time).InMillisecondsRoundedUp());
+ delegate_->OnCaptureCompleted(data);
+}
+
+void ScreenCapturerMac::CaptureCursor() {
+ 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;
+ }
+
+ // Compare the current cursor with the last one we sent to the client
+ // and exit if the cursor is the same.
+ if (current_cursor_.get() != NULL) {
+ CGImageRef current = current_cursor_.get();
+ if (CGImageGetWidth(image) == CGImageGetWidth(current) &&
+ CGImageGetHeight(image) == CGImageGetHeight(current) &&
+ CGImageGetBitsPerPixel(image) == CGImageGetBitsPerPixel(current) &&
+ CGImageGetBytesPerRow(image) == CGImageGetBytesPerRow(current) &&
+ CGImageGetBitsPerComponent(image) ==
+ CGImageGetBitsPerComponent(current)) {
+ CGDataProviderRef provider_new = CGImageGetDataProvider(image);
+ base::mac::ScopedCFTypeRef<CFDataRef> data_ref_new(
+ CGDataProviderCopyData(provider_new));
+ CGDataProviderRef provider_current = CGImageGetDataProvider(current);
+ base::mac::ScopedCFTypeRef<CFDataRef> data_ref_current(
+ CGDataProviderCopyData(provider_current));
+
+ if (data_ref_new.get() != NULL && data_ref_current.get() != NULL) {
+ int data_size = CFDataGetLength(data_ref_new);
+ CHECK(data_size == CFDataGetLength(data_ref_current));
+ const uint8* data_new = CFDataGetBytePtr(data_ref_new);
+ const uint8* data_current = CFDataGetBytePtr(data_ref_current);
+ if (memcmp(data_new, data_current, data_size) == 0) {
+ return;
+ }
+ }
+ }
+ }
+
+ // Record the last cursor image.
+ current_cursor_.reset(CGImageCreateCopy(image));
+
+ VLOG(3) << "Sending cursor: " << size.width << "x" << size.height;
+
+ CGDataProviderRef provider = CGImageGetDataProvider(image);
+ base::mac::ScopedCFTypeRef<CFDataRef> image_data_ref(
+ CGDataProviderCopyData(provider));
+ if (image_data_ref.get() == NULL) {
+ return;
+ }
+ const char* cursor_src_data =
+ reinterpret_cast<const char*>(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<MouseCursorShape> 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);
+
+ delegate_->OnCursorShapeChanged(cursor_shape.Pass());
+}
+
+void ScreenCapturerMac::GlBlitFast(const ScreenCaptureFrame& buffer,
+ const SkRegion& region) {
+ const int buffer_height = buffer.dimensions().height();
+ const int buffer_width = buffer.dimensions().width();
+
+ // Clip to the size of our current screen.
+ SkIRect clip_rect = SkIRect::MakeWH(buffer_width, buffer_height);
+ 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 = (buffer_height - 1) * buffer.bytes_per_row();
+ for(SkRegion::Iterator i(last_invalid_region_); !i.done(); i.next()) {
+ SkIRect copy_rect = i.rect();
+ if (copy_rect.intersect(clip_rect)) {
+ CopyRect(queue_.previous_frame()->pixels() + y_offset,
+ -buffer.bytes_per_row(),
+ buffer.pixels() + y_offset,
+ -buffer.bytes_per_row(),
+ 4, // Bytes for pixel for RGBA.
+ 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, buffer_width, buffer_height, GL_BGRA, GL_UNSIGNED_BYTE, 0);
+ GLubyte* ptr = static_cast<GLubyte*>(
+ 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 = (buffer_height - 1) * buffer.bytes_per_row();
+ for(SkRegion::Iterator i(region); !i.done(); i.next()) {
+ SkIRect copy_rect = i.rect();
+ if (copy_rect.intersect(clip_rect)) {
+ CopyRect(ptr + y_offset,
+ -buffer.bytes_per_row(),
+ buffer.pixels() + y_offset,
+ -buffer.bytes_per_row(),
+ 4, // Bytes for pixel for RGBA.
+ 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 ScreenCaptureFrame& buffer) {
+ 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, buffer.dimensions().width(), buffer.dimensions().height(),
+ GL_BGRA, GL_UNSIGNED_BYTE, buffer.pixels());
+ glPopClientAttrib();
+}
+
+void ScreenCapturerMac::CgBlitPreLion(const ScreenCaptureFrame& buffer,
+ const SkRegion& region) {
+ const int buffer_height = buffer.dimensions().height();
+
+ // 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(buffer.pixels(),
+ queue_.previous_frame()->pixels(),
+ buffer.bytes_per_row() * buffer_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.
+ DCHECK(cg_display_base_address_ && cg_display_bytes_per_row_ &&
+ cg_display_bits_per_pixel_);
+ uint8* display_base_address =
+ reinterpret_cast<uint8*>((*cg_display_base_address_)(display_config.id));
+ CHECK(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.
+ SkIRect display_bounds = display_config.pixel_bounds;
+ display_bounds.offset(-desktop_config_.pixel_bounds.left(),
+ -desktop_config_.pixel_bounds.top());
+
+ // Determine which parts of the blit region, if any, lay within the monitor.
+ SkRegion copy_region;
+ if (!copy_region.op(region, display_bounds, SkRegion::kIntersect_Op))
+ continue;
+
+ // Translate the region to be copied into display-relative coordinates.
+ copy_region.translate(-desktop_config_.pixel_bounds.left(),
+ -desktop_config_.pixel_bounds.top());
+
+ // Calculate where in the output buffer the display's origin is.
+ uint8* out_ptr = buffer.pixels() +
+ (display_bounds.left() * src_bytes_per_pixel) +
+ (display_bounds.top() * buffer.bytes_per_row());
+
+ // Copy the dirty region from the display buffer into our desktop buffer.
+ for(SkRegion::Iterator i(copy_region); !i.done(); i.next()) {
+ CopyRect(display_base_address,
+ src_bytes_per_row,
+ out_ptr,
+ buffer.bytes_per_row(),
+ src_bytes_per_pixel,
+ i.rect());
+ }
+ }
+}
+
+void ScreenCapturerMac::CgBlitPostLion(const ScreenCaptureFrame& buffer,
+ const SkRegion& region) {
+ const int buffer_height = buffer.dimensions().height();
+
+ // 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(buffer.pixels(),
+ queue_.previous_frame()->pixels(),
+ buffer.bytes_per_row() * buffer_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.
+ SkIRect display_bounds = display_config.pixel_bounds;
+ display_bounds.offset(-desktop_config_.pixel_bounds.left(),
+ -desktop_config_.pixel_bounds.top());
+
+ // Determine which parts of the blit region, if any, lay within the monitor.
+ SkRegion copy_region;
+ if (!copy_region.op(region, display_bounds, SkRegion::kIntersect_Op))
+ continue;
+
+ // Translate the region to be copied into display-relative coordinates.
+ copy_region.translate(-desktop_config_.pixel_bounds.left(),
+ -desktop_config_.pixel_bounds.top());
+
+ // Create an image containing a snapshot of the display.
+ base::mac::ScopedCFTypeRef<CGImageRef> image(
+ CGDisplayCreateImage(display_config.id));
+ if (image.get() == NULL)
+ continue;
+
+ // Request access to the raw pixel data via the image's DataProvider.
+ CGDataProviderRef provider = CGImageGetDataProvider(image);
+ base::mac::ScopedCFTypeRef<CFDataRef> data(
+ CGDataProviderCopyData(provider));
+ if (data.get() == NULL)
+ continue;
+
+ const uint8* 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* out_ptr = buffer.pixels() +
+ (display_bounds.left() * src_bytes_per_pixel) +
+ (display_bounds.top() * buffer.bytes_per_row());
+
+ // Copy the dirty region from the display buffer into our desktop buffer.
+ for(SkRegion::Iterator i(copy_region); !i.done(); i.next()) {
+ CopyRect(display_base_address,
+ src_bytes_per_row,
+ out_ptr,
+ buffer.bytes_per_row(),
+ src_bytes_per_pixel,
+ i.rect());
+ }
+ }
+}
+
+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();
+
+ // Re-mark the entire desktop as dirty.
+ helper_.InvalidateScreen(
+ SkISize::Make(desktop_config_.pixel_bounds.width(),
+ desktop_config_.pixel_bounds.height()));
+
+ // Make sure the frame buffers will be reallocated.
+ queue_.SetAllFramesNeedUpdate();
+
+ // 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 (base::mac::IsOSLionOrLater()) {
+ LOG(INFO) << "Using CgBlitPostLion.";
+ // No need for any OpenGL support on Lion
+ return;
+ }
+
+ // Dynamically link to the deprecated pre-Lion capture APIs.
+ std::string app_services_library_error;
+ FilePath app_services_path(kApplicationServicesLibraryName);
+ app_services_library_.Reset(
+ base::LoadNativeLibrary(app_services_path, &app_services_library_error));
+ CHECK(app_services_library_.is_valid()) << app_services_library_error;
+
+ std::string opengl_library_error;
+ FilePath opengl_path(kOpenGlLibraryName);
+ opengl_library_.Reset(
+ base::LoadNativeLibrary(opengl_path, &opengl_library_error));
+ CHECK(opengl_library_.is_valid()) << opengl_library_error;
+
+ cg_display_base_address_ = reinterpret_cast<CGDisplayBaseAddressFunc>(
+ app_services_library_.GetFunctionPointer("CGDisplayBaseAddress"));
+ cg_display_bytes_per_row_ = reinterpret_cast<CGDisplayBytesPerRowFunc>(
+ app_services_library_.GetFunctionPointer("CGDisplayBytesPerRow"));
+ cg_display_bits_per_pixel_ = reinterpret_cast<CGDisplayBitsPerPixelFunc>(
+ app_services_library_.GetFunctionPointer("CGDisplayBitsPerPixel"));
+ cgl_set_full_screen_ = reinterpret_cast<CGLSetFullScreenFunc>(
+ opengl_library_.GetFunctionPointer("CGLSetFullScreen"));
+ CHECK(cg_display_base_address_ && cg_display_bytes_per_row_ &&
+ cg_display_bits_per_pixel_ && cgl_set_full_screen_);
+
+ if (desktop_config_.displays.size() > 1) {
+ LOG(INFO) << "Using CgBlitPreLion (Multi-monitor).";
+ return;
+ }
+
+ CGDirectDisplayID mainDevice = CGMainDisplayID();
+ if (!CGDisplayUsesOpenGLAcceleration(mainDevice)) {
+ LOG(INFO) << "Using CgBlitPreLion (OpenGL unavailable).";
+ return;
+ }
+
+ LOG(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);
+ DCHECK_EQ(err, kCGLNoError);
+ err = CGLCreateContext(pixel_format, NULL, &cgl_context_);
+ DCHECK_EQ(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);
+}
+
+void ScreenCapturerMac::ScreenRefresh(CGRectCount count,
+ const CGRect* rect_array) {
+ if (desktop_config_.pixel_bounds.isEmpty()) {
+ return;
+ }
+ SkIRect skirect_array[count];
+
+ for (CGRectCount i = 0; i < count; ++i) {
+ SkRect sk_rect = gfx::CGRectToSkRect(rect_array[i]);
+
+ // Convert from Density-Independent Pixel to physical pixel coordinates.
+ sk_rect = ScaleSkRect(sk_rect, desktop_config_.dip_to_pixel_scale);
+ sk_rect.round(&skirect_array[i]);
+
+ // Translate from local desktop to capturer framebuffer coordinates.
+ skirect_array[i].offset(-desktop_config_.pixel_bounds.left(),
+ -desktop_config_.pixel_bounds.top());
+ }
+
+ SkRegion region;
+ region.setRects(skirect_array, count);
+ 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.
+ CHECK(display_configuration_capture_event_.TimedWait(
+ base::TimeDelta::FromSeconds(
+ kDisplayConfigurationEventTimeoutInSeconds)));
+ }
+
+ 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.
+ ScreenConfigurationChanged();
+ display_configuration_capture_event_.Signal();
+ }
+ }
+}
+
+void ScreenCapturerMac::ScreenRefreshCallback(CGRectCount count,
+ const CGRect* rect_array,
+ void* user_parameter) {
+ ScreenCapturerMac* capturer = reinterpret_cast<ScreenCapturerMac*>(
+ user_parameter);
+ if (capturer->desktop_config_.pixel_bounds.isEmpty()) {
+ 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<ScreenCapturerMac*>(
+ user_parameter);
+ capturer->ScreenUpdateMove(delta, count, rect_array);
+}
+
+void ScreenCapturerMac::DisplaysReconfiguredCallback(
+ CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags,
+ void* user_parameter) {
+ ScreenCapturerMac* capturer = reinterpret_cast<ScreenCapturerMac*>(
+ user_parameter);
+ capturer->DisplaysReconfigured(display, flags);
+}
+
+} // namespace
+
+// static
+scoped_ptr<ScreenCapturer> ScreenCapturer::Create() {
+ scoped_ptr<ScreenCapturerMac> capturer(new ScreenCapturerMac());
+ if (!capturer->Init())
+ capturer.reset();
+ return capturer.PassAs<ScreenCapturer>();
+}
+
+// static
+scoped_ptr<ScreenCapturer> ScreenCapturer::CreateWithFactory(
+ SharedBufferFactory* shared_buffer_factory) {
+ NOTIMPLEMENTED();
+ return scoped_ptr<ScreenCapturer>();
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capturer_mac_unittest.cc b/media/video/capture/screen/screen_capturer_mac_unittest.cc
new file mode 100644
index 0000000..a62d40b
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_mac_unittest.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include <ostream>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/video/capture/screen/screen_capture_data.h"
+#include "media/video/capture/screen/screen_capturer_mock_objects.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+
+namespace media {
+
+// Verify that the OS is at least Snow Leopard (10.6).
+// Chromoting doesn't support 10.5 or earlier.
+bool CheckSnowLeopard() {
+ long minorVersion, majorVersion;
+ Gestalt(gestaltSystemVersionMajor, &majorVersion);
+ Gestalt(gestaltSystemVersionMinor, &minorVersion);
+ return majorVersion == 10 && minorVersion > 5;
+}
+
+class ScreenCapturerMacTest : public testing::Test {
+ public:
+ // Verifies that the whole screen is initially dirty.
+ void CaptureDoneCallback1(scoped_refptr<ScreenCaptureData> capture_data);
+
+ // Verifies that a rectangle explicitly marked as dirty is propagated
+ // correctly.
+ void CaptureDoneCallback2(scoped_refptr<ScreenCaptureData> capture_data);
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ capturer_ = ScreenCapturer::Create();
+ }
+
+ void AddDirtyRect() {
+ SkIRect rect = SkIRect::MakeXYWH(0, 0, 10, 10);
+ region_.op(rect, SkRegion::kUnion_Op);
+ }
+
+ scoped_ptr<ScreenCapturer> capturer_;
+ MockScreenCapturerDelegate delegate_;
+ SkRegion region_;
+};
+
+void ScreenCapturerMacTest::CaptureDoneCallback1(
+ scoped_refptr<ScreenCaptureData> capture_data) {
+ CGDirectDisplayID mainDevice = CGMainDisplayID();
+ int width = CGDisplayPixelsWide(mainDevice);
+ int height = CGDisplayPixelsHigh(mainDevice);
+ SkRegion initial_region(SkIRect::MakeXYWH(0, 0, width, height));
+ EXPECT_EQ(initial_region, capture_data->dirty_region());
+}
+
+void ScreenCapturerMacTest::CaptureDoneCallback2(
+ scoped_refptr<ScreenCaptureData> capture_data) {
+ CGDirectDisplayID mainDevice = CGMainDisplayID();
+ int width = CGDisplayPixelsWide(mainDevice);
+ int height = CGDisplayPixelsHigh(mainDevice);
+
+ EXPECT_EQ(region_, capture_data->dirty_region());
+ EXPECT_EQ(width, capture_data->size().width());
+ EXPECT_EQ(height, capture_data->size().height());
+ EXPECT_TRUE(capture_data->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<int>(sizeof(uint32_t) * width),
+ abs(capture_data->stride()));
+}
+
+TEST_F(ScreenCapturerMacTest, Capture) {
+ if (!CheckSnowLeopard()) {
+ return;
+ }
+
+ EXPECT_CALL(delegate_, OnCaptureCompleted(_))
+ .Times(2)
+ .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback1))
+ .WillOnce(Invoke(this, &ScreenCapturerMacTest::CaptureDoneCallback2));
+ EXPECT_CALL(delegate_, OnCursorShapeChangedPtr(_))
+ .Times(AnyNumber());
+
+ SCOPED_TRACE("");
+ capturer_->Start(&delegate_);
+
+ // Check that we get an initial full-screen updated.
+ capturer_->CaptureFrame();
+
+ // Check that subsequent dirty rects are propagated correctly.
+ AddDirtyRect();
+ capturer_->InvalidateRegion(region_);
+ capturer_->CaptureFrame();
+ capturer_->Stop();
+}
+
+} // namespace media
+
+namespace gfx {
+
+std::ostream& operator<<(std::ostream& out, const SkRegion& region) {
+ out << "SkRegion(";
+ for (SkRegion::Iterator i(region); !i.done(); i.next()) {
+ const SkIRect& r = i.rect();
+ out << "(" << r.fLeft << "," << r.fTop << ","
+ << r.fRight << "," << r.fBottom << ")";
+ }
+ out << ")";
+ return out;
+}
+
+} // namespace gfx
diff --git a/media/video/capture/screen/screen_capturer_mock_objects.cc b/media/video/capture/screen/screen_capturer_mock_objects.cc
new file mode 100644
index 0000000..e4fa7fb
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_mock_objects.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer_mock_objects.h"
+
+#include "media/video/capture/screen/screen_capturer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+MockScreenCapturer::MockScreenCapturer() {
+}
+
+MockScreenCapturer::~MockScreenCapturer() {
+}
+
+MockScreenCapturerDelegate::MockScreenCapturerDelegate() {
+}
+
+MockScreenCapturerDelegate::~MockScreenCapturerDelegate() {
+}
+
+void MockScreenCapturerDelegate::OnCursorShapeChanged(
+ scoped_ptr<MouseCursorShape> cursor_shape) {
+ // Notify the mock method.
+ OnCursorShapeChangedPtr(cursor_shape.get());
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capturer_mock_objects.h b/media/video/capture/screen/screen_capturer_mock_objects.h
new file mode 100644
index 0000000..36bf3fb
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_mock_objects.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_MOCK_OBJECTS_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_MOCK_OBJECTS_H_
+
+#include "media/video/capture/screen/mouse_cursor_shape.h"
+#include "media/video/capture/screen/screen_capture_data.h"
+#include "media/video/capture/screen/screen_capturer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+class MockScreenCapturer : public ScreenCapturer {
+ public:
+ MockScreenCapturer();
+ virtual ~MockScreenCapturer();
+
+ MOCK_METHOD1(Start, void(Delegate* delegate));
+ MOCK_METHOD0(Stop, void());
+ MOCK_METHOD1(InvalidateRegion, void(const SkRegion& invalid_region));
+ MOCK_METHOD0(CaptureFrame, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockScreenCapturer);
+};
+
+class MockScreenCapturerDelegate : public ScreenCapturer::Delegate {
+ public:
+ MockScreenCapturerDelegate();
+ virtual ~MockScreenCapturerDelegate();
+
+ void OnCursorShapeChanged(scoped_ptr<MouseCursorShape> cursor_shape) OVERRIDE;
+
+ MOCK_METHOD1(OnCaptureCompleted, void(scoped_refptr<ScreenCaptureData>));
+ MOCK_METHOD1(OnCursorShapeChangedPtr,
+ void(MouseCursorShape* cursor_shape));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockScreenCapturerDelegate);
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SCREEN_CAPTURER_MOCK_OBJECTS_H_
diff --git a/media/video/capture/screen/screen_capturer_unittest.cc b/media/video/capture/screen/screen_capturer_unittest.cc
new file mode 100644
index 0000000..be08749
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer.h"
+
+#include "base/bind.h"
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif // defined(OS_MACOSX)
+#include "media/video/capture/screen/screen_capture_data.h"
+#include "media/video/capture/screen/screen_capturer_mock_objects.h"
+#include "media/video/capture/screen/shared_buffer_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+
+namespace media {
+
+class MockSharedBufferFactory : public SharedBufferFactory {
+ public:
+ MockSharedBufferFactory() {}
+ virtual ~MockSharedBufferFactory() {}
+
+ MOCK_METHOD1(CreateSharedBuffer, scoped_refptr<SharedBuffer>(uint32));
+ MOCK_METHOD1(ReleaseSharedBuffer, void(scoped_refptr<SharedBuffer>));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockSharedBufferFactory);
+};
+
+MATCHER(DirtyRegionIsNonEmptyRect, "") {
+ const SkRegion& dirty_region = arg->dirty_region();
+ const SkIRect& dirty_region_bounds = dirty_region.getBounds();
+ if (dirty_region_bounds.isEmpty()) {
+ return false;
+ }
+ return dirty_region == SkRegion(dirty_region_bounds);
+}
+
+class ScreenCapturerTest : public testing::Test {
+ public:
+ scoped_refptr<SharedBuffer> CreateSharedBuffer(uint32 size);
+
+ protected:
+ scoped_ptr<ScreenCapturer> capturer_;
+ MockSharedBufferFactory shared_buffer_factory_;
+ MockScreenCapturerDelegate delegate_;
+};
+
+scoped_refptr<SharedBuffer> ScreenCapturerTest::CreateSharedBuffer(
+ uint32 size) {
+ return scoped_refptr<SharedBuffer>(new SharedBuffer(size));
+}
+
+TEST_F(ScreenCapturerTest, StartCapturer) {
+ capturer_ = ScreenCapturer::Create();
+ capturer_->Start(&delegate_);
+ capturer_->Stop();
+}
+
+#if defined(THREAD_SANITIZER)
+// ThreadSanitizer v2 reports a use-after-free, see http://crbug.com/163641.
+#define MAYBE_Capture DISABLED_Capture
+#else
+#define MAYBE_Capture Capture
+#endif
+TEST_F(ScreenCapturerTest, MAYBE_Capture) {
+ // Assume that Start() treats the screen as invalid initially.
+ EXPECT_CALL(delegate_,
+ OnCaptureCompleted(DirtyRegionIsNonEmptyRect()));
+ EXPECT_CALL(delegate_, OnCursorShapeChangedPtr(_))
+ .Times(AnyNumber());
+
+ capturer_ = ScreenCapturer::Create();
+ capturer_->Start(&delegate_);
+ capturer_->CaptureFrame();
+ capturer_->Stop();
+}
+
+#if defined(OS_WIN)
+
+TEST_F(ScreenCapturerTest, UseSharedBuffers) {
+ EXPECT_CALL(delegate_,
+ OnCaptureCompleted(DirtyRegionIsNonEmptyRect()));
+ EXPECT_CALL(delegate_, OnCursorShapeChangedPtr(_))
+ .Times(AnyNumber());
+
+ EXPECT_CALL(shared_buffer_factory_, CreateSharedBuffer(_))
+ .Times(1)
+ .WillOnce(Invoke(this, &ScreenCapturerTest::CreateSharedBuffer));
+ EXPECT_CALL(shared_buffer_factory_, ReleaseSharedBuffer(_))
+ .Times(1);
+
+ capturer_ = ScreenCapturer::CreateWithFactory(&shared_buffer_factory_);
+ capturer_->Start(&delegate_);
+ capturer_->CaptureFrame();
+ capturer_->Stop();
+ capturer_.reset();
+}
+
+#endif // defined(OS_WIN)
+
+} // namespace media
diff --git a/media/video/capture/screen/screen_capturer_win.cc b/media/video/capture/screen/screen_capturer_win.cc
new file mode 100644
index 0000000..bf6bfae
--- /dev/null
+++ b/media/video/capture/screen/screen_capturer_win.cc
@@ -0,0 +1,599 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/screen_capturer.h"
+
+#include <windows.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_path.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_native_library.h"
+#include "base/stl_util.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "base/win/scoped_gdi_object.h"
+#include "base/win/scoped_hdc.h"
+#include "media/video/capture/screen/differ.h"
+#include "media/video/capture/screen/mouse_cursor_shape.h"
+#include "media/video/capture/screen/screen_capture_data.h"
+#include "media/video/capture/screen/screen_capture_frame.h"
+#include "media/video/capture/screen/screen_capture_frame_queue.h"
+#include "media/video/capture/screen/screen_capturer_helper.h"
+#include "media/video/capture/screen/shared_buffer_factory.h"
+#include "media/video/capture/screen/win/desktop.h"
+#include "media/video/capture/screen/win/scoped_thread_desktop.h"
+#include "third_party/skia/include/core/SkColorPriv.h"
+
+namespace media {
+
+namespace {
+
+// Constants from dwmapi.h.
+const UINT DWM_EC_DISABLECOMPOSITION = 0;
+const UINT DWM_EC_ENABLECOMPOSITION = 1;
+
+typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT);
+
+const char kDwmapiLibraryName[] = "dwmapi";
+
+// Pixel colors used when generating cursor outlines.
+const uint32 kPixelBgraBlack = 0xff000000;
+const uint32 kPixelBgraWhite = 0xffffffff;
+const uint32 kPixelBgraTransparent = 0x00000000;
+
+// A class representing a full-frame pixel buffer.
+class ScreenCaptureFrameWin : public ScreenCaptureFrame {
+ public:
+ ScreenCaptureFrameWin(HDC desktop_dc, const SkISize& size,
+ SharedBufferFactory* shared_buffer_factory);
+ virtual ~ScreenCaptureFrameWin();
+
+ // Returns handle of the device independent bitmap representing this frame
+ // buffer to GDI.
+ HBITMAP GetBitmap();
+
+ private:
+ // Allocates a device independent bitmap representing this frame buffer to
+ // GDI.
+ void AllocateBitmap(HDC desktop_dc, const SkISize& size);
+
+ // Handle of the device independent bitmap representing this frame buffer to
+ // GDI.
+ base::win::ScopedBitmap bitmap_;
+
+ // Used to allocate shared memory buffers if set.
+ SharedBufferFactory* shared_buffer_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureFrameWin);
+};
+
+// ScreenCapturerWin captures 32bit RGB using GDI.
+//
+// ScreenCapturerWin is double-buffered as required by ScreenCapturer.
+class ScreenCapturerWin : public ScreenCapturer {
+ public:
+ ScreenCapturerWin();
+ explicit ScreenCapturerWin(SharedBufferFactory* shared_buffer_factory);
+ virtual ~ScreenCapturerWin();
+
+ // Overridden from ScreenCapturer:
+ virtual void Start(Delegate* delegate) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE;
+ virtual void CaptureFrame() OVERRIDE;
+
+ private:
+ // Make sure that the device contexts match the screen configuration.
+ void PrepareCaptureResources();
+
+ // Creates a ScreenCaptureData instance wrapping the current framebuffer and
+ // notifies |delegate_|.
+ void CaptureRegion(const SkRegion& region,
+ const base::Time& capture_start_time);
+
+ // 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* dst);
+
+ // Capture the current cursor shape.
+ void CaptureCursor();
+
+ // Used to allocate shared memory buffers if set.
+ SharedBufferFactory* shared_buffer_factory_;
+
+ Delegate* delegate_;
+
+ // 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.
+ scoped_ptr<base::win::ScopedGetDC> desktop_dc_;
+ base::win::ScopedCreateDC memory_dc_;
+
+ // Queue of the frames buffers.
+ ScreenCaptureFrameQueue queue_;
+
+ // Rectangle describing the bounds of the desktop device context.
+ SkIRect desktop_dc_rect_;
+
+ // Class to calculate the difference between two screen bitmaps.
+ scoped_ptr<Differ> differ_;
+
+ base::ScopedNativeLibrary dwmapi_library_;
+ DwmEnableCompositionFunc composition_func_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWin);
+};
+
+// 3780 pixels per meter is equivalent to 96 DPI, typical on desktop monitors.
+static const int kPixelsPerMeter = 3780;
+
+ScreenCaptureFrameWin::ScreenCaptureFrameWin(
+ HDC desktop_dc,
+ const SkISize& size,
+ SharedBufferFactory* shared_buffer_factory)
+ : shared_buffer_factory_(shared_buffer_factory) {
+ // Allocate a shared memory buffer.
+ uint32 buffer_size =
+ size.width() * size.height() * ScreenCaptureData::kBytesPerPixel;
+ if (shared_buffer_factory_) {
+ scoped_refptr<SharedBuffer> shared_buffer =
+ shared_buffer_factory_->CreateSharedBuffer(buffer_size);
+ CHECK(shared_buffer->ptr() != NULL);
+ set_shared_buffer(shared_buffer);
+ }
+
+ AllocateBitmap(desktop_dc, size);
+}
+
+ScreenCaptureFrameWin::~ScreenCaptureFrameWin() {
+ if (shared_buffer())
+ shared_buffer_factory_->ReleaseSharedBuffer(shared_buffer());
+}
+
+HBITMAP ScreenCaptureFrameWin::GetBitmap() {
+ return bitmap_;
+}
+
+void ScreenCaptureFrameWin::AllocateBitmap(HDC desktop_dc,
+ const SkISize& size) {
+ int bytes_per_row = size.width() * ScreenCaptureData::kBytesPerPixel;
+
+ // Describe a device independent bitmap (DIB) that is the size of the desktop.
+ BITMAPINFO bmi;
+ memset(&bmi, 0, sizeof(bmi));
+ bmi.bmiHeader.biHeight = -size.height();
+ bmi.bmiHeader.biWidth = size.width();
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = ScreenCaptureData::kBytesPerPixel * 8;
+ bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
+ bmi.bmiHeader.biSizeImage = bytes_per_row * size.height();
+ bmi.bmiHeader.biXPelsPerMeter = kPixelsPerMeter;
+ bmi.bmiHeader.biYPelsPerMeter = kPixelsPerMeter;
+
+ // Create the DIB, and store a pointer to its pixel buffer.
+ HANDLE section_handle = NULL;
+ if (shared_buffer())
+ section_handle = shared_buffer()->handle();
+ void* data = NULL;
+ bitmap_ = CreateDIBSection(desktop_dc, &bmi, DIB_RGB_COLORS, &data,
+ section_handle, 0);
+
+ // TODO(wez): Cope gracefully with failure (crbug.com/157170).
+ CHECK(bitmap_ != NULL);
+ CHECK(data != NULL);
+
+ set_pixels(reinterpret_cast<uint8*>(data));
+ set_dimensions(SkISize::Make(bmi.bmiHeader.biWidth,
+ std::abs(bmi.bmiHeader.biHeight)));
+ set_bytes_per_row(
+ bmi.bmiHeader.biSizeImage / std::abs(bmi.bmiHeader.biHeight));
+}
+
+ScreenCapturerWin::ScreenCapturerWin()
+ : shared_buffer_factory_(NULL),
+ delegate_(NULL),
+ desktop_dc_rect_(SkIRect::MakeEmpty()),
+ composition_func_(NULL) {
+}
+
+ScreenCapturerWin::ScreenCapturerWin(
+ SharedBufferFactory* shared_buffer_factory)
+ : shared_buffer_factory_(shared_buffer_factory),
+ delegate_(NULL),
+ desktop_dc_rect_(SkIRect::MakeEmpty()),
+ composition_func_(NULL) {
+}
+
+ScreenCapturerWin::~ScreenCapturerWin() {
+}
+
+void ScreenCapturerWin::InvalidateRegion(const SkRegion& invalid_region) {
+ helper_.InvalidateRegion(invalid_region);
+}
+
+void ScreenCapturerWin::CaptureFrame() {
+ base::Time capture_start_time = base::Time::Now();
+
+ // Force the system to power-up display hardware, if it has been suspended.
+ SetThreadExecutionState(ES_DISPLAY_REQUIRED);
+
+ // Make sure the GDI capture resources are up-to-date.
+ PrepareCaptureResources();
+
+ // Copy screen bits to the current buffer.
+ CaptureImage();
+
+ const ScreenCaptureFrame* current_buffer = queue_.current_frame();
+ const ScreenCaptureFrame* last_buffer = queue_.previous_frame();
+ if (last_buffer) {
+ // Make sure the differencer is set up correctly for these previous and
+ // current screens.
+ if (!differ_.get() ||
+ (differ_->width() != current_buffer->dimensions().width()) ||
+ (differ_->height() != current_buffer->dimensions().height()) ||
+ (differ_->bytes_per_row() != current_buffer->bytes_per_row())) {
+ differ_.reset(new Differ(current_buffer->dimensions().width(),
+ current_buffer->dimensions().height(),
+ ScreenCaptureData::kBytesPerPixel,
+ current_buffer->bytes_per_row()));
+ }
+
+ // Calculate difference between the two last captured frames.
+ SkRegion region;
+ differ_->CalcDirtyRegion(last_buffer->pixels(), current_buffer->pixels(),
+ &region);
+ InvalidateRegion(region);
+ } else {
+ // No previous frame is available. Invalidate the whole screen.
+ helper_.InvalidateScreen(current_buffer->dimensions());
+ }
+
+ // Wrap the captured frame into ScreenCaptureData structure and invoke
+ // the completion callback.
+ SkRegion invalid_region;
+ helper_.SwapInvalidRegion(&invalid_region);
+ CaptureRegion(invalid_region, capture_start_time);
+
+ // Check for cursor shape update.
+ CaptureCursor();
+}
+
+void ScreenCapturerWin::Start(Delegate* delegate) {
+ DCHECK(delegate_ == NULL);
+
+ delegate_ = delegate;
+
+ // Load dwmapi.dll dynamically since it is not available on XP.
+ if (!dwmapi_library_.is_valid()) {
+ FilePath path(base::GetNativeLibraryName(UTF8ToUTF16(kDwmapiLibraryName)));
+ dwmapi_library_.Reset(base::LoadNativeLibrary(path, NULL));
+ }
+
+ if (dwmapi_library_.is_valid() && composition_func_ == NULL) {
+ composition_func_ = static_cast<DwmEnableCompositionFunc>(
+ dwmapi_library_.GetFunctionPointer("DwmEnableComposition"));
+ }
+
+ // 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_ != NULL) {
+ (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
+ }
+}
+
+void ScreenCapturerWin::Stop() {
+ // Restore Aero.
+ if (composition_func_ != NULL) {
+ (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
+ }
+
+ delegate_ = NULL;
+}
+
+void ScreenCapturerWin::PrepareCaptureResources() {
+ // Switch to the desktop receiving user input if different from the current
+ // one.
+ scoped_ptr<Desktop> input_desktop = Desktop::GetInputDesktop();
+ if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
+ // Release GDI resources otherwise SetThreadDesktop will fail.
+ desktop_dc_.reset();
+ memory_dc_.Set(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.Pass());
+ }
+
+ // If the display bounds have changed then recreate GDI resources.
+ // TODO(wez): Also check for pixel format changes.
+ SkIRect screen_rect(SkIRect::MakeXYWH(
+ GetSystemMetrics(SM_XVIRTUALSCREEN),
+ GetSystemMetrics(SM_YVIRTUALSCREEN),
+ GetSystemMetrics(SM_CXVIRTUALSCREEN),
+ GetSystemMetrics(SM_CYVIRTUALSCREEN)));
+ if (screen_rect != desktop_dc_rect_) {
+ desktop_dc_.reset();
+ memory_dc_.Set(NULL);
+ desktop_dc_rect_.setEmpty();
+ }
+
+ if (desktop_dc_.get() == NULL) {
+ DCHECK(memory_dc_.Get() == NULL);
+
+ // Create GDI device contexts to capture from the desktop into memory.
+ desktop_dc_.reset(new base::win::ScopedGetDC(NULL));
+ memory_dc_.Set(CreateCompatibleDC(*desktop_dc_));
+ desktop_dc_rect_ = screen_rect;
+
+ // Make sure the frame buffers will be reallocated.
+ queue_.SetAllFramesNeedUpdate();
+
+ helper_.ClearInvalidRegion();
+ }
+}
+
+void ScreenCapturerWin::CaptureRegion(
+ const SkRegion& region,
+ const base::Time& capture_start_time) {
+ const ScreenCaptureFrame* current_buffer = queue_.current_frame();
+
+ scoped_refptr<ScreenCaptureData> data(new ScreenCaptureData(
+ current_buffer->pixels(), current_buffer->bytes_per_row(),
+ current_buffer->dimensions()));
+ data->mutable_dirty_region() = region;
+ data->set_shared_buffer(current_buffer->shared_buffer());
+
+ helper_.set_size_most_recent(data->size());
+
+ queue_.DoneWithCurrentFrame();
+
+ data->set_capture_time_ms(
+ (base::Time::Now() - capture_start_time).InMillisecondsRoundedUp());
+ delegate_->OnCaptureCompleted(data);
+}
+
+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_needs_update()) {
+ DCHECK(desktop_dc_.get() != NULL);
+ DCHECK(memory_dc_.Get() != NULL);
+
+ SkISize size = SkISize::Make(desktop_dc_rect_.width(),
+ desktop_dc_rect_.height());
+ scoped_ptr<ScreenCaptureFrameWin> buffer(
+ new ScreenCaptureFrameWin(*desktop_dc_, size, shared_buffer_factory_));
+ queue_.ReplaceCurrentFrame(buffer.PassAs<ScreenCaptureFrame>());
+ }
+
+ // Select the target bitmap into the memory dc and copy the rect from desktop
+ // to memory.
+ ScreenCaptureFrameWin* current = static_cast<ScreenCaptureFrameWin*>(
+ queue_.current_frame());
+ HGDIOBJ previous_object = SelectObject(memory_dc_, current->GetBitmap());
+ if (previous_object != NULL) {
+ BitBlt(memory_dc_,
+ 0, 0, desktop_dc_rect_.width(), desktop_dc_rect_.height(),
+ *desktop_dc_,
+ desktop_dc_rect_.x(), desktop_dc_rect_.y(),
+ 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* 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)) {
+ VLOG(3) << "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)) {
+ VLOG(3) << "Unable to get cursor icon info. Error = " << GetLastError();
+ return;
+ }
+ int hotspot_x = iinfo.xHotspot;
+ int hotspot_y = iinfo.yHotspot;
+
+ // Get the cursor bitmap.
+ base::win::ScopedBitmap hbitmap;
+ BITMAP bitmap;
+ bool color_bitmap;
+ if (iinfo.hbmColor) {
+ // Color cursor bitmap.
+ color_bitmap = true;
+ hbitmap.Set((HBITMAP)CopyImage(iinfo.hbmColor, IMAGE_BITMAP, 0, 0,
+ LR_CREATEDIBSECTION));
+ if (!hbitmap.Get()) {
+ VLOG(3) << "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.Set(iinfo.hbmMask);
+ }
+
+ if (!GetObject(hbitmap.Get(), sizeof(BITMAP), &bitmap)) {
+ VLOG(3) << "Unable to get cursor bitmap. Error = " << GetLastError();
+ 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 * ScreenCaptureData::kBytesPerPixel;
+
+ scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape());
+ cursor->data.resize(data_size);
+ uint8* cursor_dst_data =
+ reinterpret_cast<uint8*>(string_as_array(&cursor->data));
+
+ // Copy/convert cursor bitmap into format needed by chromotocol.
+ int row_bytes = bitmap.bmWidthBytes;
+ if (color_bitmap) {
+ if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 32) {
+ VLOG(3) << "Unsupported color cursor format. Error = " << GetLastError();
+ 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* cursor_src_data = reinterpret_cast<uint8*>(bitmap.bmBits);
+ uint8* src = cursor_src_data + ((height - 1) * row_bytes);
+ uint8* dst = cursor_dst_data;
+ for (int row = 0; row < height; ++row) {
+ for (int column = 0; column < width; ++column) {
+ dst[0] = SkAlphaMul(src[0], src[3]);
+ dst[1] = SkAlphaMul(src[1], src[3]);
+ dst[2] = SkAlphaMul(src[2], src[3]);
+ dst[3] = src[3];
+ dst += ScreenCaptureData::kBytesPerPixel;
+ src += ScreenCaptureData::kBytesPerPixel;
+ }
+ src -= row_bytes + (width * ScreenCaptureData::kBytesPerPixel);
+ }
+ } else {
+ if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 1) {
+ VLOG(3) << "Unsupported cursor mask format. Error = " << GetLastError();
+ return;
+ }
+
+ // x2 because there are 2 masks in the bitmap: AND and XOR.
+ int mask_bytes = height * row_bytes * 2;
+ scoped_array<uint8> mask(new uint8[mask_bytes]);
+ if (!GetBitmapBits(hbitmap.Get(), mask_bytes, mask.get())) {
+ VLOG(3) << "Unable to get cursor mask bits. Error = " << GetLastError();
+ return;
+ }
+ uint8* and_mask = mask.get();
+ uint8* xor_mask = mask.get() + height * row_bytes;
+ uint8* 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 = and_mask[byte] & (1 << bit);
+ int xor = 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 && xor) ? 0xff : 0x00;
+ int alpha = (and && !xor) ? 0x00 : 0xff;
+ *dst++ = rgb;
+ *dst++ = rgb;
+ *dst++ = rgb;
+ *dst++ = alpha;
+ if (and && xor) {
+ add_outline = true;
+ }
+ }
+ }
+ if (add_outline) {
+ AddCursorOutline(width, height,
+ reinterpret_cast<uint32*>(cursor_dst_data));
+ }
+ }
+
+ 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 == cursor->size &&
+ last_cursor_.hotspot == cursor->hotspot &&
+ last_cursor_.data == cursor->data) {
+ return;
+ }
+
+ VLOG(3) << "Sending updated cursor: " << width << "x" << height;
+
+ // Record the last cursor image that we sent to the client.
+ last_cursor_ = *cursor;
+
+ delegate_->OnCursorShapeChanged(cursor.Pass());
+}
+
+} // namespace
+
+// static
+scoped_ptr<ScreenCapturer> ScreenCapturer::Create() {
+ return scoped_ptr<ScreenCapturer>(new ScreenCapturerWin());
+}
+
+// static
+scoped_ptr<ScreenCapturer> ScreenCapturer::CreateWithFactory(
+ SharedBufferFactory* shared_buffer_factory) {
+ scoped_ptr<ScreenCapturerWin> capturer(
+ new ScreenCapturerWin(shared_buffer_factory));
+ return capturer.PassAs<ScreenCapturer>();
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/shared_buffer.cc b/media/video/capture/screen/shared_buffer.cc
new file mode 100644
index 0000000..33eb5c3
--- /dev/null
+++ b/media/video/capture/screen/shared_buffer.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/shared_buffer.h"
+
+const bool kReadOnly = true;
+
+namespace media {
+
+SharedBuffer::SharedBuffer(uint32 size)
+ : id_(0),
+ size_(size) {
+ shared_memory_.CreateAndMapAnonymous(size);
+}
+
+SharedBuffer::SharedBuffer(
+ int id, base::SharedMemoryHandle handle, uint32 size)
+ : id_(id),
+ shared_memory_(handle, kReadOnly),
+ size_(size) {
+ shared_memory_.Map(size);
+}
+
+SharedBuffer::SharedBuffer(
+ int id, base::SharedMemoryHandle handle, base::ProcessHandle process,
+ uint32 size)
+ : id_(id),
+ shared_memory_(handle, kReadOnly, process),
+ size_(size) {
+ shared_memory_.Map(size);
+}
+
+SharedBuffer::~SharedBuffer() {
+}
+
+bool SharedBuffer::ShareToProcess(base::ProcessHandle process,
+ base::SharedMemoryHandle* new_handle) {
+ return shared_memory_.ShareToProcess(process, new_handle);
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/shared_buffer.h b/media/video/capture/screen/shared_buffer.h
new file mode 100644
index 0000000..4c77b6a0
--- /dev/null
+++ b/media/video/capture/screen/shared_buffer.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SHARED_BUFFER_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SHARED_BUFFER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/process.h"
+#include "base/shared_memory.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Represents a memory buffer that can be shared between multiple processes.
+// It is more or less a convenience wrapper around base::SharedMemory providing
+// ref-counted lifetime management and unique buffer identifiers.
+class MEDIA_EXPORT SharedBuffer
+ : public base::RefCountedThreadSafe<SharedBuffer> {
+ public:
+ // Creates a new shared memory buffer of the given size and maps it to
+ // the memory of the calling process. If the operation fails for any reason,
+ // ptr() method will return NULL. This constructor set the identifier of this
+ // buffer to 0.
+ explicit SharedBuffer(uint32 size);
+
+ // Opens an existing shared memory buffer and maps it to the memory of
+ // the calling process (in read only mode). If the operation fails for any
+ // reason, ptr() method will return NULL.
+ SharedBuffer(int id, base::SharedMemoryHandle handle, uint32 size);
+
+ // Opens an existing shared memory buffer created by a different process and
+ // maps it to the memory of the calling process (in read only mode). If
+ // the operation fails for any reason, ptr() method will return NULL.
+ SharedBuffer(int id, base::SharedMemoryHandle handle,
+ base::ProcessHandle process, uint32 size);
+
+ // Returns pointer to the beginning of the allocated data buffer. Returns NULL
+ // if the object initialization failed for any reason.
+ void* ptr() const { return shared_memory_.memory(); }
+
+ // Returns handle of the shared memory section containing the allocated
+ // data buffer.
+ base::SharedMemoryHandle handle() const { return shared_memory_.handle(); }
+
+ // Calls SharedMemory::ShareToProcess to share the handle with a different
+ // process.
+ bool ShareToProcess(base::ProcessHandle process,
+ base::SharedMemoryHandle* new_handle);
+
+ int id() const { return id_; }
+ uint32 size() const { return size_; }
+
+ void set_id(int id) { id_ = id; }
+
+ private:
+ friend class base::RefCountedThreadSafe<SharedBuffer>;
+ virtual ~SharedBuffer();
+
+ // Unique identifier of the buffer or 0 if ID hasn't been set.
+ int id_;
+
+ // Shared memory section backing up the buffer.
+ base::SharedMemory shared_memory_;
+
+ // Size of the buffer in bytes.
+ uint32 size_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedBuffer);
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SHARED_BUFFER_H_
diff --git a/media/video/capture/screen/shared_buffer_factory.h b/media/video/capture/screen/shared_buffer_factory.h
new file mode 100644
index 0000000..f5bafc6
--- /dev/null
+++ b/media/video/capture/screen/shared_buffer_factory.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_SHARED_BUFFER_FACTORY_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_SHARED_BUFFER_FACTORY_H_
+
+namespace media {
+
+class SharedBuffer;
+
+// Provides a way to create shared buffers accessible by two or more processes.
+class SharedBufferFactory {
+ public:
+ // Creates a shared memory buffer of the given size.
+ virtual scoped_refptr<SharedBuffer> CreateSharedBuffer(uint32 size) = 0;
+
+ // Notifies the factory that the buffer is no longer used by the caller and
+ // can be released. The caller still has to drop all references to free
+ // the memory.
+ virtual void ReleaseSharedBuffer(scoped_refptr<SharedBuffer> buffer) = 0;
+
+ protected:
+ virtual ~SharedBufferFactory() {}
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_SHARED_BUFFER_FACTORY_H_
diff --git a/media/video/capture/screen/shared_buffer_unittest.cc b/media/video/capture/screen/shared_buffer_unittest.cc
new file mode 100644
index 0000000..1efc798
--- /dev/null
+++ b/media/video/capture/screen/shared_buffer_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process_util.h"
+#include "media/video/capture/screen/shared_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const uint32 kBufferSize = 4096;
+const int kPattern = 0x12345678;
+
+const int kIdZero = 0;
+const int kIdOne = 1;
+
+namespace media {
+
+TEST(SharedBufferTest, Basic) {
+ scoped_refptr<SharedBuffer> source(new SharedBuffer(kBufferSize));
+
+ // Make sure that the buffer is allocated, the size is recorded correctly and
+ // its ID is reset.
+ EXPECT_TRUE(source->ptr() != NULL);
+ EXPECT_EQ(source->id(), kIdZero);
+ EXPECT_EQ(source->size(), kBufferSize);
+
+ // See if setting of the ID works.
+ source->set_id(kIdOne);
+ EXPECT_EQ(source->id(), kIdOne);
+
+ base::SharedMemoryHandle copied_handle;
+ EXPECT_TRUE(source->ShareToProcess(
+ base::GetCurrentProcessHandle(), &copied_handle));
+
+ scoped_refptr<SharedBuffer> dest(
+ new SharedBuffer(kIdZero, copied_handle, kBufferSize));
+
+ // Make sure that the buffer is allocated, the size is recorded correctly and
+ // its ID is reset.
+ EXPECT_TRUE(dest->ptr() != NULL);
+ EXPECT_EQ(dest->id(), kIdZero);
+ EXPECT_EQ(dest->size(), kBufferSize);
+
+ // Verify that the memory contents are the same for the two buffers.
+ int* source_ptr = reinterpret_cast<int*>(source->ptr());
+ *source_ptr = kPattern;
+ int* dest_ptr = reinterpret_cast<int*>(dest->ptr());
+ EXPECT_EQ(*source_ptr, *dest_ptr);
+
+ // Check that the destination buffer is still mapped even when the source
+ // buffer is destroyed.
+ source = NULL;
+ EXPECT_EQ(0x12345678, *dest_ptr);
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/win/desktop.cc b/media/video/capture/screen/win/desktop.cc
new file mode 100644
index 0000000..2b6cbb7
--- /dev/null
+++ b/media/video/capture/screen/win/desktop.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/win/desktop.h"
+
+#include <vector>
+
+#include "base/logging.h"
+
+namespace media {
+
+Desktop::Desktop(HDESK desktop, bool own) : desktop_(desktop), own_(own) {
+}
+
+Desktop::~Desktop() {
+ if (own_ && desktop_ != NULL) {
+ if (!::CloseDesktop(desktop_)) {
+ LOG_GETLASTERROR(ERROR)
+ << "Failed to close the owned desktop handle";
+ }
+ }
+}
+
+bool Desktop::GetName(string16* desktop_name_out) const {
+ if (desktop_ == NULL)
+ return false;
+
+ DWORD length;
+ CHECK(!GetUserObjectInformationW(desktop_, UOI_NAME, NULL, 0, &length));
+ CHECK(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+
+ length /= sizeof(char16);
+ std::vector<char16> buffer(length);
+ if (!GetUserObjectInformationW(desktop_, UOI_NAME, &buffer[0],
+ length * sizeof(char16), &length)) {
+ LOG_GETLASTERROR(ERROR)
+ << "Failed to query the desktop name";
+ return false;
+ }
+
+ desktop_name_out->assign(&buffer[0], length / sizeof(char16));
+ return true;
+}
+
+bool Desktop::IsSame(const Desktop& other) const {
+ string16 name;
+ if (!GetName(&name))
+ return false;
+
+ string16 other_name;
+ if (!other.GetName(&other_name))
+ return false;
+
+ return name == other_name;
+}
+
+bool Desktop::SetThreadDesktop() const {
+ if (!::SetThreadDesktop(desktop_)) {
+ LOG_GETLASTERROR(ERROR)
+ << "Failed to assign the desktop to the current thread";
+ return false;
+ }
+
+ return true;
+}
+
+scoped_ptr<Desktop> Desktop::GetDesktop(const wchar_t* 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_GETLASTERROR(ERROR)
+ << "Failed to open the desktop '" << desktop_name << "'";
+ return scoped_ptr<Desktop>();
+ }
+
+ return scoped_ptr<Desktop>(new Desktop(desktop, true));
+}
+
+scoped_ptr<Desktop> Desktop::GetInputDesktop() {
+ HDESK desktop = OpenInputDesktop(
+ 0, FALSE, GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE);
+ if (desktop == NULL)
+ return scoped_ptr<Desktop>();
+
+ return scoped_ptr<Desktop>(new Desktop(desktop, true));
+}
+
+scoped_ptr<Desktop> Desktop::GetThreadDesktop() {
+ HDESK desktop = ::GetThreadDesktop(GetCurrentThreadId());
+ if (desktop == NULL) {
+ LOG_GETLASTERROR(ERROR)
+ << "Failed to retrieve the handle of the desktop assigned to "
+ "the current thread";
+ return scoped_ptr<Desktop>();
+ }
+
+ return scoped_ptr<Desktop>(new Desktop(desktop, false));
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/win/desktop.h b/media/video/capture/screen/win/desktop.h
new file mode 100644
index 0000000..69c83b4
--- /dev/null
+++ b/media/video/capture/screen/win/desktop.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_WIN_DESKTOP_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_WIN_DESKTOP_H_
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string16.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class MEDIA_EXPORT 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(string16* 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 scoped_ptr<Desktop> GetDesktop(const wchar_t* desktop_name);
+
+ // Returns the desktop currently receiving user input or NULL if an error
+ // occurs.
+ static scoped_ptr<Desktop> GetInputDesktop();
+
+ // Returns the desktop currently assigned to the calling thread or NULL if
+ // an error occurs.
+ static scoped_ptr<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 media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_WIN_DESKTOP_H_
diff --git a/media/video/capture/screen/win/scoped_thread_desktop.cc b/media/video/capture/screen/win/scoped_thread_desktop.cc
new file mode 100644
index 0000000..8e9a80a
--- /dev/null
+++ b/media/video/capture/screen/win/scoped_thread_desktop.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/capture/screen/win/scoped_thread_desktop.h"
+
+#include "base/logging.h"
+
+#include "media/video/capture/screen/win/desktop.h"
+
+namespace media {
+
+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(scoped_ptr<Desktop> desktop) {
+ Revert();
+
+ if (initial_->IsSame(*desktop))
+ return true;
+
+ if (!desktop->SetThreadDesktop())
+ return false;
+
+ assigned_ = desktop.Pass();
+ return true;
+}
+
+} // namespace media
diff --git a/media/video/capture/screen/win/scoped_thread_desktop.h b/media/video/capture/screen/win/scoped_thread_desktop.h
new file mode 100644
index 0000000..1a9259f
--- /dev/null
+++ b/media/video/capture/screen/win/scoped_thread_desktop.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_CAPTURE_SCREEN_WIN_SCOPED_THREAD_DESKTOP_H_
+#define MEDIA_VIDEO_CAPTURE_SCREEN_WIN_SCOPED_THREAD_DESKTOP_H_
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class Desktop;
+
+class MEDIA_EXPORT 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.
+ bool SetThreadDesktop(scoped_ptr<Desktop> desktop);
+
+ private:
+ // The desktop handle assigned to the calling thread by Set
+ scoped_ptr<Desktop> assigned_;
+
+ // The desktop handle assigned to the calling thread at creation.
+ scoped_ptr<Desktop> initial_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedThreadDesktop);
+};
+
+} // namespace media
+
+#endif // MEDIA_VIDEO_CAPTURE_SCREEN_WIN_SCOPED_THREAD_DESKTOP_H_