• [C++]带动画鼠标指针的多屏采集和窗口采集,基于BitBlt和DXGI


    这个类被封装到DLL中引用,专门用来多屏或窗口图像采集,可用来编码成视频流做图像传输。

    注意,如果你不想这个项目是个DLL才能运行,而是希望能放到非DLL项目中直接运行,请去掉头文件中的以下部分

    #ifdef DESKTOPCAPTURE_EXPORTS
    #define DESKTOPCAPTURE_API __declspec(dllexport)
    #else
    #define DESKTOPCAPTURE_API __declspec(dllimport)
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果你期望了解如何将带动画的鼠标指针绘制到图像上,那么请关注m_CursorFrame变量的使用。

    那么,正式开始

    首先来介绍下这个类应该如何使用,这里主要是演示了如何每30毫秒获取一张图像的效果,另外,如果你有Opencv库,那么可以解开Opencv相关的注释,可以直观地看到图像的显示效果。

    #include 
    //#include 
    //#include 
    //#include 
    //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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    可以看见,使用这个类非常简单,初始化变量后就立马就可以用一直使用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 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #pragma comment(lib, "User32.lib")
    
    #include 
    #include 
    #include 
    #pragma comment(lib, "dxgi.lib")
    #pragma comment(lib, "dxguid.lib")
    #pragma comment(lib, "d3d11.lib")
    
    #define IS_NULL(POINTER) (nullptr == (POINTER))
    
    extern "C" {
    
        /// 
        /// 桌面图像抓取类
        /// 
        class DESKTOPCAPTURE_API DesktopCapture {
    
            //类的附属类型
    
        public:
    
            /// 
            /// 消息类型
            /// 
            enum class MessageType {
                Info
                , Warn
                , Error
            };
            /// 
            /// 调试回调方法类型
            /// 
            typedef void (*LogCallBack)(MessageType Type, const char* Message, const char* FunName, int Line);
    
            /// 
            /// 采集来源类型
            /// 
            enum class CapSrcType {
                BitBlt
                , DXGI
            };
    
            /// 
            /// 采集目标类型
            /// 
            enum class CapDstType {
                Window//窗口边框与内容
                , WindowContent//窗口内容
                , Screen//屏幕
            };
    
            //类的附属方法
    
            /// 
            /// 获取对应采集类型的名称
            /// 
            /// 采集类型
            /// 采集类型对应的名称
            const char* GetNameFromCapSourceType(CapSrcType Type);
    
            //类的公共方法
        public:
    
            /// 
            /// 基于屏幕的图像采集的构造方法
            /// 
            /// 指定采集来源[0:BitBlt][1:DXGI]
            /// 录制第几个屏幕,0是主屏,1是第一个屏幕,2是第二个屏幕
            DesktopCapture(CapSrcType CapSrcType, int ScreenNum);
    
            /// 
            /// 基于窗口的图像采集的构造方法
            /// 
            /// 指定采集来源[0:BitBlt][1:DXGI]
            /// 采集指定窗口图像
            /// 是否绘制窗口边框
            DesktopCapture(CapSrcType CapSrcType, HWND HWindow, bool IsDrawFrame);
    
            /// 
            /// 默认析构
            /// 
            virtual ~DesktopCapture();
    
            /// 
            /// 获取图像帧
            /// 
            /// 获取图像帧的数据指针,需要预先分配好空间,如果为null将不写入,一个合适的值可以通过调用一次该方法从Size上获取
            /// 获取图像帧的宽
            /// 获取图像帧的高
            /// 获取图像帧的占用字节数
            /// 是否绘入指针
            /// 获取成功与否
            bool GetFrame(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor = false);
    
            /// 
            /// 清除数据缓冲
            /// 
            void Clear();
    
            /// 
            /// 设置日志回调方法
            /// 
            void SetLogCallBack(LogCallBack LogCallBack);
    
            //类的私有方法
    
        private:
    
            /// 
            /// 获取图像帧
            /// 
            /// 存储用的图像帧,需要预先分配好空间,如果为null将不写入,一个合适的值可以通过调用一次该方法从Size上获取
            /// 是否绘入指针
            /// 获取成功与否
            bool GetFrameFromBitBlt(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor = false);
    
            /// 
            /// 获取图像帧
            /// 
            /// 存储用的图像帧
            /// 是否绘入指针
            /// 获取成功与否
            bool GetFrameFromDXGI(unsigned char* Frame, int& Width, int& Height, size_t& Size, bool IsDrawCursor = false);
    
            /// 
            /// 安装BitBlt相关的变量
            /// 
            /// 安装成功与否
            bool  InitBitBlt();
    
            /// 
            /// 卸载BitBlt相关的变量
            /// 
            void UnInitBitBlt();
    
            /// 
            /// 安装DXGI相关的变量
            /// 
            /// 安装成功与否
            bool  InitDXGI();
    
            /// 
            /// 卸载DXGI相关的变量
            /// 
            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{};
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214

    本体:

    #include "pch.h"
    #include "DesktopCapture.h"
    #include 
    #include 
    
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561
    • 562
    • 563
    • 564
    • 565
    • 566
    • 567
    • 568
    • 569
    • 570
    • 571
    • 572
    • 573
    • 574
    • 575
    • 576
    • 577
    • 578
    • 579
    • 580
    • 581
    • 582
    • 583
    • 584
    • 585
    • 586
    • 587
    • 588
    • 589
    • 590
    • 591
    • 592
    • 593
    • 594
    • 595
    • 596
    • 597
    • 598
    • 599
    • 600
    • 601
    • 602
    • 603
    • 604
    • 605
    • 606
    • 607
    • 608
    • 609
    • 610
  • 相关阅读:
    【MySQL】基于Docker搭建MySQL一主二从集群
    【EI检索征稿】第五届机器学习、大数据与商务智能国际会议(MLBDBI 2023)
    如何定义版本号?
    C和指针 第15章 输入/输出函数 15.10 格式化的行I/O
    公网访问全能知识库工具AFFINE,Notion的免费开源替代
    NVIDIA 7th SkyHackathon(四)Nemo ASR 模型训练与评估
    BetterJoy蓝牙将switch转化为xbox玩游戏,例子:双人成行(俄区版)
    REDIS00_SpringBoot整合redis、RedisTemplate使用、工具类的抽取
    C#二维码的生成及标签的制作(完整版)
    JSP页面实现验证码校验
  • 原文地址:https://blog.csdn.net/qq_43655831/article/details/133875704