mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-26 20:25:34 +08:00
wgc dll test pass
This commit is contained in:
@@ -97,9 +97,9 @@ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previousInstance,
|
|||||||
WINRT_VERIFY(comboBoxHwnd);
|
WINRT_VERIFY(comboBoxHwnd);
|
||||||
|
|
||||||
// Populate combo box
|
// Populate combo box
|
||||||
for (auto &window : g_windows) {
|
// for (auto &window : g_windows) {
|
||||||
SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, (LPARAM)window.Title().c_str());
|
// SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, (LPARAM)window.Title().c_str());
|
||||||
}
|
// }
|
||||||
|
|
||||||
for (auto &monitor : g_monitors) {
|
for (auto &monitor : g_monitors) {
|
||||||
SendMessage(comboBoxHwnd, CB_ADDSTRING, 0,
|
SendMessage(comboBoxHwnd, CB_ADDSTRING, 0,
|
||||||
@@ -135,24 +135,25 @@ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previousInstance,
|
|||||||
|
|
||||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case WM_DESTROY:
|
case WM_DESTROY:
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
break;
|
break;
|
||||||
case WM_COMMAND:
|
case WM_COMMAND:
|
||||||
if (HIWORD(wParam) == CBN_SELCHANGE) {
|
if (HIWORD(wParam) == CBN_SELCHANGE) {
|
||||||
auto index = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
|
auto index = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
|
||||||
if (index < g_windows.size() - 1) {
|
// if (index < g_windows.size() - 1) {
|
||||||
auto window = g_windows[index];
|
// auto window = g_windows[index];
|
||||||
g_app->StartCapture(window.Hwnd());
|
// g_app->StartCapture(window.Hwnd());
|
||||||
} else {
|
// } else {
|
||||||
auto monitor = g_monitors[index - g_windows.size()];
|
// auto monitor = g_monitors[index - g_windows.size()];
|
||||||
g_app->StartCapture(monitor.Hmonitor());
|
auto monitor = g_monitors[0];
|
||||||
|
g_app->StartCapture(monitor.Hmonitor());
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
break;
|
default:
|
||||||
default:
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
34
dll/main.cpp
Normal file
34
dll/main.cpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
#define AMRECORDER_IMPORT
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "export.h"
|
||||||
|
#include "head.h"
|
||||||
|
#include "record_desktop_wgc.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// bool is_supported = wgc_is_supported();
|
||||||
|
// if (!wgc_is_supported) {
|
||||||
|
// std::cout << "Not support wgc" << std::endl;
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
|
||||||
|
static am::record_desktop *recorder = new am::record_desktop_wgc();
|
||||||
|
|
||||||
|
RECORD_DESKTOP_RECT rect;
|
||||||
|
rect.left = 0;
|
||||||
|
rect.top = 0;
|
||||||
|
rect.right = GetSystemMetrics(SM_CXSCREEN);
|
||||||
|
rect.bottom = GetSystemMetrics(SM_CYSCREEN);
|
||||||
|
|
||||||
|
recorder->init(rect, 10);
|
||||||
|
|
||||||
|
recorder->start();
|
||||||
|
// int pause() override;
|
||||||
|
// int resume() override;
|
||||||
|
// int stop() override;
|
||||||
|
|
||||||
|
while(1){}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
#include "record_desktop_wgc.h"
|
#include "record_desktop_wgc.h"
|
||||||
|
|
||||||
|
#include "utils_string.h"
|
||||||
|
|
||||||
|
#include "system_error.h"
|
||||||
#include "error_define.h"
|
#include "error_define.h"
|
||||||
#include "log_helper.h"
|
#include "log_helper.h"
|
||||||
#include "system_error.h"
|
|
||||||
#include "utils_string.h"
|
|
||||||
|
|
||||||
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc,
|
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc,
|
||||||
LPARAM data) {
|
LPARAM data) {
|
||||||
|
|
||||||
MONITORINFOEX info_ex;
|
MONITORINFOEX info_ex;
|
||||||
info_ex.cbSize = sizeof(MONITORINFOEX);
|
info_ex.cbSize = sizeof(MONITORINFOEX);
|
||||||
|
|
||||||
GetMonitorInfo(hmonitor, &info_ex);
|
GetMonitorInfo(hmonitor, &info_ex);
|
||||||
|
|
||||||
if (info_ex.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
|
if (info_ex.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (info_ex.dwFlags & MONITORINFOF_PRIMARY) {
|
if (info_ex.dwFlags & MONITORINFOF_PRIMARY) {
|
||||||
*(HMONITOR *)data = hmonitor;
|
*(HMONITOR *)data = hmonitor;
|
||||||
@@ -31,6 +34,7 @@ HMONITOR GetPrimaryMonitor() {
|
|||||||
|
|
||||||
namespace am {
|
namespace am {
|
||||||
|
|
||||||
|
|
||||||
record_desktop_wgc::record_desktop_wgc() {}
|
record_desktop_wgc::record_desktop_wgc() {}
|
||||||
|
|
||||||
record_desktop_wgc::~record_desktop_wgc() {
|
record_desktop_wgc::~record_desktop_wgc() {
|
||||||
@@ -40,7 +44,8 @@ record_desktop_wgc::~record_desktop_wgc() {
|
|||||||
|
|
||||||
int record_desktop_wgc::init(const RECORD_DESKTOP_RECT &rect, const int fps) {
|
int record_desktop_wgc::init(const RECORD_DESKTOP_RECT &rect, const int fps) {
|
||||||
int error = AE_NO;
|
int error = AE_NO;
|
||||||
if (_inited == true) return error;
|
if (_inited == true)
|
||||||
|
return error;
|
||||||
|
|
||||||
_fps = fps;
|
_fps = fps;
|
||||||
_rect = rect;
|
_rect = rect;
|
||||||
@@ -49,12 +54,12 @@ int record_desktop_wgc::init(const RECORD_DESKTOP_RECT &rect, const int fps) {
|
|||||||
_pixel_fmt = AV_PIX_FMT_BGRA;
|
_pixel_fmt = AV_PIX_FMT_BGRA;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!module_.is_supported()) {
|
if (!wgc_is_supported()) {
|
||||||
error = AE_UNSUPPORT;
|
error = AE_UNSUPPORT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
session_ = module_.create_session();
|
session_ = wgc_create_session();
|
||||||
if (!session_) {
|
if (!session_) {
|
||||||
error = AE_WGC_CREATE_CAPTURER_FAILED;
|
error = AE_WGC_CREATE_CAPTURER_FAILED;
|
||||||
break;
|
break;
|
||||||
@@ -93,20 +98,23 @@ int record_desktop_wgc::start() {
|
|||||||
|
|
||||||
int record_desktop_wgc::pause() {
|
int record_desktop_wgc::pause() {
|
||||||
_paused = true;
|
_paused = true;
|
||||||
if (session_) session_->pause();
|
if (session_)
|
||||||
|
session_->pause();
|
||||||
return AE_NO;
|
return AE_NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
int record_desktop_wgc::resume() {
|
int record_desktop_wgc::resume() {
|
||||||
_paused = false;
|
_paused = false;
|
||||||
if (session_) session_->resume();
|
if (session_)
|
||||||
|
session_->resume();
|
||||||
return AE_NO;
|
return AE_NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
int record_desktop_wgc::stop() {
|
int record_desktop_wgc::stop() {
|
||||||
_running = false;
|
_running = false;
|
||||||
|
|
||||||
if (session_) session_->stop();
|
if (session_)
|
||||||
|
session_->stop();
|
||||||
|
|
||||||
return AE_NO;
|
return AE_NO;
|
||||||
}
|
}
|
||||||
@@ -128,7 +136,8 @@ void record_desktop_wgc::on_frame(const wgc_session::wgc_session_frame &frame) {
|
|||||||
av_image_fill_arrays(av_frame->data, av_frame->linesize, frame.data,
|
av_image_fill_arrays(av_frame->data, av_frame->linesize, frame.data,
|
||||||
AV_PIX_FMT_BGRA, frame.width, frame.height, 1);
|
AV_PIX_FMT_BGRA, frame.width, frame.height, 1);
|
||||||
|
|
||||||
if (_on_data) _on_data(av_frame);
|
if (_on_data)
|
||||||
|
_on_data(av_frame);
|
||||||
|
|
||||||
av_frame_free(&av_frame);
|
av_frame_free(&av_frame);
|
||||||
}
|
}
|
||||||
@@ -136,9 +145,10 @@ void record_desktop_wgc::on_frame(const wgc_session::wgc_session_frame &frame) {
|
|||||||
void record_desktop_wgc::clean_up() {
|
void record_desktop_wgc::clean_up() {
|
||||||
_inited = false;
|
_inited = false;
|
||||||
|
|
||||||
if (session_) session_->release();
|
if (session_)
|
||||||
|
session_->release();
|
||||||
|
|
||||||
session_ = nullptr;
|
session_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace am
|
} // namespace am
|
||||||
17
main.cpp
17
main.cpp
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
#define AMRECORDER_IMPORT
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "export.h"
|
|
||||||
#include "head.h"
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
bool is_supported = wgc_is_supported();
|
|
||||||
if (!wgc_is_supported) {
|
|
||||||
std::cout << "Not support wgc" << std::endl;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
rd rd;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
372
webrtc/wgc_capture_session.cc
Normal file
372
webrtc/wgc_capture_session.cc
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
/*
|
||||||
|
* 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 <windows.graphics.capture.interop.h>
|
||||||
|
#include <windows.graphics.directX.direct3d11.interop.h>
|
||||||
|
#include <wrl.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.
|
||||||
|
const auto kPixelFormat = ABI::Windows::Graphics::DirectX::DirectXPixelFormat::
|
||||||
|
DirectXPixelFormat_B8G8R8A8UIntNormalized;
|
||||||
|
|
||||||
|
// We only want 1 buffer in our frame pool to reduce latency. If we had more,
|
||||||
|
// they would sit in the pool for longer and be stale by the time we are asked
|
||||||
|
// for a new frame.
|
||||||
|
const int kNumBuffers = 1;
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
kGetItemSizeFailed = 8,
|
||||||
|
kCreateFreeThreadedFailed = 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)
|
||||||
|
: d3d11_device_(std::move(d3d11_device)), item_(std::move(item)) {}
|
||||||
|
WgcCaptureSession::~WgcCaptureSession() = default;
|
||||||
|
|
||||||
|
HRESULT WgcCaptureSession::StartCapture() {
|
||||||
|
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.
|
||||||
|
auto closed_handler =
|
||||||
|
Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler<
|
||||||
|
WGC::GraphicsCaptureItem*, IInspectable*>>(
|
||||||
|
this, &WgcCaptureSession::OnItemClosed);
|
||||||
|
EventRegistrationToken item_closed_token;
|
||||||
|
HRESULT hr = item_->add_Closed(closed_handler.Get(), &item_closed_token);
|
||||||
|
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<
|
||||||
|
ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics,
|
||||||
|
RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool>(
|
||||||
|
&frame_pool_statics);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RecordStartCaptureResult(StartCaptureResult::kFramePoolActivationFailed);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast to FramePoolStatics2 so we can use CreateFreeThreaded and avoid the
|
||||||
|
// need to have a DispatcherQueue. We don't listen for the FrameArrived event,
|
||||||
|
// so there's no difference.
|
||||||
|
ComPtr<WGC::IDirect3D11CaptureFramePoolStatics2> frame_pool_statics2;
|
||||||
|
hr = frame_pool_statics->QueryInterface(IID_PPV_ARGS(&frame_pool_statics2));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RecordStartCaptureResult(StartCaptureResult::kFramePoolCastFailed);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ABI::Windows::Graphics::SizeInt32 item_size;
|
||||||
|
hr = item_.Get()->get_Size(&item_size);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RecordStartCaptureResult(StartCaptureResult::kGetItemSizeFailed);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous_size_ = item_size;
|
||||||
|
|
||||||
|
hr = frame_pool_statics2->CreateFreeThreaded(direct3d_device_.Get(),
|
||||||
|
kPixelFormat, kNumBuffers,
|
||||||
|
item_size, &frame_pool_);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RecordStartCaptureResult(StartCaptureResult::kCreateFreeThreadedFailed);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = frame_pool_->CreateCaptureSession(item_.Get(), &session_);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RecordStartCaptureResult(StartCaptureResult::kCreateCaptureSessionFailed);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to get this CaptureFrame 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);
|
||||||
|
d3d_context->CopyResource(mapped_texture_.Get(), texture_2D.Get());
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
ABI::Windows::Graphics::SizeInt32 new_size;
|
||||||
|
hr = capture_frame->get_ContentSize(&new_size);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RecordGetFrameResult(GetFrameResult::kGetContentSizeFailed);
|
||||||
|
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(previous_size_.Height, new_size.Height);
|
||||||
|
int image_width = std::min(previous_size_.Width, new_size.Width);
|
||||||
|
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.reserve(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
d3d_context->Unmap(mapped_texture_.Get(), 0);
|
||||||
|
|
||||||
|
// If the size changed, we must resize the texture and frame pool to fit the
|
||||||
|
// new size.
|
||||||
|
if (previous_size_.Height != new_size.Height ||
|
||||||
|
previous_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordGetFrameResult(GetFrameResult::kSuccess);
|
||||||
|
|
||||||
|
previous_size_ = new_size;
|
||||||
|
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::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;
|
||||||
|
is_capture_started_ = false;
|
||||||
|
|
||||||
|
mapped_texture_ = nullptr;
|
||||||
|
session_ = nullptr;
|
||||||
|
frame_pool_ = nullptr;
|
||||||
|
direct3d_device_ = nullptr;
|
||||||
|
item_ = nullptr;
|
||||||
|
d3d11_device_ = nullptr;
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
110
webrtc/wgc_capture_session.h
Normal file
110
webrtc/wgc_capture_session.h
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_
|
||||||
|
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_
|
||||||
|
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <windows.graphics.capture.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "api/sequence_checker.h"
|
||||||
|
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||||
|
#include "modules/desktop_capture/win/wgc_capture_source.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
class WgcCaptureSession final {
|
||||||
|
public:
|
||||||
|
WgcCaptureSession(
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
|
||||||
|
Microsoft::WRL::ComPtr<
|
||||||
|
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem> item);
|
||||||
|
|
||||||
|
// Disallow copy and assign.
|
||||||
|
WgcCaptureSession(const WgcCaptureSession&) = delete;
|
||||||
|
WgcCaptureSession& operator=(const WgcCaptureSession&) = delete;
|
||||||
|
|
||||||
|
~WgcCaptureSession();
|
||||||
|
|
||||||
|
HRESULT StartCapture();
|
||||||
|
|
||||||
|
// Returns a frame from the frame pool, if any are present.
|
||||||
|
HRESULT GetFrame(std::unique_ptr<DesktopFrame>* output_frame);
|
||||||
|
|
||||||
|
bool IsCaptureStarted() const {
|
||||||
|
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||||
|
return is_capture_started_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Initializes |mapped_texture_| with the properties of the |src_texture|,
|
||||||
|
// overrides the values of some necessary properties like the
|
||||||
|
// D3D11_CPU_ACCESS_READ flag. Also has optional parameters for what size
|
||||||
|
// |mapped_texture_| should be, if they aren't provided we will use the size
|
||||||
|
// of |src_texture|.
|
||||||
|
HRESULT CreateMappedTexture(
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Texture2D> src_texture,
|
||||||
|
UINT width = 0,
|
||||||
|
UINT height = 0);
|
||||||
|
|
||||||
|
// Event handler for |item_|'s Closed event.
|
||||||
|
HRESULT OnItemClosed(
|
||||||
|
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* sender,
|
||||||
|
IInspectable* event_args);
|
||||||
|
|
||||||
|
// A Direct3D11 Device provided by the caller. We use this to create an
|
||||||
|
// IDirect3DDevice, and also to create textures that will hold the image data.
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
|
||||||
|
|
||||||
|
// This item represents what we are capturing, we use it to create the
|
||||||
|
// capture session, and also to listen for the Closed event.
|
||||||
|
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
|
||||||
|
item_;
|
||||||
|
|
||||||
|
// The IDirect3DDevice is necessary to instantiate the frame pool.
|
||||||
|
Microsoft::WRL::ComPtr<
|
||||||
|
ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>
|
||||||
|
direct3d_device_;
|
||||||
|
|
||||||
|
// The frame pool is where frames are deposited during capture, we retrieve
|
||||||
|
// them from here with TryGetNextFrame().
|
||||||
|
Microsoft::WRL::ComPtr<
|
||||||
|
ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool>
|
||||||
|
frame_pool_;
|
||||||
|
|
||||||
|
// This texture holds the final image data. We made it a member so we can
|
||||||
|
// reuse it, instead of having to create a new texture every time we grab a
|
||||||
|
// frame.
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Texture2D> mapped_texture_;
|
||||||
|
|
||||||
|
// This lets us know when the source has been resized, which is important
|
||||||
|
// because we must resize the framepool and our texture to be able to hold
|
||||||
|
// enough data for the frame.
|
||||||
|
ABI::Windows::Graphics::SizeInt32 previous_size_;
|
||||||
|
|
||||||
|
// The capture session lets us set properties about the capture before it
|
||||||
|
// starts such as whether to capture the mouse cursor, and it lets us tell WGC
|
||||||
|
// to start capturing frames.
|
||||||
|
Microsoft::WRL::ComPtr<
|
||||||
|
ABI::Windows::Graphics::Capture::IGraphicsCaptureSession>
|
||||||
|
session_;
|
||||||
|
|
||||||
|
bool item_closed_ = false;
|
||||||
|
bool is_capture_started_ = false;
|
||||||
|
|
||||||
|
SequenceChecker sequence_checker_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SESSION_H_
|
||||||
136
webrtc/wgc_capture_source.cc
Normal file
136
webrtc/wgc_capture_source.cc
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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_source.h"
|
||||||
|
#include <windows.graphics.capture.interop.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <utility>
|
||||||
|
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||||
|
#include "modules/desktop_capture/win/window_capture_utils.h"
|
||||||
|
#include "rtc_base/win/get_activation_factory.h"
|
||||||
|
using Microsoft::WRL::ComPtr;
|
||||||
|
namespace WGC = ABI::Windows::Graphics::Capture;
|
||||||
|
namespace webrtc {
|
||||||
|
WgcCaptureSource::WgcCaptureSource(DesktopCapturer::SourceId source_id)
|
||||||
|
: source_id_(source_id) {}
|
||||||
|
WgcCaptureSource::~WgcCaptureSource() = default;
|
||||||
|
bool WgcCaptureSource::IsCapturable() {
|
||||||
|
// If we can create a capture item, then we can capture it. Unfortunately,
|
||||||
|
// we can't cache this item because it may be created in a different COM
|
||||||
|
// apartment than where capture will eventually start from.
|
||||||
|
ComPtr<WGC::IGraphicsCaptureItem> item;
|
||||||
|
return SUCCEEDED(CreateCaptureItem(&item));
|
||||||
|
}
|
||||||
|
bool WgcCaptureSource::FocusOnSource() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
HRESULT WgcCaptureSource::GetCaptureItem(
|
||||||
|
ComPtr<WGC::IGraphicsCaptureItem>* result) {
|
||||||
|
HRESULT hr = S_OK;
|
||||||
|
if (!item_)
|
||||||
|
hr = CreateCaptureItem(&item_);
|
||||||
|
*result = item_;
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
WgcCaptureSourceFactory::~WgcCaptureSourceFactory() = default;
|
||||||
|
WgcWindowSourceFactory::WgcWindowSourceFactory() = default;
|
||||||
|
WgcWindowSourceFactory::~WgcWindowSourceFactory() = default;
|
||||||
|
std::unique_ptr<WgcCaptureSource> WgcWindowSourceFactory::CreateCaptureSource(
|
||||||
|
DesktopCapturer::SourceId source_id) {
|
||||||
|
return std::make_unique<WgcWindowSource>(source_id);
|
||||||
|
}
|
||||||
|
WgcScreenSourceFactory::WgcScreenSourceFactory() = default;
|
||||||
|
WgcScreenSourceFactory::~WgcScreenSourceFactory() = default;
|
||||||
|
std::unique_ptr<WgcCaptureSource> WgcScreenSourceFactory::CreateCaptureSource(
|
||||||
|
DesktopCapturer::SourceId source_id) {
|
||||||
|
return std::make_unique<WgcScreenSource>(source_id);
|
||||||
|
}
|
||||||
|
WgcWindowSource::WgcWindowSource(DesktopCapturer::SourceId source_id)
|
||||||
|
: WgcCaptureSource(source_id) {}
|
||||||
|
WgcWindowSource::~WgcWindowSource() = default;
|
||||||
|
DesktopVector WgcWindowSource::GetTopLeft() {
|
||||||
|
DesktopRect window_rect;
|
||||||
|
if (!GetWindowRect(reinterpret_cast<HWND>(GetSourceId()), &window_rect))
|
||||||
|
return DesktopVector();
|
||||||
|
return window_rect.top_left();
|
||||||
|
}
|
||||||
|
bool WgcWindowSource::IsCapturable() {
|
||||||
|
if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId())))
|
||||||
|
return false;
|
||||||
|
return WgcCaptureSource::IsCapturable();
|
||||||
|
}
|
||||||
|
bool WgcWindowSource::FocusOnSource() {
|
||||||
|
if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(GetSourceId())))
|
||||||
|
return false;
|
||||||
|
return ::BringWindowToTop(reinterpret_cast<HWND>(GetSourceId())) &&
|
||||||
|
::SetForegroundWindow(reinterpret_cast<HWND>(GetSourceId()));
|
||||||
|
}
|
||||||
|
HRESULT WgcWindowSource::CreateCaptureItem(
|
||||||
|
ComPtr<WGC::IGraphicsCaptureItem>* result) {
|
||||||
|
if (!ResolveCoreWinRTDelayload())
|
||||||
|
return E_FAIL;
|
||||||
|
ComPtr<IGraphicsCaptureItemInterop> interop;
|
||||||
|
HRESULT hr = GetActivationFactory<
|
||||||
|
IGraphicsCaptureItemInterop,
|
||||||
|
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem>(&interop);
|
||||||
|
if (FAILED(hr))
|
||||||
|
return hr;
|
||||||
|
ComPtr<WGC::IGraphicsCaptureItem> item;
|
||||||
|
hr = interop->CreateForWindow(reinterpret_cast<HWND>(GetSourceId()),
|
||||||
|
IID_PPV_ARGS(&item));
|
||||||
|
if (FAILED(hr))
|
||||||
|
return hr;
|
||||||
|
if (!item)
|
||||||
|
return E_HANDLE;
|
||||||
|
*result = std::move(item);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
WgcScreenSource::WgcScreenSource(DesktopCapturer::SourceId source_id)
|
||||||
|
: WgcCaptureSource(source_id) {
|
||||||
|
// Getting the HMONITOR could fail if the source_id is invalid. In that case,
|
||||||
|
// we leave hmonitor_ uninitialized and |IsCapturable()| will fail.
|
||||||
|
HMONITOR hmon;
|
||||||
|
if (GetHmonitorFromDeviceIndex(GetSourceId(), &hmon))
|
||||||
|
hmonitor_ = hmon;
|
||||||
|
}
|
||||||
|
WgcScreenSource::~WgcScreenSource() = default;
|
||||||
|
DesktopVector WgcScreenSource::GetTopLeft() {
|
||||||
|
if (!hmonitor_)
|
||||||
|
return DesktopVector();
|
||||||
|
return GetMonitorRect(*hmonitor_).top_left();
|
||||||
|
}
|
||||||
|
bool WgcScreenSource::IsCapturable() {
|
||||||
|
if (!hmonitor_)
|
||||||
|
return false;
|
||||||
|
if (!IsMonitorValid(*hmonitor_))
|
||||||
|
return false;
|
||||||
|
return WgcCaptureSource::IsCapturable();
|
||||||
|
}
|
||||||
|
HRESULT WgcScreenSource::CreateCaptureItem(
|
||||||
|
ComPtr<WGC::IGraphicsCaptureItem>* result) {
|
||||||
|
if (!hmonitor_)
|
||||||
|
return E_ABORT;
|
||||||
|
if (!ResolveCoreWinRTDelayload())
|
||||||
|
return E_FAIL;
|
||||||
|
ComPtr<IGraphicsCaptureItemInterop> interop;
|
||||||
|
HRESULT hr = GetActivationFactory<
|
||||||
|
IGraphicsCaptureItemInterop,
|
||||||
|
RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem>(&interop);
|
||||||
|
if (FAILED(hr))
|
||||||
|
return hr;
|
||||||
|
ComPtr<WGC::IGraphicsCaptureItem> item;
|
||||||
|
hr = interop->CreateForMonitor(*hmonitor_, IID_PPV_ARGS(&item));
|
||||||
|
if (FAILED(hr))
|
||||||
|
return hr;
|
||||||
|
if (!item)
|
||||||
|
return E_HANDLE;
|
||||||
|
*result = std::move(item);
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
} // namespace webrtc
|
||||||
105
webrtc/wgc_capture_source.h
Normal file
105
webrtc/wgc_capture_source.h
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_
|
||||||
|
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_
|
||||||
|
#include <windows.graphics.capture.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
#include <memory>
|
||||||
|
#include "absl/types/optional.h"
|
||||||
|
#include "modules/desktop_capture/desktop_capturer.h"
|
||||||
|
#include "modules/desktop_capture/desktop_geometry.h"
|
||||||
|
namespace webrtc {
|
||||||
|
// Abstract class to represent the source that WGC-based capturers capture
|
||||||
|
// from. Could represent an application window or a screen. Consumers should use
|
||||||
|
// the appropriate Wgc*SourceFactory class to create WgcCaptureSource objects
|
||||||
|
// of the appropriate type.
|
||||||
|
class WgcCaptureSource {
|
||||||
|
public:
|
||||||
|
explicit WgcCaptureSource(DesktopCapturer::SourceId source_id);
|
||||||
|
virtual ~WgcCaptureSource();
|
||||||
|
virtual DesktopVector GetTopLeft() = 0;
|
||||||
|
virtual bool IsCapturable();
|
||||||
|
virtual bool FocusOnSource();
|
||||||
|
HRESULT GetCaptureItem(
|
||||||
|
Microsoft::WRL::ComPtr<
|
||||||
|
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result);
|
||||||
|
DesktopCapturer::SourceId GetSourceId() { return source_id_; }
|
||||||
|
protected:
|
||||||
|
virtual HRESULT CreateCaptureItem(
|
||||||
|
Microsoft::WRL::ComPtr<
|
||||||
|
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result) = 0;
|
||||||
|
private:
|
||||||
|
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
|
||||||
|
item_;
|
||||||
|
const DesktopCapturer::SourceId source_id_;
|
||||||
|
};
|
||||||
|
class WgcCaptureSourceFactory {
|
||||||
|
public:
|
||||||
|
virtual ~WgcCaptureSourceFactory();
|
||||||
|
virtual std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
|
||||||
|
DesktopCapturer::SourceId) = 0;
|
||||||
|
};
|
||||||
|
class WgcWindowSourceFactory final : public WgcCaptureSourceFactory {
|
||||||
|
public:
|
||||||
|
WgcWindowSourceFactory();
|
||||||
|
// Disallow copy and assign.
|
||||||
|
WgcWindowSourceFactory(const WgcWindowSourceFactory&) = delete;
|
||||||
|
WgcWindowSourceFactory& operator=(const WgcWindowSourceFactory&) = delete;
|
||||||
|
~WgcWindowSourceFactory() override;
|
||||||
|
std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
|
||||||
|
DesktopCapturer::SourceId) override;
|
||||||
|
};
|
||||||
|
class WgcScreenSourceFactory final : public WgcCaptureSourceFactory {
|
||||||
|
public:
|
||||||
|
WgcScreenSourceFactory();
|
||||||
|
WgcScreenSourceFactory(const WgcScreenSourceFactory&) = delete;
|
||||||
|
WgcScreenSourceFactory& operator=(const WgcScreenSourceFactory&) = delete;
|
||||||
|
~WgcScreenSourceFactory() override;
|
||||||
|
std::unique_ptr<WgcCaptureSource> CreateCaptureSource(
|
||||||
|
DesktopCapturer::SourceId) override;
|
||||||
|
};
|
||||||
|
// Class for capturing application windows.
|
||||||
|
class WgcWindowSource final : public WgcCaptureSource {
|
||||||
|
public:
|
||||||
|
explicit WgcWindowSource(DesktopCapturer::SourceId source_id);
|
||||||
|
WgcWindowSource(const WgcWindowSource&) = delete;
|
||||||
|
WgcWindowSource& operator=(const WgcWindowSource&) = delete;
|
||||||
|
~WgcWindowSource() override;
|
||||||
|
DesktopVector GetTopLeft() override;
|
||||||
|
bool IsCapturable() override;
|
||||||
|
bool FocusOnSource() override;
|
||||||
|
private:
|
||||||
|
HRESULT CreateCaptureItem(
|
||||||
|
Microsoft::WRL::ComPtr<
|
||||||
|
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result)
|
||||||
|
override;
|
||||||
|
};
|
||||||
|
// Class for capturing screens/monitors/displays.
|
||||||
|
class WgcScreenSource final : public WgcCaptureSource {
|
||||||
|
public:
|
||||||
|
explicit WgcScreenSource(DesktopCapturer::SourceId source_id);
|
||||||
|
WgcScreenSource(const WgcScreenSource&) = delete;
|
||||||
|
WgcScreenSource& operator=(const WgcScreenSource&) = delete;
|
||||||
|
~WgcScreenSource() override;
|
||||||
|
DesktopVector GetTopLeft() override;
|
||||||
|
bool IsCapturable() override;
|
||||||
|
private:
|
||||||
|
HRESULT CreateCaptureItem(
|
||||||
|
Microsoft::WRL::ComPtr<
|
||||||
|
ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>* result)
|
||||||
|
override;
|
||||||
|
// To maintain compatibility with other capturers, this class accepts a
|
||||||
|
// device index as it's SourceId. However, WGC requires we use an HMONITOR to
|
||||||
|
// describe which screen to capture. So, we internally convert the supplied
|
||||||
|
// device index into an HMONITOR when |IsCapturable()| is called.
|
||||||
|
absl::optional<HMONITOR> hmonitor_;
|
||||||
|
};
|
||||||
|
} // namespace webrtc
|
||||||
|
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURE_SOURCE_H_
|
||||||
113
webrtc/wgc_capture_source_unittest.cc
Normal file
113
webrtc/wgc_capture_source_unittest.cc
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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_source.h"
|
||||||
|
#include <windows.graphics.capture.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
#include <utility>
|
||||||
|
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||||
|
#include "modules/desktop_capture/desktop_geometry.h"
|
||||||
|
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||||
|
#include "modules/desktop_capture/win/test_support/test_window.h"
|
||||||
|
#include "rtc_base/checks.h"
|
||||||
|
#include "rtc_base/logging.h"
|
||||||
|
#include "rtc_base/win/scoped_com_initializer.h"
|
||||||
|
#include "rtc_base/win/windows_version.h"
|
||||||
|
#include "test/gtest.h"
|
||||||
|
namespace webrtc {
|
||||||
|
namespace {
|
||||||
|
const WCHAR kWindowTitle[] = L"WGC Capture Source Test Window";
|
||||||
|
const int kFirstXCoord = 25;
|
||||||
|
const int kFirstYCoord = 50;
|
||||||
|
const int kSecondXCoord = 50;
|
||||||
|
const int kSecondYCoord = 75;
|
||||||
|
enum SourceType { kWindowSource = 0, kScreenSource = 1 };
|
||||||
|
} // namespace
|
||||||
|
class WgcCaptureSourceTest : public ::testing::TestWithParam<SourceType> {
|
||||||
|
public:
|
||||||
|
void SetUp() override {
|
||||||
|
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_RS5) {
|
||||||
|
RTC_LOG(LS_INFO)
|
||||||
|
<< "Skipping WgcCaptureSourceTests on Windows versions < RS5.";
|
||||||
|
GTEST_SKIP();
|
||||||
|
}
|
||||||
|
com_initializer_ =
|
||||||
|
std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
|
||||||
|
ASSERT_TRUE(com_initializer_->Succeeded());
|
||||||
|
}
|
||||||
|
void TearDown() override {
|
||||||
|
if (window_open_) {
|
||||||
|
DestroyTestWindow(window_info_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void SetUpForWindowSource() {
|
||||||
|
window_info_ = CreateTestWindow(kWindowTitle);
|
||||||
|
window_open_ = true;
|
||||||
|
source_id_ = reinterpret_cast<DesktopCapturer::SourceId>(window_info_.hwnd);
|
||||||
|
source_factory_ = std::make_unique<WgcWindowSourceFactory>();
|
||||||
|
}
|
||||||
|
void SetUpForScreenSource() {
|
||||||
|
source_id_ = kFullDesktopScreenId;
|
||||||
|
source_factory_ = std::make_unique<WgcScreenSourceFactory>();
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<ScopedCOMInitializer> com_initializer_;
|
||||||
|
std::unique_ptr<WgcCaptureSourceFactory> source_factory_;
|
||||||
|
std::unique_ptr<WgcCaptureSource> source_;
|
||||||
|
DesktopCapturer::SourceId source_id_;
|
||||||
|
WindowInfo window_info_;
|
||||||
|
bool window_open_ = false;
|
||||||
|
};
|
||||||
|
// Window specific test
|
||||||
|
TEST_F(WgcCaptureSourceTest, WindowPosition) {
|
||||||
|
SetUpForWindowSource();
|
||||||
|
source_ = source_factory_->CreateCaptureSource(source_id_);
|
||||||
|
ASSERT_TRUE(source_);
|
||||||
|
EXPECT_EQ(source_->GetSourceId(), source_id_);
|
||||||
|
MoveTestWindow(window_info_.hwnd, kFirstXCoord, kFirstYCoord);
|
||||||
|
DesktopVector source_vector = source_->GetTopLeft();
|
||||||
|
EXPECT_EQ(source_vector.x(), kFirstXCoord);
|
||||||
|
EXPECT_EQ(source_vector.y(), kFirstYCoord);
|
||||||
|
MoveTestWindow(window_info_.hwnd, kSecondXCoord, kSecondYCoord);
|
||||||
|
source_vector = source_->GetTopLeft();
|
||||||
|
EXPECT_EQ(source_vector.x(), kSecondXCoord);
|
||||||
|
EXPECT_EQ(source_vector.y(), kSecondYCoord);
|
||||||
|
}
|
||||||
|
// Screen specific test
|
||||||
|
TEST_F(WgcCaptureSourceTest, ScreenPosition) {
|
||||||
|
SetUpForScreenSource();
|
||||||
|
source_ = source_factory_->CreateCaptureSource(source_id_);
|
||||||
|
ASSERT_TRUE(source_);
|
||||||
|
EXPECT_EQ(source_id_, source_->GetSourceId());
|
||||||
|
DesktopRect screen_rect = GetFullscreenRect();
|
||||||
|
DesktopVector source_vector = source_->GetTopLeft();
|
||||||
|
EXPECT_EQ(source_vector.x(), screen_rect.left());
|
||||||
|
EXPECT_EQ(source_vector.y(), screen_rect.top());
|
||||||
|
}
|
||||||
|
// Source agnostic test
|
||||||
|
TEST_P(WgcCaptureSourceTest, CreateSource) {
|
||||||
|
if (GetParam() == SourceType::kWindowSource) {
|
||||||
|
SetUpForWindowSource();
|
||||||
|
} else {
|
||||||
|
SetUpForScreenSource();
|
||||||
|
}
|
||||||
|
source_ = source_factory_->CreateCaptureSource(source_id_);
|
||||||
|
ASSERT_TRUE(source_);
|
||||||
|
EXPECT_EQ(source_id_, source_->GetSourceId());
|
||||||
|
EXPECT_TRUE(source_->IsCapturable());
|
||||||
|
Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
|
||||||
|
item;
|
||||||
|
EXPECT_TRUE(SUCCEEDED(source_->GetCaptureItem(&item)));
|
||||||
|
EXPECT_TRUE(item);
|
||||||
|
}
|
||||||
|
INSTANTIATE_TEST_SUITE_P(SourceAgnostic,
|
||||||
|
WgcCaptureSourceTest,
|
||||||
|
::testing::Values(SourceType::kWindowSource,
|
||||||
|
SourceType::kScreenSource));
|
||||||
|
} // namespace webrtc
|
||||||
218
webrtc/wgc_capturer_win.cc
Normal file
218
webrtc/wgc_capturer_win.cc
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* 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_capturer_win.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
|
||||||
|
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||||
|
#include "modules/desktop_capture/win/wgc_desktop_frame.h"
|
||||||
|
#include "rtc_base/logging.h"
|
||||||
|
#include "rtc_base/time_utils.h"
|
||||||
|
#include "system_wrappers/include/metrics.h"
|
||||||
|
|
||||||
|
namespace WGC = ABI::Windows::Graphics::Capture;
|
||||||
|
using Microsoft::WRL::ComPtr;
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum class WgcCapturerResult {
|
||||||
|
kSuccess = 0,
|
||||||
|
kNoDirect3dDevice = 1,
|
||||||
|
kNoSourceSelected = 2,
|
||||||
|
kItemCreationFailure = 3,
|
||||||
|
kSessionStartFailure = 4,
|
||||||
|
kGetFrameFailure = 5,
|
||||||
|
kFrameDropped = 6,
|
||||||
|
kMaxValue = kFrameDropped
|
||||||
|
};
|
||||||
|
|
||||||
|
void RecordWgcCapturerResult(WgcCapturerResult error) {
|
||||||
|
RTC_HISTOGRAM_ENUMERATION("WebRTC.DesktopCapture.Win.WgcCapturerResult",
|
||||||
|
static_cast<int>(error),
|
||||||
|
static_cast<int>(WgcCapturerResult::kMaxValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
WgcCapturerWin::WgcCapturerWin(
|
||||||
|
std::unique_ptr<WgcCaptureSourceFactory> source_factory,
|
||||||
|
std::unique_ptr<SourceEnumerator> source_enumerator)
|
||||||
|
: source_factory_(std::move(source_factory)),
|
||||||
|
source_enumerator_(std::move(source_enumerator)) {}
|
||||||
|
WgcCapturerWin::~WgcCapturerWin() = default;
|
||||||
|
|
||||||
|
// static
|
||||||
|
std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawWindowCapturer(
|
||||||
|
const DesktopCaptureOptions& options) {
|
||||||
|
return std::make_unique<WgcCapturerWin>(
|
||||||
|
std::make_unique<WgcWindowSourceFactory>(),
|
||||||
|
std::make_unique<WindowEnumerator>(
|
||||||
|
options.enumerate_current_process_windows()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawScreenCapturer(
|
||||||
|
const DesktopCaptureOptions& options) {
|
||||||
|
return std::make_unique<WgcCapturerWin>(
|
||||||
|
std::make_unique<WgcScreenSourceFactory>(),
|
||||||
|
std::make_unique<ScreenEnumerator>());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WgcCapturerWin::GetSourceList(SourceList* sources) {
|
||||||
|
return source_enumerator_->FindAllSources(sources);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WgcCapturerWin::SelectSource(DesktopCapturer::SourceId id) {
|
||||||
|
capture_source_ = source_factory_->CreateCaptureSource(id);
|
||||||
|
return capture_source_->IsCapturable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WgcCapturerWin::FocusOnSelectedSource() {
|
||||||
|
if (!capture_source_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return capture_source_->FocusOnSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WgcCapturerWin::Start(Callback* callback) {
|
||||||
|
RTC_DCHECK(!callback_);
|
||||||
|
RTC_DCHECK(callback);
|
||||||
|
RecordCapturerImpl(DesktopCapturerId::kWgcCapturerWin);
|
||||||
|
|
||||||
|
callback_ = callback;
|
||||||
|
|
||||||
|
// Create a Direct3D11 device to share amongst the WgcCaptureSessions. Many
|
||||||
|
// parameters are nullptr as the implemention uses defaults that work well for
|
||||||
|
// us.
|
||||||
|
HRESULT hr = D3D11CreateDevice(
|
||||||
|
/*adapter=*/nullptr, D3D_DRIVER_TYPE_HARDWARE,
|
||||||
|
/*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
||||||
|
/*feature_levels=*/nullptr, /*feature_levels_size=*/0, D3D11_SDK_VERSION,
|
||||||
|
&d3d11_device_, /*feature_level=*/nullptr, /*device_context=*/nullptr);
|
||||||
|
if (hr == DXGI_ERROR_UNSUPPORTED) {
|
||||||
|
// If a hardware device could not be created, use WARP which is a high speed
|
||||||
|
// software device.
|
||||||
|
hr = D3D11CreateDevice(
|
||||||
|
/*adapter=*/nullptr, D3D_DRIVER_TYPE_WARP,
|
||||||
|
/*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
||||||
|
/*feature_levels=*/nullptr, /*feature_levels_size=*/0,
|
||||||
|
D3D11_SDK_VERSION, &d3d11_device_, /*feature_level=*/nullptr,
|
||||||
|
/*device_context=*/nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Failed to create D3D11Device: " << hr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WgcCapturerWin::CaptureFrame() {
|
||||||
|
RTC_DCHECK(callback_);
|
||||||
|
|
||||||
|
if (!capture_source_) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Source hasn't been selected";
|
||||||
|
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||||
|
/*frame=*/nullptr);
|
||||||
|
RecordWgcCapturerResult(WgcCapturerResult::kNoSourceSelected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!d3d11_device_) {
|
||||||
|
RTC_LOG(LS_ERROR) << "No D3D11D3evice, cannot capture.";
|
||||||
|
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||||
|
/*frame=*/nullptr);
|
||||||
|
RecordWgcCapturerResult(WgcCapturerResult::kNoDirect3dDevice);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t capture_start_time_nanos = rtc::TimeNanos();
|
||||||
|
|
||||||
|
HRESULT hr;
|
||||||
|
WgcCaptureSession* capture_session = nullptr;
|
||||||
|
std::map<SourceId, WgcCaptureSession>::iterator session_iter =
|
||||||
|
ongoing_captures_.find(capture_source_->GetSourceId());
|
||||||
|
if (session_iter == ongoing_captures_.end()) {
|
||||||
|
ComPtr<WGC::IGraphicsCaptureItem> item;
|
||||||
|
hr = capture_source_->GetCaptureItem(&item);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Failed to create a GraphicsCaptureItem: " << hr;
|
||||||
|
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||||
|
/*frame=*/nullptr);
|
||||||
|
RecordWgcCapturerResult(WgcCapturerResult::kItemCreationFailure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::map<SourceId, WgcCaptureSession>::iterator, bool>
|
||||||
|
iter_success_pair = ongoing_captures_.emplace(
|
||||||
|
std::piecewise_construct,
|
||||||
|
std::forward_as_tuple(capture_source_->GetSourceId()),
|
||||||
|
std::forward_as_tuple(d3d11_device_, item));
|
||||||
|
RTC_DCHECK(iter_success_pair.second);
|
||||||
|
capture_session = &iter_success_pair.first->second;
|
||||||
|
} else {
|
||||||
|
capture_session = &session_iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!capture_session->IsCaptureStarted()) {
|
||||||
|
hr = capture_session->StartCapture();
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RTC_LOG(LS_ERROR) << "Failed to start capture: " << hr;
|
||||||
|
ongoing_captures_.erase(capture_source_->GetSourceId());
|
||||||
|
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||||
|
/*frame=*/nullptr);
|
||||||
|
RecordWgcCapturerResult(WgcCapturerResult::kSessionStartFailure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<DesktopFrame> frame;
|
||||||
|
hr = capture_session->GetFrame(&frame);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
RTC_LOG(LS_ERROR) << "GetFrame failed: " << hr;
|
||||||
|
ongoing_captures_.erase(capture_source_->GetSourceId());
|
||||||
|
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
|
||||||
|
/*frame=*/nullptr);
|
||||||
|
RecordWgcCapturerResult(WgcCapturerResult::kGetFrameFailure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame) {
|
||||||
|
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY,
|
||||||
|
/*frame=*/nullptr);
|
||||||
|
RecordWgcCapturerResult(WgcCapturerResult::kFrameDropped);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
|
||||||
|
rtc::kNumNanosecsPerMillisec;
|
||||||
|
RTC_HISTOGRAM_COUNTS_1000("WebRTC.DesktopCapture.Win.WgcCapturerFrameTime",
|
||||||
|
capture_time_ms);
|
||||||
|
frame->set_capture_time_ms(capture_time_ms);
|
||||||
|
frame->set_capturer_id(DesktopCapturerId::kWgcCapturerWin);
|
||||||
|
frame->set_may_contain_cursor(true);
|
||||||
|
frame->set_top_left(capture_source_->GetTopLeft());
|
||||||
|
RecordWgcCapturerResult(WgcCapturerResult::kSuccess);
|
||||||
|
callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS,
|
||||||
|
std::move(frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WgcCapturerWin::IsSourceBeingCaptured(DesktopCapturer::SourceId id) {
|
||||||
|
std::map<DesktopCapturer::SourceId, WgcCaptureSession>::iterator
|
||||||
|
session_iter = ongoing_captures_.find(id);
|
||||||
|
if (session_iter == ongoing_captures_.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return session_iter->second.IsCaptureStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
138
webrtc/wgc_capturer_win.h
Normal file
138
webrtc/wgc_capturer_win.h
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_
|
||||||
|
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_
|
||||||
|
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||||
|
#include "modules/desktop_capture/desktop_capturer.h"
|
||||||
|
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||||
|
#include "modules/desktop_capture/win/wgc_capture_session.h"
|
||||||
|
#include "modules/desktop_capture/win/wgc_capture_source.h"
|
||||||
|
#include "modules/desktop_capture/win/window_capture_utils.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
// WgcCapturerWin is initialized with an implementation of this base class,
|
||||||
|
// which it uses to find capturable sources of a particular type. This way,
|
||||||
|
// WgcCapturerWin can remain source-agnostic.
|
||||||
|
class SourceEnumerator {
|
||||||
|
public:
|
||||||
|
virtual ~SourceEnumerator() = default;
|
||||||
|
|
||||||
|
virtual bool FindAllSources(DesktopCapturer::SourceList* sources) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WindowEnumerator final : public SourceEnumerator {
|
||||||
|
public:
|
||||||
|
explicit WindowEnumerator(bool enumerate_current_process_windows)
|
||||||
|
: enumerate_current_process_windows_(enumerate_current_process_windows) {}
|
||||||
|
|
||||||
|
WindowEnumerator(const WindowEnumerator&) = delete;
|
||||||
|
WindowEnumerator& operator=(const WindowEnumerator&) = delete;
|
||||||
|
|
||||||
|
~WindowEnumerator() override = default;
|
||||||
|
|
||||||
|
bool FindAllSources(DesktopCapturer::SourceList* sources) override {
|
||||||
|
// WGC fails to capture windows with the WS_EX_TOOLWINDOW style, so we
|
||||||
|
// provide it as a filter to ensure windows with the style are not returned.
|
||||||
|
return window_capture_helper_.EnumerateCapturableWindows(
|
||||||
|
sources, enumerate_current_process_windows_, WS_EX_TOOLWINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WindowCaptureHelperWin window_capture_helper_;
|
||||||
|
bool enumerate_current_process_windows_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScreenEnumerator final : public SourceEnumerator {
|
||||||
|
public:
|
||||||
|
ScreenEnumerator() = default;
|
||||||
|
|
||||||
|
ScreenEnumerator(const ScreenEnumerator&) = delete;
|
||||||
|
ScreenEnumerator& operator=(const ScreenEnumerator&) = delete;
|
||||||
|
|
||||||
|
~ScreenEnumerator() override = default;
|
||||||
|
|
||||||
|
bool FindAllSources(DesktopCapturer::SourceList* sources) override {
|
||||||
|
return webrtc::GetScreenList(sources);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A capturer that uses the Window.Graphics.Capture APIs. It is suitable for
|
||||||
|
// both window and screen capture (but only one type per instance). Consumers
|
||||||
|
// should not instantiate this class directly, instead they should use
|
||||||
|
// |CreateRawWindowCapturer()| or |CreateRawScreenCapturer()| to receive a
|
||||||
|
// capturer appropriate for the type of source they want to capture.
|
||||||
|
class WgcCapturerWin : public DesktopCapturer {
|
||||||
|
public:
|
||||||
|
WgcCapturerWin(std::unique_ptr<WgcCaptureSourceFactory> source_factory,
|
||||||
|
std::unique_ptr<SourceEnumerator> source_enumerator);
|
||||||
|
|
||||||
|
WgcCapturerWin(const WgcCapturerWin&) = delete;
|
||||||
|
WgcCapturerWin& operator=(const WgcCapturerWin&) = delete;
|
||||||
|
|
||||||
|
~WgcCapturerWin() override;
|
||||||
|
|
||||||
|
static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer(
|
||||||
|
const DesktopCaptureOptions& options);
|
||||||
|
|
||||||
|
static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer(
|
||||||
|
const DesktopCaptureOptions& options);
|
||||||
|
|
||||||
|
// DesktopCapturer interface.
|
||||||
|
bool GetSourceList(SourceList* sources) override;
|
||||||
|
bool SelectSource(SourceId id) override;
|
||||||
|
bool FocusOnSelectedSource() override;
|
||||||
|
void Start(Callback* callback) override;
|
||||||
|
void CaptureFrame() override;
|
||||||
|
|
||||||
|
// Used in WgcCapturerTests.
|
||||||
|
bool IsSourceBeingCaptured(SourceId id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Factory to create a WgcCaptureSource for us whenever SelectSource is
|
||||||
|
// called. Initialized at construction with a source-specific implementation.
|
||||||
|
std::unique_ptr<WgcCaptureSourceFactory> source_factory_;
|
||||||
|
|
||||||
|
// The source enumerator helps us find capturable sources of the appropriate
|
||||||
|
// type. Initialized at construction with a source-specific implementation.
|
||||||
|
std::unique_ptr<SourceEnumerator> source_enumerator_;
|
||||||
|
|
||||||
|
// The WgcCaptureSource represents the source we are capturing. It tells us
|
||||||
|
// if the source is capturable and it creates the GraphicsCaptureItem for us.
|
||||||
|
std::unique_ptr<WgcCaptureSource> capture_source_;
|
||||||
|
|
||||||
|
// A map of all the sources we are capturing and the associated
|
||||||
|
// WgcCaptureSession. Frames for the current source (indicated via
|
||||||
|
// SelectSource) will be retrieved from the appropriate session when
|
||||||
|
// requested via CaptureFrame.
|
||||||
|
// This helps us efficiently capture multiple sources (e.g. when consumers
|
||||||
|
// are trying to display a list of available capture targets with thumbnails).
|
||||||
|
std::map<SourceId, WgcCaptureSession> ongoing_captures_;
|
||||||
|
|
||||||
|
// The callback that we deliver frames to, synchronously, before CaptureFrame
|
||||||
|
// returns.
|
||||||
|
Callback* callback_ = nullptr;
|
||||||
|
|
||||||
|
// A Direct3D11 device that is shared amongst the WgcCaptureSessions, who
|
||||||
|
// require one to perform the capture.
|
||||||
|
Microsoft::WRL::ComPtr<::ID3D11Device> d3d11_device_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_CAPTURER_WIN_H_
|
||||||
421
webrtc/wgc_capturer_win_unittest.cc
Normal file
421
webrtc/wgc_capturer_win_unittest.cc
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
/*
|
||||||
|
* 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_capturer_win.h"
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||||
|
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||||
|
#include "modules/desktop_capture/desktop_capturer.h"
|
||||||
|
#include "modules/desktop_capture/win/test_support/test_window.h"
|
||||||
|
#include "modules/desktop_capture/win/window_capture_utils.h"
|
||||||
|
#include "rtc_base/checks.h"
|
||||||
|
#include "rtc_base/logging.h"
|
||||||
|
#include "rtc_base/thread.h"
|
||||||
|
#include "rtc_base/time_utils.h"
|
||||||
|
#include "rtc_base/win/scoped_com_initializer.h"
|
||||||
|
#include "rtc_base/win/windows_version.h"
|
||||||
|
#include "system_wrappers/include/metrics.h"
|
||||||
|
#include "test/gtest.h"
|
||||||
|
namespace webrtc {
|
||||||
|
namespace {
|
||||||
|
const char kWindowThreadName[] = "wgc_capturer_test_window_thread";
|
||||||
|
const WCHAR kWindowTitle[] = L"WGC Capturer Test Window";
|
||||||
|
const char kCapturerImplHistogram[] =
|
||||||
|
"WebRTC.DesktopCapture.Win.DesktopCapturerImpl";
|
||||||
|
const char kCapturerResultHistogram[] =
|
||||||
|
"WebRTC.DesktopCapture.Win.WgcCapturerResult";
|
||||||
|
const int kSuccess = 0;
|
||||||
|
const int kSessionStartFailure = 4;
|
||||||
|
const char kCaptureSessionResultHistogram[] =
|
||||||
|
"WebRTC.DesktopCapture.Win.WgcCaptureSessionStartResult";
|
||||||
|
const int kSourceClosed = 1;
|
||||||
|
const char kCaptureTimeHistogram[] =
|
||||||
|
"WebRTC.DesktopCapture.Win.WgcCapturerFrameTime";
|
||||||
|
const int kSmallWindowWidth = 200;
|
||||||
|
const int kSmallWindowHeight = 100;
|
||||||
|
const int kMediumWindowWidth = 300;
|
||||||
|
const int kMediumWindowHeight = 200;
|
||||||
|
const int kLargeWindowWidth = 400;
|
||||||
|
const int kLargeWindowHeight = 500;
|
||||||
|
// The size of the image we capture is slightly smaller than the actual size of
|
||||||
|
// the window.
|
||||||
|
const int kWindowWidthSubtrahend = 14;
|
||||||
|
const int kWindowHeightSubtrahend = 7;
|
||||||
|
// Custom message constants so we can direct our thread to close windows
|
||||||
|
// and quit running.
|
||||||
|
const UINT kNoOp = WM_APP;
|
||||||
|
const UINT kDestroyWindow = WM_APP + 1;
|
||||||
|
const UINT kQuitRunning = WM_APP + 2;
|
||||||
|
enum CaptureType { kWindowCapture = 0, kScreenCapture = 1 };
|
||||||
|
} // namespace
|
||||||
|
class WgcCapturerWinTest : public ::testing::TestWithParam<CaptureType>,
|
||||||
|
public DesktopCapturer::Callback {
|
||||||
|
public:
|
||||||
|
void SetUp() override {
|
||||||
|
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_RS5) {
|
||||||
|
RTC_LOG(LS_INFO)
|
||||||
|
<< "Skipping WgcCapturerWinTests on Windows versions < RS5.";
|
||||||
|
GTEST_SKIP();
|
||||||
|
}
|
||||||
|
com_initializer_ =
|
||||||
|
std::make_unique<ScopedCOMInitializer>(ScopedCOMInitializer::kMTA);
|
||||||
|
EXPECT_TRUE(com_initializer_->Succeeded());
|
||||||
|
}
|
||||||
|
void SetUpForWindowCapture(int window_width = kMediumWindowWidth,
|
||||||
|
int window_height = kMediumWindowHeight) {
|
||||||
|
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
|
||||||
|
DesktopCaptureOptions::CreateDefault());
|
||||||
|
CreateWindowOnSeparateThread(window_width, window_height);
|
||||||
|
StartWindowThreadMessageLoop();
|
||||||
|
source_id_ = GetTestWindowIdFromSourceList();
|
||||||
|
}
|
||||||
|
void SetUpForScreenCapture() {
|
||||||
|
capturer_ = WgcCapturerWin::CreateRawScreenCapturer(
|
||||||
|
DesktopCaptureOptions::CreateDefault());
|
||||||
|
source_id_ = GetScreenIdFromSourceList();
|
||||||
|
}
|
||||||
|
void TearDown() override {
|
||||||
|
if (window_open_) {
|
||||||
|
CloseTestWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The window must live on a separate thread so that we can run a message pump
|
||||||
|
// without blocking the test thread. This is necessary if we are interested in
|
||||||
|
// having GraphicsCaptureItem events (i.e. the Closed event) fire, and it more
|
||||||
|
// closely resembles how capture works in the wild.
|
||||||
|
void CreateWindowOnSeparateThread(int window_width, int window_height) {
|
||||||
|
window_thread_ = rtc::Thread::Create();
|
||||||
|
window_thread_->SetName(kWindowThreadName, nullptr);
|
||||||
|
window_thread_->Start();
|
||||||
|
window_thread_->Invoke<void>(RTC_FROM_HERE, [this, window_width,
|
||||||
|
window_height]() {
|
||||||
|
window_thread_id_ = GetCurrentThreadId();
|
||||||
|
window_info_ =
|
||||||
|
CreateTestWindow(kWindowTitle, window_height, window_width);
|
||||||
|
window_open_ = true;
|
||||||
|
while (!IsWindowResponding(window_info_.hwnd)) {
|
||||||
|
RTC_LOG(LS_INFO) << "Waiting for test window to become responsive in "
|
||||||
|
"WgcWindowCaptureTest.";
|
||||||
|
}
|
||||||
|
while (!IsWindowValidAndVisible(window_info_.hwnd)) {
|
||||||
|
RTC_LOG(LS_INFO) << "Waiting for test window to be visible in "
|
||||||
|
"WgcWindowCaptureTest.";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(window_thread_->RunningForTest());
|
||||||
|
ASSERT_FALSE(window_thread_->IsCurrent());
|
||||||
|
}
|
||||||
|
void StartWindowThreadMessageLoop() {
|
||||||
|
window_thread_->PostTask(RTC_FROM_HERE, [this]() {
|
||||||
|
MSG msg;
|
||||||
|
BOOL gm;
|
||||||
|
while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
|
||||||
|
::DispatchMessage(&msg);
|
||||||
|
if (msg.message == kDestroyWindow) {
|
||||||
|
DestroyTestWindow(window_info_);
|
||||||
|
}
|
||||||
|
if (msg.message == kQuitRunning) {
|
||||||
|
PostQuitMessage(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void CloseTestWindow() {
|
||||||
|
::PostThreadMessage(window_thread_id_, kDestroyWindow, 0, 0);
|
||||||
|
::PostThreadMessage(window_thread_id_, kQuitRunning, 0, 0);
|
||||||
|
window_thread_->Stop();
|
||||||
|
window_open_ = false;
|
||||||
|
}
|
||||||
|
DesktopCapturer::SourceId GetTestWindowIdFromSourceList() {
|
||||||
|
// Frequently, the test window will not show up in GetSourceList because it
|
||||||
|
// was created too recently. Since we are confident the window will be found
|
||||||
|
// eventually we loop here until we find it.
|
||||||
|
intptr_t src_id;
|
||||||
|
do {
|
||||||
|
DesktopCapturer::SourceList sources;
|
||||||
|
EXPECT_TRUE(capturer_->GetSourceList(&sources));
|
||||||
|
auto it = std::find_if(
|
||||||
|
sources.begin(), sources.end(),
|
||||||
|
[&](const DesktopCapturer::Source& src) {
|
||||||
|
return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd);
|
||||||
|
});
|
||||||
|
src_id = it->id;
|
||||||
|
} while (src_id != reinterpret_cast<intptr_t>(window_info_.hwnd));
|
||||||
|
return src_id;
|
||||||
|
}
|
||||||
|
DesktopCapturer::SourceId GetScreenIdFromSourceList() {
|
||||||
|
DesktopCapturer::SourceList sources;
|
||||||
|
EXPECT_TRUE(capturer_->GetSourceList(&sources));
|
||||||
|
EXPECT_GT(sources.size(), 0ULL);
|
||||||
|
return sources[0].id;
|
||||||
|
}
|
||||||
|
void DoCapture() {
|
||||||
|
// Sometimes the first few frames are empty becaues the capture engine is
|
||||||
|
// still starting up. We also may drop a few frames when the window is
|
||||||
|
// resized or un-minimized.
|
||||||
|
do {
|
||||||
|
capturer_->CaptureFrame();
|
||||||
|
} while (result_ == DesktopCapturer::Result::ERROR_TEMPORARY);
|
||||||
|
EXPECT_EQ(result_, DesktopCapturer::Result::SUCCESS);
|
||||||
|
EXPECT_TRUE(frame_);
|
||||||
|
EXPECT_GT(metrics::NumEvents(kCapturerResultHistogram, kSuccess),
|
||||||
|
successful_captures_);
|
||||||
|
++successful_captures_;
|
||||||
|
}
|
||||||
|
void ValidateFrame(int expected_width, int expected_height) {
|
||||||
|
EXPECT_EQ(frame_->size().width(), expected_width - kWindowWidthSubtrahend);
|
||||||
|
EXPECT_EQ(frame_->size().height(),
|
||||||
|
expected_height - kWindowHeightSubtrahend);
|
||||||
|
// Verify the buffer contains as much data as it should, and that the right
|
||||||
|
// colors are found.
|
||||||
|
int data_length = frame_->stride() * frame_->size().height();
|
||||||
|
// The first and last pixel should have the same color because they will be
|
||||||
|
// from the border of the window.
|
||||||
|
// Pixels have 4 bytes of data so the whole pixel needs a uint32_t to fit.
|
||||||
|
uint32_t first_pixel = static_cast<uint32_t>(*frame_->data());
|
||||||
|
uint32_t last_pixel = static_cast<uint32_t>(
|
||||||
|
*(frame_->data() + data_length - DesktopFrame::kBytesPerPixel));
|
||||||
|
EXPECT_EQ(first_pixel, last_pixel);
|
||||||
|
// Let's also check a pixel from the middle of the content area, which the
|
||||||
|
// TestWindow will paint a consistent color for us to verify.
|
||||||
|
uint8_t* middle_pixel = frame_->data() + (data_length / 2);
|
||||||
|
int sub_pixel_offset = DesktopFrame::kBytesPerPixel / 4;
|
||||||
|
EXPECT_EQ(*middle_pixel, kTestWindowBValue);
|
||||||
|
middle_pixel += sub_pixel_offset;
|
||||||
|
EXPECT_EQ(*middle_pixel, kTestWindowGValue);
|
||||||
|
middle_pixel += sub_pixel_offset;
|
||||||
|
EXPECT_EQ(*middle_pixel, kTestWindowRValue);
|
||||||
|
middle_pixel += sub_pixel_offset;
|
||||||
|
// The window is opaque so we expect 0xFF for the Alpha channel.
|
||||||
|
EXPECT_EQ(*middle_pixel, 0xFF);
|
||||||
|
}
|
||||||
|
// DesktopCapturer::Callback interface
|
||||||
|
// The capturer synchronously invokes this method before |CaptureFrame()|
|
||||||
|
// returns.
|
||||||
|
void OnCaptureResult(DesktopCapturer::Result result,
|
||||||
|
std::unique_ptr<DesktopFrame> frame) override {
|
||||||
|
result_ = result;
|
||||||
|
frame_ = std::move(frame);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<ScopedCOMInitializer> com_initializer_;
|
||||||
|
DWORD window_thread_id_;
|
||||||
|
std::unique_ptr<rtc::Thread> window_thread_;
|
||||||
|
WindowInfo window_info_;
|
||||||
|
intptr_t source_id_;
|
||||||
|
bool window_open_ = false;
|
||||||
|
DesktopCapturer::Result result_;
|
||||||
|
int successful_captures_ = 0;
|
||||||
|
std::unique_ptr<DesktopFrame> frame_;
|
||||||
|
std::unique_ptr<DesktopCapturer> capturer_;
|
||||||
|
};
|
||||||
|
TEST_P(WgcCapturerWinTest, SelectValidSource) {
|
||||||
|
if (GetParam() == CaptureType::kWindowCapture) {
|
||||||
|
SetUpForWindowCapture();
|
||||||
|
} else {
|
||||||
|
SetUpForScreenCapture();
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
}
|
||||||
|
TEST_P(WgcCapturerWinTest, SelectInvalidSource) {
|
||||||
|
if (GetParam() == CaptureType::kWindowCapture) {
|
||||||
|
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
|
||||||
|
DesktopCaptureOptions::CreateDefault());
|
||||||
|
source_id_ = kNullWindowId;
|
||||||
|
} else {
|
||||||
|
capturer_ = WgcCapturerWin::CreateRawScreenCapturer(
|
||||||
|
DesktopCaptureOptions::CreateDefault());
|
||||||
|
source_id_ = kInvalidScreenId;
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(capturer_->SelectSource(source_id_));
|
||||||
|
}
|
||||||
|
TEST_P(WgcCapturerWinTest, Capture) {
|
||||||
|
if (GetParam() == CaptureType::kWindowCapture) {
|
||||||
|
SetUpForWindowCapture();
|
||||||
|
} else {
|
||||||
|
SetUpForScreenCapture();
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
capturer_->Start(this);
|
||||||
|
EXPECT_GE(metrics::NumEvents(kCapturerImplHistogram,
|
||||||
|
DesktopCapturerId::kWgcCapturerWin),
|
||||||
|
1);
|
||||||
|
DoCapture();
|
||||||
|
EXPECT_GT(frame_->size().width(), 0);
|
||||||
|
EXPECT_GT(frame_->size().height(), 0);
|
||||||
|
}
|
||||||
|
TEST_P(WgcCapturerWinTest, CaptureTime) {
|
||||||
|
if (GetParam() == CaptureType::kWindowCapture) {
|
||||||
|
SetUpForWindowCapture();
|
||||||
|
} else {
|
||||||
|
SetUpForScreenCapture();
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
capturer_->Start(this);
|
||||||
|
int64_t start_time;
|
||||||
|
do {
|
||||||
|
start_time = rtc::TimeNanos();
|
||||||
|
capturer_->CaptureFrame();
|
||||||
|
} while (result_ == DesktopCapturer::Result::ERROR_TEMPORARY);
|
||||||
|
int capture_time_ms =
|
||||||
|
(rtc::TimeNanos() - start_time) / rtc::kNumNanosecsPerMillisec;
|
||||||
|
EXPECT_TRUE(frame_);
|
||||||
|
// The test may measure the time slightly differently than the capturer. So we
|
||||||
|
// just check if it's within 5 ms.
|
||||||
|
EXPECT_NEAR(frame_->capture_time_ms(), capture_time_ms, 5);
|
||||||
|
EXPECT_GE(
|
||||||
|
metrics::NumEvents(kCaptureTimeHistogram, frame_->capture_time_ms()), 1);
|
||||||
|
}
|
||||||
|
INSTANTIATE_TEST_SUITE_P(SourceAgnostic,
|
||||||
|
WgcCapturerWinTest,
|
||||||
|
::testing::Values(CaptureType::kWindowCapture,
|
||||||
|
CaptureType::kScreenCapture));
|
||||||
|
// Monitor specific tests.
|
||||||
|
TEST_F(WgcCapturerWinTest, FocusOnMonitor) {
|
||||||
|
SetUpForScreenCapture();
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(0));
|
||||||
|
// You can't set focus on a monitor.
|
||||||
|
EXPECT_FALSE(capturer_->FocusOnSelectedSource());
|
||||||
|
}
|
||||||
|
TEST_F(WgcCapturerWinTest, CaptureAllMonitors) {
|
||||||
|
SetUpForScreenCapture();
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(kFullDesktopScreenId));
|
||||||
|
capturer_->Start(this);
|
||||||
|
DoCapture();
|
||||||
|
EXPECT_GT(frame_->size().width(), 0);
|
||||||
|
EXPECT_GT(frame_->size().height(), 0);
|
||||||
|
}
|
||||||
|
// Window specific tests.
|
||||||
|
TEST_F(WgcCapturerWinTest, FocusOnWindow) {
|
||||||
|
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
|
||||||
|
DesktopCaptureOptions::CreateDefault());
|
||||||
|
window_info_ = CreateTestWindow(kWindowTitle);
|
||||||
|
source_id_ = GetScreenIdFromSourceList();
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
EXPECT_TRUE(capturer_->FocusOnSelectedSource());
|
||||||
|
HWND hwnd = reinterpret_cast<HWND>(source_id_);
|
||||||
|
EXPECT_EQ(hwnd, ::GetActiveWindow());
|
||||||
|
EXPECT_EQ(hwnd, ::GetForegroundWindow());
|
||||||
|
EXPECT_EQ(hwnd, ::GetFocus());
|
||||||
|
DestroyTestWindow(window_info_);
|
||||||
|
}
|
||||||
|
TEST_F(WgcCapturerWinTest, SelectMinimizedWindow) {
|
||||||
|
SetUpForWindowCapture();
|
||||||
|
MinimizeTestWindow(reinterpret_cast<HWND>(source_id_));
|
||||||
|
EXPECT_FALSE(capturer_->SelectSource(source_id_));
|
||||||
|
UnminimizeTestWindow(reinterpret_cast<HWND>(source_id_));
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
}
|
||||||
|
TEST_F(WgcCapturerWinTest, SelectClosedWindow) {
|
||||||
|
SetUpForWindowCapture();
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
CloseTestWindow();
|
||||||
|
EXPECT_FALSE(capturer_->SelectSource(source_id_));
|
||||||
|
}
|
||||||
|
TEST_F(WgcCapturerWinTest, UnsupportedWindowStyle) {
|
||||||
|
// Create a window with the WS_EX_TOOLWINDOW style, which WGC does not
|
||||||
|
// support.
|
||||||
|
window_info_ = CreateTestWindow(kWindowTitle, kMediumWindowWidth,
|
||||||
|
kMediumWindowHeight, WS_EX_TOOLWINDOW);
|
||||||
|
capturer_ = WgcCapturerWin::CreateRawWindowCapturer(
|
||||||
|
DesktopCaptureOptions::CreateDefault());
|
||||||
|
DesktopCapturer::SourceList sources;
|
||||||
|
EXPECT_TRUE(capturer_->GetSourceList(&sources));
|
||||||
|
auto it = std::find_if(
|
||||||
|
sources.begin(), sources.end(), [&](const DesktopCapturer::Source& src) {
|
||||||
|
return src.id == reinterpret_cast<intptr_t>(window_info_.hwnd);
|
||||||
|
});
|
||||||
|
// We should not find the window, since we filter for unsupported styles.
|
||||||
|
EXPECT_EQ(it, sources.end());
|
||||||
|
DestroyTestWindow(window_info_);
|
||||||
|
}
|
||||||
|
TEST_F(WgcCapturerWinTest, IncreaseWindowSizeMidCapture) {
|
||||||
|
SetUpForWindowCapture(kSmallWindowWidth, kSmallWindowHeight);
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
capturer_->Start(this);
|
||||||
|
DoCapture();
|
||||||
|
ValidateFrame(kSmallWindowWidth, kSmallWindowHeight);
|
||||||
|
ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight);
|
||||||
|
DoCapture();
|
||||||
|
// We don't expect to see the new size until the next capture, as the frame
|
||||||
|
// pool hadn't had a chance to resize yet to fit the new, larger image.
|
||||||
|
DoCapture();
|
||||||
|
ValidateFrame(kSmallWindowWidth, kMediumWindowHeight);
|
||||||
|
ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight);
|
||||||
|
DoCapture();
|
||||||
|
DoCapture();
|
||||||
|
ValidateFrame(kLargeWindowWidth, kMediumWindowHeight);
|
||||||
|
}
|
||||||
|
TEST_F(WgcCapturerWinTest, ReduceWindowSizeMidCapture) {
|
||||||
|
SetUpForWindowCapture(kLargeWindowWidth, kLargeWindowHeight);
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
capturer_->Start(this);
|
||||||
|
DoCapture();
|
||||||
|
ValidateFrame(kLargeWindowWidth, kLargeWindowHeight);
|
||||||
|
ResizeTestWindow(window_info_.hwnd, kLargeWindowWidth, kMediumWindowHeight);
|
||||||
|
// We expect to see the new size immediately because the image data has shrunk
|
||||||
|
// and will fit in the existing buffer.
|
||||||
|
DoCapture();
|
||||||
|
ValidateFrame(kLargeWindowWidth, kMediumWindowHeight);
|
||||||
|
ResizeTestWindow(window_info_.hwnd, kSmallWindowWidth, kMediumWindowHeight);
|
||||||
|
DoCapture();
|
||||||
|
ValidateFrame(kSmallWindowWidth, kMediumWindowHeight);
|
||||||
|
}
|
||||||
|
TEST_F(WgcCapturerWinTest, MinimizeWindowMidCapture) {
|
||||||
|
SetUpForWindowCapture();
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
capturer_->Start(this);
|
||||||
|
// Minmize the window and capture should continue but return temporary errors.
|
||||||
|
MinimizeTestWindow(window_info_.hwnd);
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
capturer_->CaptureFrame();
|
||||||
|
EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_TEMPORARY);
|
||||||
|
}
|
||||||
|
// Reopen the window and the capture should continue normally.
|
||||||
|
UnminimizeTestWindow(window_info_.hwnd);
|
||||||
|
DoCapture();
|
||||||
|
// We can't verify the window size here because the test window does not
|
||||||
|
// repaint itself after it is unminimized, but capturing successfully is still
|
||||||
|
// a good test.
|
||||||
|
}
|
||||||
|
TEST_F(WgcCapturerWinTest, CloseWindowMidCapture) {
|
||||||
|
SetUpForWindowCapture();
|
||||||
|
EXPECT_TRUE(capturer_->SelectSource(source_id_));
|
||||||
|
capturer_->Start(this);
|
||||||
|
DoCapture();
|
||||||
|
ValidateFrame(kMediumWindowWidth, kMediumWindowHeight);
|
||||||
|
CloseTestWindow();
|
||||||
|
// We need to call GetMessage to trigger the Closed event and the capturer's
|
||||||
|
// event handler for it. If we are too early and the Closed event hasn't
|
||||||
|
// arrived yet we should keep trying until the capturer receives it and stops.
|
||||||
|
auto* wgc_capturer = static_cast<WgcCapturerWin*>(capturer_.get());
|
||||||
|
while (wgc_capturer->IsSourceBeingCaptured(source_id_)) {
|
||||||
|
// Since the capturer handles the Closed message, there will be no message
|
||||||
|
// for us and GetMessage will hang, unless we send ourselves a message
|
||||||
|
// first.
|
||||||
|
::PostThreadMessage(GetCurrentThreadId(), kNoOp, 0, 0);
|
||||||
|
MSG msg;
|
||||||
|
::GetMessage(&msg, NULL, 0, 0);
|
||||||
|
::DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
// Occasionally, one last frame will have made it into the frame pool before
|
||||||
|
// the window closed. The first call will consume it, and in that case we need
|
||||||
|
// to make one more call to CaptureFrame.
|
||||||
|
capturer_->CaptureFrame();
|
||||||
|
if (result_ == DesktopCapturer::Result::SUCCESS)
|
||||||
|
capturer_->CaptureFrame();
|
||||||
|
EXPECT_GE(metrics::NumEvents(kCapturerResultHistogram, kSessionStartFailure),
|
||||||
|
1);
|
||||||
|
EXPECT_GE(metrics::NumEvents(kCaptureSessionResultHistogram, kSourceClosed),
|
||||||
|
1);
|
||||||
|
EXPECT_EQ(result_, DesktopCapturer::Result::ERROR_PERMANENT);
|
||||||
|
}
|
||||||
|
} // namespace webrtc
|
||||||
19
webrtc/wgc_desktop_frame.cc
Normal file
19
webrtc/wgc_desktop_frame.cc
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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_desktop_frame.h"
|
||||||
|
#include <utility>
|
||||||
|
namespace webrtc {
|
||||||
|
WgcDesktopFrame::WgcDesktopFrame(DesktopSize size,
|
||||||
|
int stride,
|
||||||
|
std::vector<uint8_t>&& image_data)
|
||||||
|
: DesktopFrame(size, stride, image_data.data(), nullptr),
|
||||||
|
image_data_(std::move(image_data)) {}
|
||||||
|
WgcDesktopFrame::~WgcDesktopFrame() = default;
|
||||||
|
} // namespace webrtc
|
||||||
35
webrtc/wgc_desktop_frame.h
Normal file
35
webrtc/wgc_desktop_frame.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifndef MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_
|
||||||
|
#define MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include "desktop_frame.h"
|
||||||
|
#include "desktop_geometry.h"
|
||||||
|
namespace webrtc {
|
||||||
|
// DesktopFrame implementation used by capturers that use the
|
||||||
|
// Windows.Graphics.Capture API.
|
||||||
|
class WgcDesktopFrame final : public DesktopFrame {
|
||||||
|
public:
|
||||||
|
// WgcDesktopFrame receives an rvalue reference to the |image_data| vector
|
||||||
|
// so that it can take ownership of it (and avoid a copy).
|
||||||
|
WgcDesktopFrame(DesktopSize size,
|
||||||
|
int stride,
|
||||||
|
std::vector<uint8_t>&& image_data);
|
||||||
|
WgcDesktopFrame(const WgcDesktopFrame&) = delete;
|
||||||
|
WgcDesktopFrame& operator=(const WgcDesktopFrame&) = delete;
|
||||||
|
~WgcDesktopFrame() override;
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> image_data_;
|
||||||
|
};
|
||||||
|
} // namespace webrtc
|
||||||
|
#endif // MODULES_DESKTOP_CAPTURE_WIN_WGC_DESKTOP_FRAME_H_
|
||||||
@@ -35,6 +35,6 @@ target("remote_desk")
|
|||||||
add_packages("log")
|
add_packages("log")
|
||||||
add_packages("ffmpeg")
|
add_packages("ffmpeg")
|
||||||
add_links("avfilter", "avdevice", "avformat", "avcodec", "swscale", "swresample", "avutil")
|
add_links("avfilter", "avdevice", "avformat", "avcodec", "swscale", "swresample", "avutil")
|
||||||
add_files("*.cpp")
|
add_files("dll/*.cpp")
|
||||||
add_includedirs("../../src/interface")
|
add_includedirs("../../src/interface")
|
||||||
-- add_links("SDL2main", "SDL2-static")
|
-- add_links("SDL2main", "SDL2-static")
|
||||||
|
|||||||
Reference in New Issue
Block a user