DXGI截取桌面圖像
使用DXGI截取桌面圖像效率高,cpu占用低,是抓取桌面圖像的好方法(只支持win8及以上)票堵。
使用DXGI截取桌面圖像主要分為下面三個步驟。
創(chuàng)建
ID3D11Device
和ID3D11DeviceContext
對象逮栅。獲取
IDXGIOutputDuplication
對象悴势。調(diào)用
AcquireNextFrame
函數(shù)獲取桌面紋理。
頭文件與庫
請在源代碼文件添加如下頭文件與庫措伐。
#include <d3d11.h>
#include <dxgi1_2.h>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
定義桌面捕獲類
// DXGIDuplicator.h
#include <d3d11.h>
#include <dxgi1_2.h>
#include <string>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
class DXGIDuplicator
{
public:
DXGIDuplicator();
~DXGIDuplicator();
bool InitD3D11Device();
bool InitDuplication();
bool GetDesktopFrame(ID3D11Texture2D** texture);
// 友元函數(shù)特纤,在main函數(shù)里面會用到,需要訪問其私有成員
friend void SaveDesktopImage(std::string filename, ID3D11Texture2D* texture, DXGIDuplicator* duplicator);
private:
ID3D11Device* device_ = nullptr;
ID3D11DeviceContext* deviceContext_ = nullptr;
IDXGIOutputDuplication* duplication_ = nullptr;
};
類中定義的方法對應(yīng)獲取桌面圖像的三個步驟废士,最終的圖像數(shù)據(jù)保存在texture中叫潦,注意這是一個二級指針。AcquireNextFrame
方法來自于duplication_
對象官硝,所以調(diào)用一系列的函數(shù)是為了得到duplication_
對象矗蕊。
桌面捕獲類實現(xiàn)
// DXGIDuplicator.cpp
#include "DXGIDuplicator.h"
// 構(gòu)造函數(shù),里面什么也不做
DXGIDuplicator::DXGIDuplicator()
{
}
// 析構(gòu)函數(shù)氢架,釋放相關(guān)對象
DXGIDuplicator::~DXGIDuplicator()
{
if (duplication_)
{
duplication_->Release();
}
if (device_)
{
device_->Release();
}
if (deviceContext_)
{
deviceContext_->Release();
}
}
InitD3D11Device
實現(xiàn)函數(shù)InitD3D11Device
傻咖。一般是固定步驟,只需要記住D3D11CreateDevice這個函數(shù)即可岖研,當然也有別的方法獲取ID3D11Device和ID3D11DeviceContext卿操,感興趣的可以搜搜。
bool DXGIDuplicator::InitD3D11Device()
{
D3D_DRIVER_TYPE DriverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
D3D_FEATURE_LEVEL FeatureLevels[] =
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_1
};
UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);
D3D_FEATURE_LEVEL FeatureLevel;
for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
{
HRESULT hr = D3D11CreateDevice(
nullptr,
DriverTypes[DriverTypeIndex],
nullptr, 0,
FeatureLevels,
NumFeatureLevels,
D3D11_SDK_VERSION,
&device_,
&FeatureLevel,
&deviceContext_);
if (SUCCEEDED(hr))
{
break;
}
}
if (device_ == nullptr || deviceContext_ == nullptr)
{
return false;
}
return true;
}
InitDuplication
實現(xiàn)函數(shù)InitDuplication
孙援。這里又分為好幾個步驟害淤,如下:
獲取IDXGIDevice對象
獲取IDXGIAdapter對象
獲取IDXGIOutput對象
獲取IDXGIOutput1對象
獲得IDXGIOutputDuplication對象
從下圖中可知一個adapter可對應(yīng)多個output,所以代碼中使用EnumOutputs
來枚舉可用的輸出拓售。其實adapter也有可能有多個窥摄,這里暫不考慮。
圖中的IDXGIOutput1础淤、IDXGIOutput2等與代碼中的IDXGIOutput1對象并無關(guān)系崭放。圖中數(shù)字只表示輸出口的個數(shù)哨苛,而代碼中代表了版本。
bool DXGIDuplicator::InitDuplication()
{
HRESULT hr = S_OK;
IDXGIDevice* dxgiDevice = nullptr;
hr = device_->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice));
if (FAILED(hr))
{
return false;
}
IDXGIAdapter* dxgiAdapter = nullptr;
hr = dxgiDevice->GetAdapter(&dxgiAdapter);
dxgiDevice->Release();
if (FAILED(hr))
{
return false;
}
UINT output = 0;
IDXGIOutput* dxgiOutput = nullptr;
while (true)
{
hr = dxgiAdapter->EnumOutputs(output++, &dxgiOutput);
if (hr == DXGI_ERROR_NOT_FOUND)
{
return false;
}
else
{
DXGI_OUTPUT_DESC desc;
dxgiOutput->GetDesc(&desc);
int width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
int height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;
break;
}
}
dxgiAdapter->Release();
IDXGIOutput1* dxgiOutput1 = nullptr;
hr = dxgiOutput->QueryInterface(__uuidof(IDXGIOutput1), reinterpret_cast<void**>(&dxgiOutput1));
dxgiOutput->Release();
if (FAILED(hr))
{
return false;
}
hr = dxgiOutput1->DuplicateOutput(device_, &duplication_);
dxgiOutput1->Release();
if (FAILED(hr))
{
return false;
}
return true;
}
獲取桌面圖像
獲取桌面圖像后還不能直接使用币砂,因為圖像還在顯存中建峭,需要拷貝到內(nèi)存里面才可以直接讀寫。
bool DXGIDuplicator::GetDesktopFrame(ID3D11Texture2D** texture)
{
HRESULT hr = S_OK;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource* resource = nullptr;
ID3D11Texture2D* acquireFrame = nullptr;
hr = duplication_->AcquireNextFrame(0, &frameInfo, &resource);
if (FAILED(hr))
{
if (hr == DXGI_ERROR_WAIT_TIMEOUT)
{
return true;
}
else
{
return false;
}
}
hr = resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&acquireFrame));
resource->Release();
if (FAILED(hr))
{
return false;
}
D3D11_TEXTURE2D_DESC desc;
acquireFrame->GetDesc(&desc);
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.BindFlags = 0;
desc.MiscFlags = 0;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.SampleDesc.Count = 1;
device_->CreateTexture2D(&desc, NULL, texture);
if (texture && *texture)
{
deviceContext_->CopyResource(*texture, acquireFrame);
}
acquireFrame->Release();
hr = duplication_->ReleaseFrame();
if (FAILED(hr))
{
return false;
}
return true;
}
主函數(shù)實現(xiàn)
// main.cpp
#include <iostream>
#include <string>
#include "DXGIDuplicator.h"
void SaveBmp(std::string filename, const uint8_t* data, int width, int height)
{
HANDLE hFile = CreateFileA(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == NULL)
{
return;
}
// 已寫入字節(jié)數(shù)
DWORD bytesWritten = 0;
// 位圖大小决摧,顏色默認為32位即rgba
int bmpSize = width * height * 4;
// 文件頭
BITMAPFILEHEADER bmpHeader;
// 文件總大小 = 文件頭 + 位圖信息頭 + 位圖數(shù)據(jù)
bmpHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bmpSize;
// 固定
bmpHeader.bfType = 0x4D42;
// 數(shù)據(jù)偏移亿蒸,即位圖數(shù)據(jù)所在位置
bmpHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
// 保留為0
bmpHeader.bfReserved1 = 0;
// 保留為0
bmpHeader.bfReserved2 = 0;
// 寫文件頭
WriteFile(hFile, (LPSTR)&bmpHeader, sizeof(bmpHeader), &bytesWritten, NULL);
// 位圖信息頭
BITMAPINFOHEADER bmiHeader;
// 位圖信息頭大小
bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
// 位圖像素寬度
bmiHeader.biWidth = width;
// 位圖像素高度,負高度即上下翻轉(zhuǎn)
bmiHeader.biHeight = -height;
// 必須為1
bmiHeader.biPlanes = 1;
// 像素所占位數(shù)
bmiHeader.biBitCount = 32;
// 0表示不壓縮
bmiHeader.biCompression = 0;
// 位圖數(shù)據(jù)大小
bmiHeader.biSizeImage = bmpSize;
// 水平分辨率(像素/米)
bmiHeader.biXPelsPerMeter = 0;
// 垂直分辨率(像素/米)
bmiHeader.biYPelsPerMeter = 0;
// 使用的顏色蜜徽,0為使用全部顏色
bmiHeader.biClrUsed = 0;
// 重要的顏色數(shù)祝懂,0為所有顏色都重要
bmiHeader.biClrImportant = 0;
// 寫位圖信息頭
WriteFile(hFile, (LPSTR)&bmiHeader, sizeof(bmiHeader), &bytesWritten, NULL);
// 寫位圖數(shù)據(jù)
WriteFile(hFile, data, bmpSize, &bytesWritten, NULL);
CloseHandle(hFile);
}
void SaveDesktopImage(std::string filename, ID3D11Texture2D* texture2D, DXGIDuplicator* duplicator)
{
D3D11_TEXTURE2D_DESC desc;
texture2D->GetDesc(&desc);
D3D11_MAPPED_SUBRESOURCE mappedResource;
duplicator->deviceContext_->Map(texture2D, 0, D3D11_MAP_READ, 0, &mappedResource);
size_t imageSize = desc.Width * desc.Height * 4;
uint8_t* rgba = (uint8_t*)malloc(imageSize);
if (rgba == nullptr)
{
return;
}
memset(rgba, 0, imageSize);
uint8_t* pData = (uint8_t*)mappedResource.pData;
for (size_t i = 0; i < desc.Height; i++)
{
memcpy(rgba + i * desc.Width * 4, pData + i * mappedResource.RowPitch, desc.Width * 4);
}
SaveBmp(filename, rgba, desc.Width, desc.Height);
free(rgba);
}
int main()
{
DXGIDuplicator* duplicator = new DXGIDuplicator;
if (!duplicator->InitD3D11Device())
{
std::cout << "Init d3d11 device failed" << std::endl;
return 1;
}
if (!duplicator->InitDuplication())
{
std::cout << "Init duplication failed" << std::endl;
return 1;
}
ID3D11Texture2D* texture2D = nullptr;
int num = 0;
while (num < 10)
{
if (!duplicator->GetDesktopFrame(&texture2D))
{
std::cout << "Acquire frame failed" << std::endl;
return 1;
}
if (texture2D == nullptr)
{
std::cout << "Acquire frame timeout" << std::endl;
continue;
}
std::string filename = "desktop" + std::to_string(num) + ".bmp";
SaveDesktopImage(filename, texture2D, duplicator);
std::cout << filename << std::endl;
texture2D->Release();
texture2D = nullptr;
num++;
Sleep(500);
}
delete duplicator;
return 0;
}
代碼中對異常處理較為簡單,沒有細分異常情況拘鞋。
參考文獻
http://cn.voidcc.com/question/p-chbugccl-ug.html
https://docs.microsoft.com/zh-cn/windows/win32/direct3ddxgi/desktop-dup-api
http://t.zoukankan.com/wickedpriest-p-13568190.html