diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-28 23:54:03 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-28 23:54:03 +0000 |
commit | 497d8a9857e7383f979b89694aff8dd403c176fe (patch) | |
tree | 2f07bb42d4a7a8e94e959c90dacbfa62eed1debd /media/video | |
parent | bac4ae80a786d68bfc18ca46f0adfcc4a1c43b0f (diff) | |
download | chromium_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')
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(®ion); + ASSERT_TRUE(region.isEmpty()); +} + +TEST_F(ScreenCapturerHelperTest, InvalidateRegion) { + SkRegion region; + capturer_helper_.SwapInvalidRegion(®ion); + ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeEmpty()), region)); + + capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(1, 2, 3, 4))); + region.setEmpty(); + capturer_helper_.SwapInvalidRegion(®ion); + 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(®ion); + 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(®ion); + 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(®ion); + 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(®ion); + ASSERT_TRUE(Equals(SkRegion(SkIRect::MakeEmpty()), region)); + + capturer_helper_.InvalidateRegion(SkRegion(SkIRect::MakeXYWH(7, 7, 1, 1))); + region.setEmpty(); + capturer_helper_.SwapInvalidRegion(®ion); + 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(®ion); + 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(®ion); + 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(®ion); + 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(®ion); + 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(®ion); + 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(®ion); + + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (queue_.current_frame_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(), + ®ion); + 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_ |