【音视频】WIN8|WIN10的桌面采集技术-DXGI(一)

本篇文章是本人音视频技术文章集中的开篇,之后会持续更新创作更多关于音视频的文章。望有大佬可以交流、指点。
文章主要表现形式是以实现为主,扩展知识为辅,所以一般比较长,不过也有更多实现细节以供参考。

技术简介

DXGI(Microsoft DirectX Graphics Infrastructure)是微软提供的一种可以在win8及以上系统使用的图形设备接口。它负责枚举图形适配器、枚举显示模式、选择缓冲区格式、在进程之间(例如,在应用程序和桌面窗口管理器(DWM)之间)共享资源,以及将呈现的帧传给窗口或监视器以供显示。其直接和硬件设备进行交互,具有很高的效率和性能。
Direct3D 10、Direct3D 11和Direct3D 12等都使用DXGI技术。

使用模块(库)

本篇代码中使用到了d3d11和dxgi两个dll库。

主要流程和代码

1、加载d3d11和dxgi库,并初始化d3d设备。

int DuplicationCaptor::init(const DesktopCaptureRect& rect, const int fps)
{
   
   
	int err = ERROR_CODE_OK;
	if (m_inited) {
   
   
		return err;
	}

	do {
   
   
		m_d3d = HELPER::SystemLib::loadSystemLib("d3d11.dll");
		if (m_d3d == nullptr) {
   
   
			err = ERROR_CODE_D3D_LOAD_LIB_FAILED;
			break;
		}
		m_dxgi = HELPER::SystemLib::loadSystemLib("dxgi.dll");
		if (m_dxgi == nullptr) {
   
   
			err = ERROR_CODE_DXGI_LOAD_LIB_FAILED;
			break;
		}

		err = initD3d();
		HCMDR_ERROR_CODE_BREAK(err);

		m_timebase = {
   
    1, AV_TIME_BASE };
		m_pixelFmt = AV_PIX_FMT_BGRA;
		m_rect = rect;
		m_fps = fps;
		m_width = rect.right - rect.left;
		m_height = rect.bottom - rect.top;
		m_bufferSize = (m_width * 32 + 31) / 32 * m_height * 4;
		m_buffer = new uint8_t[m_bufferSize];

		m_inited = true;
	} while (0);

	if (err != ERROR_CODE_OK) {
   
   
		LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] msg: %s, last error: %lu", __FUNCTION__,
			HCMDR_GET_ERROR_DESC(err), GetLastError());
		cleanup();
	}

	return err;
}

init中d3d设备初始化函数:

int DuplicationCaptor::initD3d()
{
   
   
	int err = ERROR_CODE_OK;
	do {
   
   
		IDXGIAdapter* adapter = nullptr;
		err = getAdapter(&adapter);
		HCMDR_ERROR_CODE_BREAK(err);

		err = createD3dDevice(adapter, &m_d3dDevice);
		HCMDR_ERROR_CODE_BREAK(err);
	} while (0);

	return err;
}

initD3d中获取适配器函数:

int DuplicationCaptor::getAdapter(IDXGIAdapter** adapter)
{
   
   
	int err = ERROR_CODE_OK;

	do {
   
   
		std::list<IDXGIAdapter*> adapters;
		err = getAdapters(adapters);
		HCMDR_ERROR_CODE_BREAK(err);
		if (adapters.empty()) {
   
   
			err = ERROR_CODE_DXGI_FOUND_ADAPTER_FAILED;
			break;
		}

		for (std::list<IDXGIAdapter*>::iterator it = adapters.begin(); it != adapters.end(); it++) {
   
   
			IDXGIOutput* adapterOutput = nullptr;
			DXGI_ADAPTER_DESC adapterDesc = {
   
    0 };
			(*it)->GetDesc(&adapterDesc);
			for (UINT i = 0; (*it)->EnumOutputs(i, &adapterOutput) != DXGI_ERROR_NOT_FOUND; i++) {
   
   
				DXGI_OUTPUT_DESC outputDesc = {
   
    0 };
				RECT outputRect = {
   
    0 };
				HRESULT hr = adapterOutput->GetDesc(&outputDesc);
				if (FAILED(hr)) {
   
   
					continue;
				}

				outputRect = outputDesc.DesktopCoordinates;
				// The target area is within the selected area
				if (m_rect.left >= outputRect.left && m_rect.top >= outputRect.top &&
					m_rect.right <= outputRect.right && m_rect.bottom <= outputRect.bottom) {
   
   
					break;
				}
			}

			if (err == ERROR_CODE_OK) {
   
   
				*adapter = *it;
				break;
			}
		}
	} while (0);

	return err;
}

getAdapter中获取适配器列表函数:

int DuplicationCaptor::getAdapters(std::list<IDXGIAdapter*>& adapters)
{
   
   
	int err = ERROR_CODE_OK;

	do {
   
   
		DXGI_CREATE_FACTORY1 createFactory1 = (DXGI_CREATE_FACTORY1)GetProcAddress(m_dxgi, "CreateDXGIFactory1");
		if (createFactory1 == nullptr) {
   
   
			err = ERROR_CODE_DXGI_GET_PROC_FAILED;
			break;
		}

		IDXGIFactory1* factory1 = nullptr;
		HRESULT hr = createFactory1(__uuidof(IDXGIFactory1), &factory1);
		if (FAILED(hr)) {
   
   
			err = ERROR_CODE_DXGI_GET_FACTORY_FAILED;
			break;
		}

		IDXGIAdapter* adapter = nullptr;
		adapters.clear();
		for (UINT i = 0; factory1->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND; i++) {
   
   
			if (adapter != nullptr) {
   
Windows 系统中(从 Windows 7 到 Windows 10)进行桌面采集,可以使用 **GDI** 或 **Desktop Duplication API(DXGI)**。由于性能和效率的考虑,微软推荐从 Windows 8 开始使用 **Desktop Duplication API**,但该 API 在 Windows 7 上不可用。 因此,为了兼容 **Windows 7 到 Windows 10**,我们采用 **GDI(Graphics Device Interface)** 方式进行屏幕捕获。虽然 GDI 性能不如 DXGI 高效,但它在所有支持的系统上都能运行。 以下是完整的 C++ 实现代码,用于采集整个桌面并保存为位图(BMP)文件或可用于进步处理(如编码为视频流等): ```cpp #include <windows.h> #include <iostream> #include <vector> // 将 HBITMAP 转换为 BMP 文件保存(可选) bool SaveHBITMAPToFile(HBITMAP hBitmap, const char* filename) { if (!hBitmap || !filename) return false; BITMAP bmp; GetObject(hBitmap, sizeof(BITMAP), &bmp); BITMAPFILEHEADER bmfHeader; BITMAPINFOHEADER bi; bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = bmp.bmWidth; bi.biHeight = -bmp.bmHeight; // Top-down DIB bi.biPlanes = 1; bi.biBitCount = 32; bi.biCompression = BI_RGB; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; DWORD dwBmpSize = ((bmp.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmp.bmHeight; HANDLE hDIB = GlobalAlloc(GHND, dwBmpSize); if (!hDIB) return false; uint8_t* lpbitmap = (uint8_t*)GlobalLock(hDIB); HDC hdc = CreateCompatibleDC(NULL); GetDIBits(hdc, hBitmap, 0, (UINT)bmp.bmHeight, lpbitmap, (BITMAPINFO*)&bi, DIB_RGB_COLORS); DeleteDC(hdc); HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { GlobalUnlock(hDIB); GlobalFree(hDIB); return false; } DWORD dwSizeOfDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER); bmfHeader.bfSize = dwSizeOfDIB; bmfHeader.bfType = 0x4D42; // 'BM' DWORD written; WriteFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER), &written, NULL); WriteFile(hFile, &bi, sizeof(BITMAPINFOHEADER), &written, NULL); WriteFile(hFile, lpbitmap, dwBmpSize, &written, NULL); CloseHandle(hFile); GlobalUnlock(hDIB); GlobalFree(hDIB); return true; } // 捕获主显示器桌面图像 HBITMAP CaptureScreen() { HDC hdcScreen = GetDC(NULL); // 获取整个屏幕设备上下文 if (!hdcScreen) return NULL; HDC hdcMem = CreateCompatibleDC(hdcScreen); // 内存 DC int width = GetSystemMetrics(SM_CXSCREEN); int height = GetSystemMetrics(SM_CYSCREEN); HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, width, height); if (!hBitmap) { ReleaseDC(NULL, hdcScreen); DeleteDC(hdcMem); return NULL; } HGDIOBJ hOld = SelectObject(hdcMem, hBitmap); BitBlt(hdcMem, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY | CAPTUREBLT); SelectObject(hdcMem, hOld); DeleteDC(hdcMem); ReleaseDC(NULL, hdcScreen); return hBitmap; } int main() { std::cout << "正在捕获桌面..." << std::endl; HBITMAP hBitmap = CaptureScreen(); if (hBitmap) { if (SaveHBITMAPToFile(hBitmap, "screenshot.bmp")) { std::cout << "截图已保存为 screenshot.bmp" << std::endl; } else { std::cerr << "保存截图失败!" << std::endl; } DeleteObject(hBitmap); } else { std::cerr << "截图失败!" << std::endl; } return 0; } ``` --- ### ✅ 功能说明: - `CaptureScreen()`:使用 GDI 的 `GetDC(NULL)` 获取全屏 DC,创建兼容内存 DC 和位图,使用 `BitBlt` 复制屏幕内容。 - `SaveHBITMAPToFile()`:将捕获的 `HBITMAP` 保存为 `.bmp` 文件以便查看。 - 兼容性:适用于 **Windows 7、810**,无需 DirectX 支持。 - 编译方式:可用 Visual Studio 或 MinGW 编译此代码。 --- ### ⚠️ 注意事项: 1. **权限问题**:某些情况下(如 UAC 提升程序),可能无法捕获受保护的内容(如 DRM 视频)。 2. **性能限制**:GDI 抓屏速度慢于 DXGI,不适合高帧率录屏(>30fps),适合低频快照。 3. **透明窗口/特效**:`CAPTUREBLT` 标志帮助捕获部分合成效果,但仍有限。 4. **多显示器支持**:本代码仅捕获主显示器;可通过枚举显示器扩展支持。 --- ### 🔧 如何编译运行? 使用 **Visual Studio**: - 创建Win32 控制台应用程序。 - 将上述代码粘贴到 `.cpp` 文件中。 - 确保链接了必要的库(默认包含 `user32.lib`, `gdi32.lib`)。 命令行编译示例(使用 cl.exe): ```bash cl screen_capture.cpp user32.lib gdi32.lib ``` --- ### ✅ 示例输出: 运行后生成 `screenshot.bmp`,即当前桌面截图。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值