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