This reverts commit e20fbb00d0e0219b710da24664e81a10b12c703a. Reason for revert: Breaks WebRTC roll to Chromium, see: https://chromium-review.googlesource.com/c/chromium/src/+/6218060 Example of error: https://ci.chromium.org/ui/p/chrome/builders/ci/win-arm64-rel-ready/51821/test-results?sortby=&groupby= Original change's description: > Get DeviceScaleFactor for the captured monitor/screen > > Accesses DeviceScaleFactor using the windows API > GetScaleFactorForMonitor and adds it to the DesktopFrame. In a follow-up > CL, this value is propagated to > DesktopCaptureDevice::Core::OnCaptureResult where it is added to the > frame metadata. > > In a follow-up CL, add RegisterScaleChangeEvent to get notified whenever > the device scale factor changes. > > Design doc: go/expose-captured-surface-resolution > > Bug: chromium:383946052 > Change-Id: I363af33c569419d95ddf31a0cc2f9cecf6fb0c7b > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/374344 > Reviewed-by: Henrik Andreassson <henrika@webrtc.org> > Reviewed-by: Mark Foltz <mfoltz@chromium.org> > Commit-Queue: Palak Agarwal <agpalak@google.com> > Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org> > Cr-Commit-Position: refs/heads/main@{#43827} Bug: chromium:383946052 Change-Id: I3065b278939ca0e686ee6da0f0721082bc0c99e8 No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/375902 Owners-Override: Mirko Bonadei <mbonadei@webrtc.org> Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com> Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/main@{#43829}
586 lines
21 KiB
C++
586 lines
21 KiB
C++
/*
|
|
* Copyright (c) 2020 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 "modules/desktop_capture/win/wgc_capture_session.h"
|
|
|
|
#include <DispatcherQueue.h>
|
|
#include <windows.graphics.capture.interop.h>
|
|
#include <windows.graphics.directX.direct3d11.interop.h>
|
|
#include <windows.graphics.h>
|
|
#include <wrl/client.h>
|
|
#include <wrl/event.h>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "modules/desktop_capture/win/wgc_desktop_frame.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/time_utils.h"
|
|
#include "rtc_base/win/create_direct3d_device.h"
|
|
#include "rtc_base/win/get_activation_factory.h"
|
|
#include "system_wrappers/include/metrics.h"
|
|
#include "system_wrappers/include/sleep.h"
|
|
|
|
using Microsoft::WRL::ComPtr;
|
|
namespace WGC = ABI::Windows::Graphics::Capture;
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
|
|
// We must use a BGRA pixel format that has 4 bytes per pixel, as required by
|
|
// the DesktopFrame interface.
|
|
constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX::
|
|
DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized;
|
|
|
|
// These values are persisted to logs. Entries should not be renumbered and
|
|
// numeric values should never be reused.
|
|
enum class StartCaptureResult {
|
|
kSuccess = 0,
|
|
kSourceClosed = 1,
|
|
kAddClosedFailed = 2,
|
|
kDxgiDeviceCastFailed = 3,
|
|
kD3dDelayLoadFailed = 4,
|
|
kD3dDeviceCreationFailed = 5,
|
|
kFramePoolActivationFailed = 6,
|
|
// kFramePoolCastFailed = 7, (deprecated)
|
|
// kGetItemSizeFailed = 8, (deprecated)
|
|
kCreateFramePoolFailed = 9,
|
|
kCreateCaptureSessionFailed = 10,
|
|
kStartCaptureFailed = 11,
|
|
kMaxValue = kStartCaptureFailed
|
|
};
|
|
|
|
// These values are persisted to logs. Entries should not be renumbered and
|
|
// numeric values should never be reused.
|
|
enum class GetFrameResult {
|
|
kSuccess = 0,
|
|
kItemClosed = 1,
|
|
kTryGetNextFrameFailed = 2,
|
|
kFrameDropped = 3,
|
|
kGetSurfaceFailed = 4,
|
|
kDxgiInterfaceAccessFailed = 5,
|
|
kTexture2dCastFailed = 6,
|
|
kCreateMappedTextureFailed = 7,
|
|
kMapFrameFailed = 8,
|
|
kGetContentSizeFailed = 9,
|
|
kResizeMappedTextureFailed = 10,
|
|
kRecreateFramePoolFailed = 11,
|
|
kFramePoolEmpty = 12,
|
|
kMaxValue = kFramePoolEmpty
|
|
};
|
|
|
|
void RecordStartCaptureResult(StartCaptureResult error) {
|
|
RTC_HISTOGRAM_ENUMERATION(
|
|
"WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult",
|
|
static_cast<int>(error), static_cast<int>(StartCaptureResult::kMaxValue));
|
|
}
|
|
|
|
void RecordGetFrameResult(GetFrameResult error) {
|
|
RTC_HISTOGRAM_ENUMERATION(
|
|
"WebRTC.DesktopCapture.Win.WgcCaptureSessionGetFrameResult",
|
|
static_cast<int>(error), static_cast<int>(GetFrameResult::kMaxValue));
|
|
}
|
|
|
|
bool SizeHasChanged(ABI::Windows::Graphics::SizeInt32 size_new,
|
|
ABI::Windows::Graphics::SizeInt32 size_old) {
|
|
return (size_new.Height != size_old.Height ||
|
|
size_new.Width != size_old.Width);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
WgcCaptureSession::WgcCaptureSession(ComPtr<ID3D11Device> d3d11_device,
|
|
ComPtr<WGC::IGraphicsCaptureItem> item,
|
|
ABI::Windows::Graphics::SizeInt32 size)
|
|
: d3d11_device_(std::move(d3d11_device)),
|
|
item_(std::move(item)),
|
|
size_(size) {}
|
|
|
|
WgcCaptureSession::~WgcCaptureSession() {
|
|
RemoveEventHandler();
|
|
}
|
|
|
|
HRESULT WgcCaptureSession::StartCapture(const DesktopCaptureOptions& options) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
RTC_DCHECK(!is_capture_started_);
|
|
|
|
if (item_closed_) {
|
|
RTC_LOG(LS_ERROR) << "The target source has been closed.";
|
|
RecordStartCaptureResult(StartCaptureResult::kSourceClosed);
|
|
return E_ABORT;
|
|
}
|
|
|
|
RTC_DCHECK(d3d11_device_);
|
|
RTC_DCHECK(item_);
|
|
|
|
// Listen for the Closed event, to detect if the source we are capturing is
|
|
// closed (e.g. application window is closed or monitor is disconnected). If
|
|
// it is, we should abort the capture.
|
|
item_closed_token_ = std::make_unique<EventRegistrationToken>();
|
|
auto closed_handler =
|
|
Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler<
|
|
WGC::GraphicsCaptureItem*, IInspectable*>>(
|
|
this, &WgcCaptureSession::OnItemClosed);
|
|
HRESULT hr =
|
|
item_->add_Closed(closed_handler.Get(), item_closed_token_.get());
|
|
if (FAILED(hr)) {
|
|
RecordStartCaptureResult(StartCaptureResult::kAddClosedFailed);
|
|
return hr;
|
|
}
|
|
|
|
ComPtr<IDXGIDevice> dxgi_device;
|
|
hr = d3d11_device_->QueryInterface(IID_PPV_ARGS(&dxgi_device));
|
|
if (FAILED(hr)) {
|
|
RecordStartCaptureResult(StartCaptureResult::kDxgiDeviceCastFailed);
|
|
return hr;
|
|
}
|
|
|
|
if (!ResolveCoreWinRTDirect3DDelayload()) {
|
|
RecordStartCaptureResult(StartCaptureResult::kD3dDelayLoadFailed);
|
|
return E_FAIL;
|
|
}
|
|
|
|
hr = CreateDirect3DDeviceFromDXGIDevice(dxgi_device.Get(), &direct3d_device_);
|
|
if (FAILED(hr)) {
|
|
RecordStartCaptureResult(StartCaptureResult::kD3dDeviceCreationFailed);
|
|
return hr;
|
|
}
|
|
|
|
ComPtr<WGC::IDirect3D11CaptureFramePoolStatics> frame_pool_statics;
|
|
hr = GetActivationFactory<
|
|
WGC::IDirect3D11CaptureFramePoolStatics,
|
|
RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool>(
|
|
&frame_pool_statics);
|
|
if (FAILED(hr)) {
|
|
RecordStartCaptureResult(StartCaptureResult::kFramePoolActivationFailed);
|
|
return hr;
|
|
}
|
|
|
|
hr = frame_pool_statics->Create(direct3d_device_.Get(), kPixelFormat,
|
|
kNumBuffers, size_, &frame_pool_);
|
|
if (FAILED(hr)) {
|
|
RecordStartCaptureResult(StartCaptureResult::kCreateFramePoolFailed);
|
|
return hr;
|
|
}
|
|
|
|
hr = frame_pool_->CreateCaptureSession(item_.Get(), &session_);
|
|
if (FAILED(hr)) {
|
|
RecordStartCaptureResult(StartCaptureResult::kCreateCaptureSessionFailed);
|
|
return hr;
|
|
}
|
|
|
|
if (!options.prefer_cursor_embedded()) {
|
|
ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureSession2> session2;
|
|
if (SUCCEEDED(session_->QueryInterface(
|
|
ABI::Windows::Graphics::Capture::IID_IGraphicsCaptureSession2,
|
|
&session2))) {
|
|
session2->put_IsCursorCaptureEnabled(false);
|
|
}
|
|
}
|
|
|
|
// By default, the WGC capture API adds a yellow border around the captured
|
|
// window or display to indicate that a capture is in progress. The section
|
|
// below is an attempt to remove this yellow border to make the capture
|
|
// experience more inline with the DXGI capture path.
|
|
// This requires 10.0.20348.0 or later, which practically means Windows 11.
|
|
ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureSession3> session3;
|
|
if (SUCCEEDED(session_->QueryInterface(
|
|
ABI::Windows::Graphics::Capture::IID_IGraphicsCaptureSession3,
|
|
&session3))) {
|
|
session3->put_IsBorderRequired(false);
|
|
}
|
|
|
|
allow_zero_hertz_ = options.allow_wgc_zero_hertz();
|
|
|
|
hr = session_->StartCapture();
|
|
if (FAILED(hr)) {
|
|
RTC_LOG(LS_ERROR) << "Failed to start CaptureSession: " << hr;
|
|
RecordStartCaptureResult(StartCaptureResult::kStartCaptureFailed);
|
|
return hr;
|
|
}
|
|
|
|
RecordStartCaptureResult(StartCaptureResult::kSuccess);
|
|
|
|
is_capture_started_ = true;
|
|
return hr;
|
|
}
|
|
|
|
void WgcCaptureSession::EnsureFrame() {
|
|
// Try to process the captured frame and copy it to the `queue_`.
|
|
HRESULT hr = ProcessFrame();
|
|
if (SUCCEEDED(hr)) {
|
|
RTC_CHECK(queue_.current_frame());
|
|
return;
|
|
}
|
|
|
|
// We failed to process the frame, but we do have a frame so just return that.
|
|
if (queue_.current_frame()) {
|
|
RTC_LOG(LS_ERROR) << "ProcessFrame failed, using existing frame: " << hr;
|
|
return;
|
|
}
|
|
|
|
// ProcessFrame failed and we don't have a current frame. This could indicate
|
|
// a startup path where we may need to try/wait a few times to ensure that we
|
|
// have a frame. We try to get a new frame from the frame pool for a maximum
|
|
// of 10 times after sleeping for 20ms. We choose 20ms as it's just a bit
|
|
// longer than 17ms (for 60fps*) and hopefully avoids unlucky timing causing
|
|
// us to wait two frames when we mostly seem to only need to wait for one.
|
|
// This approach should ensure that GetFrame() always delivers a valid frame
|
|
// with a max latency of 200ms and often after sleeping only once.
|
|
// The scheme is heuristic and based on manual testing.
|
|
// (*) On a modern system, the FPS / monitor refresh rate is usually larger
|
|
// than or equal to 60.
|
|
|
|
const int max_sleep_count = 10;
|
|
const int sleep_time_ms = 20;
|
|
|
|
int sleep_count = 0;
|
|
while (!queue_.current_frame() && sleep_count < max_sleep_count) {
|
|
sleep_count++;
|
|
webrtc::SleepMs(sleep_time_ms);
|
|
hr = ProcessFrame();
|
|
if (FAILED(hr)) {
|
|
RTC_DLOG(LS_WARNING) << "ProcessFrame failed during startup: " << hr;
|
|
}
|
|
}
|
|
RTC_LOG_IF(LS_ERROR, !queue_.current_frame())
|
|
<< "Unable to process a valid frame even after trying 10 times.";
|
|
}
|
|
|
|
bool WgcCaptureSession::GetFrame(std::unique_ptr<DesktopFrame>* output_frame,
|
|
bool source_should_be_capturable) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
|
|
if (item_closed_) {
|
|
RTC_LOG(LS_ERROR) << "The target source has been closed.";
|
|
RecordGetFrameResult(GetFrameResult::kItemClosed);
|
|
return false;
|
|
}
|
|
|
|
// Try to process the captured frame and wait some if needed. Avoid trying
|
|
// if we know that the source will not be capturable. This can happen e.g.
|
|
// when captured window is minimized and if EnsureFrame() was called in this
|
|
// state a large amount of kFrameDropped errors would be logged.
|
|
if (source_should_be_capturable)
|
|
EnsureFrame();
|
|
|
|
// Return a NULL frame and false as `result` if we still don't have a valid
|
|
// frame. This will lead to a DesktopCapturer::Result::ERROR_PERMANENT being
|
|
// posted by the WGC capturer.
|
|
DesktopFrame* current_frame = queue_.current_frame();
|
|
if (!current_frame) {
|
|
RTC_LOG(LS_ERROR) << "GetFrame failed.";
|
|
return false;
|
|
}
|
|
|
|
// Swap in the DesktopRegion in `damage_region_` which is updated in
|
|
// ProcessFrame(). The updated region is either empty or the full rect being
|
|
// captured where an empty damage region corresponds to "no change in content
|
|
// since last frame".
|
|
current_frame->mutable_updated_region()->Swap(&damage_region_);
|
|
damage_region_.Clear();
|
|
|
|
// Emit the current frame.
|
|
std::unique_ptr<DesktopFrame> new_frame = queue_.current_frame()->Share();
|
|
*output_frame = std::move(new_frame);
|
|
|
|
return true;
|
|
}
|
|
|
|
HRESULT WgcCaptureSession::CreateMappedTexture(
|
|
ComPtr<ID3D11Texture2D> src_texture,
|
|
UINT width,
|
|
UINT height) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
|
|
D3D11_TEXTURE2D_DESC src_desc;
|
|
src_texture->GetDesc(&src_desc);
|
|
D3D11_TEXTURE2D_DESC map_desc;
|
|
map_desc.Width = width == 0 ? src_desc.Width : width;
|
|
map_desc.Height = height == 0 ? src_desc.Height : height;
|
|
map_desc.MipLevels = src_desc.MipLevels;
|
|
map_desc.ArraySize = src_desc.ArraySize;
|
|
map_desc.Format = src_desc.Format;
|
|
map_desc.SampleDesc = src_desc.SampleDesc;
|
|
map_desc.Usage = D3D11_USAGE_STAGING;
|
|
map_desc.BindFlags = 0;
|
|
map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
|
map_desc.MiscFlags = 0;
|
|
return d3d11_device_->CreateTexture2D(&map_desc, nullptr, &mapped_texture_);
|
|
}
|
|
|
|
HRESULT WgcCaptureSession::ProcessFrame() {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
|
|
RTC_DCHECK(is_capture_started_);
|
|
|
|
ComPtr<WGC::IDirect3D11CaptureFrame> capture_frame;
|
|
HRESULT hr = frame_pool_->TryGetNextFrame(&capture_frame);
|
|
if (FAILED(hr)) {
|
|
RTC_LOG(LS_ERROR) << "TryGetNextFrame failed: " << hr;
|
|
RecordGetFrameResult(GetFrameResult::kTryGetNextFrameFailed);
|
|
return hr;
|
|
}
|
|
|
|
if (!capture_frame) {
|
|
if (!queue_.current_frame()) {
|
|
// The frame pool was empty and so is the external queue.
|
|
RTC_DLOG(LS_ERROR) << "Frame pool was empty => kFrameDropped.";
|
|
RecordGetFrameResult(GetFrameResult::kFrameDropped);
|
|
} else {
|
|
// The frame pool was empty but there is still one old frame available in
|
|
// external the queue.
|
|
RTC_DLOG(LS_WARNING) << "Frame pool was empty => kFramePoolEmpty.";
|
|
RecordGetFrameResult(GetFrameResult::kFramePoolEmpty);
|
|
}
|
|
return E_FAIL;
|
|
}
|
|
|
|
queue_.MoveToNextFrame();
|
|
if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
|
|
RTC_DLOG(LS_VERBOSE) << "Overwriting frame that is still shared.";
|
|
}
|
|
|
|
// We need to get `capture_frame` as an `ID3D11Texture2D` so that we can get
|
|
// the raw image data in the format required by the `DesktopFrame` interface.
|
|
ComPtr<ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface>
|
|
d3d_surface;
|
|
hr = capture_frame->get_Surface(&d3d_surface);
|
|
if (FAILED(hr)) {
|
|
RecordGetFrameResult(GetFrameResult::kGetSurfaceFailed);
|
|
return hr;
|
|
}
|
|
|
|
ComPtr<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
|
|
direct3DDxgiInterfaceAccess;
|
|
hr = d3d_surface->QueryInterface(IID_PPV_ARGS(&direct3DDxgiInterfaceAccess));
|
|
if (FAILED(hr)) {
|
|
RecordGetFrameResult(GetFrameResult::kDxgiInterfaceAccessFailed);
|
|
return hr;
|
|
}
|
|
|
|
ComPtr<ID3D11Texture2D> texture_2D;
|
|
hr = direct3DDxgiInterfaceAccess->GetInterface(IID_PPV_ARGS(&texture_2D));
|
|
if (FAILED(hr)) {
|
|
RecordGetFrameResult(GetFrameResult::kTexture2dCastFailed);
|
|
return hr;
|
|
}
|
|
|
|
if (!mapped_texture_) {
|
|
hr = CreateMappedTexture(texture_2D);
|
|
if (FAILED(hr)) {
|
|
RecordGetFrameResult(GetFrameResult::kCreateMappedTextureFailed);
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
// We need to copy `texture_2D` into `mapped_texture_` as the latter has the
|
|
// D3D11_CPU_ACCESS_READ flag set, which lets us access the image data.
|
|
// Otherwise it would only be readable by the GPU.
|
|
ComPtr<ID3D11DeviceContext> d3d_context;
|
|
d3d11_device_->GetImmediateContext(&d3d_context);
|
|
|
|
ABI::Windows::Graphics::SizeInt32 new_size;
|
|
hr = capture_frame->get_ContentSize(&new_size);
|
|
if (FAILED(hr)) {
|
|
RecordGetFrameResult(GetFrameResult::kGetContentSizeFailed);
|
|
return hr;
|
|
}
|
|
|
|
// If the size changed, we must resize `mapped_texture_` and `frame_pool_` to
|
|
// fit the new size. This must be done before `CopySubresourceRegion` so that
|
|
// the textures are the same size.
|
|
if (SizeHasChanged(new_size, size_)) {
|
|
hr = CreateMappedTexture(texture_2D, new_size.Width, new_size.Height);
|
|
if (FAILED(hr)) {
|
|
RecordGetFrameResult(GetFrameResult::kResizeMappedTextureFailed);
|
|
return hr;
|
|
}
|
|
|
|
hr = frame_pool_->Recreate(direct3d_device_.Get(), kPixelFormat,
|
|
kNumBuffers, new_size);
|
|
if (FAILED(hr)) {
|
|
RecordGetFrameResult(GetFrameResult::kRecreateFramePoolFailed);
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
// If the size has changed since the last capture, we must be sure to use
|
|
// the smaller dimensions. Otherwise we might overrun our buffer, or
|
|
// read stale data from the last frame.
|
|
int image_height = std::min(size_.Height, new_size.Height);
|
|
int image_width = std::min(size_.Width, new_size.Width);
|
|
|
|
D3D11_BOX copy_region;
|
|
copy_region.left = 0;
|
|
copy_region.top = 0;
|
|
copy_region.right = image_width;
|
|
copy_region.bottom = image_height;
|
|
// Our textures are 2D so we just want one "slice" of the box.
|
|
copy_region.front = 0;
|
|
copy_region.back = 1;
|
|
d3d_context->CopySubresourceRegion(mapped_texture_.Get(),
|
|
/*dst_subresource_index=*/0, /*dst_x=*/0,
|
|
/*dst_y=*/0, /*dst_z=*/0, texture_2D.Get(),
|
|
/*src_subresource_index=*/0, ©_region);
|
|
|
|
D3D11_MAPPED_SUBRESOURCE map_info;
|
|
hr = d3d_context->Map(mapped_texture_.Get(), /*subresource_index=*/0,
|
|
D3D11_MAP_READ, /*D3D11_MAP_FLAG_DO_NOT_WAIT=*/0,
|
|
&map_info);
|
|
if (FAILED(hr)) {
|
|
RecordGetFrameResult(GetFrameResult::kMapFrameFailed);
|
|
return hr;
|
|
}
|
|
|
|
// Allocate the current frame buffer only if it is not already allocated or
|
|
// if the size has changed. Note that we can't reallocate other buffers at
|
|
// this point, since the caller may still be reading from them. The queue can
|
|
// hold up to two frames.
|
|
DesktopSize image_size(image_width, image_height);
|
|
if (!queue_.current_frame() ||
|
|
!queue_.current_frame()->size().equals(image_size)) {
|
|
std::unique_ptr<DesktopFrame> buffer =
|
|
std::make_unique<BasicDesktopFrame>(image_size);
|
|
queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(buffer)));
|
|
}
|
|
|
|
DesktopFrame* current_frame = queue_.current_frame();
|
|
DesktopFrame* previous_frame = queue_.previous_frame();
|
|
|
|
// Will be set to true while copying the frame data to the `current_frame` if
|
|
// we can already determine that the content of the new frame differs from the
|
|
// previous. The idea is to get a low-complexity indication of if the content
|
|
// is static or not without performing a full/deep memory comparison when
|
|
// updating the damaged region.
|
|
bool frame_content_has_changed = false;
|
|
|
|
// Check if the queue contains two frames whose content can be compared.
|
|
const bool frame_content_can_be_compared = FrameContentCanBeCompared();
|
|
|
|
// Make a copy of the data pointed to by `map_info.pData` to the preallocated
|
|
// `current_frame` so we are free to unmap our texture. If possible, also
|
|
// perform a light-weight scan of the vertical line of pixels in the middle
|
|
// of the screen. A comparison is performed between two 32-bit pixels (RGBA);
|
|
// one from the current frame and one from the previous, and as soon as a
|
|
// difference is detected the scan stops and `frame_content_has_changed` is
|
|
// set to true.
|
|
uint8_t* src_data = static_cast<uint8_t*>(map_info.pData);
|
|
uint8_t* dst_data = current_frame->data();
|
|
uint8_t* prev_data =
|
|
frame_content_can_be_compared ? previous_frame->data() : nullptr;
|
|
|
|
const int width_in_bytes =
|
|
current_frame->size().width() * DesktopFrame::kBytesPerPixel;
|
|
RTC_DCHECK_GE(current_frame->stride(), width_in_bytes);
|
|
RTC_DCHECK_GE(map_info.RowPitch, width_in_bytes);
|
|
const int middle_pixel_offset =
|
|
(image_width / 2) * DesktopFrame::kBytesPerPixel;
|
|
for (int i = 0; i < image_height; i++) {
|
|
memcpy(dst_data, src_data, width_in_bytes);
|
|
if (prev_data && !frame_content_has_changed) {
|
|
uint8_t* previous_pixel = prev_data + middle_pixel_offset;
|
|
uint8_t* current_pixel = dst_data + middle_pixel_offset;
|
|
frame_content_has_changed =
|
|
memcmp(previous_pixel, current_pixel, DesktopFrame::kBytesPerPixel);
|
|
prev_data += current_frame->stride();
|
|
}
|
|
dst_data += current_frame->stride();
|
|
src_data += map_info.RowPitch;
|
|
}
|
|
|
|
d3d_context->Unmap(mapped_texture_.Get(), 0);
|
|
|
|
if (allow_zero_hertz()) {
|
|
if (previous_frame) {
|
|
const int previous_frame_size =
|
|
previous_frame->stride() * previous_frame->size().height();
|
|
const int current_frame_size =
|
|
current_frame->stride() * current_frame->size().height();
|
|
|
|
// Compare the latest frame with the previous and check if the frames are
|
|
// equal (both contain the exact same pixel values). Avoid full memory
|
|
// comparison if indication of a changed frame already exists from the
|
|
// stage above.
|
|
if (current_frame_size == previous_frame_size) {
|
|
if (frame_content_has_changed) {
|
|
// Mark frame as damaged based on existing light-weight indicator.
|
|
// Avoids deep memcmp of complete frame and saves resources.
|
|
damage_region_.SetRect(DesktopRect::MakeSize(current_frame->size()));
|
|
} else {
|
|
// Perform full memory comparison for all bytes between the current
|
|
// and the previous frames.
|
|
const bool frames_are_equal =
|
|
!memcmp(current_frame->data(), previous_frame->data(),
|
|
current_frame_size);
|
|
if (!frames_are_equal) {
|
|
// TODO(https://crbug.com/1421242): If we had an API to report
|
|
// proper damage regions we should be doing AddRect() with a
|
|
// SetRect() call on a resize.
|
|
damage_region_.SetRect(
|
|
DesktopRect::MakeSize(current_frame->size()));
|
|
}
|
|
}
|
|
} else {
|
|
// Mark resized frames as damaged.
|
|
damage_region_.SetRect(DesktopRect::MakeSize(current_frame->size()));
|
|
}
|
|
}
|
|
}
|
|
|
|
size_ = new_size;
|
|
RecordGetFrameResult(GetFrameResult::kSuccess);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT WgcCaptureSession::OnItemClosed(WGC::IGraphicsCaptureItem* sender,
|
|
IInspectable* event_args) {
|
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
|
|
|
RTC_LOG(LS_INFO) << "Capture target has been closed.";
|
|
item_closed_ = true;
|
|
|
|
RemoveEventHandler();
|
|
|
|
// Do not attempt to free resources in the OnItemClosed handler, as this
|
|
// causes a race where we try to delete the item that is calling us. Removing
|
|
// the event handlers and setting `item_closed_` above is sufficient to ensure
|
|
// that the resources are no longer used, and the next time the capturer tries
|
|
// to get a frame, we will report a permanent failure and be destroyed.
|
|
return S_OK;
|
|
}
|
|
|
|
void WgcCaptureSession::RemoveEventHandler() {
|
|
HRESULT hr;
|
|
if (item_ && item_closed_token_) {
|
|
hr = item_->remove_Closed(*item_closed_token_);
|
|
item_closed_token_.reset();
|
|
if (FAILED(hr))
|
|
RTC_LOG(LS_WARNING) << "Failed to remove Closed event handler: " << hr;
|
|
}
|
|
}
|
|
|
|
bool WgcCaptureSession::FrameContentCanBeCompared() {
|
|
DesktopFrame* current_frame = queue_.current_frame();
|
|
DesktopFrame* previous_frame = queue_.previous_frame();
|
|
if (!current_frame || !previous_frame) {
|
|
return false;
|
|
}
|
|
if (current_frame->stride() != previous_frame->stride()) {
|
|
return false;
|
|
}
|
|
return current_frame->size().equals(previous_frame->size());
|
|
}
|
|
|
|
} // namespace webrtc
|