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);
|
||||
|
||||
// Populate combo box
|
||||
for (auto &window : g_windows) {
|
||||
SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, (LPARAM)window.Title().c_str());
|
||||
}
|
||||
// for (auto &window : g_windows) {
|
||||
// SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, (LPARAM)window.Title().c_str());
|
||||
// }
|
||||
|
||||
for (auto &monitor : g_monitors) {
|
||||
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) {
|
||||
switch (msg) {
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
case WM_COMMAND:
|
||||
if (HIWORD(wParam) == CBN_SELCHANGE) {
|
||||
auto index = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
|
||||
if (index < g_windows.size() - 1) {
|
||||
auto window = g_windows[index];
|
||||
g_app->StartCapture(window.Hwnd());
|
||||
} else {
|
||||
auto monitor = g_monitors[index - g_windows.size()];
|
||||
g_app->StartCapture(monitor.Hmonitor());
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
case WM_COMMAND:
|
||||
if (HIWORD(wParam) == CBN_SELCHANGE) {
|
||||
auto index = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
|
||||
// if (index < g_windows.size() - 1) {
|
||||
// auto window = g_windows[index];
|
||||
// g_app->StartCapture(window.Hwnd());
|
||||
// } else {
|
||||
// auto monitor = g_monitors[index - g_windows.size()];
|
||||
auto monitor = g_monitors[0];
|
||||
g_app->StartCapture(monitor.Hmonitor());
|
||||
// }
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
break;
|
||||
}
|
||||
|
||||
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 "utils_string.h"
|
||||
|
||||
#include "system_error.h"
|
||||
#include "error_define.h"
|
||||
#include "log_helper.h"
|
||||
#include "system_error.h"
|
||||
#include "utils_string.h"
|
||||
|
||||
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc,
|
||||
LPARAM data) {
|
||||
|
||||
MONITORINFOEX info_ex;
|
||||
info_ex.cbSize = sizeof(MONITORINFOEX);
|
||||
|
||||
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) {
|
||||
*(HMONITOR *)data = hmonitor;
|
||||
@@ -31,6 +34,7 @@ HMONITOR GetPrimaryMonitor() {
|
||||
|
||||
namespace am {
|
||||
|
||||
|
||||
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 error = AE_NO;
|
||||
if (_inited == true) return error;
|
||||
if (_inited == true)
|
||||
return error;
|
||||
|
||||
_fps = fps;
|
||||
_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;
|
||||
|
||||
do {
|
||||
if (!module_.is_supported()) {
|
||||
if (!wgc_is_supported()) {
|
||||
error = AE_UNSUPPORT;
|
||||
break;
|
||||
}
|
||||
|
||||
session_ = module_.create_session();
|
||||
session_ = wgc_create_session();
|
||||
if (!session_) {
|
||||
error = AE_WGC_CREATE_CAPTURER_FAILED;
|
||||
break;
|
||||
@@ -93,20 +98,23 @@ int record_desktop_wgc::start() {
|
||||
|
||||
int record_desktop_wgc::pause() {
|
||||
_paused = true;
|
||||
if (session_) session_->pause();
|
||||
if (session_)
|
||||
session_->pause();
|
||||
return AE_NO;
|
||||
}
|
||||
|
||||
int record_desktop_wgc::resume() {
|
||||
_paused = false;
|
||||
if (session_) session_->resume();
|
||||
if (session_)
|
||||
session_->resume();
|
||||
return AE_NO;
|
||||
}
|
||||
|
||||
int record_desktop_wgc::stop() {
|
||||
_running = false;
|
||||
|
||||
if (session_) session_->stop();
|
||||
if (session_)
|
||||
session_->stop();
|
||||
|
||||
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_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);
|
||||
}
|
||||
@@ -136,9 +145,10 @@ void record_desktop_wgc::on_frame(const wgc_session::wgc_session_frame &frame) {
|
||||
void record_desktop_wgc::clean_up() {
|
||||
_inited = false;
|
||||
|
||||
if (session_) session_->release();
|
||||
if (session_)
|
||||
session_->release();
|
||||
|
||||
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("ffmpeg")
|
||||
add_links("avfilter", "avdevice", "avformat", "avcodec", "swscale", "swresample", "avutil")
|
||||
add_files("*.cpp")
|
||||
add_files("dll/*.cpp")
|
||||
add_includedirs("../../src/interface")
|
||||
-- add_links("SDL2main", "SDL2-static")
|
||||
|
||||
Reference in New Issue
Block a user