[DesktopCapture] Detect screen resolution changes in DirectX capturer

This change adds a ResolutionChangeDetector to help dxgi components, say
DxgiDuplicatorController and DxgiTexture to detect resolution changes.

BUG=684162

Review-Url: https://codereview.webrtc.org/2682913002
Cr-Commit-Position: refs/heads/master@{#16654}
This commit is contained in:
zijiehe 2017-02-16 12:07:44 -08:00 committed by Commit bot
parent 94b9600e2e
commit 5fea5fb183
14 changed files with 218 additions and 63 deletions

View File

@ -200,6 +200,8 @@ rtc_static_library("desktop_capture") {
"mouse_cursor_monitor.h",
"mouse_cursor_monitor_mac.mm",
"mouse_cursor_monitor_win.cc",
"resolution_change_detector.cc",
"resolution_change_detector.h",
"screen_capture_frame_queue.h",
"screen_capturer_helper.cc",
"screen_capturer_helper.h",

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2017 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/resolution_change_detector.h"
namespace webrtc {
bool ResolutionChangeDetector::IsChanged(DesktopSize size) {
if (!initialized_) {
initialized_ = true;
last_size_ = size;
return false;
}
return !last_size_.equals(size);
}
void ResolutionChangeDetector::Reset() {
initialized_ = false;
}
} // namespace webrtc

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2017 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_RESOLUTION_CHANGE_DETECTOR_H_
#define WEBRTC_MODULES_DESKTOP_CAPTURE_RESOLUTION_CHANGE_DETECTOR_H_
#include "webrtc/modules/desktop_capture/desktop_geometry.h"
namespace webrtc {
class ResolutionChangeDetector {
public:
bool IsChanged(DesktopSize size);
void Reset();
private:
DesktopSize last_size_;
bool initialized_ = false;
};
} // namespace webrtc
#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_RESOLUTION_CHANGE_DETECTOR_H_

View File

@ -13,17 +13,24 @@
#include <windows.h>
#include <algorithm>
#include <string>
#include "webrtc/base/checks.h"
#include "webrtc/modules/desktop_capture/desktop_capture_types.h"
#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
namespace webrtc {
DxgiDuplicatorController::Context::Context() {}
DxgiDuplicatorController::Context::Context() = default;
DxgiDuplicatorController::Context::~Context() {
DxgiDuplicatorController::Instance()->Unregister(this);
}
void DxgiDuplicatorController::Context::Reset() {
identity_ = 0;
}
// static
DxgiDuplicatorController* DxgiDuplicatorController::Instance() {
// The static instance won't be deleted to ensure it can be used by other
@ -44,6 +51,11 @@ bool DxgiDuplicatorController::IsSupported() {
return Initialize();
}
void DxgiDuplicatorController::Reset() {
rtc::CritScope lock(&lock_);
Deinitialize();
}
bool DxgiDuplicatorController::RetrieveD3dInfo(D3dInfo* info) {
rtc::CritScope lock(&lock_);
if (!Initialize()) {
@ -182,6 +194,7 @@ bool DxgiDuplicatorController::DoInitialize() {
void DxgiDuplicatorController::Deinitialize() {
desktop_rect_ = DesktopRect();
duplicators_.clear();
resolution_change_detector_.Reset();
}
bool DxgiDuplicatorController::ContextExpired(
@ -219,17 +232,34 @@ bool DxgiDuplicatorController::DoDuplicate(Context* context,
RTC_DCHECK(target);
target->mutable_updated_region()->Clear();
rtc::CritScope lock(&lock_);
if (DoDuplicateUnlocked(context, monitor_id, target)) {
return true;
}
Deinitialize();
return false;
}
bool DxgiDuplicatorController::DoDuplicateUnlocked(Context* context,
int monitor_id,
SharedDesktopFrame* target) {
if (!Initialize()) {
// Cannot initialize COM components now, display mode may be changing.
return false;
}
if (resolution_change_detector_.IsChanged(
GetScreenRect(kFullDesktopScreenId, std::wstring()).size())) {
// Resolution of entire screen has been changed, which usually means a new
// monitor has been attached or one has been removed. The simplest way is to
// Deinitialize() and returns false to indicate downstream components.
return false;
}
Setup(context);
if (monitor_id < 0) {
// Capture entire screen.
for (size_t i = 0; i < duplicators_.size(); i++) {
if (!duplicators_[i].Duplicate(&context->contexts_[i], target)) {
Deinitialize();
return false;
}
}
@ -248,7 +278,6 @@ bool DxgiDuplicatorController::DoDuplicate(Context* context,
target->set_dpi(dpi());
return true;
}
Deinitialize();
return false;
}
}

View File

@ -19,6 +19,7 @@
#include "webrtc/base/criticalsection.h"
#include "webrtc/modules/desktop_capture/desktop_geometry.h"
#include "webrtc/modules/desktop_capture/desktop_region.h"
#include "webrtc/modules/desktop_capture/resolution_change_detector.h"
#include "webrtc/modules/desktop_capture/shared_desktop_frame.h"
#include "webrtc/modules/desktop_capture/win/d3d_device.h"
#include "webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h"
@ -47,6 +48,9 @@ class DxgiDuplicatorController {
// destructing.
~Context();
// Reset current Context, so it will be reinitialized next time.
void Reset();
private:
friend class DxgiDuplicatorController;
@ -87,6 +91,11 @@ class DxgiDuplicatorController {
// Detects whether the system supports DXGI based capturer.
bool IsSupported();
// Calls Deinitialize() function with lock. Consumers can call this function
// to force the DxgiDuplicatorController to be reinitialized to avoid an
// expected failure in next Duplicate() call.
void Reset();
// Returns a copy of D3dInfo composed by last Initialize() function call.
bool RetrieveD3dInfo(D3dInfo* info);
@ -167,6 +176,10 @@ class DxgiDuplicatorController {
int monitor_id,
SharedDesktopFrame* target);
bool DoDuplicateUnlocked(Context* context,
int monitor_id,
SharedDesktopFrame* target);
// This lock must be locked whenever accessing any of the following objects.
rtc::CriticalSection lock_;
@ -177,6 +190,7 @@ class DxgiDuplicatorController {
DesktopVector dpi_;
std::vector<DxgiAdapterDuplicator> duplicators_;
D3dInfo d3d_info_;
ResolutionChangeDetector resolution_change_detector_;
};
} // namespace webrtc

View File

@ -85,13 +85,10 @@ DxgiOutputDuplicator::~DxgiOutputDuplicator() {
bool DxgiOutputDuplicator::Initialize() {
if (DuplicateOutput()) {
DesktopSize unrotated_size =
RotateSize(desktop_rect().size(), ReverseRotation(rotation_));
if (desc_.DesktopImageInSystemMemory) {
texture_.reset(
new DxgiTextureMapping(unrotated_size, duplication_.Get()));
texture_.reset(new DxgiTextureMapping(duplication_.Get()));
} else {
texture_.reset(new DxgiTextureStaging(unrotated_size, device_));
texture_.reset(new DxgiTextureStaging(device_));
}
return true;
} else {

View File

@ -10,7 +10,15 @@
#include "webrtc/modules/desktop_capture/win/dxgi_texture.h"
#include <comdef.h>
#include <wrl/client.h>
#include <D3D11.h>
#include "webrtc/base/checks.h"
#include "webrtc/modules/desktop_capture/desktop_region.h"
#include "webrtc/system_wrappers/include/logging.h"
using Microsoft::WRL::ComPtr;
namespace webrtc {
@ -29,10 +37,33 @@ class DxgiDesktopFrame : public DesktopFrame {
} // namespace
DxgiTexture::DxgiTexture(const DesktopSize& desktop_size)
: desktop_size_(desktop_size) {}
DxgiTexture::DxgiTexture() = default;
DxgiTexture::~DxgiTexture() = default;
DxgiTexture::~DxgiTexture() {}
bool DxgiTexture::CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource) {
RTC_DCHECK(resource && frame_info.AccumulatedFrames > 0);
ComPtr<ID3D11Texture2D> texture;
_com_error error = resource->QueryInterface(
__uuidof(ID3D11Texture2D),
reinterpret_cast<void**>(texture.GetAddressOf()));
if (error.Error() != S_OK || !texture) {
LOG(LS_ERROR) << "Failed to convert IDXGIResource to ID3D11Texture2D, "
"error "
<< error.ErrorMessage() << ", code " << error.Error();
return false;
}
D3D11_TEXTURE2D_DESC desc = {0};
texture->GetDesc(&desc);
desktop_size_.set(desc.Width, desc.Height);
if (resolution_change_detector_.IsChanged(desktop_size_)) {
LOG(LS_ERROR) << "Texture size is not consistent with current DxgiTexture.";
return false;
}
return CopyFromTexture(frame_info, texture.Get());
}
const DesktopFrame& DxgiTexture::AsDesktopFrame() {
if (!frame_) {
@ -46,4 +77,8 @@ bool DxgiTexture::Release() {
return DoRelease();
}
DXGI_MAPPED_RECT* DxgiTexture::rect() {
return &rect_;
}
} // namespace webrtc

View File

@ -11,12 +11,14 @@
#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_
#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_
#include <D3D11.h>
#include <DXGI1_2.h>
#include <memory>
#include "webrtc/modules/desktop_capture/desktop_frame.h"
#include "webrtc/modules/desktop_capture/desktop_geometry.h"
#include "webrtc/modules/desktop_capture/resolution_change_detector.h"
namespace webrtc {
@ -27,14 +29,14 @@ class DxgiTexture {
public:
// Creates a DxgiTexture instance, which represents the |desktop_size| area of
// entire screen -- usually a monitor on the system.
explicit DxgiTexture(const DesktopSize& desktop_size);
DxgiTexture();
virtual ~DxgiTexture();
// Copies selected regions of a frame represented by frame_info and resource.
// Returns false if anything wrong.
virtual bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource) = 0;
bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource);
const DesktopSize& desktop_size() const { return desktop_size_; }
@ -54,13 +56,18 @@ class DxgiTexture {
const DesktopFrame& AsDesktopFrame();
protected:
DXGI_MAPPED_RECT rect_ = {0};
DXGI_MAPPED_RECT* rect();
virtual bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) = 0;
private:
virtual bool DoRelease() = 0;
const DesktopSize desktop_size_;
private:
DXGI_MAPPED_RECT rect_ = {0};
DesktopSize desktop_size_;
std::unique_ptr<DesktopFrame> frame_;
ResolutionChangeDetector resolution_change_detector_;
};
} // namespace webrtc

View File

@ -19,21 +19,21 @@
namespace webrtc {
DxgiTextureMapping::DxgiTextureMapping(const DesktopSize& desktop_size,
IDXGIOutputDuplication* duplication)
: DxgiTexture(desktop_size), duplication_(duplication) {
DxgiTextureMapping::DxgiTextureMapping(IDXGIOutputDuplication* duplication)
: duplication_(duplication) {
RTC_DCHECK(duplication_);
}
DxgiTextureMapping::~DxgiTextureMapping() = default;
bool DxgiTextureMapping::CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource) {
RTC_DCHECK(resource && frame_info.AccumulatedFrames > 0);
rect_ = {0};
_com_error error = duplication_->MapDesktopSurface(&rect_);
bool DxgiTextureMapping::CopyFromTexture(
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) {
RTC_DCHECK(texture && frame_info.AccumulatedFrames > 0);
*rect() = {0};
_com_error error = duplication_->MapDesktopSurface(rect());
if (error.Error() != S_OK) {
rect_ = {0};
*rect() = {0};
LOG(LS_ERROR) << "Failed to map the IDXGIOutputDuplication to a bitmap, "
"error "
<< error.ErrorMessage() << ", code " << error.Error();

View File

@ -27,14 +27,14 @@ namespace webrtc {
class DxgiTextureMapping : public DxgiTexture {
public:
// Creates a DxgiTextureMapping instance. Caller must maintain the lifetime
// of input duplication to make sure it outlives this instance.
DxgiTextureMapping(const DesktopSize& desktop_size,
IDXGIOutputDuplication* duplication);
// of input |duplication| to make sure it outlives this instance.
explicit DxgiTextureMapping(IDXGIOutputDuplication* duplication);
~DxgiTextureMapping() override;
bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource) override;
protected:
bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) override;
bool DoRelease() override;

View File

@ -22,9 +22,8 @@ using Microsoft::WRL::ComPtr;
namespace webrtc {
DxgiTextureStaging::DxgiTextureStaging(const DesktopSize& desktop_size,
const D3dDevice& device)
: DxgiTexture(desktop_size), device_(device) {}
DxgiTextureStaging::DxgiTextureStaging(const D3dDevice& device)
: device_(device) {}
DxgiTextureStaging::~DxgiTextureStaging() = default;
@ -32,11 +31,6 @@ bool DxgiTextureStaging::InitializeStage(ID3D11Texture2D* texture) {
RTC_DCHECK(texture);
D3D11_TEXTURE2D_DESC desc = {0};
texture->GetDesc(&desc);
if (static_cast<int>(desc.Width) != desktop_size().width() ||
static_cast<int>(desc.Height) != desktop_size().height()) {
LOG(LS_ERROR) << "Texture size is not consistent with current DxgiTexture.";
return false;
}
desc.ArraySize = 1;
desc.BindFlags = 0;
@ -90,33 +84,24 @@ void DxgiTextureStaging::AssertStageAndSurfaceAreSameObject() {
RTC_DCHECK(left.Get() == right.Get());
}
bool DxgiTextureStaging::CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource) {
RTC_DCHECK(resource && frame_info.AccumulatedFrames > 0);
ComPtr<ID3D11Texture2D> texture;
_com_error error = resource->QueryInterface(
__uuidof(ID3D11Texture2D),
reinterpret_cast<void**>(texture.GetAddressOf()));
if (error.Error() != S_OK || !texture) {
LOG(LS_ERROR) << "Failed to convert IDXGIResource to ID3D11Texture2D, "
"error "
<< error.ErrorMessage() << ", code " << error.Error();
return false;
}
bool DxgiTextureStaging::CopyFromTexture(
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) {
RTC_DCHECK(texture && frame_info.AccumulatedFrames > 0);
// AcquireNextFrame returns a CPU inaccessible IDXGIResource, so we need to
// copy it to a CPU accessible staging ID3D11Texture2D.
if (!InitializeStage(texture.Get())) {
if (!InitializeStage(texture)) {
return false;
}
device_.context()->CopyResource(static_cast<ID3D11Resource*>(stage_.Get()),
static_cast<ID3D11Resource*>(texture.Get()));
static_cast<ID3D11Resource*>(texture));
rect_ = {0};
error = surface_->Map(&rect_, DXGI_MAP_READ);
*rect() = {0};
_com_error error = surface_->Map(rect(), DXGI_MAP_READ);
if (error.Error() != S_OK) {
rect_ = {0};
*rect() = {0};
LOG(LS_ERROR) << "Failed to map the IDXGISurface to a bitmap, error "
<< error.ErrorMessage() << ", code " << error.Error();
return false;

View File

@ -33,14 +33,15 @@ class DxgiTextureStaging : public DxgiTexture {
public:
// Creates a DxgiTextureStaging instance. Caller must maintain the lifetime
// of input device to make sure it outlives this instance.
DxgiTextureStaging(const DesktopSize& desktop_size, const D3dDevice& device);
explicit DxgiTextureStaging(const D3dDevice& device);
~DxgiTextureStaging() override;
// Copies selected regions of a frame represented by frame_info and resource.
protected:
// Copies selected regions of a frame represented by frame_info and texture.
// Returns false if anything wrong.
bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
IDXGIResource* resource) override;
bool CopyFromTexture(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
ID3D11Texture2D* texture) override;
bool DoRelease() override;

View File

@ -10,12 +10,14 @@
#include "webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h"
#include <string>
#include <utility>
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/timeutils.h"
#include "webrtc/modules/desktop_capture/desktop_frame.h"
#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
namespace webrtc {
@ -66,6 +68,26 @@ void ScreenCapturerWinDirectx::CaptureFrame() {
int64_t capture_start_time_nanos = rtc::TimeNanos();
// The dxgi components and APIs do not update the screen resolution without
// a reinitialization. So we use the GetDC() function to retrieve the screen
// resolution to decide whether dxgi components need to be reinitialized.
// If the screen resolution changed, it's very likely the next Duplicate()
// function call will fail because of a missing monitor or the frame size is
// not enough to store the output. So we reinitialize dxgi components in-place
// to avoid a capture failure.
// But there is no guarantee GetDC() function returns the same resolution as
// dxgi APIs, we still rely on dxgi components to return the output frame
// size.
// TODO(zijiehe): Confirm whether IDXGIOutput::GetDesc() and
// IDXGIOutputDuplication::GetDesc() can detect the resolution change without
// reinitialization.
if (resolution_change_detector_.IsChanged(
GetScreenRect(kFullDesktopScreenId, std::wstring()).size())) {
frames_.Reset();
DxgiDuplicatorController::Instance()->Reset();
resolution_change_detector_.Reset();
}
frames_.MoveToNextFrame();
if (!frames_.current_frame()) {
std::unique_ptr<DesktopFrame> new_frame;
@ -90,6 +112,7 @@ void ScreenCapturerWinDirectx::CaptureFrame() {
&context_, frames_.current_frame())) {
// Screen size may be changed, so we need to reset the frames.
frames_.Reset();
resolution_change_detector_.Reset();
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
return;
}
@ -98,6 +121,7 @@ void ScreenCapturerWinDirectx::CaptureFrame() {
&context_, current_screen_id_, frames_.current_frame())) {
// Screen size may be changed, so we need to reset the frames.
frames_.Reset();
resolution_change_detector_.Reset();
if (current_screen_id_ >=
DxgiDuplicatorController::Instance()->ScreenCount()) {
// Current monitor has been removed from the system.
@ -133,12 +157,14 @@ bool ScreenCapturerWinDirectx::SelectSource(SourceId id) {
// frames only when a Duplicate() function call returns false.
if (id == kFullDesktopScreenId) {
current_screen_id_ = id;
context_.Reset();
return true;
}
int screen_count = DxgiDuplicatorController::Instance()->ScreenCount();
if (id >= 0 && id < screen_count) {
current_screen_id_ = id;
context_.Reset();
return true;
}
return false;

View File

@ -19,6 +19,7 @@
#include "webrtc/modules/desktop_capture/desktop_capturer.h"
#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
#include "webrtc/modules/desktop_capture/desktop_region.h"
#include "webrtc/modules/desktop_capture/resolution_change_detector.h"
#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
#include "webrtc/modules/desktop_capture/shared_desktop_frame.h"
#include "webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h"
@ -61,10 +62,9 @@ class ScreenCapturerWinDirectx : public DesktopCapturer {
ScreenCaptureFrameQueue<SharedDesktopFrame> frames_;
std::unique_ptr<SharedMemoryFactory> shared_memory_factory_;
Callback* callback_ = nullptr;
DxgiDuplicatorController::Context context_;
SourceId current_screen_id_ = kFullDesktopScreenId;
ResolutionChangeDetector resolution_change_detector_;
RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinDirectx);
};