diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn index 6a1564b7d2..fa58999572 100644 --- a/webrtc/modules/BUILD.gn +++ b/webrtc/modules/BUILD.gn @@ -8,10 +8,11 @@ import("../build/webrtc.gni") import("audio_coding/audio_coding.gni") +import("desktop_capture/desktop_capture.gni") declare_args() { # Desktop capturer is supported only on Windows, OSX and Linux. - rtc_desktop_capture_supported = is_win || is_mac || is_linux + rtc_desktop_capture_supported = rtc_desktop_capture_supported } group("modules") { @@ -102,8 +103,11 @@ if (rtc_include_tests) { ] if (rtc_desktop_capture_supported) { - deps += [ "desktop_capture:desktop_capture_test_tools" ] - sources += [ "desktop_capture/screen_capturer_integration_test.cc" ] + deps += [ "desktop_capture:screen_drawer" ] + sources += [ + "desktop_capture/screen_capturer_integration_test.cc", + "desktop_capture/screen_drawer_unittest.cc", + ] } data = modules_tests_resources @@ -545,14 +549,10 @@ if (rtc_include_tests) { } if (rtc_desktop_capture_supported) { - deps += [ "desktop_capture:desktop_capture_test_tools" ] + deps += [ "desktop_capture:desktop_capture_mock" ] sources += [ "desktop_capture/desktop_and_cursor_composer_unittest.cc", - "desktop_capture/desktop_frame_generator.cc", - "desktop_capture/desktop_frame_generator.h", - "desktop_capture/fake_desktop_capturer.h", - "desktop_capture/fake_screen_capturer.cc", - "desktop_capture/fake_screen_capturer.h", + "desktop_capture/desktop_capturer_differ_wrapper_unittest.cc", "desktop_capture/mouse_cursor_monitor_unittest.cc", "desktop_capture/rgba_color_unittest.cc", "desktop_capture/screen_capturer_differ_wrapper_unittest.cc", @@ -560,7 +560,6 @@ if (rtc_include_tests) { "desktop_capture/screen_capturer_mac_unittest.cc", "desktop_capture/screen_capturer_mock_objects.h", "desktop_capture/screen_capturer_unittest.cc", - "desktop_capture/screen_drawer_unittest.cc", "desktop_capture/win/cursor_unittest.cc", "desktop_capture/win/cursor_unittest_resources.h", "desktop_capture/win/cursor_unittest_resources.rc", diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn index 5529537f5f..1cb421ef35 100644 --- a/webrtc/modules/desktop_capture/BUILD.gn +++ b/webrtc/modules/desktop_capture/BUILD.gn @@ -8,6 +8,7 @@ import("//build/config/ui.gni") import("../../build/webrtc.gni") +import("desktop_capture.gni") use_desktop_capture_differ_sse2 = !is_ios && (current_cpu == "x86" || current_cpu == "x64") @@ -31,7 +32,7 @@ rtc_static_library("primitives") { } if (rtc_include_tests) { - source_set("desktop_capture_test_tools") { + source_set("rgba_color") { testonly = true public_deps = [ @@ -41,6 +42,17 @@ if (rtc_include_tests) { sources = [ "rgba_color.cc", "rgba_color.h", + ] + } + + source_set("screen_drawer") { + testonly = true + + public_deps = [ + ":rgba_color", + ] + + sources = [ "screen_drawer.cc", "screen_drawer.h", "screen_drawer_linux.cc", @@ -48,6 +60,25 @@ if (rtc_include_tests) { "screen_drawer_win.cc", ] } + + source_set("desktop_capture_mock") { + testonly = true + + public_deps = [ + ":desktop_capture", + ":rgba_color", + ] + + sources = [ + "desktop_frame_generator.cc", + "desktop_frame_generator.h", + "fake_desktop_capturer.h", + "fake_screen_capturer.cc", + "fake_screen_capturer.h", + "mock_desktop_capturer_callback.h", + "screen_capturer_mock_objects.h", + ] + } } rtc_static_library("desktop_capture") { @@ -151,6 +182,8 @@ rtc_static_library("desktop_capture") { if (!is_ios) { sources += [ + "desktop_capturer_differ_wrapper.cc", + "desktop_capturer_differ_wrapper.h", "differ_block.cc", "differ_block.h", "screen_capturer_differ_wrapper.cc", @@ -182,6 +215,10 @@ rtc_static_library("desktop_capture") { if (use_desktop_capture_differ_sse2) { deps += [ ":desktop_capture_differ_sse2" ] } + + if (rtc_desktop_capture_supported) { + defines = [ "RTC_DESKTOP_CAPTURE_SUPPORTED" ] + } } if (use_desktop_capture_differ_sse2) { diff --git a/webrtc/modules/desktop_capture/desktop_capture.gni b/webrtc/modules/desktop_capture/desktop_capture.gni new file mode 100644 index 0000000000..02ad81472f --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capture.gni @@ -0,0 +1,11 @@ +# Copyright (c) 2016 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. + +import("../../build/webrtc.gni") + +rtc_desktop_capture_supported = is_win || is_mac || is_linux diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index ab1fd32f9f..b6d6f9992c 100644 --- a/webrtc/modules/desktop_capture/desktop_capture.gypi +++ b/webrtc/modules/desktop_capture/desktop_capture.gypi @@ -141,6 +141,8 @@ }], ['OS!="ios" ', { 'sources': [ + 'desktop_capturer_differ_wrapper.cc', + 'desktop_capturer_differ_wrapper.h', 'differ_block.cc', 'differ_block.h', 'screen_capturer_differ_wrapper.cc', diff --git a/webrtc/modules/desktop_capture/desktop_capturer.cc b/webrtc/modules/desktop_capture/desktop_capturer.cc index a18976a626..f28630e9c4 100644 --- a/webrtc/modules/desktop_capture/desktop_capturer.cc +++ b/webrtc/modules/desktop_capture/desktop_capturer.cc @@ -11,7 +11,7 @@ #include "webrtc/modules/desktop_capture/desktop_capturer.h" #include "webrtc/modules/desktop_capture/desktop_capture_options.h" -#include "webrtc/modules/desktop_capture/screen_capturer_differ_wrapper.h" +#include "webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.h" namespace webrtc { @@ -34,4 +34,32 @@ bool DesktopCapturer::FocusOnSelectedSource() { return false; } +// Some platforms, such as ChromeOS, have their DesktopCapturer implementations +// in Chromium. So even we do not have CreateRawWindowCapturer() or +// CreateRawScreenCapturer() implemented in WebRTC, we still need to build other +// DesktopCapturer components when RTC_DESKTOP_CAPTURE_SUPPORTED is not defined. +#if defined(RTC_DESKTOP_CAPTURE_SUPPORTED) +// static +std::unique_ptr DesktopCapturer::CreateWindowCapturer( + const DesktopCaptureOptions& options) { + std::unique_ptr capturer = CreateRawWindowCapturer(options); + if (options.detect_updated_region()) { + capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer))); + } + + return capturer; +} + +// static +std::unique_ptr DesktopCapturer::CreateScreenCapturer( + const DesktopCaptureOptions& options) { + std::unique_ptr capturer = CreateRawScreenCapturer(options); + if (options.detect_updated_region()) { + capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer))); + } + + return capturer; +} +#endif // defined(RTC_DESKTOP_CAPTURE_SUPPORTED) + } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_capturer.h b/webrtc/modules/desktop_capture/desktop_capturer.h index 6b9c820ce9..ecae4cb2e0 100644 --- a/webrtc/modules/desktop_capture/desktop_capturer.h +++ b/webrtc/modules/desktop_capture/desktop_capturer.h @@ -110,6 +110,28 @@ class DesktopCapturer { // Returns false in case of a failure or no source has been selected or the // implementation does not support this functionality. virtual bool FocusOnSelectedSource(); + + // Creates a DesktopCapturer instance which targets to capture windows. + static std::unique_ptr CreateWindowCapturer( + const DesktopCaptureOptions& options); + + // Creates a DesktopCapturer instance which targets to capture screens. + static std::unique_ptr CreateScreenCapturer( + const DesktopCaptureOptions& options); + + protected: + // CroppingWindowCapturer needs to create raw capturers without wrappers, so + // the following two functions are protected. + + // Creates a platform specific DesktopCapturer instance which targets to + // capture windows. + static std::unique_ptr CreateRawWindowCapturer( + const DesktopCaptureOptions& options); + + // Creates a platform specific DesktopCapturer instance which targets to + // capture screens. + static std::unique_ptr CreateRawScreenCapturer( + const DesktopCaptureOptions& options); }; } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.cc b/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.cc new file mode 100644 index 0000000000..1ad9ce4705 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.cc @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2016 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/desktop_capturer_differ_wrapper.h" + +#include + +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/differ_block.h" + +namespace webrtc { + +namespace { + +// Returns true if (0, 0) - (|width|, |height|) vector in |old_buffer| and +// |new_buffer| are equal. |width| should be less than 32 +// (defined by kBlockSize), otherwise BlockDifference() should be used. +bool PartialBlockDifference(const uint8_t* old_buffer, + const uint8_t* new_buffer, + int width, + int height, + int stride) { + RTC_DCHECK_LT(width, kBlockSize); + const int width_bytes = width * DesktopFrame::kBytesPerPixel; + for (int i = 0; i < height; i++) { + if (memcmp(old_buffer, new_buffer, width_bytes) != 0) { + return true; + } + old_buffer += stride; + new_buffer += stride; + } + return false; +} + +// Compares columns in the range of [|left|, |right|), in a row in the +// range of [|top|, |top| + |height|), starts from |old_buffer| and +// |new_buffer|, and outputs updated regions into |output|. |stride| is the +// DesktopFrame::stride(). +void CompareRow(const uint8_t* old_buffer, + const uint8_t* new_buffer, + const int left, + const int right, + const int top, + const int bottom, + const int stride, + DesktopRegion* const output) { + const int block_x_offset = kBlockSize * DesktopFrame::kBytesPerPixel; + const int width = right - left; + const int height = bottom - top; + const int block_count = (width - 1) / kBlockSize; + const int last_block_width = width - block_count * kBlockSize; + RTC_DCHECK(last_block_width <= kBlockSize && last_block_width > 0); + + // The first block-column in a continuous dirty area in current block-row. + int first_dirty_x_block = -1; + + // We always need to add dirty area into |output| in the last block, so handle + // it separatedly. + for (int x = 0; x < block_count; x++) { + if (BlockDifference(old_buffer, new_buffer, height, stride)) { + if (first_dirty_x_block == -1) { + // This is the first dirty block in a continuous dirty area. + first_dirty_x_block = x; + } + } else if (first_dirty_x_block != -1) { + // The block on the left is the last dirty block in a continuous + // dirty area. + output->AddRect( + DesktopRect::MakeLTRB(first_dirty_x_block * kBlockSize + left, top, + x * kBlockSize + left, bottom)); + first_dirty_x_block = -1; + } + old_buffer += block_x_offset; + new_buffer += block_x_offset; + } + + bool last_block_diff; + if (last_block_width < kBlockSize) { + // The last one is a partial vector. + last_block_diff = PartialBlockDifference(old_buffer, new_buffer, + last_block_width, height, stride); + } else { + last_block_diff = BlockDifference(old_buffer, new_buffer, height, stride); + } + if (last_block_diff) { + if (first_dirty_x_block == -1) { + first_dirty_x_block = block_count; + } + output->AddRect(DesktopRect::MakeLTRB( + first_dirty_x_block * kBlockSize + left, top, right, bottom)); + } else if (first_dirty_x_block != -1) { + output->AddRect( + DesktopRect::MakeLTRB(first_dirty_x_block * kBlockSize + left, top, + block_count * kBlockSize + left, bottom)); + } +} + +// Compares |rect| area in |old_frame| and |new_frame|, and outputs dirty +// regions into |output|. +void CompareFrames(const DesktopFrame& old_frame, + const DesktopFrame& new_frame, + DesktopRect rect, + DesktopRegion* const output) { + RTC_DCHECK(old_frame.size().equals(new_frame.size())); + RTC_DCHECK_EQ(old_frame.stride(), new_frame.stride()); + rect.IntersectWith(DesktopRect::MakeSize(old_frame.size())); + + const int y_block_count = (rect.height() - 1) / kBlockSize; + const int last_y_block_height = rect.height() - y_block_count * kBlockSize; + // Offset from the start of one block-row to the next. + const int block_y_stride = old_frame.stride() * kBlockSize; + const uint8_t* prev_block_row_start = + old_frame.GetFrameDataAtPos(rect.top_left()); + const uint8_t* curr_block_row_start = + new_frame.GetFrameDataAtPos(rect.top_left()); + + int top = rect.top(); + // The last row may have a different height, so we handle it separately. + for (int y = 0; y < y_block_count; y++) { + CompareRow(prev_block_row_start, curr_block_row_start, rect.left(), + rect.right(), top, top + kBlockSize, old_frame.stride(), output); + top += kBlockSize; + prev_block_row_start += block_y_stride; + curr_block_row_start += block_y_stride; + } + CompareRow(prev_block_row_start, curr_block_row_start, rect.left(), + rect.right(), top, top + last_y_block_height, old_frame.stride(), + output); +} + +} // namespace + +DesktopCapturerDifferWrapper::DesktopCapturerDifferWrapper( + std::unique_ptr base_capturer) + : base_capturer_(std::move(base_capturer)) { + RTC_DCHECK(base_capturer_); +} + +DesktopCapturerDifferWrapper::~DesktopCapturerDifferWrapper() {} + +void DesktopCapturerDifferWrapper::Start(DesktopCapturer::Callback* callback) { + callback_ = callback; + base_capturer_->Start(this); +} + +void DesktopCapturerDifferWrapper::SetSharedMemoryFactory( + std::unique_ptr shared_memory_factory) { + base_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory)); +} + +void DesktopCapturerDifferWrapper::CaptureFrame() { + base_capturer_->CaptureFrame(); +} + +void DesktopCapturerDifferWrapper::SetExcludedWindow(WindowId window) { + base_capturer_->SetExcludedWindow(window); +} + +bool DesktopCapturerDifferWrapper::GetSourceList(SourceList* sources) { + return base_capturer_->GetSourceList(sources); +} + +bool DesktopCapturerDifferWrapper::SelectSource(SourceId id) { + return base_capturer_->SelectSource(id); +} + +bool DesktopCapturerDifferWrapper::FocusOnSelectedSource() { + return base_capturer_->FocusOnSelectedSource(); +} + +void DesktopCapturerDifferWrapper::OnCaptureResult( + Result result, + std::unique_ptr input_frame) { + int64_t start_time_nanos = rtc::TimeNanos(); + if (!input_frame) { + callback_->OnCaptureResult(result, nullptr); + return; + } + RTC_DCHECK(result == Result::SUCCESS); + + std::unique_ptr frame = + SharedDesktopFrame::Wrap(std::move(input_frame)); + if (last_frame_ && (last_frame_->size().width() != frame->size().width() || + last_frame_->size().height() != frame->size().height() || + last_frame_->stride() != frame->stride())) { + last_frame_.reset(); + } + + if (last_frame_) { + DesktopRegion hints; + hints.Swap(frame->GetUnderlyingFrame()->mutable_updated_region()); + for (DesktopRegion::Iterator it(hints); !it.IsAtEnd(); it.Advance()) { + CompareFrames(*last_frame_, *frame, it.rect(), + frame->mutable_updated_region()); + } + } else { + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + } + last_frame_ = frame->Share(); + + frame->set_capture_time_ms(frame->GetUnderlyingFrame()->capture_time_ms() + + (rtc::TimeNanos() - start_time_nanos) / + rtc::kNumNanosecsPerMillisec); + callback_->OnCaptureResult(result, std::move(frame)); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.h b/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.h new file mode 100644 index 0000000000..0747a4ea1f --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016 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_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_ + +#include + +#include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/modules/desktop_capture/shared_desktop_frame.h" + +namespace webrtc { + +// DesktopCapturer wrapper that calculates updated_region() by comparing frames +// content. This class always expects the underlying DesktopCapturer +// implementation returns a superset of updated regions in DestkopFrame. If a +// DesktopCapturer implementation does not know the updated region, it should +// set updated_region() to full frame. +// +// This class marks entire frame as updated if the frame size or frame stride +// has been changed. +class DesktopCapturerDifferWrapper : public DesktopCapturer, + public DesktopCapturer::Callback { + public: + // Creates a DesktopCapturerDifferWrapper with a DesktopCapturer + // implementation, and takes its ownership. + explicit DesktopCapturerDifferWrapper( + std::unique_ptr base_capturer); + + ~DesktopCapturerDifferWrapper() override; + + // DesktopCapturer interface. + void Start(DesktopCapturer::Callback* callback) override; + void SetSharedMemoryFactory( + std::unique_ptr shared_memory_factory) override; + void CaptureFrame() override; + void SetExcludedWindow(WindowId window) override; + bool GetSourceList(SourceList* screens) override; + bool SelectSource(SourceId id) override; + bool FocusOnSelectedSource() override; + + private: + // DesktopCapturer::Callback interface. + void OnCaptureResult(Result result, + std::unique_ptr frame) override; + + const std::unique_ptr base_capturer_; + DesktopCapturer::Callback* callback_; + std::unique_ptr last_frame_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_ diff --git a/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper_unittest.cc b/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper_unittest.cc new file mode 100644 index 0000000000..594ad13274 --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_capturer_differ_wrapper_unittest.cc @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2016 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/desktop_capturer_differ_wrapper.h" + +#include +#include +#include +#include + +#include "webrtc/base/random.h" +#include "webrtc/base/timeutils.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/differ_block.h" +#include "webrtc/modules/desktop_capture/fake_screen_capturer.h" +#include "webrtc/modules/desktop_capture/mock_desktop_capturer_callback.h" +#include "webrtc/system_wrappers/include/cpu_features_wrapper.h" +#include "webrtc/test/gtest.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +namespace { + +// Compares and asserts |frame|.updated_region() equals to |rects|. This +// function does not care about the order of the |rects| and it does not expect +// DesktopRegion to return an exact area for each rectangle in |rects|. +template