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 {