summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc')
-rw-r--r--third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc429
1 files changed, 429 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
new file mode 100644
index 0000000000..ea5565c89c
--- /dev/null
+++ b/third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
@@ -0,0 +1,429 @@
+/*
+ * 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 <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"
+
+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;
+
+// The maximum time `GetFrame` will wait for a frame to arrive, if we don't have
+// any in the pool.
+constexpr TimeDelta kMaxWaitForFrame = TimeDelta::Millis(50);
+constexpr TimeDelta kMaxWaitForFirstFrame = TimeDelta::Millis(500);
+
+// 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,
+ kMaxValue = kRecreateFramePoolFailed
+};
+
+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));
+}
+
+} // 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() {
+ RemoveEventHandlers();
+}
+
+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;
+ }
+
+ frames_in_pool_ = 0;
+
+ // Because `WgcCapturerWin` created a `DispatcherQueue`, and we created
+ // `frame_pool_` via `Create`, the `FrameArrived` event will be delivered on
+ // the current thread.
+ frame_arrived_token_ = std::make_unique<EventRegistrationToken>();
+ auto frame_arrived_handler =
+ Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler<
+ WGC::Direct3D11CaptureFramePool*, IInspectable*>>(
+ this, &WgcCaptureSession::OnFrameArrived);
+ hr = frame_pool_->add_FrameArrived(frame_arrived_handler.Get(),
+ frame_arrived_token_.get());
+
+ 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);
+ }
+ }
+
+ 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;
+}
+
+HRESULT WgcCaptureSession::GetFrame(
+ std::unique_ptr<DesktopFrame>* output_frame) {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+
+ if (item_closed_) {
+ RTC_LOG(LS_ERROR) << "The target source has been closed.";
+ RecordGetFrameResult(GetFrameResult::kItemClosed);
+ return E_ABORT;
+ }
+
+ RTC_DCHECK(is_capture_started_);
+
+ if (frames_in_pool_ < 1)
+ wait_for_frame_event_.Wait(first_frame_ ? kMaxWaitForFirstFrame
+ : kMaxWaitForFrame);
+
+ 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) {
+ RecordGetFrameResult(GetFrameResult::kFrameDropped);
+ return hr;
+ }
+
+ first_frame_ = false;
+ --frames_in_pool_;
+
+ // 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 (size_.Height != new_size.Height || size_.Width != new_size.Width) {
+ 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, &copy_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;
+ }
+
+ int row_data_length = image_width * DesktopFrame::kBytesPerPixel;
+
+ // Make a copy of the data pointed to by `map_info.pData` so we are free to
+ // unmap our texture.
+ uint8_t* src_data = static_cast<uint8_t*>(map_info.pData);
+ std::vector<uint8_t> image_data;
+ image_data.resize(image_height * row_data_length);
+ uint8_t* image_data_ptr = image_data.data();
+ for (int i = 0; i < image_height; i++) {
+ memcpy(image_data_ptr, src_data, row_data_length);
+ image_data_ptr += row_data_length;
+ src_data += map_info.RowPitch;
+ }
+
+ d3d_context->Unmap(mapped_texture_.Get(), 0);
+
+ // Transfer ownership of `image_data` to the output_frame.
+ DesktopSize size(image_width, image_height);
+ *output_frame = std::make_unique<WgcDesktopFrame>(size, row_data_length,
+ std::move(image_data));
+
+ size_ = new_size;
+ RecordGetFrameResult(GetFrameResult::kSuccess);
+ return hr;
+}
+
+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::OnFrameArrived(
+ WGC::IDirect3D11CaptureFramePool* sender,
+ IInspectable* event_args) {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ RTC_DCHECK_LT(frames_in_pool_, kNumBuffers);
+ ++frames_in_pool_;
+ wait_for_frame_event_.Set();
+ return S_OK;
+}
+
+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;
+
+ RemoveEventHandlers();
+
+ // 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::RemoveEventHandlers() {
+ HRESULT hr;
+ if (frame_pool_ && frame_arrived_token_) {
+ hr = frame_pool_->remove_FrameArrived(*frame_arrived_token_);
+ frame_arrived_token_.reset();
+ if (FAILED(hr)) {
+ RTC_LOG(LS_WARNING) << "Failed to remove FrameArrived event handler: "
+ << 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;
+ }
+}
+
+} // namespace webrtc