mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-26 20:25:34 +08:00
Use kcp as QoS module
This commit is contained in:
@@ -1,85 +0,0 @@
|
||||
//*********************************************************
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// This code is licensed under the MIT License (MIT).
|
||||
// THE SOFTWARE IS PROVIDED <20>AS IS? WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
//*********************************************************
|
||||
|
||||
#include "pch.h"
|
||||
#include "App.h"
|
||||
#include "SimpleCapture.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::System;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::UI;
|
||||
using namespace Windows::UI::Composition;
|
||||
using namespace Windows::Graphics::Capture;
|
||||
|
||||
void App::Initialize(ContainerVisual const &root) {
|
||||
auto queue = DispatcherQueue::GetForCurrentThread();
|
||||
|
||||
m_compositor = root.Compositor();
|
||||
m_root = m_compositor.CreateContainerVisual();
|
||||
m_content = m_compositor.CreateSpriteVisual();
|
||||
m_brush = m_compositor.CreateSurfaceBrush();
|
||||
|
||||
m_root.RelativeSizeAdjustment({1, 1});
|
||||
root.Children().InsertAtTop(m_root);
|
||||
|
||||
m_content.AnchorPoint({0.5f, 0.5f});
|
||||
m_content.RelativeOffsetAdjustment({0.5f, 0.5f, 0});
|
||||
m_content.RelativeSizeAdjustment({1, 1});
|
||||
m_content.Size({-80, -80});
|
||||
m_content.Brush(m_brush);
|
||||
m_brush.HorizontalAlignmentRatio(0.5f);
|
||||
m_brush.VerticalAlignmentRatio(0.5f);
|
||||
m_brush.Stretch(CompositionStretch::Uniform);
|
||||
auto shadow = m_compositor.CreateDropShadow();
|
||||
shadow.Mask(m_brush);
|
||||
m_content.Shadow(shadow);
|
||||
m_root.Children().InsertAtTop(m_content);
|
||||
|
||||
auto d3dDevice = CreateD3DDevice();
|
||||
auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
|
||||
m_device = CreateDirect3DDevice(dxgiDevice.get());
|
||||
}
|
||||
|
||||
void App::StartCapture(HWND hwnd) {
|
||||
if (m_capture) {
|
||||
m_capture->Close();
|
||||
m_capture = nullptr;
|
||||
}
|
||||
|
||||
auto item = CreateCaptureItemForWindow(hwnd);
|
||||
|
||||
m_capture = std::make_unique<SimpleCapture>(m_device, item);
|
||||
|
||||
auto surface = m_capture->CreateSurface(m_compositor);
|
||||
m_brush.Surface(surface);
|
||||
|
||||
m_capture->StartCapture();
|
||||
}
|
||||
|
||||
void App::StartCapture(HMONITOR hmonitor) {
|
||||
if (m_capture) {
|
||||
m_capture->Close();
|
||||
m_capture = nullptr;
|
||||
}
|
||||
|
||||
auto item = CreateCaptureItemForMonitor(hmonitor);
|
||||
|
||||
m_capture = std::make_unique<SimpleCapture>(m_device, item);
|
||||
|
||||
auto surface = m_capture->CreateSurface(m_compositor);
|
||||
m_brush.Surface(surface);
|
||||
|
||||
m_capture->StartCapture();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
class SimpleCapture;
|
||||
|
||||
class App {
|
||||
public:
|
||||
App() {}
|
||||
~App() {}
|
||||
|
||||
void Initialize(winrt::Windows::UI::Composition::ContainerVisual const &root);
|
||||
|
||||
void StartCapture(HWND hwnd);
|
||||
void StartCapture(HMONITOR hmonitor);
|
||||
|
||||
private:
|
||||
winrt::Windows::UI::Composition::Compositor m_compositor{nullptr};
|
||||
winrt::Windows::UI::Composition::ContainerVisual m_root{nullptr};
|
||||
winrt::Windows::UI::Composition::SpriteVisual m_content{nullptr};
|
||||
winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
|
||||
|
||||
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device{
|
||||
nullptr};
|
||||
std::unique_ptr<SimpleCapture> m_capture{nullptr};
|
||||
};
|
||||
@@ -1,217 +0,0 @@
|
||||
//*********************************************************
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// This code is licensed under the MIT License (MIT).
|
||||
// THE SOFTWARE IS PROVIDED <20>AS IS? WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
//*********************************************************
|
||||
|
||||
#include "SimpleCapture.h"
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::System;
|
||||
using namespace Windows::Graphics;
|
||||
using namespace Windows::Graphics::Capture;
|
||||
using namespace Windows::Graphics::DirectX;
|
||||
using namespace Windows::Graphics::DirectX::Direct3D11;
|
||||
using namespace Windows::Foundation::Numerics;
|
||||
using namespace Windows::UI;
|
||||
using namespace Windows::UI::Composition;
|
||||
|
||||
SimpleCapture::SimpleCapture(IDirect3DDevice const &device,
|
||||
GraphicsCaptureItem const &item) {
|
||||
m_item = item;
|
||||
m_device = device;
|
||||
|
||||
// Set up
|
||||
auto d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(m_device);
|
||||
d3dDevice->GetImmediateContext(m_d3dContext.put());
|
||||
|
||||
auto size = m_item.Size();
|
||||
|
||||
m_swapChain = CreateDXGISwapChain(
|
||||
d3dDevice, static_cast<uint32_t>(size.Width),
|
||||
static_cast<uint32_t>(size.Height),
|
||||
static_cast<DXGI_FORMAT>(DirectXPixelFormat::B8G8R8A8UIntNormalized), 2);
|
||||
|
||||
// Create framepool, define pixel format (DXGI_FORMAT_B8G8R8A8_UNORM), and
|
||||
// frame size.
|
||||
m_framePool = Direct3D11CaptureFramePool::Create(
|
||||
m_device, DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, size);
|
||||
m_session = m_framePool.CreateCaptureSession(m_item);
|
||||
m_lastSize = size;
|
||||
m_frameArrived = m_framePool.FrameArrived(
|
||||
auto_revoke, {this, &SimpleCapture::OnFrameArrived});
|
||||
}
|
||||
|
||||
// Start sending capture frames
|
||||
void SimpleCapture::StartCapture() {
|
||||
CheckClosed();
|
||||
m_session.StartCapture();
|
||||
}
|
||||
|
||||
ICompositionSurface SimpleCapture::CreateSurface(Compositor const &compositor) {
|
||||
CheckClosed();
|
||||
return CreateCompositionSurfaceForSwapChain(compositor, m_swapChain.get());
|
||||
}
|
||||
|
||||
// Process captured frames
|
||||
void SimpleCapture::Close() {
|
||||
auto expected = false;
|
||||
if (m_closed.compare_exchange_strong(expected, true)) {
|
||||
m_frameArrived.revoke();
|
||||
m_framePool.Close();
|
||||
m_session.Close();
|
||||
|
||||
m_swapChain = nullptr;
|
||||
m_framePool = nullptr;
|
||||
m_session = nullptr;
|
||||
m_item = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleCapture::OnFrameArrived(
|
||||
Direct3D11CaptureFramePool const &sender,
|
||||
winrt::Windows::Foundation::IInspectable const &) {
|
||||
auto newSize = false;
|
||||
|
||||
{
|
||||
auto frame = sender.TryGetNextFrame();
|
||||
auto frameContentSize = frame.ContentSize();
|
||||
|
||||
if (frameContentSize.Width != m_lastSize.Width ||
|
||||
frameContentSize.Height != m_lastSize.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.
|
||||
newSize = true;
|
||||
m_lastSize = frameContentSize;
|
||||
m_swapChain->ResizeBuffers(
|
||||
2, static_cast<uint32_t>(m_lastSize.Width),
|
||||
static_cast<uint32_t>(m_lastSize.Height),
|
||||
static_cast<DXGI_FORMAT>(DirectXPixelFormat::B8G8R8A8UIntNormalized),
|
||||
0);
|
||||
}
|
||||
|
||||
// copy to swapChain
|
||||
{
|
||||
auto frameSurface =
|
||||
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
||||
|
||||
com_ptr<ID3D11Texture2D> backBuffer;
|
||||
check_hresult(m_swapChain->GetBuffer(0, guid_of<ID3D11Texture2D>(),
|
||||
backBuffer.put_void()));
|
||||
|
||||
m_d3dContext->CopyResource(backBuffer.get(), frameSurface.get());
|
||||
|
||||
DXGI_PRESENT_PARAMETERS presentParameters = {0};
|
||||
m_swapChain->Present1(1, 0, &presentParameters);
|
||||
}
|
||||
|
||||
// copy to mapped texture
|
||||
{
|
||||
auto frameSurface =
|
||||
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
||||
|
||||
if (!m_mappedTexture || newSize) CreateMappedTexture(frameSurface);
|
||||
|
||||
m_d3dContext->CopyResource(m_mappedTexture.get(), frameSurface.get());
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapInfo;
|
||||
m_d3dContext->Map(m_mappedTexture.get(), 0, D3D11_MAP_READ,
|
||||
D3D11_MAP_FLAG_DO_NOT_WAIT, &mapInfo);
|
||||
|
||||
// copy data from mapInfo.pData
|
||||
#if 1
|
||||
if (mapInfo.pData) {
|
||||
static unsigned char *buffer = nullptr;
|
||||
if (buffer && newSize) delete[] buffer;
|
||||
|
||||
if (!buffer)
|
||||
buffer = new unsigned char[frameContentSize.Width *
|
||||
frameContentSize.Height * 4];
|
||||
|
||||
int dstRowPitch = frameContentSize.Width * 4;
|
||||
for (int h = 0; h < frameContentSize.Height; h++) {
|
||||
memcpy_s(buffer + h * dstRowPitch, dstRowPitch,
|
||||
(BYTE *)mapInfo.pData + h * mapInfo.RowPitch,
|
||||
min(mapInfo.RowPitch, dstRowPitch));
|
||||
}
|
||||
|
||||
BITMAPINFOHEADER bi;
|
||||
|
||||
bi.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bi.biWidth = frameContentSize.Width;
|
||||
bi.biHeight = frameContentSize.Height * (-1);
|
||||
bi.biPlanes = 1;
|
||||
bi.biBitCount = 32; // should get from system color bits
|
||||
bi.biCompression = BI_RGB;
|
||||
bi.biSizeImage = 0;
|
||||
bi.biXPelsPerMeter = 0;
|
||||
bi.biYPelsPerMeter = 0;
|
||||
bi.biClrUsed = 0;
|
||||
bi.biClrImportant = 0;
|
||||
|
||||
BITMAPFILEHEADER bf;
|
||||
bf.bfType = 0x4d42;
|
||||
bf.bfReserved1 = 0;
|
||||
bf.bfReserved2 = 0;
|
||||
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
|
||||
bf.bfSize =
|
||||
bf.bfOffBits + frameContentSize.Width * frameContentSize.Height * 4;
|
||||
|
||||
FILE *fp = nullptr;
|
||||
|
||||
fopen_s(&fp, ".\\save.bmp", "wb+");
|
||||
|
||||
fwrite(&bf, 1, sizeof(bf), fp);
|
||||
fwrite(&bi, 1, sizeof(bi), fp);
|
||||
fwrite(buffer, 1, frameContentSize.Width * frameContentSize.Height * 4,
|
||||
fp);
|
||||
|
||||
fflush(fp);
|
||||
fclose(fp);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_d3dContext->Unmap(m_mappedTexture.get(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (newSize) {
|
||||
m_framePool.Recreate(m_device, DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||
2, m_lastSize);
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT
|
||||
SimpleCapture::CreateMappedTexture(winrt::com_ptr<ID3D11Texture2D> src_texture,
|
||||
UINT width, UINT 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>(m_device);
|
||||
|
||||
return d3dDevice->CreateTexture2D(&map_desc, nullptr, m_mappedTexture.put());
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
class SimpleCapture {
|
||||
public:
|
||||
SimpleCapture(
|
||||
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice const
|
||||
&device,
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const &item);
|
||||
~SimpleCapture() { Close(); }
|
||||
|
||||
void StartCapture();
|
||||
winrt::Windows::UI::Composition::ICompositionSurface
|
||||
CreateSurface(winrt::Windows::UI::Composition::Compositor const &compositor);
|
||||
|
||||
void Close();
|
||||
|
||||
private:
|
||||
void OnFrameArrived(
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const
|
||||
&sender,
|
||||
winrt::Windows::Foundation::IInspectable const &args);
|
||||
|
||||
void CheckClosed() {
|
||||
if (m_closed.load() == true) {
|
||||
throw winrt::hresult_error(RO_E_CLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT
|
||||
CreateMappedTexture(winrt::com_ptr<ID3D11Texture2D> src_texture,
|
||||
UINT width = 0, UINT height = 0);
|
||||
|
||||
private:
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem m_item{nullptr};
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool m_framePool{
|
||||
nullptr};
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureSession m_session{nullptr};
|
||||
winrt::Windows::Graphics::SizeInt32 m_lastSize;
|
||||
|
||||
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device{
|
||||
nullptr};
|
||||
winrt::com_ptr<IDXGISwapChain1> m_swapChain{nullptr};
|
||||
winrt::com_ptr<ID3D11DeviceContext> m_d3dContext{nullptr};
|
||||
winrt::com_ptr<ID3D11Texture2D> m_mappedTexture{nullptr};
|
||||
|
||||
std::atomic<bool> m_closed = false;
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
|
||||
FrameArrived_revoker m_frameArrived;
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
#pragma once
|
||||
#include <dwmapi.h>
|
||||
|
||||
struct Monitor {
|
||||
public:
|
||||
Monitor(nullptr_t) {}
|
||||
Monitor(HMONITOR hmonitor, std::wstring &className, bool isPrimary) {
|
||||
m_hmonitor = hmonitor;
|
||||
m_className = className;
|
||||
m_bIsPrimary = isPrimary;
|
||||
}
|
||||
|
||||
HMONITOR Hmonitor() const noexcept { return m_hmonitor; }
|
||||
std::wstring ClassName() const noexcept { return m_className; }
|
||||
bool IsPrimary() const noexcept { return m_bIsPrimary; }
|
||||
|
||||
private:
|
||||
HMONITOR m_hmonitor;
|
||||
std::wstring m_className;
|
||||
bool m_bIsPrimary;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
auto monitors = ((std::vector<Monitor> *)data);
|
||||
std::wstring name = info_ex.szDevice;
|
||||
auto monitor =
|
||||
Monitor(hmonitor, name, info_ex.dwFlags & MONITORINFOF_PRIMARY);
|
||||
|
||||
monitors->emplace_back(monitor);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<Monitor> EnumerateMonitors() {
|
||||
std::vector<Monitor> monitors;
|
||||
|
||||
::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&monitors);
|
||||
|
||||
return monitors;
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
#pragma once
|
||||
#include <dwmapi.h>
|
||||
|
||||
struct Window {
|
||||
public:
|
||||
Window(nullptr_t) {}
|
||||
Window(HWND hwnd, std::wstring const &title, std::wstring &className) {
|
||||
m_hwnd = hwnd;
|
||||
m_title = title;
|
||||
m_className = className;
|
||||
}
|
||||
|
||||
HWND Hwnd() const noexcept { return m_hwnd; }
|
||||
std::wstring Title() const noexcept { return m_title; }
|
||||
std::wstring ClassName() const noexcept { return m_className; }
|
||||
|
||||
private:
|
||||
HWND m_hwnd;
|
||||
std::wstring m_title;
|
||||
std::wstring m_className;
|
||||
};
|
||||
|
||||
std::wstring GetClassName(HWND hwnd) {
|
||||
std::array<WCHAR, 1024> className;
|
||||
|
||||
::GetClassName(hwnd, className.data(), (int)className.size());
|
||||
|
||||
std::wstring title(className.data());
|
||||
return title;
|
||||
}
|
||||
|
||||
std::wstring GetWindowText(HWND hwnd) {
|
||||
std::array<WCHAR, 1024> windowText;
|
||||
|
||||
::GetWindowText(hwnd, windowText.data(), (int)windowText.size());
|
||||
|
||||
std::wstring title(windowText.data());
|
||||
return title;
|
||||
}
|
||||
|
||||
bool IsAltTabWindow(Window const &window) {
|
||||
HWND hwnd = window.Hwnd();
|
||||
HWND shellWindow = GetShellWindow();
|
||||
|
||||
auto title = window.Title();
|
||||
auto className = window.ClassName();
|
||||
|
||||
if (hwnd == shellWindow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (title.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsWindowVisible(hwnd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetAncestor(hwnd, GA_ROOT) != hwnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LONG style = GetWindowLong(hwnd, GWL_STYLE);
|
||||
if (!((style & WS_DISABLED) != WS_DISABLED)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD cloaked = FALSE;
|
||||
HRESULT hrTemp =
|
||||
DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked));
|
||||
if (SUCCEEDED(hrTemp) && cloaked == DWM_CLOAKED_SHELL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
|
||||
auto class_name = GetClassName(hwnd);
|
||||
auto title = GetWindowText(hwnd);
|
||||
|
||||
auto window = Window(hwnd, title, class_name);
|
||||
|
||||
if (!IsAltTabWindow(window)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::vector<Window> &windows =
|
||||
*reinterpret_cast<std::vector<Window> *>(lParam);
|
||||
windows.push_back(window);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
const std::vector<Window> EnumerateWindows() {
|
||||
std::vector<Window> windows;
|
||||
EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&windows));
|
||||
|
||||
return windows;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
#include <windows.graphics.capture.h>
|
||||
#include <windows.graphics.capture.interop.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
inline auto 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};
|
||||
interop_factory->CreateForWindow(
|
||||
hwnd,
|
||||
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
||||
reinterpret_cast<void **>(winrt::put_abi(item)));
|
||||
return item;
|
||||
}
|
||||
|
||||
inline auto 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};
|
||||
interop_factory->CreateForMonitor(
|
||||
hmonitor,
|
||||
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
||||
reinterpret_cast<void **>(winrt::put_abi(item)));
|
||||
return item;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
#pragma once
|
||||
#include <d2d1_1.h>
|
||||
#include <windows.ui.composition.interop.h>
|
||||
#include <winrt/Windows.UI.Composition.h>
|
||||
|
||||
inline auto CreateCompositionGraphicsDevice(
|
||||
winrt::Windows::UI::Composition::Compositor const &compositor,
|
||||
::IUnknown *device) {
|
||||
winrt::Windows::UI::Composition::CompositionGraphicsDevice graphicsDevice{
|
||||
nullptr};
|
||||
auto compositorInterop =
|
||||
compositor.as<ABI::Windows::UI::Composition::ICompositorInterop>();
|
||||
winrt::com_ptr<ABI::Windows::UI::Composition::ICompositionGraphicsDevice>
|
||||
graphicsInterop;
|
||||
winrt::check_hresult(
|
||||
compositorInterop->CreateGraphicsDevice(device, graphicsInterop.put()));
|
||||
winrt::check_hresult(graphicsInterop->QueryInterface(
|
||||
winrt::guid_of<
|
||||
winrt::Windows::UI::Composition::CompositionGraphicsDevice>(),
|
||||
reinterpret_cast<void **>(winrt::put_abi(graphicsDevice))));
|
||||
return graphicsDevice;
|
||||
}
|
||||
|
||||
inline void ResizeSurface(
|
||||
winrt::Windows::UI::Composition::CompositionDrawingSurface const &surface,
|
||||
winrt::Windows::Foundation::Size const &size) {
|
||||
auto surfaceInterop = surface.as<
|
||||
ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>();
|
||||
SIZE newSize = {};
|
||||
newSize.cx = static_cast<LONG>(std::round(size.Width));
|
||||
newSize.cy = static_cast<LONG>(std::round(size.Height));
|
||||
winrt::check_hresult(surfaceInterop->Resize(newSize));
|
||||
}
|
||||
|
||||
inline auto SurfaceBeginDraw(
|
||||
winrt::Windows::UI::Composition::CompositionDrawingSurface const &surface) {
|
||||
auto surfaceInterop = surface.as<
|
||||
ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>();
|
||||
winrt::com_ptr<ID2D1DeviceContext> context;
|
||||
POINT offset = {};
|
||||
winrt::check_hresult(surfaceInterop->BeginDraw(
|
||||
nullptr, __uuidof(ID2D1DeviceContext), context.put_void(), &offset));
|
||||
context->SetTransform(
|
||||
D2D1::Matrix3x2F::Translation((FLOAT)offset.x, (FLOAT)offset.y));
|
||||
return context;
|
||||
}
|
||||
|
||||
inline void SurfaceEndDraw(
|
||||
winrt::Windows::UI::Composition::CompositionDrawingSurface const &surface) {
|
||||
auto surfaceInterop = surface.as<
|
||||
ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>();
|
||||
winrt::check_hresult(surfaceInterop->EndDraw());
|
||||
}
|
||||
|
||||
inline auto CreateCompositionSurfaceForSwapChain(
|
||||
winrt::Windows::UI::Composition::Compositor const &compositor,
|
||||
::IUnknown *swapChain) {
|
||||
winrt::Windows::UI::Composition::ICompositionSurface surface{nullptr};
|
||||
auto compositorInterop =
|
||||
compositor.as<ABI::Windows::UI::Composition::ICompositorInterop>();
|
||||
winrt::com_ptr<ABI::Windows::UI::Composition::ICompositionSurface>
|
||||
surfaceInterop;
|
||||
winrt::check_hresult(compositorInterop->CreateCompositionSurfaceForSwapChain(
|
||||
swapChain, surfaceInterop.put()));
|
||||
winrt::check_hresult(surfaceInterop->QueryInterface(
|
||||
winrt::guid_of<winrt::Windows::UI::Composition::ICompositionSurface>(),
|
||||
reinterpret_cast<void **>(winrt::put_abi(surface))));
|
||||
return surface;
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
#pragma once
|
||||
#include "composition.interop.h"
|
||||
|
||||
struct SurfaceContext {
|
||||
public:
|
||||
SurfaceContext(std::nullptr_t) {}
|
||||
SurfaceContext(
|
||||
winrt::Windows::UI::Composition::CompositionDrawingSurface surface) {
|
||||
m_surface = surface;
|
||||
m_d2dContext = SurfaceBeginDraw(m_surface);
|
||||
}
|
||||
~SurfaceContext() {
|
||||
SurfaceEndDraw(m_surface);
|
||||
m_d2dContext = nullptr;
|
||||
m_surface = nullptr;
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID2D1DeviceContext> GetDeviceContext() { return m_d2dContext; }
|
||||
|
||||
private:
|
||||
winrt::com_ptr<ID2D1DeviceContext> m_d2dContext;
|
||||
winrt::Windows::UI::Composition::CompositionDrawingSurface m_surface{nullptr};
|
||||
};
|
||||
|
||||
struct D3D11DeviceLock {
|
||||
public:
|
||||
D3D11DeviceLock(std::nullopt_t) {}
|
||||
D3D11DeviceLock(ID3D11Multithread *pMultithread) {
|
||||
m_multithread.copy_from(pMultithread);
|
||||
m_multithread->Enter();
|
||||
}
|
||||
~D3D11DeviceLock() {
|
||||
m_multithread->Leave();
|
||||
m_multithread = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
winrt::com_ptr<ID3D11Multithread> m_multithread;
|
||||
};
|
||||
|
||||
inline auto CreateWICFactory() {
|
||||
winrt::com_ptr<IWICImagingFactory2> wicFactory;
|
||||
winrt::check_hresult(::CoCreateInstance(
|
||||
CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
|
||||
winrt::guid_of<IWICImagingFactory>(), wicFactory.put_void()));
|
||||
|
||||
return wicFactory;
|
||||
}
|
||||
|
||||
inline auto CreateD2DDevice(winrt::com_ptr<ID2D1Factory1> const &factory,
|
||||
winrt::com_ptr<ID3D11Device> const &device) {
|
||||
winrt::com_ptr<ID2D1Device> result;
|
||||
winrt::check_hresult(
|
||||
factory->CreateDevice(device.as<IDXGIDevice>().get(), result.put()));
|
||||
return result;
|
||||
}
|
||||
|
||||
inline auto CreateD3DDevice(D3D_DRIVER_TYPE const type,
|
||||
winrt::com_ptr<ID3D11Device> &device) {
|
||||
WINRT_ASSERT(!device);
|
||||
|
||||
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||
|
||||
//#ifdef _DEBUG
|
||||
// flags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
//#endif
|
||||
|
||||
return D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
|
||||
D3D11_SDK_VERSION, device.put(), nullptr, nullptr);
|
||||
}
|
||||
|
||||
inline auto CreateD3DDevice() {
|
||||
winrt::com_ptr<ID3D11Device> device;
|
||||
HRESULT hr = CreateD3DDevice(D3D_DRIVER_TYPE_HARDWARE, device);
|
||||
|
||||
if (DXGI_ERROR_UNSUPPORTED == hr) {
|
||||
hr = CreateD3DDevice(D3D_DRIVER_TYPE_WARP, device);
|
||||
}
|
||||
|
||||
winrt::check_hresult(hr);
|
||||
return device;
|
||||
}
|
||||
|
||||
inline auto CreateD2DFactory() {
|
||||
D2D1_FACTORY_OPTIONS options{};
|
||||
|
||||
//#ifdef _DEBUG
|
||||
// options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
|
||||
//#endif
|
||||
|
||||
winrt::com_ptr<ID2D1Factory1> factory;
|
||||
|
||||
winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
|
||||
options, factory.put()));
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
inline auto CreateDXGISwapChain(winrt::com_ptr<ID3D11Device> const &device,
|
||||
const DXGI_SWAP_CHAIN_DESC1 *desc) {
|
||||
auto dxgiDevice = device.as<IDXGIDevice2>();
|
||||
winrt::com_ptr<IDXGIAdapter> adapter;
|
||||
winrt::check_hresult(dxgiDevice->GetParent(winrt::guid_of<IDXGIAdapter>(),
|
||||
adapter.put_void()));
|
||||
winrt::com_ptr<IDXGIFactory2> factory;
|
||||
winrt::check_hresult(
|
||||
adapter->GetParent(winrt::guid_of<IDXGIFactory2>(), factory.put_void()));
|
||||
|
||||
winrt::com_ptr<IDXGISwapChain1> swapchain;
|
||||
winrt::check_hresult(factory->CreateSwapChainForComposition(
|
||||
device.get(), desc, nullptr, swapchain.put()));
|
||||
|
||||
return swapchain;
|
||||
}
|
||||
|
||||
inline auto CreateDXGISwapChain(winrt::com_ptr<ID3D11Device> const &device,
|
||||
uint32_t width, uint32_t height,
|
||||
DXGI_FORMAT format, uint32_t bufferCount) {
|
||||
DXGI_SWAP_CHAIN_DESC1 desc = {};
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.Format = format;
|
||||
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.BufferCount = bufferCount;
|
||||
desc.Scaling = DXGI_SCALING_STRETCH;
|
||||
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
|
||||
desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
|
||||
|
||||
return CreateDXGISwapChain(device, &desc);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
#include <winrt/windows.graphics.directx.direct3d11.h>
|
||||
|
||||
extern "C" {
|
||||
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
|
||||
::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
|
||||
|
||||
HRESULT __stdcall CreateDirect3D11SurfaceFromDXGISurface(
|
||||
::IDXGISurface *dgxiSurface, ::IInspectable **graphicsSurface);
|
||||
}
|
||||
|
||||
struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
|
||||
IDirect3DDxgiInterfaceAccess : ::IUnknown {
|
||||
virtual HRESULT __stdcall GetInterface(GUID const &id, void **object) = 0;
|
||||
};
|
||||
|
||||
inline auto CreateDirect3DDevice(IDXGIDevice *dxgi_device) {
|
||||
winrt::com_ptr<::IInspectable> d3d_device;
|
||||
winrt::check_hresult(
|
||||
CreateDirect3D11DeviceFromDXGIDevice(dxgi_device, d3d_device.put()));
|
||||
return d3d_device
|
||||
.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();
|
||||
}
|
||||
|
||||
inline auto CreateDirect3DSurface(IDXGISurface *dxgi_surface) {
|
||||
winrt::com_ptr<::IInspectable> d3d_surface;
|
||||
winrt::check_hresult(
|
||||
CreateDirect3D11SurfaceFromDXGISurface(dxgi_surface, d3d_surface.put()));
|
||||
return d3d_surface
|
||||
.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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;
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
//*********************************************************
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// This code is licensed under the MIT License (MIT).
|
||||
// THE SOFTWARE IS PROVIDED <20>AS IS? WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
||||
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
//*********************************************************
|
||||
|
||||
#include "pch.h"
|
||||
#include "App.h"
|
||||
#include "SimpleCapture.h"
|
||||
#include "Win32MonitorEnumeration.h"
|
||||
#include "Win32WindowEnumeration.h"
|
||||
#include <ShObjIdl.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::UI;
|
||||
using namespace Windows::UI::Composition;
|
||||
using namespace Windows::UI::Composition::Desktop;
|
||||
|
||||
// Direct3D11CaptureFramePool requires a DispatcherQueue
|
||||
auto CreateDispatcherQueueController() {
|
||||
namespace abi = ABI::Windows::System;
|
||||
|
||||
DispatcherQueueOptions options{sizeof(DispatcherQueueOptions),
|
||||
DQTYPE_THREAD_CURRENT, DQTAT_COM_STA};
|
||||
|
||||
Windows::System::DispatcherQueueController controller{nullptr};
|
||||
check_hresult(CreateDispatcherQueueController(
|
||||
options, reinterpret_cast<abi::IDispatcherQueueController **>(
|
||||
put_abi(controller))));
|
||||
return controller;
|
||||
}
|
||||
|
||||
DesktopWindowTarget CreateDesktopWindowTarget(Compositor const &compositor,
|
||||
HWND window) {
|
||||
namespace abi = ABI::Windows::UI::Composition::Desktop;
|
||||
|
||||
auto interop = compositor.as<abi::ICompositorDesktopInterop>();
|
||||
DesktopWindowTarget target{nullptr};
|
||||
check_hresult(interop->CreateDesktopWindowTarget(
|
||||
window, true,
|
||||
reinterpret_cast<abi::IDesktopWindowTarget **>(put_abi(target))));
|
||||
return target;
|
||||
}
|
||||
|
||||
int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previousInstance,
|
||||
LPSTR cmdLine, int cmdShow);
|
||||
|
||||
auto g_app = std::make_shared<App>();
|
||||
auto g_windows = EnumerateWindows();
|
||||
auto g_monitors = EnumerateMonitors();
|
||||
|
||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previousInstance,
|
||||
LPSTR cmdLine, int cmdShow) {
|
||||
// Init COM
|
||||
init_apartment(apartment_type::single_threaded);
|
||||
|
||||
// Create the window
|
||||
WNDCLASSEX wcex = {};
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wcex.lpfnWndProc = WndProc;
|
||||
wcex.cbClsExtra = 0;
|
||||
wcex.cbWndExtra = 0;
|
||||
wcex.hInstance = instance;
|
||||
wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_APPLICATION));
|
||||
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||
wcex.lpszMenuName = NULL;
|
||||
wcex.lpszClassName = L"ScreenCaptureforHWND";
|
||||
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
|
||||
WINRT_VERIFY(RegisterClassEx(&wcex));
|
||||
|
||||
HWND hwnd = CreateWindow(L"ScreenCaptureforHWND", L"ScreenCaptureforHWND",
|
||||
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
800, 600, NULL, NULL, instance, NULL);
|
||||
WINRT_VERIFY(hwnd);
|
||||
|
||||
ShowWindow(hwnd, cmdShow);
|
||||
UpdateWindow(hwnd);
|
||||
|
||||
// Create combo box
|
||||
HWND comboBoxHwnd =
|
||||
CreateWindow(WC_COMBOBOX, L"",
|
||||
CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_VSCROLL | WS_CHILD |
|
||||
WS_OVERLAPPED | WS_VISIBLE,
|
||||
10, 10, 200, 200, hwnd, NULL, instance, NULL);
|
||||
WINRT_VERIFY(comboBoxHwnd);
|
||||
|
||||
// Populate combo box
|
||||
// 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,
|
||||
(LPARAM)monitor.ClassName().c_str());
|
||||
}
|
||||
|
||||
// SendMessage(comboBoxHwnd, CB_SETCURSEL, 0, 0);
|
||||
|
||||
// Create a DispatcherQueue for our thread
|
||||
auto controller = CreateDispatcherQueueController();
|
||||
|
||||
// Initialize Composition
|
||||
auto compositor = Compositor();
|
||||
auto target = CreateDesktopWindowTarget(compositor, hwnd);
|
||||
auto root = compositor.CreateContainerVisual();
|
||||
root.RelativeSizeAdjustment({1.0f, 1.0f});
|
||||
target.Root(root);
|
||||
|
||||
// Enqueue our capture work on the dispatcher
|
||||
auto queue = controller.DispatcherQueue();
|
||||
auto success = queue.TryEnqueue([=]() -> void { g_app->Initialize(root); });
|
||||
WINRT_VERIFY(success);
|
||||
|
||||
// Message pump
|
||||
MSG msg;
|
||||
while (GetMessage(&msg, NULL, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
return (int)msg.wParam;
|
||||
}
|
||||
|
||||
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()];
|
||||
auto monitor = g_monitors[0];
|
||||
g_app->StartCapture(monitor.Hmonitor());
|
||||
// }
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
#include "pch.h"
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Unknwn.h>
|
||||
#include <inspectable.h>
|
||||
|
||||
// WinRT
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.System.h>
|
||||
#include <winrt/Windows.UI.h>
|
||||
#include <winrt/Windows.UI.Composition.h>
|
||||
#include <winrt/Windows.UI.Composition.Desktop.h>
|
||||
#include <winrt/Windows.UI.Popups.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
#include <winrt/Windows.Graphics.DirectX.h>
|
||||
#include <winrt/Windows.Graphics.DirectX.Direct3d11.h>
|
||||
|
||||
#include <windows.ui.composition.interop.h>
|
||||
#include <DispatcherQueue.h>
|
||||
|
||||
// STL
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
// D3D
|
||||
#include <d3d11_4.h>
|
||||
#include <dxgi1_6.h>
|
||||
#include <d2d1_3.h>
|
||||
#include <wincodec.h>
|
||||
|
||||
// Helpers
|
||||
#include "composition.interop.h"
|
||||
#include "d3dHelpers.h"
|
||||
#include "direct3d11.interop.h"
|
||||
#include "capture.interop.h"
|
||||
@@ -1,368 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavdevice/avdevice.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
#include "SDL2/SDL.h"
|
||||
};
|
||||
#else
|
||||
// Linux...
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <SDL/SDL.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavdevice/avdevice.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Output YUV420P
|
||||
#define OUTPUT_YUV420P 0
|
||||
//'1' Use Dshow
|
||||
//'0' Use GDIgrab
|
||||
#define USE_DSHOW 0
|
||||
|
||||
// Refresh Event
|
||||
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
|
||||
|
||||
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
|
||||
|
||||
int thread_exit = 0;
|
||||
SDL_Texture *sdlTexture = nullptr;
|
||||
SDL_Renderer *sdlRenderer = nullptr;
|
||||
SDL_Rect sdlRect;
|
||||
|
||||
int YUV420ToNV12FFmpeg(unsigned char *src_buffer, int width, int height,
|
||||
unsigned char *des_buffer) {
|
||||
AVFrame *Input_pFrame = av_frame_alloc();
|
||||
AVFrame *Output_pFrame = av_frame_alloc();
|
||||
struct SwsContext *img_convert_ctx = sws_getContext(
|
||||
width, height, AV_PIX_FMT_NV12, width, height, AV_PIX_FMT_YUV420P,
|
||||
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
|
||||
|
||||
av_image_fill_arrays(Input_pFrame->data, Input_pFrame->linesize, src_buffer,
|
||||
AV_PIX_FMT_NV12, width, height, 1);
|
||||
av_image_fill_arrays(Output_pFrame->data, Output_pFrame->linesize, des_buffer,
|
||||
AV_PIX_FMT_YUV420P, width, height, 1);
|
||||
|
||||
sws_scale(img_convert_ctx, (uint8_t const **)Input_pFrame->data,
|
||||
Input_pFrame->linesize, 0, height, Output_pFrame->data,
|
||||
Output_pFrame->linesize);
|
||||
|
||||
if (Input_pFrame) av_free(Input_pFrame);
|
||||
if (Output_pFrame) av_free(Output_pFrame);
|
||||
if (img_convert_ctx) sws_freeContext(img_convert_ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sfp_refresh_thread(void *opaque) {
|
||||
thread_exit = 0;
|
||||
while (!thread_exit) {
|
||||
SDL_Event event;
|
||||
event.type = SFM_REFRESH_EVENT;
|
||||
SDL_PushEvent(&event);
|
||||
SDL_Delay(40);
|
||||
}
|
||||
thread_exit = 0;
|
||||
// Break
|
||||
SDL_Event event;
|
||||
event.type = SFM_BREAK_EVENT;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Show Dshow Device
|
||||
// void show_dshow_device() {
|
||||
// AVFormatContext *pFormatCtx = avformat_alloc_context();
|
||||
// AVDictionary *options = NULL;
|
||||
// av_dict_set(&options, "list_devices", "true", 0);
|
||||
// AVInputFormat *iformat = av_find_input_format("dshow");
|
||||
// printf("========Device Info=============\n");
|
||||
// avformat_open_input(&pFormatCtx, "video=dummy", iformat, &options);
|
||||
// printf("================================\n");
|
||||
// }
|
||||
|
||||
// Show AVFoundation Device
|
||||
void show_avfoundation_device() {
|
||||
const AVFormatContext *const_pFormatCtx = avformat_alloc_context();
|
||||
AVFormatContext *pFormatCtx = const_cast<AVFormatContext *>(const_pFormatCtx);
|
||||
|
||||
AVDictionary *options = NULL;
|
||||
av_dict_set(&options, "list_devices", "true", 0);
|
||||
const AVInputFormat *const_iformat = av_find_input_format("avfoundation");
|
||||
AVInputFormat *iformat = const_cast<AVInputFormat *>(const_iformat);
|
||||
|
||||
printf("==AVFoundation Device Info===\n");
|
||||
avformat_open_input(&pFormatCtx, "", iformat, &options);
|
||||
printf("=============================\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
AVFormatContext *pFormatCtx;
|
||||
int i, videoindex;
|
||||
AVCodecContext *pCodecCtx;
|
||||
AVCodec *pCodec;
|
||||
|
||||
avformat_network_init();
|
||||
pFormatCtx = avformat_alloc_context();
|
||||
|
||||
// Open File
|
||||
// char filepath[]="src01_480x272_22.h265";
|
||||
// avformat_open_input(&pFormatCtx,filepath,NULL,NULL)
|
||||
|
||||
// Register Device
|
||||
avdevice_register_all();
|
||||
// Windows
|
||||
#ifdef _WIN32
|
||||
#if USE_DSHOW
|
||||
// Use dshow
|
||||
//
|
||||
// Need to Install screen-capture-recorder
|
||||
// screen-capture-recorder
|
||||
// Website: http://sourceforge.net/projects/screencapturer/
|
||||
//
|
||||
AVInputFormat *ifmt = av_find_input_format("dshow");
|
||||
if (avformat_open_input(&pFormatCtx, "video=screen-capture-recorder", ifmt,
|
||||
NULL) != 0) {
|
||||
printf("Couldn't open input stream.\n");
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
// Use gdigrab
|
||||
AVDictionary *options = NULL;
|
||||
// Set some options
|
||||
// grabbing frame rate
|
||||
// av_dict_set(&options,"framerate","5",0);
|
||||
// The distance from the left edge of the screen or desktop
|
||||
// av_dict_set(&options,"offset_x","20",0);
|
||||
// The distance from the top edge of the screen or desktop
|
||||
// av_dict_set(&options,"offset_y","40",0);
|
||||
// Video frame size. The default is to capture the full screen
|
||||
// av_dict_set(&options,"video_size","640x480",0);
|
||||
const AVInputFormat *ifmt = av_find_input_format("gdigrab");
|
||||
if (avformat_open_input(&pFormatCtx, "desktop", ifmt, &options) != 0) {
|
||||
printf("Couldn't open input stream.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
#elif defined linux
|
||||
// Linux
|
||||
AVDictionary *options = NULL;
|
||||
// Set some options
|
||||
// grabbing frame rate
|
||||
// av_dict_set(&options,"framerate","5",0);
|
||||
// Make the grabbed area follow the mouse
|
||||
// av_dict_set(&options,"follow_mouse","centered",0);
|
||||
// Video frame size. The default is to capture the full screen
|
||||
// av_dict_set(&options,"video_size","640x480",0);
|
||||
AVInputFormat *ifmt = av_find_input_format("x11grab");
|
||||
// Grab at position 10,20
|
||||
if (avformat_open_input(&pFormatCtx, ":0.0+10,20", ifmt, &options) != 0) {
|
||||
printf("Couldn't open input stream.\n");
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
show_avfoundation_device();
|
||||
// Mac
|
||||
AVInputFormat *ifmt = av_find_input_format("avfoundation");
|
||||
// Avfoundation
|
||||
//[video]:[audio]
|
||||
if (avformat_open_input(&pFormatCtx, "1", ifmt, NULL) != 0) {
|
||||
printf("Couldn't open input stream.\n");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
|
||||
printf("Couldn't find stream information.\n");
|
||||
return -1;
|
||||
}
|
||||
videoindex = -1;
|
||||
for (i = 0; i < pFormatCtx->nb_streams; i++)
|
||||
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
videoindex = i;
|
||||
break;
|
||||
}
|
||||
if (videoindex == -1) {
|
||||
printf("Didn't find a video stream.\n");
|
||||
return -1;
|
||||
}
|
||||
// pCodecCtx = pFormatCtx->streams[videoindex]->codec;
|
||||
|
||||
pCodecCtx = avcodec_alloc_context3(NULL);
|
||||
avcodec_parameters_to_context(pCodecCtx,
|
||||
pFormatCtx->streams[videoindex]->codecpar);
|
||||
|
||||
pCodec = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx->codec_id));
|
||||
// pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
|
||||
if (pCodec == NULL) {
|
||||
printf("Codec not found.\n");
|
||||
return -1;
|
||||
}
|
||||
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
|
||||
printf("Could not open codec.\n");
|
||||
return -1;
|
||||
}
|
||||
AVFrame *pFrame, *pFrameYUV;
|
||||
pFrame = av_frame_alloc();
|
||||
pFrameYUV = av_frame_alloc();
|
||||
// unsigned char *out_buffer=(unsigned char
|
||||
// *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width,
|
||||
// pCodecCtx->height)); avpicture_fill((AVPicture *)pFrameYUV, out_buffer,
|
||||
// AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
|
||||
// SDL----------------------------
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
|
||||
printf("Could not initialize SDL - %s\n", SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
const int pixel_w = 640, pixel_h = 360;
|
||||
int screen_w = 640, screen_h = 360;
|
||||
// const SDL_VideoInfo *vi = SDL_GetVideoInfo();
|
||||
// Half of the Desktop's width and height.
|
||||
screen_w = 640;
|
||||
screen_h = 360;
|
||||
// SDL_Surface *screen;
|
||||
// screen = SDL_SetVideoMode(screen_w, screen_h, 0, 0);
|
||||
SDL_Window *screen;
|
||||
screen = SDL_CreateWindow("RTS Receiver", SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h,
|
||||
SDL_WINDOW_RESIZABLE);
|
||||
|
||||
if (!screen) {
|
||||
printf("SDL: could not set video mode - exiting:%s\n", SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
// SDL_Overlay *bmp;
|
||||
// bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,
|
||||
// SDL_YV12_OVERLAY, screen);
|
||||
|
||||
sdlRenderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED);
|
||||
|
||||
Uint32 pixformat = 0;
|
||||
pixformat = SDL_PIXELFORMAT_NV12;
|
||||
|
||||
SDL_Texture *sdlTexture = nullptr;
|
||||
sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat,
|
||||
SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);
|
||||
|
||||
SDL_Rect rect;
|
||||
rect.x = 0;
|
||||
rect.y = 0;
|
||||
rect.w = screen_w;
|
||||
rect.h = screen_h;
|
||||
// SDL End------------------------
|
||||
int ret, got_picture;
|
||||
|
||||
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
|
||||
|
||||
#if OUTPUT_YUV420P
|
||||
FILE *fp_yuv = fopen("output.yuv", "wb+");
|
||||
#endif
|
||||
|
||||
struct SwsContext *img_convert_ctx;
|
||||
img_convert_ctx = sws_getContext(
|
||||
pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width,
|
||||
pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
|
||||
//------------------------------
|
||||
SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
|
||||
//
|
||||
// SDL_WM_SetCaption("Simplest FFmpeg Grab Desktop", NULL);
|
||||
// Event Loop
|
||||
SDL_Event event;
|
||||
|
||||
for (;;) {
|
||||
// Wait
|
||||
SDL_WaitEvent(&event);
|
||||
if (event.type == SFM_REFRESH_EVENT) {
|
||||
//------------------------------
|
||||
if (av_read_frame(pFormatCtx, packet) >= 0) {
|
||||
if (packet->stream_index == videoindex) {
|
||||
avcodec_send_packet(pCodecCtx, packet);
|
||||
got_picture = avcodec_receive_frame(pCodecCtx, pFrame);
|
||||
// ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,
|
||||
// packet);
|
||||
if (ret < 0) {
|
||||
printf("Decode Error.\n");
|
||||
return -1;
|
||||
}
|
||||
if (got_picture) {
|
||||
// SDL_LockYUVOverlay(bmp);
|
||||
// pFrameYUV->data[0] = bmp->pixels[0];
|
||||
// pFrameYUV->data[1] = bmp->pixels[2];
|
||||
// pFrameYUV->data[2] = bmp->pixels[1];
|
||||
// pFrameYUV->linesize[0] = bmp->pitches[0];
|
||||
// pFrameYUV->linesize[1] = bmp->pitches[2];
|
||||
// pFrameYUV->linesize[2] = bmp->pitches[1];
|
||||
// sws_scale(img_convert_ctx,
|
||||
// (const unsigned char *const *)pFrame->data,
|
||||
// pFrame->linesize, 0, pCodecCtx->height,
|
||||
// pFrameYUV->data, pFrameYUV->linesize);
|
||||
|
||||
// #if OUTPUT_YUV420P
|
||||
// int y_size = pCodecCtx->width * pCodecCtx->height;
|
||||
// fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); // Y
|
||||
// fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //
|
||||
// U fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);
|
||||
// // V
|
||||
// #endif
|
||||
// SDL_UnlockYUVOverlay(bmp);
|
||||
|
||||
// SDL_DisplayYUVOverlay(bmp, &rect);
|
||||
|
||||
// YUV420ToNV12FFmpeg(buffer, pixel_w, pixel_h, dst_buffer);
|
||||
|
||||
// SDL_UpdateTexture(sdlTexture, NULL, dst_buffer, pixel_w);
|
||||
|
||||
// // FIX: If window is resize
|
||||
// sdlRect.x = 0;
|
||||
// sdlRect.y = 0;
|
||||
// sdlRect.w = screen_w;
|
||||
// sdlRect.h = screen_h;
|
||||
|
||||
// SDL_RenderClear(sdlRenderer);
|
||||
// SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
|
||||
// SDL_RenderPresent(sdlRenderer);
|
||||
}
|
||||
}
|
||||
// av_free_packet(packet);
|
||||
} else {
|
||||
// Exit Thread
|
||||
thread_exit = 1;
|
||||
}
|
||||
} else if (event.type == SDL_QUIT) {
|
||||
thread_exit = 1;
|
||||
} else if (event.type == SFM_BREAK_EVENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sws_freeContext(img_convert_ctx);
|
||||
|
||||
#if OUTPUT_YUV420P
|
||||
fclose(fp_yuv);
|
||||
#endif
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
// av_free(out_buffer);
|
||||
av_free(pFrameYUV);
|
||||
avcodec_close(pCodecCtx);
|
||||
avformat_close_input(&pFormatCtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
12
application/remote_desk/remote_desk_server/main.cpp
Normal file
12
application/remote_desk/remote_desk_server/main.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "remote_desk_server.h"
|
||||
|
||||
int main() {
|
||||
RemoteDeskServer remote_desk_server;
|
||||
remote_desk_server.Init();
|
||||
|
||||
while (1) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "remote_desk_server.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libswscale/swscale.h>
|
||||
};
|
||||
|
||||
#define NV12_BUFFER_SIZE 2560 * 1440 * 3 / 2
|
||||
|
||||
RemoteDeskServer ::RemoteDeskServer() {}
|
||||
|
||||
RemoteDeskServer ::~RemoteDeskServer() {
|
||||
if (nv12_buffer_) {
|
||||
delete nv12_buffer_;
|
||||
nv12_buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int BGRAToNV12FFmpeg(unsigned char *src_buffer, int width, int height,
|
||||
unsigned char *dst_buffer) {
|
||||
AVFrame *Input_pFrame = av_frame_alloc();
|
||||
AVFrame *Output_pFrame = av_frame_alloc();
|
||||
struct SwsContext *img_convert_ctx = sws_getContext(
|
||||
width, height, AV_PIX_FMT_BGRA, width, height, AV_PIX_FMT_NV12,
|
||||
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
|
||||
|
||||
av_image_fill_arrays(Input_pFrame->data, Input_pFrame->linesize, src_buffer,
|
||||
AV_PIX_FMT_BGRA, width, height, 1);
|
||||
av_image_fill_arrays(Output_pFrame->data, Output_pFrame->linesize, dst_buffer,
|
||||
AV_PIX_FMT_NV12, width, height, 1);
|
||||
|
||||
sws_scale(img_convert_ctx, (uint8_t const **)Input_pFrame->data,
|
||||
Input_pFrame->linesize, 0, height, Output_pFrame->data,
|
||||
Output_pFrame->linesize);
|
||||
|
||||
if (Input_pFrame) av_free(Input_pFrame);
|
||||
if (Output_pFrame) av_free(Output_pFrame);
|
||||
if (img_convert_ctx) sws_freeContext(img_convert_ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RemoteDeskServer::HostReceiveBuffer(const char *data, size_t size,
|
||||
const char *user_id,
|
||||
size_t user_id_size) {
|
||||
std::string msg(data, size);
|
||||
std::string user(user_id, user_id_size);
|
||||
|
||||
std::cout << "Receive: [" << user << "] " << msg << std::endl;
|
||||
}
|
||||
|
||||
int RemoteDeskServer::Init() {
|
||||
Params params;
|
||||
params.cfg_path = "../../../../config/config.ini";
|
||||
params.on_receive_buffer = [](const char *data, size_t size,
|
||||
const char *user_id, size_t user_id_size) {
|
||||
// std::string msg(data, size);
|
||||
// std::string user(user_id, user_id_size);
|
||||
|
||||
// std::cout << "Receive: [" << user << "] " << msg << std::endl;
|
||||
};
|
||||
|
||||
std::string transmission_id = "000000";
|
||||
std::string user_id = "Server";
|
||||
peer = CreatePeer(¶ms);
|
||||
CreateConnection(peer, transmission_id.c_str(), user_id.c_str());
|
||||
|
||||
nv12_buffer_ = new char[NV12_BUFFER_SIZE];
|
||||
|
||||
screen_capture = new ScreenCaptureWgc();
|
||||
|
||||
RECORD_DESKTOP_RECT rect;
|
||||
rect.left = 0;
|
||||
rect.top = 0;
|
||||
rect.right = GetSystemMetrics(SM_CXSCREEN);
|
||||
rect.bottom = GetSystemMetrics(SM_CYSCREEN);
|
||||
|
||||
screen_capture->Init(
|
||||
rect, 60,
|
||||
[this](unsigned char *data, int size, int width, int height) -> void {
|
||||
// std::cout << "Send" << std::endl;
|
||||
BGRAToNV12FFmpeg(data, width, height, (unsigned char *)nv12_buffer_);
|
||||
SendData(peer, DATA_TYPE::VIDEO, (const char *)nv12_buffer_,
|
||||
NV12_BUFFER_SIZE);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
});
|
||||
|
||||
screen_capture->Start();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef _REMOTE_DESK_SERVER_H_
|
||||
#define _REMOTE_DESK_SERVER_H_
|
||||
|
||||
#include "screen_capture_wgc.h"
|
||||
#include "x.h"
|
||||
|
||||
class RemoteDeskServer {
|
||||
public:
|
||||
RemoteDeskServer();
|
||||
~RemoteDeskServer();
|
||||
|
||||
public:
|
||||
int Init();
|
||||
|
||||
static void HostReceiveBuffer(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size);
|
||||
|
||||
private:
|
||||
PeerPtr* peer = nullptr;
|
||||
ScreenCaptureWgc* screen_capture = nullptr;
|
||||
|
||||
char* nv12_buffer_ = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -19,9 +19,7 @@ extern "C" {
|
||||
int screen_w = 2560, screen_h = 1440;
|
||||
const int pixel_w = 2560, pixel_h = 1440;
|
||||
|
||||
unsigned char buffer[pixel_w * pixel_h * 3 / 2];
|
||||
unsigned char dst_buffer[pixel_w * pixel_h * 3 / 2];
|
||||
unsigned char rgbData[pixel_w * pixel_h * 4];
|
||||
SDL_Texture *sdlTexture = nullptr;
|
||||
SDL_Renderer *sdlRenderer = nullptr;
|
||||
SDL_Rect sdlRect;
|
||||
@@ -55,10 +55,7 @@ int ScreenCaptureWgc::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
|
||||
if (_inited == true) return error;
|
||||
|
||||
_fps = fps;
|
||||
_rect = rect;
|
||||
_start_time = av_gettime_relative();
|
||||
_time_base = {1, AV_TIME_BASE};
|
||||
_pixel_fmt = AV_PIX_FMT_BGRA;
|
||||
|
||||
_on_data = cb;
|
||||
|
||||
do {
|
||||
@@ -126,71 +123,9 @@ int ScreenCaptureWgc::Stop() {
|
||||
}
|
||||
|
||||
void ScreenCaptureWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
|
||||
// std::cout << "onframe" << std::endl;
|
||||
// AVFrame *av_frame = av_frame_alloc();
|
||||
|
||||
// av_frame->pts = av_gettime_relative();
|
||||
// av_frame->pkt_dts = av_frame->pts;
|
||||
// // av_frame->pkt_pts = av_frame->pts;
|
||||
|
||||
// av_frame->width = frame.width;
|
||||
// av_frame->height = frame.height;
|
||||
// av_frame->format = AV_PIX_FMT_BGRA;
|
||||
// av_frame->pict_type = AV_PICTURE_TYPE_NONE;
|
||||
// av_frame->pkt_size = frame.width * frame.height * 4;
|
||||
|
||||
// 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((unsigned char *)frame.data, frame.width * frame.height * 4,
|
||||
frame.width, frame.height);
|
||||
|
||||
// av_frame_free(&av_frame);
|
||||
|
||||
// BGRA to YUV
|
||||
// auto swrCtxBGRA2YUV = sws_getContext(
|
||||
// frame.width, frame.height, AV_PIX_FMT_BGRA, frame.width, frame.height,
|
||||
// AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
|
||||
|
||||
// create BGRA
|
||||
// AVFrame *frame_bgra = av_frame;
|
||||
// AVFrame *frame_bgra = av_frame_alloc();
|
||||
// frame_bgra->format = AV_PIX_FMT_BGRA;
|
||||
// frame_bgra->width = frame.width;
|
||||
// frame_bgra->height = frame.height;
|
||||
// if (av_frame_get_buffer(frame_bgra, 32) < 0) {
|
||||
// printf("Failed: av_frame_get_buffer\n");
|
||||
// return;
|
||||
// }
|
||||
// frame_bgra->data[0] = cropImage;
|
||||
|
||||
// YUV
|
||||
// AVFrame *frame_yuv = av_frame_alloc();
|
||||
// frame_yuv->width = frame.width;
|
||||
// frame_yuv->height = frame.height;
|
||||
// frame_yuv->format = AV_PIX_FMT_YUV420P;
|
||||
|
||||
// uint8_t *picture_buf =
|
||||
// (uint8_t *)av_malloc(frame.width * frame.height * 3 / 2);
|
||||
// if (av_image_fill_arrays(frame_yuv->data, frame_yuv->linesize, picture_buf,
|
||||
// AV_PIX_FMT_YUV420P, frame.width, frame.height,
|
||||
// 1) < 0) {
|
||||
// std::cout << "Failed: av_image_fill_arrays" << std::endl;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (sws_scale(swrCtxBGRA2YUV, frame_bgra->data, frame_bgra->linesize, 0,
|
||||
// frame.height, frame_yuv->data, frame_yuv->linesize) < 0) {
|
||||
// std::cout << "BGRA to YUV failed" << std::endl;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// frame_yuv->pts = av_gettime();
|
||||
|
||||
// if (_on_data)
|
||||
// _on_data((unsigned char *)frame_yuv->data,
|
||||
// frame.width * frame.height * 3 / 2, frame.width, frame.height);
|
||||
}
|
||||
|
||||
void ScreenCaptureWgc::CleanUp() {
|
||||
@@ -3,23 +3,14 @@
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "wgc_session.h"
|
||||
#include "wgc_session_impl.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec\avcodec.h>
|
||||
#include <libavfilter\avfilter.h>
|
||||
#include <libavformat\avformat.h>
|
||||
#include <libavutil\imgutils.h>
|
||||
#include <libavutil\time.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "wgc_session.h"
|
||||
#include "wgc_session_impl.h"
|
||||
|
||||
typedef struct {
|
||||
int left;
|
||||
int top;
|
||||
@@ -67,10 +58,6 @@ class ScreenCaptureWgc : public WgcSession::wgc_session_observer {
|
||||
|
||||
cb_desktop_data _on_data;
|
||||
cb_desktop_error _on_error;
|
||||
|
||||
AVRational _time_base;
|
||||
int64_t _start_time;
|
||||
AVPixelFormat _pixel_fmt;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,372 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* 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_
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* 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_
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* 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_
|
||||
@@ -1,421 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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_
|
||||
@@ -29,16 +29,30 @@ target("log")
|
||||
add_headerfiles("../../src/log/log.h")
|
||||
add_includedirs("../../src/log", {public = true})
|
||||
|
||||
target("remote_desk")
|
||||
set_kind("binary")
|
||||
add_deps("projectx")
|
||||
target("screen_capture")
|
||||
set_kind("static")
|
||||
add_packages("log")
|
||||
add_packages("ffmpeg")
|
||||
add_packages("vcpkg::sdl2")
|
||||
add_links("avfilter", "avdevice", "avformat", "avcodec", "swscale", "swresample", "avutil")
|
||||
add_files("dll/*.cpp")
|
||||
add_files("screen_capture/*.cpp")
|
||||
add_includedirs("screen_capture", {public = true})
|
||||
|
||||
target("remote_desk_server")
|
||||
set_kind("binary")
|
||||
add_packages("log", "ffmpeg")
|
||||
add_deps("projectx", "screen_capture")
|
||||
add_files("remote_desk_server/*.cpp")
|
||||
add_includedirs("../../src/interface")
|
||||
add_links("SDL2-static", "SDL2main", "Shell32", "gdi32", "winmm",
|
||||
"setupapi", "version", "WindowsApp", "Imm32", "avutil")
|
||||
-- add_links("avformat", "swscale")
|
||||
|
||||
-- target("remote_desk")
|
||||
-- set_kind("binary")
|
||||
-- add_deps("projectx")
|
||||
-- add_packages("log")
|
||||
-- add_packages("ffmpeg")
|
||||
-- add_packages("vcpkg::sdl2")
|
||||
-- add_links("avfilter", "avdevice", "avformat", "avcodec", "swscale", "swresample", "avutil")
|
||||
-- add_files("**.cpp")
|
||||
-- add_includedirs("../../src/interface")
|
||||
-- add_links("SDL2-static", "SDL2main", "Shell32", "gdi32", "winmm",
|
||||
-- "setupapi", "version", "WindowsApp", "Imm32", "avutil")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user