From 8fefe9889d2e3a8fc781f842ee7cabfdce8efe3f Mon Sep 17 00:00:00 2001 From: zijiehe Date: Fri, 17 Feb 2017 14:32:04 -0800 Subject: [PATCH] [DesktopCapturer] FallbackDesktopCapturerWrapper and its tests FallbackDesktopCapturerWrapper is a DesktopCapturer implementation, which owns two DesktopCapturer implementations. If the main DesktopCapturer fails, it uses the secondary capturer. The logic is now used in ScreenCapturerWinMagnifier, and it can also be shared in ScreenCapturerWinDirectx to fallback to Gdi capturer on privilege prompt or login screen. BUG=684937 Review-Url: https://codereview.webrtc.org/2697453002 Cr-Commit-Position: refs/heads/master@{#16677} --- webrtc/modules/desktop_capture/BUILD.gn | 3 + .../desktop_capture/fake_desktop_capturer.cc | 21 +- .../desktop_capture/fake_desktop_capturer.h | 16 +- .../fallback_desktop_capturer_wrapper.cc | 159 ++++++++++++++ .../fallback_desktop_capturer_wrapper.h | 62 ++++++ ...lback_desktop_capturer_wrapper_unittest.cc | 205 ++++++++++++++++++ .../modules/desktop_capture/shared_memory.h | 1 + 7 files changed, 459 insertions(+), 8 deletions(-) create mode 100644 webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc create mode 100644 webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.h create mode 100644 webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper_unittest.cc diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn index 0c427993d4..151627de39 100644 --- a/webrtc/modules/desktop_capture/BUILD.gn +++ b/webrtc/modules/desktop_capture/BUILD.gn @@ -63,6 +63,7 @@ if (rtc_include_tests) { "desktop_frame_rotation_unittest.cc", "desktop_region_unittest.cc", "differ_block_unittest.cc", + "fallback_desktop_capturer_wrapper_unittest.cc", "mouse_cursor_monitor_unittest.cc", "rgba_color_unittest.cc", "test_utils.cc", @@ -185,6 +186,8 @@ rtc_static_library("desktop_capture") { "desktop_frame_win.h", "differ_block.cc", "differ_block.h", + "fallback_desktop_capturer_wrapper.cc", + "fallback_desktop_capturer_wrapper.h", "mac/desktop_configuration.h", "mac/desktop_configuration.mm", "mac/desktop_configuration_monitor.cc", diff --git a/webrtc/modules/desktop_capture/fake_desktop_capturer.cc b/webrtc/modules/desktop_capture/fake_desktop_capturer.cc index bd4c37e8b5..ea622650b8 100644 --- a/webrtc/modules/desktop_capture/fake_desktop_capturer.cc +++ b/webrtc/modules/desktop_capture/fake_desktop_capturer.cc @@ -14,17 +14,21 @@ namespace webrtc { -FakeDesktopCapturer::FakeDesktopCapturer() - : callback_(nullptr), - result_(DesktopCapturer::Result::SUCCESS), - generator_(nullptr) {} - +FakeDesktopCapturer::FakeDesktopCapturer() = default; FakeDesktopCapturer::~FakeDesktopCapturer() = default; void FakeDesktopCapturer::set_result(DesktopCapturer::Result result) { result_ = result; } +int FakeDesktopCapturer::num_frames_captured() const { + return num_frames_captured_; +} + +int FakeDesktopCapturer::num_capture_attempts() const { + return num_capture_attempts_; +} + // Uses the |generator| provided as DesktopFrameGenerator, FakeDesktopCapturer // does // not take the ownership of |generator|. @@ -38,10 +42,17 @@ void FakeDesktopCapturer::Start(DesktopCapturer::Callback* callback) { } void FakeDesktopCapturer::CaptureFrame() { + num_capture_attempts_++; if (generator_) { + if (result_ != DesktopCapturer::Result::SUCCESS) { + callback_->OnCaptureResult(result_, nullptr); + return; + } + std::unique_ptr frame( generator_->GetNextFrame(shared_memory_factory_.get())); if (frame) { + num_frames_captured_++; callback_->OnCaptureResult(result_, std::move(frame)); } else { callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY, diff --git a/webrtc/modules/desktop_capture/fake_desktop_capturer.h b/webrtc/modules/desktop_capture/fake_desktop_capturer.h index 33073a00f7..9cab2d6ede 100644 --- a/webrtc/modules/desktop_capture/fake_desktop_capturer.h +++ b/webrtc/modules/desktop_capture/fake_desktop_capturer.h @@ -43,6 +43,14 @@ class FakeDesktopCapturer : public DesktopCapturer { // does not take the ownership of |generator|. void set_frame_generator(DesktopFrameGenerator* generator); + // Count of DesktopFrame(s) have been returned by this instance. This field + // would never be negative. + int num_frames_captured() const; + + // Count of CaptureFrame() calls have been made. This field would never be + // negative. + int num_capture_attempts() const; + // DesktopCapturer interface void Start(DesktopCapturer::Callback* callback) override; void CaptureFrame() override; @@ -55,10 +63,12 @@ class FakeDesktopCapturer : public DesktopCapturer { static constexpr DesktopCapturer::SourceId kWindowId = 1378277495; static constexpr DesktopCapturer::SourceId kScreenId = 1378277496; - DesktopCapturer::Callback* callback_; + DesktopCapturer::Callback* callback_ = nullptr; std::unique_ptr shared_memory_factory_; - DesktopCapturer::Result result_; - DesktopFrameGenerator* generator_; + DesktopCapturer::Result result_ = Result::SUCCESS; + DesktopFrameGenerator* generator_ = nullptr; + int num_frames_captured_ = 0; + int num_capture_attempts_ = 0; }; } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc b/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc new file mode 100644 index 0000000000..e8762ada11 --- /dev/null +++ b/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc @@ -0,0 +1,159 @@ +/* + * 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/fallback_desktop_capturer_wrapper.h" + +#include + +#include "webrtc/base/checks.h" + +namespace webrtc { + +namespace { + +// Implementation to share a SharedMemoryFactory between DesktopCapturer +// instances. This class is designed for synchronized DesktopCapturer +// implementations only. +class SharedMemoryFactoryProxy : public SharedMemoryFactory { + public: + // Users should maintain the lifetime of |factory| to ensure it overlives + // current instance. + static std::unique_ptr Create( + SharedMemoryFactory* factory); + ~SharedMemoryFactoryProxy() override; + + // Forwards CreateSharedMemory() calls to |factory_|. Users should always call + // this function in one thread. Users should not call this function after the + // SharedMemoryFactory which current instance created from has been destroyed. + std::unique_ptr CreateSharedMemory(size_t size) override; + + private: + explicit SharedMemoryFactoryProxy(SharedMemoryFactory* factory); + + SharedMemoryFactory* factory_ = nullptr; + rtc::ThreadChecker thread_checker_; +}; + +} // namespace + +SharedMemoryFactoryProxy::SharedMemoryFactoryProxy( + SharedMemoryFactory* factory) { + RTC_DCHECK(factory); + factory_ = factory; +} + +// static +std::unique_ptr +SharedMemoryFactoryProxy::Create(SharedMemoryFactory* factory) { + return std::unique_ptr( + new SharedMemoryFactoryProxy(factory)); +} + +SharedMemoryFactoryProxy::~SharedMemoryFactoryProxy() = default; + +std::unique_ptr +SharedMemoryFactoryProxy::CreateSharedMemory(size_t size) { + RTC_DCHECK(thread_checker_.CalledOnValidThread()); + return factory_->CreateSharedMemory(size); +} + +FallbackDesktopCapturerWrapper::FallbackDesktopCapturerWrapper( + std::unique_ptr main_capturer, + std::unique_ptr secondary_capturer) + : main_capturer_(std::move(main_capturer)), + secondary_capturer_(std::move(secondary_capturer)) { + RTC_DCHECK(main_capturer_); + RTC_DCHECK(secondary_capturer_); +} + +FallbackDesktopCapturerWrapper::~FallbackDesktopCapturerWrapper() = default; + +void FallbackDesktopCapturerWrapper::Start( + DesktopCapturer::Callback* callback) { + // FallbackDesktopCapturerWrapper catchs the callback of the main capturer, + // and checks its return value to decide whether the secondary capturer should + // be involved. + main_capturer_->Start(this); + // For the secondary capturer, we do not have a backup plan anymore, so + // FallbackDesktopCapturerWrapper won't check its return value any more. It + // will directly return to the input |callback|. + secondary_capturer_->Start(callback); + callback_ = callback; +} + +void FallbackDesktopCapturerWrapper::SetSharedMemoryFactory( + std::unique_ptr shared_memory_factory) { + shared_memory_factory_ = std::move(shared_memory_factory); + if (shared_memory_factory_) { + main_capturer_->SetSharedMemoryFactory( + SharedMemoryFactoryProxy::Create(shared_memory_factory_.get())); + secondary_capturer_->SetSharedMemoryFactory( + SharedMemoryFactoryProxy::Create(shared_memory_factory_.get())); + } else { + main_capturer_->SetSharedMemoryFactory( + std::unique_ptr()); + secondary_capturer_->SetSharedMemoryFactory( + std::unique_ptr()); + } +} + +void FallbackDesktopCapturerWrapper::CaptureFrame() { + RTC_DCHECK(callback_); + if (main_capturer_permanent_error_) { + secondary_capturer_->CaptureFrame(); + } else { + main_capturer_->CaptureFrame(); + } +} + +void FallbackDesktopCapturerWrapper::SetExcludedWindow(WindowId window) { + main_capturer_->SetExcludedWindow(window); + secondary_capturer_->SetExcludedWindow(window); +} + +bool FallbackDesktopCapturerWrapper::GetSourceList(SourceList* sources) { + if (main_capturer_permanent_error_) { + return secondary_capturer_->GetSourceList(sources); + } + return main_capturer_->GetSourceList(sources); +} + +bool FallbackDesktopCapturerWrapper::SelectSource(SourceId id) { + if (main_capturer_permanent_error_) { + return secondary_capturer_->SelectSource(id); + } + return main_capturer_->SelectSource(id) && + secondary_capturer_->SelectSource(id); +} + +bool FallbackDesktopCapturerWrapper::FocusOnSelectedSource() { + if (main_capturer_permanent_error_) { + return secondary_capturer_->FocusOnSelectedSource(); + } + return main_capturer_->FocusOnSelectedSource() || + secondary_capturer_->FocusOnSelectedSource(); +} + +void FallbackDesktopCapturerWrapper::OnCaptureResult( + Result result, + std::unique_ptr frame) { + RTC_DCHECK(callback_); + if (result == Result::SUCCESS) { + callback_->OnCaptureResult(result, std::move(frame)); + return; + } + + if (result == Result::ERROR_PERMANENT) { + main_capturer_permanent_error_ = true; + } + secondary_capturer_->CaptureFrame(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.h b/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.h new file mode 100644 index 0000000000..1ef3b6f7c9 --- /dev/null +++ b/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.h @@ -0,0 +1,62 @@ +/* + * 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_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_ + +#include + +#include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/modules/desktop_capture/desktop_capture_types.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/shared_memory.h" + +namespace webrtc { + +// A DesktopCapturer wrapper owns two DesktopCapturer implementations. If the +// main DesktopCapturer fails, it uses the secondary one instead. Two capturers +// are expected to return same SourceList, and the meaning of each SourceId is +// identical, otherwise FallbackDesktopCapturerWrapper may return frames from +// different sources. Using asynchronized DesktopCapturer implementations with +// SharedMemoryFactory is not supported, and may result crash or assertion +// failure. +class FallbackDesktopCapturerWrapper final : public DesktopCapturer, + public DesktopCapturer::Callback { + public: + FallbackDesktopCapturerWrapper( + std::unique_ptr main_capturer, + std::unique_ptr secondary_capturer); + ~FallbackDesktopCapturerWrapper() 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* sources) 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 main_capturer_; + const std::unique_ptr secondary_capturer_; + std::unique_ptr shared_memory_factory_; + bool main_capturer_permanent_error_ = false; + DesktopCapturer::Callback* callback_ = nullptr; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_ diff --git a/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper_unittest.cc b/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper_unittest.cc new file mode 100644 index 0000000000..b3bf51b733 --- /dev/null +++ b/webrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper_unittest.cc @@ -0,0 +1,205 @@ +/* + * 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/fallback_desktop_capturer_wrapper.h" + +#include +#include +#include + +#include "webrtc/modules/desktop_capture/desktop_capturer.h" +#include "webrtc/modules/desktop_capture/desktop_frame_generator.h" +#include "webrtc/modules/desktop_capture/fake_desktop_capturer.h" +#include "webrtc/test/gtest.h" + +namespace webrtc { + +namespace { + +std::unique_ptr CreateDesktopCapturer( + PainterDesktopFrameGenerator* frame_generator) { + std::unique_ptr capturer(new FakeDesktopCapturer()); + capturer->set_frame_generator(frame_generator); + return std::move(capturer); +} + +class FakeSharedMemory : public SharedMemory { + public: + explicit FakeSharedMemory(size_t size); + ~FakeSharedMemory() override; + + private: + static int next_id_; +}; + +// static +int FakeSharedMemory::next_id_ = 0; + +FakeSharedMemory::FakeSharedMemory(size_t size) + : SharedMemory(new char[size], size, 0, next_id_++) {} + +FakeSharedMemory::~FakeSharedMemory() { + delete[] static_cast(data_); +} + +class FakeSharedMemoryFactory : public SharedMemoryFactory { + public: + FakeSharedMemoryFactory() = default; + ~FakeSharedMemoryFactory() override = default; + + std::unique_ptr CreateSharedMemory(size_t size) override; +}; + +std::unique_ptr FakeSharedMemoryFactory::CreateSharedMemory( + size_t size) { + return std::unique_ptr(new FakeSharedMemory(size)); +} + +} // namespace + +class FallbackDesktopCapturerWrapperTest : public testing::Test, + public DesktopCapturer::Callback { + public: + FallbackDesktopCapturerWrapperTest(); + ~FallbackDesktopCapturerWrapperTest() override = default; + + protected: + std::vector> results_; + FakeDesktopCapturer* main_capturer_ = nullptr; + FakeDesktopCapturer* secondary_capturer_ = nullptr; + std::unique_ptr wrapper_; + + private: + // DesktopCapturer::Callback interface + void OnCaptureResult(DesktopCapturer::Result result, + std::unique_ptr frame) override; + PainterDesktopFrameGenerator frame_generator; +}; + +FallbackDesktopCapturerWrapperTest::FallbackDesktopCapturerWrapperTest() { + frame_generator.size()->set(1024, 768); + std::unique_ptr main_capturer = + CreateDesktopCapturer(&frame_generator); + std::unique_ptr secondary_capturer = + CreateDesktopCapturer(&frame_generator); + main_capturer_ = static_cast(main_capturer.get()); + secondary_capturer_ = + static_cast(secondary_capturer.get()); + wrapper_.reset(new FallbackDesktopCapturerWrapper( + std::move(main_capturer), std::move(secondary_capturer))); + wrapper_->Start(this); +} + +void FallbackDesktopCapturerWrapperTest::OnCaptureResult( + DesktopCapturer::Result result, + std::unique_ptr frame) { + results_.emplace_back(result, !!frame); +} + +TEST_F(FallbackDesktopCapturerWrapperTest, MainNeverFailed) { + wrapper_->CaptureFrame(); + ASSERT_EQ(main_capturer_->num_capture_attempts(), 1); + ASSERT_EQ(main_capturer_->num_frames_captured(), 1); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 0); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 0); + ASSERT_EQ(results_.size(), 1U); + ASSERT_EQ(results_[0], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); +} + +TEST_F(FallbackDesktopCapturerWrapperTest, MainFailedTemporarily) { + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_TEMPORARY); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::SUCCESS); + wrapper_->CaptureFrame(); + + ASSERT_EQ(main_capturer_->num_capture_attempts(), 3); + ASSERT_EQ(main_capturer_->num_frames_captured(), 2); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 1); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 1); + ASSERT_EQ(results_.size(), 3U); + for (int i = 0; i < 3; i++) { + ASSERT_EQ(results_[i], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); + } +} + +TEST_F(FallbackDesktopCapturerWrapperTest, MainFailedPermanently) { + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_PERMANENT); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::SUCCESS); + wrapper_->CaptureFrame(); + + ASSERT_EQ(main_capturer_->num_capture_attempts(), 2); + ASSERT_EQ(main_capturer_->num_frames_captured(), 1); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 2); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 2); + ASSERT_EQ(results_.size(), 3U); + for (int i = 0; i < 3; i++) { + ASSERT_EQ(results_[i], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); + } +} + +TEST_F(FallbackDesktopCapturerWrapperTest, BothFailed) { + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_PERMANENT); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::SUCCESS); + wrapper_->CaptureFrame(); + secondary_capturer_->set_result(DesktopCapturer::Result::ERROR_TEMPORARY); + wrapper_->CaptureFrame(); + secondary_capturer_->set_result(DesktopCapturer::Result::ERROR_PERMANENT); + wrapper_->CaptureFrame(); + wrapper_->CaptureFrame(); + + ASSERT_EQ(main_capturer_->num_capture_attempts(), 2); + ASSERT_EQ(main_capturer_->num_frames_captured(), 1); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 5); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 2); + ASSERT_EQ(results_.size(), 6U); + for (int i = 0; i < 3; i++) { + ASSERT_EQ(results_[i], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); + } + ASSERT_EQ(results_[3], + std::make_pair(DesktopCapturer::Result::ERROR_TEMPORARY, false)); + ASSERT_EQ(results_[4], + std::make_pair(DesktopCapturer::Result::ERROR_PERMANENT, false)); + ASSERT_EQ(results_[5], + std::make_pair(DesktopCapturer::Result::ERROR_PERMANENT, false)); +} + +TEST_F(FallbackDesktopCapturerWrapperTest, WithSharedMemory) { + wrapper_->SetSharedMemoryFactory(std::unique_ptr( + new FakeSharedMemoryFactory())); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_TEMPORARY); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::SUCCESS); + wrapper_->CaptureFrame(); + main_capturer_->set_result(DesktopCapturer::Result::ERROR_PERMANENT); + wrapper_->CaptureFrame(); + wrapper_->CaptureFrame(); + + ASSERT_EQ(main_capturer_->num_capture_attempts(), 4); + ASSERT_EQ(main_capturer_->num_frames_captured(), 2); + ASSERT_EQ(secondary_capturer_->num_capture_attempts(), 3); + ASSERT_EQ(secondary_capturer_->num_frames_captured(), 3); + ASSERT_EQ(results_.size(), 5U); + for (int i = 0; i < 5; i++) { + ASSERT_EQ(results_[i], + std::make_pair(DesktopCapturer::Result::SUCCESS, true)); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/shared_memory.h b/webrtc/modules/desktop_capture/shared_memory.h index 6e15f23f6b..e654d95f2d 100644 --- a/webrtc/modules/desktop_capture/shared_memory.h +++ b/webrtc/modules/desktop_capture/shared_memory.h @@ -20,6 +20,7 @@ #include #include "webrtc/base/constructormagic.h" +#include "webrtc/base/thread_checker.h" #include "webrtc/typedefs.h" namespace webrtc {