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

这个类被封装到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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值