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

2023-10-17 11:12

本文主要是介绍[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();//BitBltstatic 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{};//已经搜索的次数//Allint m_ScreenX{};//屏幕起始Xint m_ScreenY{};//屏幕起始Yint m_ScreenW{};//屏幕宽int m_ScreenH{};//屏幕高double m_ScreenZoom{ 1.0 };//屏幕缩放//BitBltHDC m_ScreenDC{};//屏幕DCHDC m_CompatibleDC{};//抓取后存储用的DCHBITMAP m_HBitmap{};//存储用的BitMapLONG m_DataSize{};//图像数据大小CURSORINFO m_CurInfo{};ICONINFO m_IconInfo{};clock_t m_CursorClockStart{};//鼠标动画开始时的时间const int m_CursorFrame{ 18 };//鼠标动画的帧数//DXGIIDXGIFactory1* 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);}
#endiffinalFramePtr = 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://获取屏幕DCthisPtr->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;
}

这篇关于[C++]带动画鼠标指针的多屏采集和窗口采集,基于BitBlt和DXGI的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/224999

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

韦季李输入法_输入法和鼠标的深度融合

在数字化输入的新纪元,传统键盘输入方式正悄然进化。以往,面对实体键盘,我们常需目光游离于屏幕与键盘之间,以确认指尖下的精准位置。而屏幕键盘虽直观可见,却常因占据屏幕空间,迫使我们在操作与视野间做出妥协,频繁调整布局以兼顾输入与界面浏览。 幸而,韦季李输入法的横空出世,彻底颠覆了这一现状。它不仅对输入界面进行了革命性的重构,更巧妙地将鼠标这一传统外设融入其中,开创了一种前所未有的交互体验。 想象

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给