mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-10 17:49:20 +08:00
[refactor] reorganize platform and wire sources
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
#include "screen_capturer_dxgi.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <display_stream_id.h>
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
namespace {
|
||||
std::string WideToUtf8(const std::wstring& wstr) {
|
||||
if (wstr.empty()) return {};
|
||||
int size_needed = WideCharToMultiByte(
|
||||
CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
|
||||
std::string result(size_needed, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(),
|
||||
size_needed, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GetDisplayLabel(const std::wstring& wide_name) {
|
||||
std::string name = WideToUtf8(wide_name);
|
||||
constexpr char kDevicePrefix[] = "\\\\.\\";
|
||||
if (name.rfind(kDevicePrefix, 0) == 0) {
|
||||
name.erase(0, sizeof(kDevicePrefix) - 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ScreenCapturerDxgi::ScreenCapturerDxgi() {}
|
||||
ScreenCapturerDxgi::~ScreenCapturerDxgi() {
|
||||
Stop();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
int ScreenCapturerDxgi::Init(const int fps, cb_desktop_data cb) {
|
||||
fps_ = fps;
|
||||
callback_ = cb;
|
||||
if (!callback_) {
|
||||
LOG_ERROR("DXGI: callback is null");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!InitializeDxgi()) {
|
||||
LOG_ERROR("DXGI: initialize DXGI failed");
|
||||
return -2;
|
||||
}
|
||||
|
||||
EnumerateDisplays();
|
||||
if (display_info_list_.empty()) {
|
||||
LOG_ERROR("DXGI: no displays found");
|
||||
return -3;
|
||||
}
|
||||
|
||||
monitor_index_ = 0;
|
||||
initial_monitor_index_ = monitor_index_;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerDxgi::Destroy() {
|
||||
Stop();
|
||||
ReleaseDuplication();
|
||||
outputs_.clear();
|
||||
d3d_context_.Reset();
|
||||
d3d_device_.Reset();
|
||||
dxgi_factory_.Reset();
|
||||
if (nv12_frame_) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = nullptr;
|
||||
nv12_width_ = 0;
|
||||
nv12_height_ = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerDxgi::Start(bool show_cursor) {
|
||||
if (running_) return 0;
|
||||
show_cursor_ = show_cursor;
|
||||
|
||||
if (!CreateDuplicationForMonitor(monitor_index_)) {
|
||||
LOG_ERROR("DXGI: create duplication failed for monitor {}",
|
||||
monitor_index_.load());
|
||||
return -1;
|
||||
}
|
||||
|
||||
paused_ = false;
|
||||
running_ = true;
|
||||
thread_ = std::thread([this]() { CaptureLoop(); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerDxgi::Stop() {
|
||||
if (!running_) return 0;
|
||||
running_ = false;
|
||||
if (thread_.joinable()) thread_.join();
|
||||
ReleaseDuplication();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerDxgi::Pause(int monitor_index) {
|
||||
paused_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerDxgi::Resume(int monitor_index) {
|
||||
paused_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerDxgi::SwitchTo(int monitor_index) {
|
||||
std::lock_guard<std::mutex> lock(switch_mutex_);
|
||||
if (monitor_index < 0 || monitor_index >= (int)display_info_list_.size()) {
|
||||
LOG_ERROR("DXGI: invalid monitor index {}", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
paused_ = true;
|
||||
monitor_index_ = monitor_index;
|
||||
ReleaseDuplication();
|
||||
if (!CreateDuplicationForMonitor(monitor_index_)) {
|
||||
LOG_ERROR("DXGI: create duplication failed for monitor {}",
|
||||
monitor_index_.load());
|
||||
paused_ = false; // Reset paused_ on failure
|
||||
return -2;
|
||||
}
|
||||
paused_ = false;
|
||||
LOG_INFO("DXGI: switched to monitor {}:{}", monitor_index_.load(),
|
||||
display_info_list_[monitor_index_].name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerDxgi::ResetToInitialMonitor() {
|
||||
std::lock_guard<std::mutex> lock(switch_mutex_);
|
||||
if (display_info_list_.empty()) return -1;
|
||||
int target = initial_monitor_index_;
|
||||
if (target < 0 || target >= (int)display_info_list_.size()) return -1;
|
||||
if (monitor_index_ == target) return 0;
|
||||
if (running_) {
|
||||
paused_ = true;
|
||||
monitor_index_ = target;
|
||||
ReleaseDuplication();
|
||||
if (!CreateDuplicationForMonitor(monitor_index_)) {
|
||||
paused_ = false;
|
||||
return -2;
|
||||
}
|
||||
paused_ = false;
|
||||
LOG_INFO("DXGI: reset to initial monitor {}:{}", monitor_index_.load(),
|
||||
display_info_list_[monitor_index_].name);
|
||||
} else {
|
||||
monitor_index_ = target;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ScreenCapturerDxgi::InitializeDxgi() {
|
||||
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||
#ifdef _DEBUG
|
||||
flags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
#endif
|
||||
|
||||
D3D_FEATURE_LEVEL feature_levels[] = {
|
||||
D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0};
|
||||
|
||||
D3D_FEATURE_LEVEL out_level{};
|
||||
HRESULT hr = D3D11CreateDevice(
|
||||
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags, feature_levels,
|
||||
ARRAYSIZE(feature_levels), D3D11_SDK_VERSION, d3d_device_.GetAddressOf(),
|
||||
&out_level, d3d_context_.GetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, flags,
|
||||
feature_levels, ARRAYSIZE(feature_levels),
|
||||
D3D11_SDK_VERSION, d3d_device_.GetAddressOf(),
|
||||
&out_level, d3d_context_.GetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR("DXGI: D3D11CreateDevice failed, hr={}", (int)hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
hr = CreateDXGIFactory1(
|
||||
__uuidof(IDXGIFactory1),
|
||||
reinterpret_cast<void**>(dxgi_factory_.GetAddressOf()));
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR("DXGI: CreateDXGIFactory1 failed, hr={}", (int)hr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenCapturerDxgi::EnumerateDisplays() {
|
||||
display_info_list_.clear();
|
||||
outputs_.clear();
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
|
||||
for (UINT a = 0;
|
||||
dxgi_factory_->EnumAdapters(a, adapter.ReleaseAndGetAddressOf()) !=
|
||||
DXGI_ERROR_NOT_FOUND;
|
||||
++a) {
|
||||
Microsoft::WRL::ComPtr<IDXGIOutput> output;
|
||||
for (UINT o = 0; adapter->EnumOutputs(o, output.ReleaseAndGetAddressOf()) !=
|
||||
DXGI_ERROR_NOT_FOUND;
|
||||
++o) {
|
||||
DXGI_OUTPUT_DESC desc{};
|
||||
if (FAILED(output->GetDesc(&desc))) {
|
||||
continue;
|
||||
}
|
||||
std::string name = GetDisplayLabel(desc.DeviceName);
|
||||
MONITORINFOEX mi{};
|
||||
mi.cbSize = sizeof(MONITORINFOEX);
|
||||
if (GetMonitorInfo(desc.Monitor, &mi)) {
|
||||
bool is_primary = (mi.dwFlags & MONITORINFOF_PRIMARY) ? true : false;
|
||||
DisplayInfo info((void*)desc.Monitor, name, is_primary,
|
||||
mi.rcMonitor.left, mi.rcMonitor.top,
|
||||
mi.rcMonitor.right, mi.rcMonitor.bottom);
|
||||
// primary first
|
||||
if (is_primary)
|
||||
display_info_list_.insert(display_info_list_.begin(), info);
|
||||
else
|
||||
display_info_list_.push_back(info);
|
||||
outputs_.push_back(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ScreenCapturerDxgi::CreateDuplicationForMonitor(int monitor_index) {
|
||||
if (monitor_index < 0 || monitor_index >= (int)outputs_.size()) return false;
|
||||
Microsoft::WRL::ComPtr<IDXGIOutput1> output1;
|
||||
HRESULT hr = outputs_[monitor_index]->QueryInterface(
|
||||
IID_PPV_ARGS(output1.GetAddressOf()));
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR("DXGI: Query IDXGIOutput1 failed, hr={}", (int)hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
duplication_.Reset();
|
||||
hr = output1->DuplicateOutput(d3d_device_.Get(), duplication_.GetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR("DXGI: DuplicateOutput failed, hr={}", (int)hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
staging_.Reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScreenCapturerDxgi::RecreateDuplicationForCurrentMonitor() {
|
||||
std::lock_guard<std::mutex> lock(switch_mutex_);
|
||||
ReleaseDuplication();
|
||||
int current_monitor = monitor_index_.load();
|
||||
if (CreateDuplicationForMonitor(current_monitor)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
EnumerateDisplays();
|
||||
if (display_info_list_.empty()) {
|
||||
LOG_ERROR("DXGI: no displays found while recreating duplication");
|
||||
return false;
|
||||
}
|
||||
if (current_monitor < 0 ||
|
||||
current_monitor >= static_cast<int>(display_info_list_.size())) {
|
||||
current_monitor = 0;
|
||||
monitor_index_ = 0;
|
||||
}
|
||||
if (CreateDuplicationForMonitor(current_monitor)) {
|
||||
LOG_INFO("DXGI: recreated duplication for monitor {}",
|
||||
monitor_index_.load());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScreenCapturerDxgi::ReleaseDuplication() {
|
||||
staging_.Reset();
|
||||
if (duplication_) {
|
||||
duplication_->ReleaseFrame();
|
||||
}
|
||||
duplication_.Reset();
|
||||
}
|
||||
|
||||
void ScreenCapturerDxgi::CaptureLoop() {
|
||||
const int timeout_ms = 33;
|
||||
auto last_duplication_retry =
|
||||
std::chrono::steady_clock::now() - std::chrono::milliseconds(1000);
|
||||
while (running_) {
|
||||
if (paused_) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!duplication_) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now - last_duplication_retry >= std::chrono::milliseconds(500)) {
|
||||
last_duplication_retry = now;
|
||||
RecreateDuplicationForCurrentMonitor();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
DXGI_OUTDUPL_FRAME_INFO frame_info{};
|
||||
Microsoft::WRL::ComPtr<IDXGIResource> desktop_resource;
|
||||
HRESULT hr = duplication_->AcquireNextFrame(
|
||||
timeout_ms, &frame_info, desktop_resource.GetAddressOf());
|
||||
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
|
||||
continue;
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR("DXGI: AcquireNextFrame failed, hr={}", (int)hr);
|
||||
RecreateDuplicationForCurrentMonitor();
|
||||
continue;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3D11Texture2D> acquired_tex;
|
||||
if (desktop_resource) {
|
||||
hr = desktop_resource->QueryInterface(
|
||||
IID_PPV_ARGS(acquired_tex.GetAddressOf()));
|
||||
if (FAILED(hr)) {
|
||||
duplication_->ReleaseFrame();
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
duplication_->ReleaseFrame();
|
||||
continue;
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC src_desc{};
|
||||
acquired_tex->GetDesc(&src_desc);
|
||||
|
||||
if (!staging_) {
|
||||
D3D11_TEXTURE2D_DESC staging_desc = src_desc;
|
||||
staging_desc.Usage = D3D11_USAGE_STAGING;
|
||||
staging_desc.BindFlags = 0;
|
||||
staging_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
staging_desc.MiscFlags = 0;
|
||||
hr = d3d_device_->CreateTexture2D(&staging_desc, nullptr,
|
||||
staging_.GetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR("DXGI: CreateTexture2D staging failed, hr={}", (int)hr);
|
||||
duplication_->ReleaseFrame();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
d3d_context_->CopyResource(staging_.Get(), acquired_tex.Get());
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapped{};
|
||||
hr = d3d_context_->Map(staging_.Get(), 0, D3D11_MAP_READ, 0, &mapped);
|
||||
if (FAILED(hr)) {
|
||||
duplication_->ReleaseFrame();
|
||||
continue;
|
||||
}
|
||||
|
||||
int logical_width = static_cast<int>(src_desc.Width);
|
||||
int even_width = logical_width & ~1;
|
||||
int even_height = static_cast<int>(src_desc.Height) & ~1;
|
||||
if (even_width <= 0 || even_height <= 0) {
|
||||
d3d_context_->Unmap(staging_.Get(), 0);
|
||||
duplication_->ReleaseFrame();
|
||||
continue;
|
||||
}
|
||||
|
||||
int nv12_size = even_width * even_height * 3 / 2;
|
||||
if (!nv12_frame_ || nv12_width_ != even_width ||
|
||||
nv12_height_ != even_height) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = new unsigned char[nv12_size];
|
||||
nv12_width_ = even_width;
|
||||
nv12_height_ = even_height;
|
||||
}
|
||||
|
||||
libyuv::ARGBToNV12(static_cast<const uint8_t*>(mapped.pData),
|
||||
static_cast<int>(mapped.RowPitch), nv12_frame_,
|
||||
even_width, nv12_frame_ + even_width * even_height,
|
||||
even_width, even_width, even_height);
|
||||
|
||||
if (callback_) {
|
||||
int idx = monitor_index_.load();
|
||||
if (idx >= 0 && idx < static_cast<int>(display_info_list_.size())) {
|
||||
const std::string stream_id = MakeDisplayStreamId(idx);
|
||||
callback_(nv12_frame_, nv12_size, even_width, even_height,
|
||||
stream_id.c_str());
|
||||
} else {
|
||||
LOG_ERROR("DXGI: CaptureLoop invalid monitor_index {} (list size {})",
|
||||
idx, display_info_list_.size());
|
||||
}
|
||||
}
|
||||
|
||||
d3d_context_->Unmap(staging_.Get(), 0);
|
||||
duplication_->ReleaseFrame();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-02-27
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_DXGI_H_
|
||||
#define _SCREEN_CAPTURER_DXGI_H_
|
||||
|
||||
#include <Windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "rd_log.h"
|
||||
#include "screen_capturer.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class ScreenCapturerDxgi : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerDxgi();
|
||||
~ScreenCapturerDxgi();
|
||||
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
|
||||
int SwitchTo(int monitor_index) override;
|
||||
int ResetToInitialMonitor() override;
|
||||
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() override {
|
||||
return display_info_list_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool InitializeDxgi();
|
||||
void EnumerateDisplays();
|
||||
bool CreateDuplicationForMonitor(int monitor_index);
|
||||
bool RecreateDuplicationForCurrentMonitor();
|
||||
void CaptureLoop();
|
||||
void ReleaseDuplication();
|
||||
|
||||
private:
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
std::vector<Microsoft::WRL::ComPtr<IDXGIOutput>> outputs_;
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory_;
|
||||
Microsoft::WRL::ComPtr<ID3D11Device> d3d_device_;
|
||||
Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d_context_;
|
||||
Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication_;
|
||||
Microsoft::WRL::ComPtr<ID3D11Texture2D> staging_;
|
||||
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> paused_{false};
|
||||
std::atomic<int> monitor_index_{0};
|
||||
int initial_monitor_index_ = 0;
|
||||
std::atomic<bool> show_cursor_{true};
|
||||
std::thread thread_;
|
||||
int fps_ = 60;
|
||||
cb_desktop_data callback_ = nullptr;
|
||||
std::mutex switch_mutex_;
|
||||
|
||||
unsigned char* nv12_frame_ = nullptr;
|
||||
int nv12_width_ = 0;
|
||||
int nv12_height_ = 0;
|
||||
};
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "screen_capturer_factory.h"
|
||||
|
||||
#include "screen_capturer_win.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
ScreenCapturer* ScreenCapturerFactory::Create() {
|
||||
return new ScreenCapturerWin();
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,227 @@
|
||||
#include "screen_capturer_gdi.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <display_stream_id.h>
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
namespace {
|
||||
std::string WideToUtf8(const std::wstring& wstr) {
|
||||
if (wstr.empty()) return {};
|
||||
int size_needed = WideCharToMultiByte(
|
||||
CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
|
||||
std::string result(size_needed, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(),
|
||||
size_needed, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GetDisplayLabel(const std::wstring& wide_name) {
|
||||
std::string name = WideToUtf8(wide_name);
|
||||
constexpr char kDevicePrefix[] = "\\\\.\\";
|
||||
if (name.rfind(kDevicePrefix, 0) == 0) {
|
||||
name.erase(0, sizeof(kDevicePrefix) - 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ScreenCapturerGdi::ScreenCapturerGdi() {}
|
||||
ScreenCapturerGdi::~ScreenCapturerGdi() {
|
||||
Stop();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
BOOL CALLBACK ScreenCapturerGdi::EnumMonitorProc(HMONITOR hMonitor, HDC, LPRECT,
|
||||
LPARAM data) {
|
||||
auto displays = reinterpret_cast<std::vector<DisplayInfo>*>(data);
|
||||
MONITORINFOEX mi{};
|
||||
mi.cbSize = sizeof(MONITORINFOEX);
|
||||
if (GetMonitorInfo(hMonitor, &mi)) {
|
||||
std::string name = GetDisplayLabel(mi.szDevice);
|
||||
bool is_primary = (mi.dwFlags & MONITORINFOF_PRIMARY) ? true : false;
|
||||
DisplayInfo info((void*)hMonitor, name, is_primary, mi.rcMonitor.left,
|
||||
mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom);
|
||||
if (is_primary)
|
||||
displays->insert(displays->begin(), info);
|
||||
else
|
||||
displays->push_back(info);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void ScreenCapturerGdi::EnumerateDisplays() {
|
||||
display_info_list_.clear();
|
||||
EnumDisplayMonitors(nullptr, nullptr, EnumMonitorProc,
|
||||
(LPARAM)&display_info_list_);
|
||||
}
|
||||
|
||||
int ScreenCapturerGdi::Init(const int fps, cb_desktop_data cb) {
|
||||
fps_ = fps;
|
||||
callback_ = cb;
|
||||
if (!callback_) {
|
||||
LOG_ERROR("GDI: callback is null");
|
||||
return -1;
|
||||
}
|
||||
EnumerateDisplays();
|
||||
if (display_info_list_.empty()) {
|
||||
LOG_ERROR("GDI: no displays found");
|
||||
return -2;
|
||||
}
|
||||
monitor_index_ = 0;
|
||||
initial_monitor_index_ = monitor_index_;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerGdi::Destroy() {
|
||||
Stop();
|
||||
if (nv12_frame_) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = nullptr;
|
||||
nv12_width_ = 0;
|
||||
nv12_height_ = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerGdi::Start(bool show_cursor) {
|
||||
if (running_) return 0;
|
||||
show_cursor_ = show_cursor;
|
||||
paused_ = false;
|
||||
running_ = true;
|
||||
thread_ = std::thread([this]() { CaptureLoop(); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerGdi::Stop() {
|
||||
if (!running_) return 0;
|
||||
running_ = false;
|
||||
if (thread_.joinable()) thread_.join();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerGdi::Pause(int monitor_index) {
|
||||
paused_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerGdi::Resume(int monitor_index) {
|
||||
paused_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerGdi::SwitchTo(int monitor_index) {
|
||||
if (monitor_index < 0 || monitor_index >= (int)display_info_list_.size()) {
|
||||
LOG_ERROR("GDI: invalid monitor index {}", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
monitor_index_ = monitor_index;
|
||||
LOG_INFO("GDI: switched to monitor {}:{}", monitor_index_.load(),
|
||||
display_info_list_[monitor_index_].name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerGdi::ResetToInitialMonitor() {
|
||||
if (display_info_list_.empty()) return -1;
|
||||
int target = initial_monitor_index_;
|
||||
if (target < 0 || target >= (int)display_info_list_.size()) return -1;
|
||||
monitor_index_ = target;
|
||||
LOG_INFO("GDI: reset to initial monitor {}:{}", monitor_index_.load(),
|
||||
display_info_list_[monitor_index_].name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerGdi::CaptureLoop() {
|
||||
int interval_ms = fps_ > 0 ? (1000 / fps_) : 16;
|
||||
HDC screen_dc = GetDC(nullptr);
|
||||
while (running_) {
|
||||
if (paused_) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
}
|
||||
if (!screen_dc) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
int idx = monitor_index_.load();
|
||||
if (idx < 0 || idx >= static_cast<int>(display_info_list_.size())) {
|
||||
LOG_ERROR("GDI: CaptureLoop invalid monitor_index {} (list size {})",
|
||||
idx, display_info_list_.size());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms));
|
||||
continue;
|
||||
}
|
||||
const auto& di = display_info_list_[idx];
|
||||
int left = di.left;
|
||||
int top = di.top;
|
||||
int width = di.width & ~1;
|
||||
int height = di.height & ~1;
|
||||
if (width <= 0 || height <= 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms));
|
||||
continue;
|
||||
}
|
||||
|
||||
BITMAPINFO bmi{};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = width;
|
||||
bmi.bmiHeader.biHeight = -height;
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* bits = nullptr;
|
||||
HDC mem_dc = CreateCompatibleDC(screen_dc);
|
||||
HBITMAP dib =
|
||||
CreateDIBSection(mem_dc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
|
||||
HGDIOBJ old = SelectObject(mem_dc, dib);
|
||||
|
||||
BitBlt(mem_dc, 0, 0, width, height, screen_dc, left, top,
|
||||
SRCCOPY | CAPTUREBLT);
|
||||
|
||||
if (show_cursor_) {
|
||||
CURSORINFO ci{};
|
||||
ci.cbSize = sizeof(CURSORINFO);
|
||||
if (GetCursorInfo(&ci) && ci.flags == CURSOR_SHOWING && ci.hCursor) {
|
||||
POINT pt = ci.ptScreenPos;
|
||||
int cx = pt.x - left;
|
||||
int cy = pt.y - top;
|
||||
if (cx >= -64 && cy >= -64 && cx < width + 64 && cy < height + 64) {
|
||||
DrawIconEx(mem_dc, cx, cy, ci.hCursor, 0, 0, 0, nullptr, DI_NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int stride_argb = width * 4;
|
||||
int nv12_size = width * height * 3 / 2;
|
||||
if (!nv12_frame_ || nv12_width_ != width || nv12_height_ != height) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = new unsigned char[nv12_size];
|
||||
nv12_width_ = width;
|
||||
nv12_height_ = height;
|
||||
}
|
||||
|
||||
libyuv::ARGBToNV12(static_cast<const uint8_t*>(bits), stride_argb,
|
||||
nv12_frame_, width, nv12_frame_ + width * height, width,
|
||||
width, height);
|
||||
|
||||
if (callback_) {
|
||||
const std::string stream_id = MakeDisplayStreamId(idx);
|
||||
callback_(nv12_frame_, nv12_size, width, height, stream_id.c_str());
|
||||
}
|
||||
|
||||
SelectObject(mem_dc, old);
|
||||
DeleteObject(dib);
|
||||
DeleteDC(mem_dc);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms));
|
||||
}
|
||||
ReleaseDC(nullptr, screen_dc);
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-02-27
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_GDI_H_
|
||||
#define _SCREEN_CAPTURER_GDI_H_
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "rd_log.h"
|
||||
#include "screen_capturer.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class ScreenCapturerGdi : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerGdi();
|
||||
~ScreenCapturerGdi();
|
||||
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
|
||||
int SwitchTo(int monitor_index) override;
|
||||
int ResetToInitialMonitor() override;
|
||||
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() override {
|
||||
return display_info_list_;
|
||||
}
|
||||
|
||||
private:
|
||||
static BOOL CALLBACK EnumMonitorProc(HMONITOR hMonitor, HDC, LPRECT,
|
||||
LPARAM data);
|
||||
void EnumerateDisplays();
|
||||
void CaptureLoop();
|
||||
|
||||
private:
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> paused_{false};
|
||||
std::atomic<int> monitor_index_{0};
|
||||
int initial_monitor_index_ = 0;
|
||||
std::atomic<bool> show_cursor_{true};
|
||||
std::thread thread_;
|
||||
int fps_ = 60;
|
||||
cb_desktop_data callback_ = nullptr;
|
||||
|
||||
unsigned char* nv12_frame_ = nullptr;
|
||||
int nv12_width_ = 0;
|
||||
int nv12_height_ = 0;
|
||||
};
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,426 @@
|
||||
#include "screen_capturer_wgc.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <d3d11_4.h>
|
||||
#include <winrt/Windows.Foundation.Metadata.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <display_stream_id.h>
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
static std::vector<DisplayInfo> gs_display_list;
|
||||
|
||||
std::string WideToUtf8(const std::wstring& wstr) {
|
||||
if (wstr.empty()) return {};
|
||||
int size_needed = WideCharToMultiByte(
|
||||
CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
|
||||
std::string result(size_needed, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(),
|
||||
size_needed, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GetDisplayLabel(const std::wstring& wide_name) {
|
||||
std::string name = WideToUtf8(wide_name);
|
||||
constexpr char kDevicePrefix[] = "\\\\.\\";
|
||||
if (name.rfind(kDevicePrefix, 0) == 0) {
|
||||
name.erase(0, sizeof(kDevicePrefix) - 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
|
||||
[[maybe_unused]] LPRECT lprc, LPARAM data) {
|
||||
MONITORINFOEX monitor_info_;
|
||||
monitor_info_.cbSize = sizeof(MONITORINFOEX);
|
||||
|
||||
if (GetMonitorInfo(hmonitor, &monitor_info_)) {
|
||||
std::string display_name = GetDisplayLabel(monitor_info_.szDevice);
|
||||
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
|
||||
gs_display_list.insert(
|
||||
gs_display_list.begin(),
|
||||
{(void*)hmonitor, display_name,
|
||||
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
|
||||
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
|
||||
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
|
||||
*(HMONITOR*)data = hmonitor;
|
||||
} else {
|
||||
gs_display_list.push_back(DisplayInfo(
|
||||
(void*)hmonitor, display_name,
|
||||
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
|
||||
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
|
||||
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HMONITOR GetPrimaryMonitor() {
|
||||
HMONITOR hmonitor = nullptr;
|
||||
|
||||
gs_display_list.clear();
|
||||
::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor);
|
||||
|
||||
return hmonitor;
|
||||
}
|
||||
|
||||
ScreenCapturerWgc::ScreenCapturerWgc() : monitor_(nullptr) {}
|
||||
|
||||
ScreenCapturerWgc::~ScreenCapturerWgc() {
|
||||
Stop();
|
||||
CleanUp();
|
||||
|
||||
if (nv12_frame_) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = nullptr;
|
||||
nv12_width_ = 0;
|
||||
nv12_height_ = 0;
|
||||
}
|
||||
|
||||
if (nv12_frame_scaled_) {
|
||||
delete[] nv12_frame_scaled_;
|
||||
nv12_frame_scaled_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScreenCapturerWgc::IsWgcSupported() {
|
||||
try {
|
||||
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
|
||||
return winrt::Windows::Foundation::Metadata::ApiInformation::
|
||||
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
|
||||
} catch (const winrt::hresult_error&) {
|
||||
return false;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
|
||||
if (inited_ == true) return 0;
|
||||
|
||||
// nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2];
|
||||
// nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2];
|
||||
|
||||
fps_ = fps;
|
||||
|
||||
on_data_ = cb;
|
||||
|
||||
if (!IsWgcSupported()) {
|
||||
LOG_ERROR("WGC not supported");
|
||||
return 2;
|
||||
}
|
||||
|
||||
return RebuildSessions(monitor_index_);
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::RebuildSessions(int preferred_monitor_index) {
|
||||
CleanUp();
|
||||
|
||||
if (!IsWgcSupported()) {
|
||||
LOG_ERROR("WGC not supported");
|
||||
return 2;
|
||||
}
|
||||
|
||||
monitor_ = GetPrimaryMonitor();
|
||||
|
||||
display_info_list_ = gs_display_list;
|
||||
|
||||
if (display_info_list_.empty()) {
|
||||
LOG_ERROR("No display found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (preferred_monitor_index < 0 ||
|
||||
preferred_monitor_index >= static_cast<int>(display_info_list_.size())) {
|
||||
preferred_monitor_index = 0;
|
||||
}
|
||||
monitor_index_ = preferred_monitor_index;
|
||||
|
||||
int error = 0;
|
||||
for (int i = 0; i < display_info_list_.size(); i++) {
|
||||
const auto& display = display_info_list_[i];
|
||||
LOG_INFO(
|
||||
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
|
||||
"({}, {})",
|
||||
i, display.name, (display.is_primary ? "yes" : "no"), display.left,
|
||||
display.top, display.right, display.bottom);
|
||||
|
||||
sessions_.push_back(
|
||||
{std::make_unique<WgcSessionImpl>(i), false, false, false});
|
||||
sessions_.back().session_->RegisterObserver(this);
|
||||
error = sessions_.back().session_->Initialize((HMONITOR)display.handle);
|
||||
if (error != 0) {
|
||||
LOG_ERROR("WGC: initialize session {} failed, ret={}", i, error);
|
||||
CleanUp();
|
||||
return error;
|
||||
}
|
||||
sessions_[i].inited_ = true;
|
||||
}
|
||||
|
||||
LOG_INFO("Default on monitor {}:{}", monitor_index_,
|
||||
display_info_list_[monitor_index_].name);
|
||||
|
||||
if (initial_monitor_index_ < 0 ||
|
||||
initial_monitor_index_ >= static_cast<int>(display_info_list_.size())) {
|
||||
initial_monitor_index_ = monitor_index_;
|
||||
}
|
||||
inited_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Destroy() {
|
||||
CleanUp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Start(bool show_cursor) {
|
||||
if (running_ == true) {
|
||||
LOG_ERROR("Screen capturer already running");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (inited_ == false) {
|
||||
const int ret = RebuildSessions(monitor_index_);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("Screen capturer not inited");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = StartSessions(show_cursor);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_WARN("WGC: start failed, rebuilding sessions");
|
||||
ret = RebuildSessions(monitor_index_);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
return StartSessions(show_cursor);
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::StartSessions(bool show_cursor) {
|
||||
bool any_started = false;
|
||||
bool active_started = false;
|
||||
int last_error = 0;
|
||||
int active_monitor = monitor_index_;
|
||||
if (active_monitor < 0 ||
|
||||
active_monitor >= static_cast<int>(sessions_.size())) {
|
||||
active_monitor = 0;
|
||||
monitor_index_ = 0;
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(sessions_.size()); i++) {
|
||||
if (sessions_[i].inited_ == false) {
|
||||
LOG_ERROR("Session {} not inited", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sessions_[i].running_) {
|
||||
LOG_ERROR("Session {} is already running", i);
|
||||
} else {
|
||||
int ret = sessions_[i].session_->Start(show_cursor);
|
||||
if (ret != 0) {
|
||||
LOG_ERROR("Session {} start failed, ret={}", i, ret);
|
||||
last_error = ret;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i != active_monitor) {
|
||||
sessions_[i].session_->Pause();
|
||||
sessions_[i].paused_ = true;
|
||||
} else {
|
||||
sessions_[i].session_->Resume();
|
||||
sessions_[i].paused_ = false;
|
||||
}
|
||||
sessions_[i].running_ = true;
|
||||
any_started = true;
|
||||
if (i == active_monitor) {
|
||||
active_started = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
running_ = active_started;
|
||||
if (!active_started) {
|
||||
LOG_ERROR("WGC: active session did not start successfully");
|
||||
Stop();
|
||||
return last_error != 0 ? last_error : -1;
|
||||
}
|
||||
if (!any_started) {
|
||||
LOG_ERROR("WGC: no session started successfully");
|
||||
return last_error != 0 ? last_error : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Pause(int monitor_index) {
|
||||
if (monitor_index >= sessions_.size() || monitor_index < 0) {
|
||||
LOG_ERROR("Invalid session index: {}", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!sessions_[monitor_index].paused_) {
|
||||
sessions_[monitor_index].session_->Pause();
|
||||
sessions_[monitor_index].paused_ = true;
|
||||
LOG_INFO("Pausing session {}", monitor_index);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Resume(int monitor_index) {
|
||||
if (monitor_index >= sessions_.size() || monitor_index < 0) {
|
||||
LOG_ERROR("Invalid session index: {}", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sessions_[monitor_index].paused_) {
|
||||
sessions_[monitor_index].session_->Resume();
|
||||
sessions_[monitor_index].paused_ = false;
|
||||
LOG_INFO("Resuming session {}", monitor_index);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Stop() {
|
||||
running_ = false;
|
||||
|
||||
for (int i = 0; i < sessions_.size(); i++) {
|
||||
if (sessions_[i].running_) {
|
||||
sessions_[i].session_->Stop();
|
||||
sessions_[i].running_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::SwitchTo(int monitor_index) {
|
||||
if (monitor_index_ == monitor_index) {
|
||||
LOG_INFO("Already on monitor {}:{}", monitor_index_ + 1,
|
||||
display_info_list_[monitor_index_].name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (monitor_index < 0 || monitor_index >= static_cast<int>(display_info_list_.size())) {
|
||||
LOG_ERROR("Invalid monitor index: {}", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!sessions_[monitor_index].inited_) {
|
||||
LOG_ERROR("Monitor {} not inited", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Pause(monitor_index_);
|
||||
|
||||
monitor_index_ = monitor_index;
|
||||
LOG_INFO("Switching to monitor {}:{}", monitor_index_,
|
||||
display_info_list_[monitor_index_].name);
|
||||
|
||||
Resume(monitor_index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::ResetToInitialMonitor() {
|
||||
if (display_info_list_.empty()) return -1;
|
||||
if (initial_monitor_index_ < 0 ||
|
||||
initial_monitor_index_ >= static_cast<int>(display_info_list_.size())) {
|
||||
return -1;
|
||||
}
|
||||
if (monitor_index_ == initial_monitor_index_) {
|
||||
return 0;
|
||||
}
|
||||
if (running_) {
|
||||
Pause(monitor_index_);
|
||||
}
|
||||
monitor_index_ = initial_monitor_index_;
|
||||
LOG_INFO("Reset to initial monitor {}:{}", monitor_index_,
|
||||
display_info_list_[monitor_index_].name);
|
||||
if (running_) {
|
||||
Resume(monitor_index_);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
|
||||
int id) {
|
||||
if (!running_ || !on_data_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(frame_mutex_);
|
||||
|
||||
if (on_data_) {
|
||||
if (id < 0 || id >= static_cast<int>(display_info_list_.size())) {
|
||||
LOG_ERROR("WGC OnFrame invalid display index: {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frame.data || frame.row_pitch == 0) {
|
||||
LOG_ERROR("WGC OnFrame received invalid frame: data={}, row_pitch={}",
|
||||
(void*)frame.data, frame.row_pitch);
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate the maximum width that can be contained in one row according to
|
||||
// row_pitch (BGRA: 4 bytes per pixel), and take the minimum with logical
|
||||
// width to avoid out-of-bounds access.
|
||||
unsigned int max_width_by_pitch = frame.row_pitch / 4u;
|
||||
int logical_width = static_cast<int>(
|
||||
frame.width < max_width_by_pitch ? frame.width : max_width_by_pitch);
|
||||
|
||||
// libyuv::ARGBToNV12 requires even width/height
|
||||
int even_width = logical_width & ~1;
|
||||
int even_height = static_cast<int>(frame.height) & ~1;
|
||||
|
||||
if (even_width <= 0 || even_height <= 0) {
|
||||
LOG_ERROR(
|
||||
"WGC OnFrame invalid frame size after adjust: width={} "
|
||||
"(frame.width={}, max_by_pitch={}), height={}",
|
||||
logical_width, frame.width, max_width_by_pitch, frame.height);
|
||||
return;
|
||||
}
|
||||
|
||||
int nv12_size = even_width * even_height * 3 / 2;
|
||||
|
||||
if (!nv12_frame_ || nv12_width_ != even_width ||
|
||||
nv12_height_ != even_height) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = new unsigned char[nv12_size];
|
||||
nv12_width_ = even_width;
|
||||
nv12_height_ = even_height;
|
||||
}
|
||||
|
||||
libyuv::ARGBToNV12((const uint8_t*)frame.data,
|
||||
static_cast<int>(frame.row_pitch), (uint8_t*)nv12_frame_,
|
||||
even_width,
|
||||
(uint8_t*)(nv12_frame_ + even_width * even_height),
|
||||
even_width, even_width, even_height);
|
||||
|
||||
const std::string stream_id = MakeDisplayStreamId(id);
|
||||
on_data_(nv12_frame_, nv12_size, even_width, even_height,
|
||||
stream_id.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerWgc::CleanUp() {
|
||||
running_ = false;
|
||||
for (auto& session : sessions_) {
|
||||
if (session.session_) {
|
||||
session.session_->Stop();
|
||||
}
|
||||
}
|
||||
sessions_.clear();
|
||||
display_info_list_.clear();
|
||||
gs_display_list.clear();
|
||||
monitor_ = nullptr;
|
||||
inited_ = false;
|
||||
}
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef _SCREEN_CAPTURER_WGC_H_
|
||||
#define _SCREEN_CAPTURER_WGC_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
#include "wgc_session.h"
|
||||
#include "wgc_session_impl.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class ScreenCapturerWgc : public ScreenCapturer,
|
||||
public WgcSession::wgc_session_observer {
|
||||
public:
|
||||
ScreenCapturerWgc();
|
||||
~ScreenCapturerWgc();
|
||||
|
||||
public:
|
||||
bool IsWgcSupported();
|
||||
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
|
||||
|
||||
int SwitchTo(int monitor_index);
|
||||
int ResetToInitialMonitor() override;
|
||||
|
||||
void OnFrame(const WgcSession::wgc_session_frame& frame, int id);
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
int RebuildSessions(int preferred_monitor_index);
|
||||
int StartSessions(bool show_cursor);
|
||||
|
||||
private:
|
||||
HMONITOR monitor_;
|
||||
MONITORINFOEX monitor_info_;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
int monitor_index_ = 0;
|
||||
int initial_monitor_index_ = 0;
|
||||
|
||||
private:
|
||||
class WgcSessionInfo {
|
||||
public:
|
||||
std::unique_ptr<WgcSession> session_;
|
||||
bool inited_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
};
|
||||
|
||||
std::vector<WgcSessionInfo> sessions_;
|
||||
|
||||
std::atomic_bool running_{false};
|
||||
std::atomic_bool inited_{false};
|
||||
|
||||
int fps_ = 60;
|
||||
|
||||
cb_desktop_data on_data_ = nullptr;
|
||||
|
||||
unsigned char* nv12_frame_ = nullptr;
|
||||
unsigned char* nv12_frame_scaled_ = nullptr;
|
||||
int nv12_width_ = 0;
|
||||
int nv12_height_ = 0;
|
||||
|
||||
std::mutex frame_mutex_;
|
||||
};
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-02-27
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_WIN_H_
|
||||
#define _SCREEN_CAPTURER_WIN_H_
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class ScreenCapturerWin : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerWin();
|
||||
~ScreenCapturerWin();
|
||||
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
|
||||
int SwitchTo(int monitor_index) override;
|
||||
int ResetToInitialMonitor() override;
|
||||
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ScreenCapturer> impl_;
|
||||
bool impl_is_wgc_plugin_ = false;
|
||||
int fps_ = 60;
|
||||
cb_desktop_data cb_;
|
||||
cb_desktop_data cb_orig_;
|
||||
|
||||
std::unordered_map<void*, size_t> handle_to_canonical_index_;
|
||||
std::unordered_map<std::string, std::string> stream_id_alias_;
|
||||
std::mutex alias_mutex_;
|
||||
std::vector<DisplayInfo> canonical_displays_;
|
||||
std::atomic<bool> invalid_stream_id_logged_{false};
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> paused_{false};
|
||||
std::atomic<bool> show_cursor_{true};
|
||||
std::atomic<int> monitor_index_{0};
|
||||
int initial_monitor_index_ = 0;
|
||||
std::atomic<bool> secure_desktop_capture_active_{false};
|
||||
std::atomic<bool> post_secure_desktop_waiting_for_frame_{false};
|
||||
std::atomic<bool> post_secure_desktop_drop_logged_{false};
|
||||
std::atomic<ULONGLONG> post_secure_desktop_started_tick_{0};
|
||||
std::thread secure_capture_thread_;
|
||||
HANDLE secure_frame_mapping_ = nullptr;
|
||||
HANDLE secure_frame_ready_event_ = nullptr;
|
||||
uint8_t* secure_frame_view_ = nullptr;
|
||||
size_t secure_frame_view_size_ = 0;
|
||||
DWORD secure_shared_session_id_ = 0xFFFFFFFF;
|
||||
int secure_shared_left_ = 0;
|
||||
int secure_shared_top_ = 0;
|
||||
int secure_shared_width_ = 0;
|
||||
int secure_shared_height_ = 0;
|
||||
int secure_shared_fps_ = 0;
|
||||
bool secure_shared_show_cursor_ = true;
|
||||
std::string secure_shared_stage_;
|
||||
std::string secure_shared_desktop_;
|
||||
bool secure_shared_capture_started_ = false;
|
||||
|
||||
void BuildCanonicalFromImpl();
|
||||
void RebuildAliasesFromImpl();
|
||||
void StopSecureCaptureThread();
|
||||
bool RestartCaptureBackendAfterSecureDesktop();
|
||||
void SecureDesktopCaptureLoop();
|
||||
bool GetCurrentCaptureRegion(int* left, int* top, int* width, int* height,
|
||||
std::string* display_name);
|
||||
bool StartSecureDesktopSharedCapture(DWORD session_id, int left, int top,
|
||||
int width, int height,
|
||||
const std::string& stage,
|
||||
const std::string& desktop,
|
||||
bool show_cursor, int fps,
|
||||
std::string* error_out);
|
||||
void StopSecureDesktopSharedCapture(DWORD session_id);
|
||||
bool OpenSecureDesktopSharedFrame(DWORD session_id, size_t min_size,
|
||||
std::string* error_out);
|
||||
bool ReadSecureDesktopSharedFrame(DWORD wait_ms,
|
||||
std::vector<uint8_t>* nv12_frame_out,
|
||||
int* width_out, int* height_out,
|
||||
std::string* error_out);
|
||||
void CloseSecureDesktopSharedFrame();
|
||||
};
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-03-20
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _WGC_PLUGIN_API_H_
|
||||
#define _WGC_PLUGIN_API_H_
|
||||
|
||||
#include "screen_capturer.h"
|
||||
|
||||
namespace crossdesk {
|
||||
class ScreenCapturer;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && defined(CROSSDESK_WGC_PLUGIN_BUILD)
|
||||
#define CROSSDESK_WGC_PLUGIN_API __declspec(dllexport)
|
||||
#else
|
||||
#define CROSSDESK_WGC_PLUGIN_API
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
CROSSDESK_WGC_PLUGIN_API crossdesk::ScreenCapturer*
|
||||
CrossDeskCreateWgcCapturer();
|
||||
CROSSDESK_WGC_PLUGIN_API void CrossDeskDestroyWgcCapturer(
|
||||
crossdesk::ScreenCapturer* capturer);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "path_manager.h"
|
||||
#include "rd_log.h"
|
||||
#include "screen_capturer_wgc.h"
|
||||
#include "wgc_plugin_api.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void InitializePluginLogger() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []() {
|
||||
crossdesk::PathManager path_manager("CrossDesk");
|
||||
crossdesk::InitLogger(path_manager.GetLogPath().string());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
crossdesk::ScreenCapturer* CrossDeskCreateWgcCapturer() {
|
||||
InitializePluginLogger();
|
||||
return new crossdesk::ScreenCapturerWgc();
|
||||
}
|
||||
|
||||
void CrossDeskDestroyWgcCapturer(crossdesk::ScreenCapturer* capturer) {
|
||||
delete capturer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef _WGC_SESSION_H_
|
||||
#define _WGC_SESSION_H_
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class WgcSession {
|
||||
public:
|
||||
struct wgc_session_frame {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int row_pitch;
|
||||
|
||||
const unsigned char* data;
|
||||
};
|
||||
|
||||
class wgc_session_observer {
|
||||
public:
|
||||
virtual ~wgc_session_observer() {}
|
||||
virtual void OnFrame(const wgc_session_frame& frame, int id) = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
virtual void Release() = 0;
|
||||
|
||||
virtual int Initialize(HWND hwnd) = 0;
|
||||
virtual int Initialize(HMONITOR hmonitor) = 0;
|
||||
|
||||
virtual void RegisterObserver(wgc_session_observer* observer) = 0;
|
||||
|
||||
virtual int Start(bool show_cursor) = 0;
|
||||
virtual int Stop() = 0;
|
||||
|
||||
virtual int Pause() = 0;
|
||||
virtual int Resume() = 0;
|
||||
|
||||
virtual ~WgcSession() {};
|
||||
};
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
@@ -0,0 +1,454 @@
|
||||
#include "wgc_session_impl.h"
|
||||
|
||||
#include <Windows.Graphics.Capture.Interop.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
extern "C" {
|
||||
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
|
||||
::IDXGIDevice* dxgiDevice, ::IInspectable** graphicsDevice);
|
||||
}
|
||||
|
||||
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
|
||||
|
||||
WgcSessionImpl::~WgcSessionImpl() {
|
||||
Stop();
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
void WgcSessionImpl::Release() { delete this; }
|
||||
|
||||
int WgcSessionImpl::Initialize(HWND hwnd) {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
target_.hwnd = hwnd;
|
||||
target_.is_window = true;
|
||||
return InitializeLocked();
|
||||
}
|
||||
|
||||
int WgcSessionImpl::Initialize(HMONITOR hmonitor) {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
target_.hmonitor = hmonitor;
|
||||
target_.is_window = false;
|
||||
return InitializeLocked();
|
||||
}
|
||||
|
||||
void WgcSessionImpl::RegisterObserver(wgc_session_observer* observer) {
|
||||
std::lock_guard locker(lock_);
|
||||
observer_ = observer;
|
||||
}
|
||||
|
||||
int WgcSessionImpl::Start(bool show_cursor) {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
return StartLocked(show_cursor);
|
||||
}
|
||||
|
||||
int WgcSessionImpl::Stop() {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
CleanUpLocked();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WgcSessionImpl::Pause() {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
is_paused_ = true;
|
||||
|
||||
if (!is_initialized_) {
|
||||
LOG_ERROR("AE_NEED_INIT");
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WgcSessionImpl::Resume() {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
is_paused_ = false;
|
||||
|
||||
if (!is_initialized_) {
|
||||
LOG_ERROR("AE_NEED_INIT");
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto WgcSessionImpl::CreateD3D11Device() {
|
||||
winrt::com_ptr<ID3D11Device> d3d_device;
|
||||
HRESULT hr;
|
||||
|
||||
WINRT_ASSERT(!d3d_device);
|
||||
|
||||
D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_HARDWARE;
|
||||
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
|
||||
D3D11_SDK_VERSION, d3d_device.put(), nullptr, nullptr);
|
||||
|
||||
if (DXGI_ERROR_UNSUPPORTED == hr) {
|
||||
// change D3D_DRIVER_TYPE
|
||||
type = D3D_DRIVER_TYPE_WARP;
|
||||
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
|
||||
D3D11_SDK_VERSION, d3d_device.put(), nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
winrt::check_hresult(hr);
|
||||
|
||||
winrt::com_ptr<::IInspectable> d3d11_device;
|
||||
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(
|
||||
d3d_device.as<IDXGIDevice>().get(), d3d11_device.put()));
|
||||
return d3d11_device
|
||||
.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();
|
||||
}
|
||||
|
||||
auto WgcSessionImpl::CreateCaptureItemForWindow(HWND hwnd) {
|
||||
auto activation_factory = winrt::get_activation_factory<
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
|
||||
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
|
||||
winrt::check_hresult(interop_factory->CreateForWindow(
|
||||
hwnd,
|
||||
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
||||
reinterpret_cast<void**>(winrt::put_abi(item))));
|
||||
return item;
|
||||
}
|
||||
|
||||
auto WgcSessionImpl::CreateCaptureItemForMonitor(HMONITOR hmonitor) {
|
||||
auto activation_factory = winrt::get_activation_factory<
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
|
||||
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
|
||||
winrt::check_hresult(interop_factory->CreateForMonitor(
|
||||
hmonitor,
|
||||
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
||||
reinterpret_cast<void**>(winrt::put_abi(item))));
|
||||
return item;
|
||||
}
|
||||
|
||||
HRESULT WgcSessionImpl::CreateMappedTexture(
|
||||
winrt::com_ptr<ID3D11Texture2D> src_texture, unsigned int width,
|
||||
unsigned int height) {
|
||||
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;
|
||||
|
||||
auto d3dDevice =
|
||||
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
|
||||
|
||||
return d3dDevice->CreateTexture2D(&map_desc, nullptr,
|
||||
d3d11_texture_mapped_.put());
|
||||
}
|
||||
|
||||
int WgcSessionImpl::StartCaptureLocked(bool show_cursor) {
|
||||
if (!is_initialized_) {
|
||||
LOG_ERROR("AE_NEED_INIT");
|
||||
return 4;
|
||||
}
|
||||
if (!capture_item_) {
|
||||
LOG_ERROR("WGC: capture item is null");
|
||||
return 4;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!capture_session_) {
|
||||
auto current_size = capture_item_.Size();
|
||||
capture_framepool_ =
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
|
||||
CreateFreeThreaded(d3d11_direct_device_,
|
||||
winrt::Windows::Graphics::DirectX::
|
||||
DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||
2, current_size);
|
||||
capture_session_ = capture_framepool_.CreateCaptureSession(capture_item_);
|
||||
capture_frame_size_ = current_size;
|
||||
capture_framepool_trigger_ = capture_framepool_.FrameArrived(
|
||||
winrt::auto_revoke, {this, &WgcSessionImpl::OnFrame});
|
||||
capture_close_trigger_ = capture_item_.Closed(
|
||||
winrt::auto_revoke, {this, &WgcSessionImpl::OnClosed});
|
||||
}
|
||||
|
||||
if (!capture_framepool_ || !capture_session_) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
capture_session_.IsCursorCaptureEnabled(show_cursor);
|
||||
capture_session_.StartCapture();
|
||||
is_running_ = true;
|
||||
return 0;
|
||||
} catch (const winrt::hresult_error&) {
|
||||
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
|
||||
return 86;
|
||||
} catch (...) {
|
||||
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
|
||||
return 86;
|
||||
}
|
||||
}
|
||||
|
||||
int WgcSessionImpl::StartLocked(bool show_cursor) {
|
||||
if (is_running_) return 0;
|
||||
|
||||
last_show_cursor_ = show_cursor;
|
||||
if (!is_initialized_) {
|
||||
const int init_ret = InitializeLocked();
|
||||
if (init_ret != 0) {
|
||||
return init_ret;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = StartCaptureLocked(show_cursor);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_WARN("WGC: start capture failed, rebuilding capture item");
|
||||
CleanUpLocked();
|
||||
ret = InitializeLocked();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
return StartCaptureLocked(show_cursor);
|
||||
}
|
||||
|
||||
void WgcSessionImpl::StopLocked() {
|
||||
is_running_ = false;
|
||||
|
||||
// if (loop_.joinable()) loop_.join();
|
||||
|
||||
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
|
||||
if (capture_close_trigger_) capture_close_trigger_.revoke();
|
||||
|
||||
if (capture_session_) {
|
||||
try {
|
||||
capture_session_.Close();
|
||||
} catch (...) {
|
||||
LOG_WARN("WGC: capture session close failed");
|
||||
}
|
||||
capture_session_ = nullptr;
|
||||
}
|
||||
|
||||
if (capture_framepool_) {
|
||||
try {
|
||||
capture_framepool_.Close();
|
||||
} catch (...) {
|
||||
LOG_WARN("WGC: frame pool close failed");
|
||||
}
|
||||
capture_framepool_ = nullptr;
|
||||
}
|
||||
|
||||
d3d11_texture_mapped_ = nullptr;
|
||||
}
|
||||
|
||||
void WgcSessionImpl::OnFrame(
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
|
||||
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
auto is_new_size = false;
|
||||
|
||||
try {
|
||||
auto frame = sender.TryGetNextFrame();
|
||||
auto frame_size = frame.ContentSize();
|
||||
|
||||
if (frame_size.Width != capture_frame_size_.Width ||
|
||||
frame_size.Height != capture_frame_size_.Height) {
|
||||
// The thing we have been capturing has changed size.
|
||||
// We need to resize our swap chain first, then blit the pixels.
|
||||
// After we do that, retire the frame and then recreate our frame pool.
|
||||
is_new_size = true;
|
||||
capture_frame_size_ = frame_size;
|
||||
}
|
||||
|
||||
// copy to mapped texture
|
||||
if (is_paused_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto frame_captured =
|
||||
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
||||
|
||||
if (!d3d11_texture_mapped_ || is_new_size) {
|
||||
HRESULT tex_hr = CreateMappedTexture(frame_captured);
|
||||
if (FAILED(tex_hr)) {
|
||||
OutputDebugStringW(
|
||||
(L"CreateMappedTexture failed: " + std::to_wstring(tex_hr))
|
||||
.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
|
||||
frame_captured.get());
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE map_result;
|
||||
HRESULT hr = d3d11_device_context_->Map(
|
||||
d3d11_texture_mapped_.get(), 0, D3D11_MAP_READ,
|
||||
0 /*coz we use CreateFreeThreaded, so we cant use flags
|
||||
D3D11_MAP_FLAG_DO_NOT_WAIT*/
|
||||
,
|
||||
&map_result);
|
||||
if (FAILED(hr)) {
|
||||
OutputDebugStringW(
|
||||
(L"map resource failed: " + std::to_wstring(hr)).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// copy data from map_result.pData
|
||||
if (map_result.pData && observer_) {
|
||||
observer_->OnFrame(
|
||||
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
|
||||
static_cast<unsigned int>(frame_size.Height),
|
||||
map_result.RowPitch,
|
||||
const_cast<const unsigned char*>(
|
||||
(unsigned char*)map_result.pData)},
|
||||
id_);
|
||||
}
|
||||
|
||||
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);
|
||||
|
||||
if (is_new_size) {
|
||||
capture_framepool_.Recreate(
|
||||
d3d11_direct_device_,
|
||||
winrt::Windows::Graphics::DirectX::
|
||||
DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||
2, capture_frame_size_);
|
||||
}
|
||||
} catch (const winrt::hresult_error&) {
|
||||
LOG_WARN("WGC: frame processing failed");
|
||||
} catch (...) {
|
||||
LOG_WARN("WGC: frame processing failed");
|
||||
}
|
||||
}
|
||||
|
||||
void WgcSessionImpl::OnClosed(
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
|
||||
winrt::Windows::Foundation::IInspectable const&) {
|
||||
std::lock_guard locker(lock_);
|
||||
const bool was_running = is_running_;
|
||||
const bool was_paused = is_paused_;
|
||||
try {
|
||||
CleanUpLocked();
|
||||
is_paused_ = was_paused;
|
||||
if (InitializeLocked() == 0) {
|
||||
int ret = was_running ? StartCaptureLocked(last_show_cursor_) : 0;
|
||||
if (ret == 0) {
|
||||
OutputDebugStringW(L"WgcSessionImpl::OnClosed: auto recovered");
|
||||
} else {
|
||||
OutputDebugStringW(L"WgcSessionImpl::OnClosed: recover Start failed");
|
||||
}
|
||||
} else {
|
||||
OutputDebugStringW(
|
||||
L"WgcSessionImpl::OnClosed: recover Initialize failed");
|
||||
}
|
||||
} catch (...) {
|
||||
OutputDebugStringW(L"WgcSessionImpl::OnClosed: exception during recover");
|
||||
}
|
||||
}
|
||||
|
||||
int WgcSessionImpl::InitializeLocked() {
|
||||
if (is_initialized_) return 0;
|
||||
|
||||
d3d11_texture_mapped_ = nullptr;
|
||||
d3d11_device_context_ = nullptr;
|
||||
d3d11_direct_device_ = nullptr;
|
||||
capture_frame_size_ = {};
|
||||
|
||||
if (!(d3d11_direct_device_ = CreateD3D11Device())) {
|
||||
LOG_ERROR("AE_D3D_CREATE_DEVICE_FAILED");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
if (target_.is_window)
|
||||
capture_item_ = CreateCaptureItemForWindow(target_.hwnd);
|
||||
else
|
||||
capture_item_ = CreateCaptureItemForMonitor(target_.hmonitor);
|
||||
if (!capture_item_) {
|
||||
LOG_ERROR("WGC: create capture item returned null");
|
||||
return 86;
|
||||
}
|
||||
|
||||
// Set up
|
||||
auto d3d11_device =
|
||||
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
|
||||
d3d11_device->GetImmediateContext(d3d11_device_context_.put());
|
||||
|
||||
} catch (winrt::hresult_error) {
|
||||
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
|
||||
return 86;
|
||||
} catch (...) {
|
||||
return 86;
|
||||
}
|
||||
|
||||
is_initialized_ = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WgcSessionImpl::CleanUp() {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
CleanUpLocked();
|
||||
}
|
||||
|
||||
void WgcSessionImpl::CleanUpLocked() {
|
||||
StopLocked();
|
||||
|
||||
capture_item_ = nullptr;
|
||||
d3d11_device_context_ = nullptr;
|
||||
d3d11_direct_device_ = nullptr;
|
||||
capture_frame_size_ = {};
|
||||
is_initialized_ = false;
|
||||
is_paused_ = false;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
|
||||
LPARAM l_param) {
|
||||
return DefWindowProc(window, message, w_param, l_param);
|
||||
}
|
||||
|
||||
// void WgcSessionImpl::message_func() {
|
||||
// const std::wstring kClassName = L"am_fake_window";
|
||||
|
||||
// WNDCLASS wc = {};
|
||||
|
||||
// wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
// wc.lpfnWndProc = DefWindowProc;
|
||||
// wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
// wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW);
|
||||
// wc.lpszClassName = kClassName.c_str();
|
||||
|
||||
// if (!::RegisterClassW(&wc)) return;
|
||||
|
||||
// hwnd_ = ::CreateWindowW(kClassName.c_str(), nullptr, WS_OVERLAPPEDWINDOW,
|
||||
// 0,
|
||||
// 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
|
||||
// MSG msg;
|
||||
// while (is_running_) {
|
||||
// while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
// if (!is_running_) break;
|
||||
// TranslateMessage(&msg);
|
||||
// DispatchMessage(&msg);
|
||||
// }
|
||||
// Sleep(10);
|
||||
// }
|
||||
|
||||
// ::CloseWindow(hwnd_);
|
||||
// ::DestroyWindow(hwnd_);
|
||||
// }
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,123 @@
|
||||
#ifndef _WGC_SESSION_IMPL_H_
|
||||
#define _WGC_SESSION_IMPL_H_
|
||||
|
||||
#include <d3d11_4.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "wgc_session.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class WgcSessionImpl : public WgcSession {
|
||||
struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
|
||||
IDirect3DDxgiInterfaceAccess : ::IUnknown {
|
||||
virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline auto GetDXGIInterfaceFromObject(
|
||||
winrt::Windows::Foundation::IInspectable const& object) {
|
||||
auto access = object.as<IDirect3DDxgiInterfaceAccess>();
|
||||
winrt::com_ptr<T> result;
|
||||
winrt::check_hresult(
|
||||
access->GetInterface(winrt::guid_of<T>(), result.put_void()));
|
||||
return result;
|
||||
}
|
||||
|
||||
struct {
|
||||
union {
|
||||
HWND hwnd;
|
||||
HMONITOR hmonitor;
|
||||
};
|
||||
bool is_window;
|
||||
} target_{0};
|
||||
|
||||
public:
|
||||
WgcSessionImpl(int id);
|
||||
~WgcSessionImpl() override;
|
||||
|
||||
public:
|
||||
void Release() override;
|
||||
|
||||
int Initialize(HWND hwnd) override;
|
||||
int Initialize(HMONITOR hmonitor) override;
|
||||
|
||||
void RegisterObserver(wgc_session_observer* observer) override;
|
||||
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause() override;
|
||||
int Resume() override;
|
||||
|
||||
private:
|
||||
auto CreateD3D11Device();
|
||||
auto CreateCaptureItemForWindow(HWND hwnd);
|
||||
auto CreateCaptureItemForMonitor(HMONITOR hmonitor);
|
||||
|
||||
HRESULT CreateMappedTexture(winrt::com_ptr<ID3D11Texture2D> src_texture,
|
||||
unsigned int width = 0, unsigned int height = 0);
|
||||
void OnFrame(
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const&
|
||||
sender,
|
||||
winrt::Windows::Foundation::IInspectable const& args);
|
||||
void OnClosed(winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
|
||||
winrt::Windows::Foundation::IInspectable const&);
|
||||
|
||||
int InitializeLocked();
|
||||
int StartLocked(bool show_cursor);
|
||||
int StartCaptureLocked(bool show_cursor);
|
||||
void StopLocked();
|
||||
void CleanUp();
|
||||
void CleanUpLocked();
|
||||
|
||||
// void message_func();
|
||||
|
||||
private:
|
||||
int id_ = -1;
|
||||
std::mutex lock_;
|
||||
bool is_initialized_ = false;
|
||||
bool is_running_ = false;
|
||||
bool is_paused_ = false;
|
||||
bool last_show_cursor_ = false;
|
||||
|
||||
wgc_session_observer* observer_ = nullptr;
|
||||
|
||||
// wgc
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem capture_item_{nullptr};
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureSession capture_session_{
|
||||
nullptr};
|
||||
winrt::Windows::Graphics::SizeInt32 capture_frame_size_;
|
||||
|
||||
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice
|
||||
d3d11_direct_device_{nullptr};
|
||||
winrt::com_ptr<ID3D11DeviceContext> d3d11_device_context_{nullptr};
|
||||
winrt::com_ptr<ID3D11Texture2D> d3d11_texture_mapped_{nullptr};
|
||||
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool
|
||||
capture_framepool_{nullptr};
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
|
||||
FrameArrived_revoker capture_framepool_trigger_;
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem::Closed_revoker
|
||||
capture_close_trigger_;
|
||||
|
||||
// message loop
|
||||
std::thread loop_;
|
||||
HWND hwnd_ = nullptr;
|
||||
};
|
||||
|
||||
// template <typename T>
|
||||
// inline auto WgcSessionImpl::GetDXGIInterfaceFromObject(
|
||||
// winrt::Windows::Foundation::IInspectable const &object) {
|
||||
// auto access = object.as<IDirect3DDxgiInterfaceAccess>();
|
||||
// winrt::com_ptr<T> result;
|
||||
// winrt::check_hresult(
|
||||
// access->GetInterface(winrt::guid_of<T>(), result.put_void()));
|
||||
// return result;
|
||||
// }
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
Reference in New Issue
Block a user