这个类被封装到DLL中引用,专门用来多屏或窗口图像采集,可用来编码成视频流做图像传输。
注意,如果你不想这个项目是个DLL才能运行,而是希望能放到非DLL项目中直接运行,请去掉头文件中的以下部分
#ifdef DESKTOPCAPTURE_EXPORTS
#define DESKTOPCAPTURE_API __declspec(dllexport)
#else
#define DESKTOPCAPTURE_API __declspec(dllimport)
#endif
如果你期望了解如何将带动画的鼠标指针绘制到图像上,那么请关注m_CursorFrame变量的使用。
那么,正式开始。
首先来介绍下这个类应该如何使用,这里主要是演示了如何每30毫秒获取一张图像的效果,另外,如果你有Opencv库,那么可以解开Opencv相关的注释,可以直观地看到图像的显示效果。
#include <DesktopCapture.h>
//#include <opencv2/opencv.hpp>
//#include <opencv2/imgproc.hpp>
//#include <opencv2/imgproc/types_c.h>
//using namespace cv;
int main() {
//使用BitBlt采集方法,采集默认屏幕
DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::BitBlt, 0);
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::BitBlt, 1);//采集第一个屏幕
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::BitBlt, 2);//采集第二个屏幕
//HWND hWindow = GetForegroundWindow();//获取最前窗口的句柄
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::BitBlt, hWindow, true);//采集窗口,包括他的标题边框
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::BitBlt, hWindow, false);//采集窗口,仅窗口中的内容
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::DXGI, 0);//DXGI工厂采集默认屏幕
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::DXGI, 1);//DXGI工厂采集第一个屏幕
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::DXGI, 2);//DXGI工厂采集第二个屏幕
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::DXGI, hWindow, true);//暂不支持
//DesktopCapture DesktopCaptureClass(DesktopCapture::CapSrcType::DXGI, hWindow, false);//暂不支持
//准备好获取图像帧的数据
unsigned char* dataPtr = nullptr;
int w, h;
size_t size;
//获取图像帧的宽高和大小,这里主要是获取大小
DesktopCaptureClass.GetFrame(nullptr, w, h, size, true/*代表绘入鼠标*/);
//根据回馈的大小分配内存到dataPtr中
dataPtr = new unsigned char[size];
while (true) {
//获取一帧图像
if (!DesktopCaptureClass.GetFrame(dataPtr, w, h, size, true)) {
std::cerr << "获取图像帧失败" << std::endl;
break;
}
//此时dataPtr存放好了图像,请使用该dataPtr
//imshow("showMat", Mat(h, w, CV_8UC4, dataPtr));
//waitKey(1);
Sleep(30);
}
//回收内存空间
delete[] dataPtr;
dataPtr = nullptr;
//清除类中的缓存变量,也可不写这句,析构也会自动调用
DesktopCaptureClass.Clear();
return 0;
}
可以看见,使用这个类非常简单,初始化变量后就立马就可以用一直使用GetFrame方法采集到图像帧,目前支持BitBlt和DXGI方式进行屏幕采集,而窗口采集目前仅支持用BItBlt来完成。
其中窗口采集的鼠标显示比较灵活,当采集的窗口不前置的时候,鼠标将不会被绘制到窗口上。
那么我们直接来观察源码,整个项目也只有这一个类,他的头文件和本体在文章末尾提供。
以下信息用于方便定位到相关的采集代码
关于BitBlt采集的核心方法是
GetFrameFromBitBlt
其中依赖的缓存变量来源于InitBitBlt()
关于DXGI采集的核心方法是
GetFrameFromDXGI
其中依赖的缓存变量来源于InitDXGI()
头文件:
#ifdef DESKTOPCAPTURE_EXPORTS
#define DESKTOPCAPTURE_API __declspec(dllexport)
#else
#define DESKTOPCAPTURE_API __declspec(dllimport)
#endif
#include <iostream>
#include <string>
#include <vector>
#include <windows.h>
#include <assert.h>
#include <dshow.h>
#pragma comment(lib, "User32.lib")
#include <d3d11.h>
#include <dxgi.h>
#include <dxgi1_5.h>
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "d3d11.lib")
#define IS_NULL(POINTER) (nullptr == (POINTER))
extern "C" {
/// <summary>
/// 桌面图像抓取类
/// </summary>
class DESKTOPCAPTURE_API DesktopCapture {
//类的附属类型
public:
/// <summary>
/// 消息类型
/// </summary>
enum class MessageType {
Info
, Warn
, Error
};
/// <summary>
/// 调试回调方法类型
/// </summary>
typedef void (*LogCallBack)(MessageType Type, const char* Message, const char* FunName, int Line);
/// <summary>
/// 采集来源类型
/// </summary>
enum class CapSrcType {
BitBlt
, DXGI
};
/// <summary>
/// 采集目标类型
/// </summary>
enum class CapDstType {
Window//窗口边框与内容
, WindowContent//窗口内容
, Screen//屏幕
};
//类的附属方法
/// <summary>
/// 获取对应采集类型的名称
/// </summary>
/// <param name="Type">采集类型</param>
/// <returns>采集类型对应的名称</returns>
const char* GetNameFromCapSourceType(CapSrcType Type);
//类的公共方法
public:
/// <summary>
/// 基于屏幕的图像采集的构造方法
/// </summary>
/// <param name="CapSourceNum">指定采集来源[0:BitBlt][1:DXGI]</param>
/// <param name="ScreenNum">录制第几个屏幕,0是主屏,1是第一个屏幕,2是第二个屏幕</param>
DesktopCapture(CapSrcType CapSrcType, int ScreenNum);
/// <summary>
/// 基于窗口的图像采集的构造方法
/// </summary>
/// <param name="CapSourceNum">指定采集来源[0:BitBlt][1:DXGI]</param>
/// <param name="HWindow">采集指定窗口图像</param>
/// <param name="IsDrawFrame">是否绘制窗口边框</param>
DesktopCapture(CapSrcType CapSrcType, HWND HWindow, bool IsDrawFrame);
/// <summary>
/// 默认析构
/// </summary>
virtual ~DesktopCapture();
/// <summary>
/// 获取图像帧
/// </summary>
/// <param name="Frame">获取图像帧的数据指针,需要预先分配好空间,如果为null将不写入,一个合适的值可以通过调用一次该方法从Size上获取</param>
/// <param name="Width">获取图像帧的宽</param>
/// <param name="Height">获取图像帧的高</param>
/// <param name="Size">获取图像帧的占用字节数</param>
/// <param name="IsDrawCursor">是否绘入指针</param>
/// <returns>获取成功与否</returns>
bool GetFrame(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor = false);
/// <summary>
/// 清除数据缓冲
/// </summary>
void Clear();
/// <summary>
/// 设置日志回调方法
/// </summary>
void SetLogCallBack(LogCallBack LogCallBack);
//类的私有方法
private:
/// <summary>
/// 获取图像帧
/// </summary>
/// <param name="Frame">存储用的图像帧,需要预先分配好空间,如果为null将不写入,一个合适的值可以通过调用一次该方法从Size上获取</param>
/// <param name="IsDrawCursor">是否绘入指针</param>
/// <returns>获取成功与否</returns>
bool GetFrameFromBitBlt(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor = false);
/// <summary>
/// 获取图像帧
/// </summary>
/// <param name="Frame">存储用的图像帧</param>
/// <param name="IsDrawCursor">是否绘入指针</param>
/// <returns>获取成功与否</returns>
bool GetFrameFromDXGI(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor = false);
/// <summary>
/// 安装BitBlt相关的变量
/// </summary>
/// <returns>安装成功与否</returns>
bool InitBitBlt();
/// <summary>
/// 卸载BitBlt相关的变量
/// </summary>
void UnInitBitBlt();
/// <summary>
/// 安装DXGI相关的变量
/// </summary>
/// <returns>安装成功与否</returns>
bool InitDXGI();
/// <summary>
/// 卸载DXGI相关的变量
/// </summary>
void UnInitDXGI();
//BitBlt
static BOOL CALLBACK MonitorEnumProc(
HMONITOR hMonitor, // 显示器句柄
HDC hdcMonitor, // 监视器相关设备上下文的句柄
LPRECT lprcMonitor, // 指向监视器相交矩形的指针
LPARAM dwData // 从EnumDisplayMonitors传递的数据
);
private:
static LogCallBack m_LogFun;//日志回调方法指针
const CapSrcType m_CapSrcType;//采集来源
const CapDstType m_CapDstType;//采集目标
const int m_ScreenNum; //屏幕序号[0,...]
const HWND m_HWindow; //窗口句柄
bool m_SeachStart{};//搜索是否开始
int m_SeachNum{};//已经搜索的次数
//All
int m_ScreenX{};//屏幕起始X
int m_ScreenY{};//屏幕起始Y
int m_ScreenW{};//屏幕宽
int m_ScreenH{};//屏幕高
double m_ScreenZoom{ 1.0 };//屏幕缩放
//BitBlt
HDC m_ScreenDC{};//屏幕DC
HDC m_CompatibleDC{};//抓取后存储用的DC
HBITMAP m_HBitmap{};//存储用的BitMap
LONG m_DataSize{};//图像数据大小
CURSORINFO m_CurInfo{};
ICONINFO m_IconInfo{};
clock_t m_CursorClockStart{};//鼠标动画开始时的时间
const int m_CursorFrame{ 18 };//鼠标动画的帧数
//DXGI
IDXGIFactory1* m_Factory{ nullptr };
IDXGIAdapter1* m_Adapter{ nullptr };
IDXGIOutput* m_Output{ nullptr };
DXGI_OUTPUT_DESC m_OutPutDesc{};
DXGI_OUTDUPL_DESC m_OutPutlDesc{};
IDXGIOutput1* m_Inner{ nullptr };
ID3D11Device* m_Device{ nullptr };
ID3D11DeviceContext* m_Context{ nullptr };
IDXGIOutputDuplication* m_Duplication{ nullptr };
bool mFastlane{};
};
}
本体:
#include "pch.h"
#include "DesktopCapture.h"
#include <iostream>
#include <windows.h>
using std::string;
using std::to_string;
using std::vector;
#define LogFun(Type,Message) if(m_LogFun)m_LogFun((Type),(Message),__FUNCTION__,__LINE__)
DesktopCapture::LogCallBack DesktopCapture::m_LogFun{ nullptr };
const char* DesktopCapture::GetNameFromCapSourceType(CapSrcType Type)
{
switch (Type) {
case CapSrcType::BitBlt: {
return "BitBlt";
}
case CapSrcType::DXGI: {
return "DXGI";
}
default:
LogFun(MessageType::Error, "未知参数引入");
assert(false);
return "";
}
}
DesktopCapture::DesktopCapture(CapSrcType CapSrcType, int ScreenNum)
:m_CapSrcType(CapSrcType)
, m_CapDstType(CapDstType::Screen)
, m_ScreenNum(ScreenNum)
, m_HWindow(nullptr)
{
LogFun(MessageType::Info, (string("屏幕采集,采集来源设置为 ") + GetNameFromCapSourceType(m_CapSrcType)).c_str());
}
DesktopCapture::DesktopCapture(CapSrcType CapSrcType, HWND HWindow, bool IsDrawFrame)
: m_CapSrcType(CapSrcType)
, m_CapDstType(IsDrawFrame ? CapDstType::WindowContent : CapDstType::Window)
, m_ScreenNum(-1)
, m_HWindow(HWindow)
{
LogFun(MessageType::Info, (string("窗口采集,采集来源设置为 ") + GetNameFromCapSourceType(m_CapSrcType)).c_str());
}
DesktopCapture::~DesktopCapture()
{
Clear();
}
bool DesktopCapture::GetFrame(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor)
{
switch (this->m_CapSrcType) {
case CapSrcType::BitBlt: {
return GetFrameFromBitBlt(Frame, Width, Height, Size, IsDrawCursor);
}
case CapSrcType::DXGI: {
return GetFrameFromDXGI(Frame, Width, Height, Size, IsDrawCursor);
}
default: {
LogFun(MessageType::Error, "未知参数引入");
assert(false);
return false;
}
}
}
void DesktopCapture::Clear()
{
UnInitBitBlt();
UnInitDXGI();
}
void DesktopCapture::SetLogCallBack(LogCallBack LogCallBack)
{
m_LogFun = LogCallBack;
}
bool DesktopCapture::GetFrameFromBitBlt(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor)
{
if (IS_NULL(m_HBitmap)) {
if (!InitBitBlt()) {
LogFun(MessageType::Error, "BitBlt装载失败");
return false;
}
assert(m_HBitmap);
}
Width = m_ScreenW;
Height = m_ScreenH;
Size = m_DataSize;
if (IS_NULL(Frame)) {
return false;
}
if (!BitBlt(m_CompatibleDC, 0, 0, m_ScreenW, m_ScreenH, m_ScreenDC, 0, 0, SRCCOPY)) {
LogFun(MessageType::Error, "BitBlt采集失败");
return false;
}
if (IsDrawCursor && (IS_NULL(m_HWindow) || GetForegroundWindow() == m_HWindow)) {
m_CurInfo.cbSize = sizeof(m_CurInfo);
if (GetCursorInfo(&m_CurInfo)
&& m_CurInfo.hCursor
&& m_CurInfo.flags == CURSOR_SHOWING)
{
//鼠标在屏幕之内
if (m_CurInfo.ptScreenPos.x > m_ScreenX
&& m_CurInfo.ptScreenPos.x < m_ScreenX + m_ScreenW
&& m_CurInfo.ptScreenPos.y > m_ScreenY
&& m_CurInfo.ptScreenPos.y < m_ScreenY + m_ScreenH) {
if (m_CurInfo.hCursor) {
if (GetIconInfo(m_CurInfo.hCursor, &m_IconInfo)) {
DrawIconEx(m_CompatibleDC
, static_cast<int>((m_CurInfo.ptScreenPos.x - m_IconInfo.xHotspot - m_ScreenX) * m_ScreenZoom)
, static_cast<int>((m_CurInfo.ptScreenPos.y - m_IconInfo.yHotspot - m_ScreenY) * m_ScreenZoom)
, m_CurInfo.hCursor
, static_cast<int>(GetSystemMetrics(SM_CXCURSOR) * m_ScreenZoom)
, static_cast<int>(GetSystemMetrics(SM_CYCURSOR) * m_ScreenZoom)
, ((clock() - m_CursorClockStart) / 60) % m_CursorFrame, NULL, DI_NORMAL);
}
if (m_IconInfo.hbmMask)
DeleteObject(m_IconInfo.hbmMask);
if (m_IconInfo.hbmColor)
DeleteObject(m_IconInfo.hbmColor);
memset(&m_IconInfo, 0, sizeof(m_IconInfo));
}
}
}
memset(&m_CurInfo, 0, sizeof(m_CurInfo));
}
GetBitmapBits(m_HBitmap, m_DataSize, Frame);
return true;
}
bool DesktopCapture::GetFrameFromDXGI(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor)
{
if (IS_NULL(m_Duplication)) {
if (!InitDXGI()) {
LogFun(MessageType::Error, "DXGI装载失败");
return false;
}
assert(m_Duplication);
}
Width = m_ScreenW;
Height = m_ScreenH;
Size = m_DataSize;
if (IS_NULL(Frame)) {
return false;
}
HRESULT hr;
IDXGIResource* resource{ nullptr };
DXGI_OUTDUPL_FRAME_INFO frameInfo;
ID3D11Texture2D* texture{ nullptr };
D3D11_TEXTURE2D_DESC textureDesc;
ID3D11Texture2D* readable{ nullptr };
IDXGISurface* surface{ nullptr };
DXGI_MAPPED_RECT rect;
unsigned char* finalFramePtr{ nullptr };
hr = m_Duplication->AcquireNextFrame(/*timeoutInMilliseconds缓存的毫秒数*/30, &frameInfo, &resource);
if (FAILED(hr)) {
goto END_ERR;
}
//意味着图像还没更新
if (frameInfo.LastPresentTime.QuadPart == 0) {
goto END_ERR;
}
if (mFastlane) {
hr = m_Duplication->MapDesktopSurface(&rect);
finalFramePtr = rect.pBits;
}
else {
hr = resource->QueryInterface(IID_ID3D11Texture2D, (void**)&texture);
if (IS_NULL(texture)) {
goto END_ERR;
}
texture->GetDesc(&textureDesc);
textureDesc.Usage = D3D11_USAGE_STAGING;
textureDesc.BindFlags = 0;
textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
textureDesc.MiscFlags = 0;
hr = m_Device->CreateTexture2D(
&textureDesc,
nullptr,
&readable
);
if (FAILED(hr) || IS_NULL(readable)) {
goto END_ERR;
}
readable->SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
hr = readable->QueryInterface(
IID_IDXGISurface,
(void**)&surface
);
m_Context->CopyResource(readable, texture);
hr = surface->Map(&rect, DXGI_MAP_READ);
#if FALSE //暂未利用画面旋转角度参数
int rotate = m_OutPutlDesc.Rotation;
switch (rotate) {
case DXGI_MODE_ROTATION_IDENTITY | DXGI_MODE_ROTATION_UNSPECIFIED:
rotate = 0;
break;
case DXGI_MODE_ROTATION_ROTATE90:
rotate = 90;
break;
case DXGI_MODE_ROTATION_ROTATE180:
rotate = 180;
break;
case DXGI_MODE_ROTATION_ROTATE270:
rotate = 270;
break;
default:
LogFun(MessageType::Error, ("Unknown rotation : " + std::to_string(rotate)).c_str());
assert(0);
}
#endif
finalFramePtr = rect.pBits;
}
if (IsDrawCursor) {
// 创建一个设备上下文
HDC hdc = GetDC(NULL);
// 创建一个位图信息头
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = Width;
bmi.bmiHeader.biHeight = -Height; // 负数表示图像数据从上到下
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
// 创建位图
HBITMAP hBitmap = CreateDIBitmap(hdc, &(bmi.bmiHeader), CBM_INIT, finalFramePtr, &bmi, DIB_RGB_COLORS);
HDC compatibleDC = CreateCompatibleDC(/*m_ScreenDC*/NULL);//使用了DC后,会导致看不到普通指针等指针图标没有了的现象
if (IS_NULL(compatibleDC) || IS_NULL(hBitmap)) {
LogFun(MessageType::Error, "申请DC或HBitmap失败");
goto END_ERR;
}
SelectObject(compatibleDC, hBitmap);
if (IS_NULL(m_HWindow) || GetForegroundWindow() == m_HWindow) {
m_CurInfo.cbSize = sizeof(m_CurInfo);
if (GetCursorInfo(&m_CurInfo)
&& m_CurInfo.hCursor
&& m_CurInfo.flags == CURSOR_SHOWING)
{
//鼠标在屏幕之内
if (m_CurInfo.ptScreenPos.x > m_ScreenX
&& m_CurInfo.ptScreenPos.x < m_ScreenX + m_ScreenW
&& m_CurInfo.ptScreenPos.y > m_ScreenY
&& m_CurInfo.ptScreenPos.y < m_ScreenY + m_ScreenH) {
if (m_CurInfo.hCursor) {
if (GetIconInfo(m_CurInfo.hCursor, &m_IconInfo)) {
DrawIconEx(compatibleDC
, static_cast<int>((m_CurInfo.ptScreenPos.x - m_IconInfo.xHotspot - m_ScreenX) * m_ScreenZoom)
, static_cast<int>((m_CurInfo.ptScreenPos.y - m_IconInfo.yHotspot - m_ScreenY) * m_ScreenZoom)
, m_CurInfo.hCursor
, static_cast<int>(GetSystemMetrics(SM_CXCURSOR) * m_ScreenZoom)
, static_cast<int>(GetSystemMetrics(SM_CYCURSOR) * m_ScreenZoom)
, ((clock() - m_CursorClockStart) / 60) % m_CursorFrame, NULL, DI_NORMAL);
}
if (m_IconInfo.hbmMask)
DeleteObject(m_IconInfo.hbmMask);
if (m_IconInfo.hbmColor)
DeleteObject(m_IconInfo.hbmColor);
memset(&m_IconInfo, 0, sizeof(m_IconInfo));
}
}
}
memset(&m_CurInfo, 0, sizeof(m_CurInfo));
}
GetBitmapBits(hBitmap, m_DataSize, Frame);
// 清理资源
DeleteDC(compatibleDC);
DeleteObject(hBitmap);
ReleaseDC(NULL, hdc);
}
else {
memcpy_s(Frame, Size, finalFramePtr, Size);
}
END_ERR:
if (resource) {
resource->Release();
resource = nullptr;
}
m_Duplication->ReleaseFrame();
if (mFastlane) {
m_Duplication->UnMapDesktopSurface();
}
else if (surface) {
surface->Unmap();
}
if (readable) {
readable->Release();
readable = nullptr;
}
if (texture) {
texture->Release();
texture = nullptr;
}
if (surface) {
surface->Release();
surface = nullptr;
}
return SUCCEEDED(hr);
}
bool DesktopCapture::InitBitBlt()
{
UnInitBitBlt();
if (CapDstType::Window == m_CapDstType
|| CapDstType::WindowContent == m_CapDstType) {
RECT rect;
if (IS_NULL(m_HWindow)) {
LogFun(MessageType::Error, "窗口句柄为空");
goto END_ERR;
}
if (CapDstType::Window == m_CapDstType) {
if (!GetWindowRect(m_HWindow, &rect)) {
LogFun(MessageType::Error, "获取窗口宽高失败");
goto END_ERR;
}
}
else {
if (!GetClientRect(m_HWindow, &rect)) {
LogFun(MessageType::Error, "获取窗口宽高失败");
goto END_ERR;
}
}
m_ScreenW = rect.right - rect.left;
m_ScreenH = rect.bottom - rect.top;
m_ScreenX = rect.left;
m_ScreenY = rect.top;
// 获取窗口所在的显示屏句柄
HMONITOR monitor = MonitorFromWindow(m_HWindow, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX infoEx;
memset(&infoEx, 0, sizeof(infoEx));
infoEx.cbSize = sizeof(infoEx);
if (!GetMonitorInfo(monitor, &infoEx)) {
LogFun(MessageType::Error, "获取显示屏信息失败");
goto END_ERR;
}
// 获取监视器物理宽高
DEVMODE dm;
dm.dmSize = sizeof(dm);
dm.dmDriverExtra = 0;
EnumDisplaySettings(infoEx.szDevice, ENUM_CURRENT_SETTINGS, &dm);
m_ScreenZoom = dm.dmPelsWidth * 1.0 / (infoEx.rcMonitor.right - infoEx.rcMonitor.left);
m_ScreenW = static_cast<int>(m_ScreenW * m_ScreenZoom);
m_ScreenH = static_cast<int>(m_ScreenH * m_ScreenZoom);
m_ScreenDC = GetWindowDC(m_HWindow);
//如果没找到我们想要的屏幕,那就失败
if (IS_NULL(m_ScreenDC)) {
LogFun(MessageType::Error, "未能找到指定程序窗口");
goto END_ERR;
}
}
else if (CapDstType::Screen == m_CapDstType) {
m_SeachStart = false;
m_SeachNum = 0;
while (EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)this));
//如果没找到我们想要的屏幕,那就失败
if (IS_NULL(m_ScreenDC)) {
LogFun(MessageType::Error, "未能找到指定屏幕");
goto END_ERR;
}
}
else {
LogFun(MessageType::Error, "未知参数引入");
assert(false);
goto END_ERR;
}
m_DataSize = 4l * m_ScreenW * m_ScreenH;
m_CompatibleDC = CreateCompatibleDC(/*m_ScreenDC*/NULL);//使用了DC后,会导致看不到普通指针等指针图标没有了的现象
m_HBitmap = CreateCompatibleBitmap(m_ScreenDC, m_ScreenW, m_ScreenH);
if (IS_NULL(m_CompatibleDC) || IS_NULL(m_HBitmap)) {
LogFun(MessageType::Error, "申请DC或HBitmap失败");
goto END_ERR;
}
SelectObject(m_CompatibleDC, m_HBitmap);
goto END_FIN;
END_ERR:
UnInitBitBlt();
return false;
END_FIN:
return true;
}
void DesktopCapture::UnInitBitBlt()
{
if (m_HBitmap) {
DeleteObject(m_HBitmap);
m_HBitmap = nullptr;
}
if (m_CompatibleDC) {
DeleteDC(m_CompatibleDC);
m_CompatibleDC = nullptr;
}
if (m_ScreenDC) {
DeleteDC(m_ScreenDC);
m_ScreenDC = nullptr;
}
}
bool DesktopCapture::InitDXGI()
{
UnInitDXGI();
HRESULT hr;
if (CapDstType::Screen == m_CapDstType) {
int adpaterNum = m_ScreenNum;
if (adpaterNum > 0)
--adpaterNum;
hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&m_Factory);
if (FAILED(hr) || IS_NULL(m_Factory)) {
LogFun(MessageType::Error, ("无法创建DXGI工厂 错误代码:" + std::to_string(hr)).c_str());
goto END_ERR;
}
while (S_OK == m_Factory->EnumAdapters1(adpaterNum, &m_Adapter) && m_Adapter) {
int outputNum = 0;
while (S_OK == m_Adapter->EnumOutputs(outputNum, &m_Output) && m_Output) {
hr = m_Output->GetDesc(&m_OutPutDesc);
if (SUCCEEDED(hr)) {
bool is_primary = (m_OutPutDesc.DesktopCoordinates.left == 0 && m_OutPutDesc.DesktopCoordinates.top == 0);
//如果我在寻找默认屏幕 或者 指定屏幕
if ((0 == m_ScreenNum && is_primary) || (adpaterNum == m_ScreenNum - 1)) {
m_Output->QueryInterface(IID_IDXGIOutput1, (void**)&m_Inner);
if (FAILED(hr) || IS_NULL(m_Inner)) {
LogFun(MessageType::Error, ("获取inner失败 错误代码:" + std::to_string(hr)).c_str());
if (m_Inner) {
m_Inner->Release();
m_Inner = nullptr;
}
if (m_Output) {
m_Output->Release();
m_Output = nullptr;
}
++outputNum;
continue;
}
m_Adapter->AddRef();
goto END_FIN;
}
}
else {
LogFun(MessageType::Error, ("获取desc失败 错误代码:" + std::to_string(hr)).c_str());
}
if (m_Output) {
m_Output->Release();
m_Output = nullptr;
}
++outputNum;
}
if (m_Adapter) {
m_Adapter->Release();
m_Adapter = nullptr;
}
++adpaterNum;
//如果指定屏幕打开失败
if (m_ScreenNum > 0) {
LogFun(MessageType::Error, "指定屏幕获取失败");
goto END_ERR;
}
}
LogFun(MessageType::Error, "无法创建任何adapte");
goto END_ERR;
}
else {
LogFun(MessageType::Error, "暂时不支持基于DXGI的程序窗口捕捉");
return false;
}
END_ERR:
UnInitDXGI();
return false;
END_FIN:
m_ScreenX = m_OutPutDesc.DesktopCoordinates.left;
m_ScreenY = m_OutPutDesc.DesktopCoordinates.top;
m_ScreenW = m_OutPutDesc.DesktopCoordinates.right - m_OutPutDesc.DesktopCoordinates.left;
m_ScreenH = m_OutPutDesc.DesktopCoordinates.bottom - m_OutPutDesc.DesktopCoordinates.top;
m_DataSize = 4l * m_ScreenW * m_ScreenH;
//开始创建设备
hr = D3D11CreateDevice(
m_Adapter,
D3D_DRIVER_TYPE_UNKNOWN,
nullptr, // No software rasterizer.
0, // No m_Device flags.
nullptr, // Feature levels.
0, // Feature levels' length.
D3D11_SDK_VERSION,
&m_Device,
nullptr,
&m_Context
);
if (FAILED(hr) || IS_NULL(m_Device) || IS_NULL(m_Context)) {
LogFun(MessageType::Error, "无法创建任何device和context");
goto END_ERR;
}
hr = m_Inner->DuplicateOutput(m_Device, &m_Duplication);
if (FAILED(hr) || IS_NULL(m_Duplication)) {
LogFun(MessageType::Error, "无法创建任何duplication");
goto END_ERR;
}
m_Duplication->GetDesc(&m_OutPutlDesc);
mFastlane = m_OutPutlDesc.DesktopImageInSystemMemory == TRUE;
return true;
}
void DesktopCapture::UnInitDXGI()
{
if (m_Duplication) {
m_Duplication->ReleaseFrame();
if (mFastlane) {
m_Duplication->UnMapDesktopSurface();
}
m_Duplication->Release();
m_Duplication = nullptr;
}
if (m_Context) {
m_Context->Release();
m_Context = nullptr;
}
if (m_Device) {
m_Device->Release();
m_Device = nullptr;
}
if (m_Inner) {
m_Inner->Release();
m_Inner = nullptr;
}
if (m_Output) {
m_Output->Release();
m_Output = nullptr;
}
if (m_Adapter) {
m_Adapter->Release();
m_Adapter = nullptr;
}
if (m_Factory) {
m_Factory->Release();
m_Factory = nullptr;
}
}
BOOL DesktopCapture::MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
DesktopCapture* thisPtr = (DesktopCapture*)dwData;
MONITORINFOEX infoEx;
memset(&infoEx, 0, sizeof(infoEx));
infoEx.cbSize = sizeof(infoEx);
if (!GetMonitorInfo(hMonitor, &infoEx)) {
LogFun(MessageType::Error, "获取显示屏信息失败");
return false;
}
if (!thisPtr->m_SeachStart) {//如果搜索还没开始,那么寻找主屏幕
if (MONITORINFOF_PRIMARY == infoEx.dwFlags) {
thisPtr->m_SeachStart = true;
}
else {
return true;
}
}
//来到这里意味着搜索已经开始了
//如果这里是主屏
if (infoEx.dwFlags == MONITORINFOF_PRIMARY) {
//如果我要获取的是主屏
if (0 == thisPtr->m_ScreenNum) {
goto END_FIN;
}
//如果循环回来了主屏,仍找不到指定屏幕
if (thisPtr->m_SeachNum > 0) {
return false;
}
}
//如果我要获取的是指定屏幕,且这里就是指定屏幕
if (++thisPtr->m_SeachNum == thisPtr->m_ScreenNum) {
goto END_FIN;
}
//继续寻找
return true;
END_FIN:
//获取屏幕DC
thisPtr->m_ScreenDC = CreateDC(L"DISPLAY", infoEx.szDevice, NULL, NULL);
// 获取监视器物理宽高
DEVMODE dm;
dm.dmSize = sizeof(dm);
dm.dmDriverExtra = 0;
EnumDisplaySettings(infoEx.szDevice, ENUM_CURRENT_SETTINGS, &dm);
thisPtr->m_ScreenW = dm.dmPelsWidth;
thisPtr->m_ScreenH = dm.dmPelsHeight;
thisPtr->m_ScreenX = infoEx.rcMonitor.left;
thisPtr->m_ScreenY = infoEx.rcMonitor.top;
thisPtr->m_ScreenZoom = thisPtr->m_ScreenW * 1.0 / (infoEx.rcMonitor.right - infoEx.rcMonitor.left);
return false;
}