[WebRTC] Two DirectX capturers cannot work concurrently

DirectX capturer won't be able to update the DesktopFrame for the second
ScreenCapturerWinDirectx instance, if AcquireNextFrame() returns timeout or
unchanged error. Current solution uses |last_frame| of the second
ScreenCapturerWinDirectx instance, which also does not contain the updated frame
between this and last AcquireNextFrame() calls. Considering following situation,
(C1: capturer 1, C2: capturer 2, update: screen updated, next AcquireNextFrame()
call will return a new frame, Fx: a frame x)
update -> C2.capture returns F1 -> update -> C1.capture returns F2 ->
C2.capture unchanged
So using F1 to update the last capture frame is not correct, we need to use F2.
Refer to design doc https://goo.gl/hU1ifG for a detail description.

The change also makes DxgiDuplicatorController work with 2+ DesktopFrame queue.
Now TwoDirectxCapturers test can pass.

BUG=314516

Review-Url: https://codereview.webrtc.org/2299663003
Cr-Commit-Position: refs/heads/master@{#14077}
This commit is contained in:
zijiehe 2016-09-05 15:40:00 -07:00 committed by Commit bot
parent fef8653c5a
commit aa90b313b4
7 changed files with 83 additions and 86 deletions

View File

@ -113,11 +113,10 @@ void DxgiAdapterDuplicator::Unregister(const Context* const context) {
}
bool DxgiAdapterDuplicator::Duplicate(Context* context,
const DesktopFrame* last_frame,
DesktopFrame* target) {
SharedDesktopFrame* target) {
RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size());
for (size_t i = 0; i < duplicators_.size(); i++) {
if (!duplicators_[i].Duplicate(&context->contexts[i], last_frame,
if (!duplicators_[i].Duplicate(&context->contexts[i],
duplicators_[i].desktop_rect().top_left(),
target)) {
return false;
@ -128,13 +127,12 @@ bool DxgiAdapterDuplicator::Duplicate(Context* context,
bool DxgiAdapterDuplicator::DuplicateMonitor(Context* context,
int monitor_id,
const DesktopFrame* last_frame,
DesktopFrame* target) {
SharedDesktopFrame* target) {
RTC_DCHECK(monitor_id >= 0 &&
monitor_id < static_cast<int>(duplicators_.size()) &&
context->contexts.size() == duplicators_.size());
return duplicators_[monitor_id].Duplicate(
&context->contexts[monitor_id], last_frame, DesktopVector(), target);
return duplicators_[monitor_id].Duplicate(&context->contexts[monitor_id],
DesktopVector(), target);
}
DesktopRect DxgiAdapterDuplicator::ScreenRect(int id) const {

View File

@ -15,9 +15,9 @@
#include <vector>
#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/shared_desktop_frame.h"
#include "webrtc/modules/desktop_capture/win/d3d_device.h"
#include "webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h"
@ -44,18 +44,15 @@ class DxgiAdapterDuplicator {
// Initializes the DxgiAdapterDuplicator from a D3dDevice.
bool Initialize();
// Sequential calls Duplicate function of all the DxgiOutputDuplicators owned
// by this instance.
bool Duplicate(Context* context,
const DesktopFrame* last_frame,
DesktopFrame* target);
// Sequentially calls Duplicate function of all the DxgiOutputDuplicator
// instances owned by this instance, and writes into |target|.
bool Duplicate(Context* context, SharedDesktopFrame* target);
// Captures one monitor and writes into target. |monitor_id| should be between
// [0, screen_count()).
// Captures one monitor and writes into |target|. |monitor_id| should be
// between [0, screen_count()).
bool DuplicateMonitor(Context* context,
int monitor_id,
const DesktopFrame* last_frame,
DesktopFrame* target);
SharedDesktopFrame* target);
// Returns desktop rect covered by this DxgiAdapterDuplicator.
DesktopRect desktop_rect() const { return desktop_rect_; }

View File

@ -176,39 +176,33 @@ void DxgiDuplicatorController::Setup(Context* context) {
}
bool DxgiDuplicatorController::Duplicate(Context* context,
const DesktopFrame* last_frame,
DesktopFrame* target) {
return DoDuplicate(context, -1, last_frame, target);
SharedDesktopFrame* target) {
return DoDuplicate(context, -1, target);
}
bool DxgiDuplicatorController::DuplicateMonitor(Context* context,
int monitor_id,
const DesktopFrame* last_frame,
DesktopFrame* target) {
SharedDesktopFrame* target) {
RTC_DCHECK_GE(monitor_id, 0);
return DoDuplicate(context, monitor_id, last_frame, target);
return DoDuplicate(context, monitor_id, target);
}
bool DxgiDuplicatorController::DoDuplicate(Context* context,
int monitor_id,
const DesktopFrame* last_frame,
DesktopFrame* target) {
SharedDesktopFrame* target) {
RTC_DCHECK(target);
if (last_frame && !target->size().equals(last_frame->size())) {
return false;
}
target->mutable_updated_region()->Clear();
rtc::CritScope lock(&lock_);
if (!Initialize()) {
// Cannot initialize COM components now, display mode may be changing.
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], last_frame,
target)) {
if (!duplicators_[i].Duplicate(&context->contexts_[i], target)) {
Deinitialize();
return false;
}
@ -224,7 +218,7 @@ bool DxgiDuplicatorController::DoDuplicate(Context* context,
monitor_id -= duplicators_[i].screen_count();
} else {
if (duplicators_[i].DuplicateMonitor(&context->contexts_[i], monitor_id,
last_frame, target)) {
target)) {
target->set_dpi(dpi());
return true;
}

View File

@ -11,12 +11,13 @@
#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_
#include <memory>
#include <vector>
#include "webrtc/base/criticalsection.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/shared_desktop_frame.h"
#include "webrtc/modules/desktop_capture/win/d3d_device.h"
#include "webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h"
@ -33,16 +34,6 @@ namespace webrtc {
// but a later Duplicate() returns false, this usually means the display mode is
// changing. Consumers should retry after a while. (Typically 50 milliseconds,
// but according to hardware performance, this time may vary.)
//
// This class is normally used with double buffering, e.g. as in
// ScreenCapturerWinDirectx, but it should work with consumers with one buffer,
// i.e. consumers can always send nullptr for |last_frame|. Some minor changes
// in DxgiOutputDuplicator class are nice to have to reduce size of data to copy
// (Commented in dxgi_output_duplicator.cc). But this class won't work
// with three or more buffers, the updated region merging logic will be broken
// in such scenarios. If a consumer does have this requirement, one can always
// send a new Context instance to Duplicate() function to force duplicator to
// treat it as a new consumer.
class DxgiDuplicatorController {
public:
// A context to store the status of a single consumer of
@ -82,18 +73,15 @@ class DxgiDuplicatorController {
// TODO(zijiehe): Windows cannot guarantee the frames returned by each
// IDXGIOutputDuplication are synchronized. But we are using a totally
// different threading model than the way Windows suggested, it's hard to
// synchronize them manually. But we should find a way to do it.
bool Duplicate(Context* context,
const DesktopFrame* last_frame,
DesktopFrame* target);
// synchronize them manually. We should find a way to do it.
bool Duplicate(Context* context, SharedDesktopFrame* target);
// Captures one monitor and writes into target. |monitor_id| should >= 0. If
// |monitor_id| is greater than the total screen count of all the Duplicators,
// this function returns false.
bool DuplicateMonitor(Context* context,
int monitor_id,
const DesktopFrame* last_frame,
DesktopFrame* target);
SharedDesktopFrame* target);
// Returns dpi of current system. Returns an empty DesktopVector if system
// does not support DXGI based capturer.
@ -153,8 +141,7 @@ class DxgiDuplicatorController {
// Do the real duplication work. |monitor_id < 0| to capture entire screen.
bool DoDuplicate(Context* context,
int monitor_id,
const DesktopFrame* last_frame,
DesktopFrame* target);
SharedDesktopFrame* target);
// This lock must be locked whenever accessing any of the following objects.
rtc::CriticalSection lock_;

View File

@ -118,12 +118,17 @@ bool DxgiOutputDuplicator::ReleaseFrame() {
}
bool DxgiOutputDuplicator::Duplicate(Context* context,
const DesktopFrame* last_frame,
const DesktopVector offset,
DesktopFrame* target) {
DesktopVector offset,
SharedDesktopFrame* target) {
RTC_DCHECK(duplication_);
RTC_DCHECK(texture_);
RTC_DCHECK(target);
if (!DesktopRect::MakeSize(target->size())
.ContainsRect(TranslatedDesktopRect(offset))) {
// target size is not large enough to cover current output region.
return false;
}
DXGI_OUTDUPL_FRAME_INFO frame_info;
memset(&frame_info, 0, sizeof(frame_info));
ComPtr<IDXGIResource> resource;
@ -140,40 +145,36 @@ bool DxgiOutputDuplicator::Duplicate(Context* context,
// buffering implementation, as what we have in ScreenCapturerWinDirectx. If
// a consumer uses single buffering, we should clear context->updated_region
// after it has been merged to updated_region.
DesktopRegion updated_region = context->updated_region;
DesktopRegion updated_region;
updated_region.Swap(&context->updated_region);
if (error.Error() == S_OK && frame_info.AccumulatedFrames > 0) {
DetectUpdatedRegion(frame_info, offset, &context->updated_region);
SpreadContextChange(context);
updated_region.AddRegion(context->updated_region);
if (!texture_->CopyFrom(frame_info, resource.Get(), updated_region)) {
if (!texture_->CopyFrom(frame_info, resource.Get(),
context->updated_region)) {
return false;
}
SpreadContextChange(context);
updated_region.AddRegion(context->updated_region);
const DesktopFrame& source = texture_->AsDesktopFrame();
DesktopRect target_rect(DesktopRect::MakeSize(target->size()));
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
it.Advance()) {
if (!target_rect.ContainsRect(it.rect())) {
// target size is not large enough to copy the pixel from texture.
return false;
}
target->CopyPixelsFrom(source, it.rect().top_left().subtract(offset),
it.rect());
target->CopyPixelsFrom(source, SourceRect(it.rect()).top_left(),
TargetRect(it.rect(), offset));
}
last_frame_ = target->Share();
last_frame_offset_ = offset;
target->mutable_updated_region()->AddRegion(updated_region);
return texture_->Release() && ReleaseFrame();
}
if (last_frame != nullptr) {
// DxgiOutputDuplicatorContainer::Duplicate() makes sure target size and
// last frame size are consistent.
RTC_DCHECK(target->size().equals(last_frame->size()));
if (last_frame_) {
// No change since last frame or AcquireNextFrame() timed out, we will
// export last frame to the target.
context->updated_region.Clear();
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
it.Advance()) {
target->CopyPixelsFrom(*last_frame, it.rect().top_left(), it.rect());
target->CopyPixelsFrom(*last_frame_, SourceRect(it.rect()).top_left(),
TargetRect(it.rect(), offset));
}
target->mutable_updated_region()->AddRegion(updated_region);
}
@ -182,8 +183,7 @@ bool DxgiOutputDuplicator::Duplicate(Context* context,
return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame();
}
DesktopRect DxgiOutputDuplicator::TranslatedDesktopRect(
const DesktopVector offset) {
DesktopRect DxgiOutputDuplicator::TranslatedDesktopRect(DesktopVector offset) {
DesktopRect result(DesktopRect::MakeSize(desktop_rect_.size()));
result.Translate(offset);
return result;
@ -191,7 +191,7 @@ DesktopRect DxgiOutputDuplicator::TranslatedDesktopRect(
void DxgiOutputDuplicator::DetectUpdatedRegion(
const DXGI_OUTDUPL_FRAME_INFO& frame_info,
const DesktopVector offset,
DesktopVector offset,
DesktopRegion* updated_region) {
if (DoDetectUpdatedRegion(frame_info, updated_region)) {
updated_region->Translate(offset.x(), offset.y());
@ -301,4 +301,17 @@ void DxgiOutputDuplicator::SpreadContextChange(const Context* const source) {
}
}
DesktopRect DxgiOutputDuplicator::SourceRect(DesktopRect rect) {
// |texture_|->AsDesktopFrame() starts from (0, 0).
rect.Translate(-desktop_rect_.left(), -desktop_rect_.top());
return rect;
}
DesktopRect DxgiOutputDuplicator::TargetRect(DesktopRect rect,
DesktopVector offset) {
rect = SourceRect(rect);
rect.Translate(offset);
return rect;
}
} // namespace webrtc

View File

@ -21,9 +21,9 @@
#include "webrtc/base/criticalsection.h"
#include "webrtc/base/thread_annotations.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/shared_desktop_frame.h"
#include "webrtc/modules/desktop_capture/win/d3d_device.h"
#include "webrtc/modules/desktop_capture/win/dxgi_texture.h"
@ -60,17 +60,14 @@ class DxgiOutputDuplicator {
// Copies the content of current IDXGIOutput to the |target|. To improve the
// performance, this function copies only regions merged from
// |last_frame|.updated_region and DetectUpdatedRegion(). The |offset| decides
// the
// offset in the |target| where the content should be copied to. i.e. this
// |context|->updated_region and DetectUpdatedRegion(). The |offset| decides
// the offset in the |target| where the content should be copied to. i.e. this
// function copies the content to the rectangle of (offset.x(), offset.y()) to
// (offset.x() + desktop_rect_.width(), offset.y() + desktop_rect_.height()).
// The |last_frame| is always expected to be translated by the same offset.
// Returns false in case of a failure.
bool Duplicate(Context* context,
const DesktopFrame* last_frame,
const DesktopVector offset,
DesktopFrame* target);
DesktopVector offset,
SharedDesktopFrame* target);
// Returns the desktop rect covered by this DxgiOutputDuplicator.
DesktopRect desktop_rect() const { return desktop_rect_; }
@ -82,7 +79,7 @@ class DxgiOutputDuplicator {
// function will set the |updated_region| as entire DesktopRect starts from
// offset if it failed to execute Windows APIs.
void DetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
const DesktopVector offset,
DesktopVector offset,
DesktopRegion* updated_region);
// Returns untranslated updated region, which are directly returned by Windows
@ -98,7 +95,7 @@ class DxgiOutputDuplicator {
// Returns a DesktopRect with the same size of desktop_size_, but translated
// by offset.
DesktopRect TranslatedDesktopRect(const DesktopVector offset);
DesktopRect TranslatedDesktopRect(DesktopVector offset);
void Setup(Context* context);
@ -108,6 +105,12 @@ class DxgiOutputDuplicator {
// contexts_.
void SpreadContextChange(const Context* const context);
// Returns a DesktopRect in the coordinate of |texture_|->AsDesktopFrame().
DesktopRect SourceRect(DesktopRect rect);
// Returns a DesktopRect in the coordinate of |offset|.
DesktopRect TargetRect(DesktopRect rect, DesktopVector offset);
const D3dDevice& device_;
const Microsoft::WRL::ComPtr<IDXGIOutput1> output_;
const DesktopRect desktop_rect_;
@ -121,6 +124,12 @@ class DxgiOutputDuplicator {
// change this time. And during next Duplicate() function call, their
// updated_region_ will be merged and copied.
std::vector<Context*> contexts_;
// The last full frame of this output and its offset. If on AcquireNextFrame()
// failed because of timeout, i.e. no update, we can copy content from
// |last_frame_|.
std::unique_ptr<SharedDesktopFrame> last_frame_;
DesktopVector last_frame_offset_;
};
} // namespace webrtc

View File

@ -79,7 +79,7 @@ void ScreenCapturerWinDirectx::Capture(const DesktopRegion& region) {
if (current_screen_id == kFullDesktopScreenId) {
if (!DxgiDuplicatorController::Instance()->Duplicate(
&context_, frames_.previous_frame(), frames_.current_frame())) {
&context_, frames_.current_frame())) {
// Screen size may be changed, so we need to reset the frames.
frames_.Reset();
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
@ -87,8 +87,7 @@ void ScreenCapturerWinDirectx::Capture(const DesktopRegion& region) {
}
} else {
if (!DxgiDuplicatorController::Instance()->DuplicateMonitor(
&context_, current_screen_id, frames_.previous_frame(),
frames_.current_frame())) {
&context_, current_screen_id, frames_.current_frame())) {
// Screen size may be changed, so we need to reset the frames.
frames_.Reset();
if (current_screen_id >=