使用场景
win8以上系统,捕获电脑屏幕图像
优点
快速、可直接通过纹理进行硬件编码减少GPU-CPU的拷贝
流程
1、获取自己要捕获显示器的显卡以及输出设备(CreateDXGIFactory1/EnumAdapters1/EnumOutputs)
2、调用D3D11CreateDevice创建D3D11设备以及上下文
3、创建桌面重复接口(DuplicateOutput)
4、创建纹理(如果是跨进程这边可以用共享纹理GetSharedHandle/如果个人需要再CPU处理,可以创建CPU可访问发纹理CreateTexture2D)
5、通过AcquireNextFrame轮询去获取是否有桌面刷新
AcquireNextFrame函数官方说明:
HRESULT AcquireNextFrame(
[in] UINT TimeoutInMilliseconds,
[out] DXGI_OUTDUPL_FRAME_INFO *pFrameInfo,
[out] IDXGIResource **ppDesktopResource
);
参数
[in] TimeoutInMilliseconds
超时间隔(以毫秒为单位)。 此间隔指定此方法在返回给调用方之前等待新帧的时间。 如果间隔已过,并且新的桌面映像不可用,则此方法返回 。
有关超时间隔的详细信息,请参阅备注。
[out] pFrameInfo
指向内存位置的指针,该内存位置接收描述帧的计时和呈现统计信息 的DXGI_OUTDUPL_FRAME_INFO 结构。
[out] ppDesktopResource
指向一个变量的指针,该变量接收包含桌面位图的图面的 IDXGIResource 接口。
返回值
AcquireNextFrame 返回:
S_OK是否成功收到下一个桌面映像。
如果桌面重复接口无效,DXGI_ERROR_ACCESS_LOST。 当桌面上显示不同类型的图像时,桌面重复界面通常会失效。 这种情况的示例包括:
桌面交换机
模式更改
从 DWM 打开、DWM 关闭或其他全屏应用程序切换
在这种情况下,应用程序必须释放 IDXGIOutputDuplication 接口,并为新内容创建新的 IDXGIOutputDuplication 。
DXGI_ERROR_WAIT_TIMEOUT超时间隔是否在下一个桌面帧可用之前已过。
如果应用程序调用 AcquireNextFrame 而不释放上一帧,则DXGI_ERROR_INVALID_CALL。
如果 AcquireNextFrame 的参数之一不正确,E_INVALIDARG;例如,如果 pFrameInfo 为 NULL。
可能是 DXGI_ERROR 主题中描述的其他错误代码。
详细代码
CDxgiCaptureImpl.h
#pragma once
#include <d3d11.h>
#include <dxgi1_2.h>
#include <vector>
#include <string>
using namespace std;
// 显卡以及输出设备的结构体
typedef struct stDxgiAdapterOutput
{
IDXGIAdapter* pAdapter;
IDXGIOutput* pOutput;
}TDxgiAdapterOutput;
class CDxgiCaptureImpl
{
public:
CDxgiCaptureImpl(BOOL bUseShareTexture = TRUE);//使用共享纹理方式
~CDxgiCaptureImpl();
//这边目前是获取电脑默认的显示器,可能存在多个显示器,可以通过EnumDisplayDevices获取设备名称
TDxgiAdapterOutput get(wstring strDeviceName );
//DXGI初始化
BOOL InitDxgiCapture(TDxgiAdapterOutput);
//创建纹理
BOOL CreateD3D11Texture();
//屏幕捕获函数
BOOL CaptureScreen(ID3D11Texture2D ** pTex2D, void **pVideoData,INT &nWidthPitch);
//捕获一帧结束调用
void ReleaseFrame();
//获取屏幕宽度
int getWidth();
//屏幕高度
int getHeight();
//是否是共享纹理
BOOL IsShareTexturCap();
//回收DXGI捕获的资源
void UnInitDxgiCapture();
private:
ID3D11Device *m_pD3D11Device;
ID3D11DeviceContext *m_pD3D11Context;
IDXGIOutputDuplication *m_pDeskDupl;
IDXGIResource * m_pDesktopResource;
ID3D11Texture2D *m_pShareTexture;
ID3D11Texture2D *m_pCpuTexture;
IDXGIOutput1 *m_pDxgiOutput;
IDXGISurface *m_pDxgiSurf;
BOOL m_bUseShareTexture;
HANDLE m_hShareHanle;int m_nScreenWidth;
int m_nScreenHeight;
};
CDxgiCaptureImpl.cpp
#include "pch.h"
#include "CDxgiCaptureImpl.h"
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#define SAFE_RELEASE(obj) if(obj) obj->Release();obj = NULL;
CDxgiCaptureImpl::CDxgiCaptureImpl(BOOL bUseShareTexture)
{
m_bUseShareTexture = bUseShareTexture;
}
CDxgiCaptureImpl::~CDxgiCaptureImpl()
{
}TDxgiAdapterOutput CDxgiCaptureImpl::get(wstring strDeviceName)
{
IDXGIFactory1* pFactory1 = NULL;
TDxgiAdapterOutput dxgiOutput = { NULL };do
{
bool bFind = false;
int i = 0;
HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory1));
if (FAILED(hr))
{
break;
}for (;;++i)
{
IDXGIAdapter1* pAdapter1 = nullptr;
hr = pFactory1->EnumAdapters1(i, &pAdapter1);//枚举显卡设备
if (hr == DXGI_ERROR_NOT_FOUND)//枚举结束
{
break;
}if (FAILED(hr))//枚举失败退出
{
break;
}UINT j = 0;
for (;; ++j)
{
IDXGIOutput* pOutput = nullptr;
HRESULT hr = pAdapter1->EnumOutputs(j, &pOutput);
if (hr == DXGI_ERROR_NOT_FOUND)//枚举结束
{
break;
}if (FAILED(hr))//枚举失败
{
SAFE_RELEASE(pAdapter1)
break;
}DXGI_OUTPUT_DESC outdesc;
pOutput->GetDesc(&outdesc);
if ((strDeviceName.length() != 0) && strDeviceName.compare(outdesc.DeviceName) != 0)//判断要捕获的显示器名称已 枚举的是否一致
{
SAFE_RELEASE(pOutput)
}
else
{
dxgiOutput.pAdapter = pAdapter1;
dxgiOutput.pOutput = pOutput;
bFind = true;
break;
}
}if (bFind)//已经找到匹配的显示器设备
{
break;
}
else
{
SAFE_RELEASE(pAdapter1)
}
}
SAFE_RELEASE(pFactory1)
} while (false);
return dxgiOutput;
}BOOL CDxgiCaptureImpl::InitDxgiCapture(TDxgiAdapterOutput dxgiobj)
{
BOOL bRet = FALSE;
do
{
HRESULT hr = 0;
D3D_FEATURE_LEVEL FeatureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
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 < NumFeatureLevels; ++DriverTypeIndex)
{
FeatureLevel = FeatureLevels[DriverTypeIndex];
hr = D3D11CreateDevice(dxgiobj.pAdapter,
D3D_DRIVER_TYPE_UNKNOWN,
NULL,
0,
0,
0,
D3D11_SDK_VERSION,
&m_pD3D11Device,
&FeatureLevel,
&m_pD3D11Context);
if (SUCCEEDED(hr))
break;
}if (FAILED(hr))
{
break;
}if (FAILED(dxgiobj.pOutput->QueryInterface(__uuidof(IDXGIOutput1), reinterpret_cast<void**>(&m_pDxgiOutput))))
{
break;
}
SAFE_RELEASE(dxgiobj.pOutput);//对象用到提前释放hr = m_pDxgiOutput->DuplicateOutput(m_pD3D11Device, &m_pDeskDupl);//获取桌面重复接口
if (FAILED(hr))
{
SAFE_RELEASE(m_pDxgiOutput);
break;
}if (!CreateD3D11Texture())
{
SAFE_RELEASE(m_pDxgiOutput);
break;
}
bRet = TRUE;
} while (false);
SAFE_RELEASE(dxgiobj.pAdapter);
SAFE_RELEASE(dxgiobj.pOutput);
return bRet;
}BOOL CDxgiCaptureImpl::CreateD3D11Texture()
{
DXGI_OUTDUPL_DESC outdupldesc;
m_pDeskDupl->GetDesc(&outdupldesc);
D3D11_TEXTURE2D_DESC desc = {0};
m_nScreenHeight = desc.Height = outdupldesc.ModeDesc.Height;
desc.Format = outdupldesc.ModeDesc.Format;
m_nScreenWidth = desc.Width = outdupldesc.ModeDesc.Width;
desc.SampleDesc.Count = 1;
desc.MipLevels = 1;
desc.ArraySize = 1;if (m_bUseShareTexture)
{
//使用共享纹理方式
IDXGIResource *pDxgiRes = NULL;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
if (FAILED(m_pD3D11Device->CreateTexture2D(&desc, NULL, &m_pShareTexture))) {
return FALSE;
}if (FAILED(m_pShareTexture->QueryInterface(__uuidof(IDXGIResource), (void **)&pDxgiRes))) {
SAFE_RELEASE(m_pShareTexture)
return FALSE;
}if (FAILED(pDxgiRes->GetSharedHandle(&m_hShareHanle))) {
SAFE_RELEASE(pDxgiRes)
SAFE_RELEASE(m_pShareTexture)
return FALSE;
}
SAFE_RELEASE(pDxgiRes)
}
else
{
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.BindFlags = 0;
desc.MiscFlags = 0;
if (FAILED(m_pD3D11Device->CreateTexture2D(&desc, NULL, &m_pCpuTexture)))
return FALSE;
}
return TRUE;
}BOOL CDxgiCaptureImpl::CaptureScreen(ID3D11Texture2D ** pTex2D, void **pVideoData, INT &nWidthPitch)
{
*pVideoData = NULL;
nWidthPitch = 0;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
ZeroMemory(&frameInfo, sizeof(frameInfo));
m_pDeskDupl->ReleaseFrame();
HRESULT hr = m_pDeskDupl->AcquireNextFrame(16, &frameInfo, &m_pDesktopResource);
if (FAILED(hr))
{
switch (hr)
{
case DXGI_ERROR_WAIT_TIMEOUT:
SAFE_RELEASE(m_pDesktopResource)
return TRUE;
case DXGI_ERROR_ACCESS_LOST:
{
if (m_pDeskDupl)
{
m_pDeskDupl->ReleaseFrame();
SAFE_RELEASE(m_pDeskDupl)
}if (m_pDxgiOutput == NULL)
{
return FALSE;
}if (FAILED(m_pDxgiOutput->DuplicateOutput(m_pD3D11Device, &m_pDeskDupl)))
{
return FALSE;
}hr = m_pDeskDupl->AcquireNextFrame(16, &frameInfo, &m_pDesktopResource);
if (FAILED(hr))
{
SAFE_RELEASE(m_pDesktopResource)
m_pDeskDupl->ReleaseFrame();
if (hr == DXGI_ERROR_WAIT_TIMEOUT)
{
return TRUE;
}
return FALSE;
}
}
break;
case DXGI_ERROR_INVALID_CALL:
default:
SAFE_RELEASE(m_pDesktopResource)
return FALSE;
break;
}
}//可能只是屏幕鼠标样式更改,屏幕数据并未变化
if (frameInfo.AccumulatedFrames == 0 || frameInfo.LastPresentTime.QuadPart == 0)
{
SAFE_RELEASE(m_pDesktopResource)
return TRUE;
}if (FAILED(m_pDesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), (void**)pTex2D)))
{
SAFE_RELEASE(m_pDesktopResource)
return FALSE;
}if (m_bUseShareTexture)
{
m_pD3D11Context->CopyResource(m_pShareTexture, *pTex2D);
(*pTex2D)->Release();
}else
{
m_pD3D11Context->CopyResource(m_pCpuTexture, *pTex2D);
SAFE_RELEASE((*pTex2D));
if (FAILED(m_pCpuTexture->QueryInterface(__uuidof(IDXGISurface), (void**)(&m_pDxgiSurf))))
{
SAFE_RELEASE(m_pDesktopResource)
return FALSE;
}
DXGI_MAPPED_RECT mappedRect;
hr = m_pDxgiSurf->Map(&mappedRect, DXGI_MAP_READ);
if (SUCCEEDED(hr))
{
nWidthPitch = mappedRect.Pitch;
*pVideoData = mappedRect.pBits;
}
else
{
SAFE_RELEASE(m_pDxgiSurf)
}
}
SAFE_RELEASE(m_pDesktopResource)
return TRUE;
}void CDxgiCaptureImpl::ReleaseFrame()
{
if (m_pDxgiSurf)
{
m_pDxgiSurf->Unmap();
SAFE_RELEASE(m_pDxgiSurf)
}
}int CDxgiCaptureImpl::getWidth()
{
return m_nScreenWidth;
}int CDxgiCaptureImpl::getHeight()
{
return m_nScreenHeight;
}BOOL CDxgiCaptureImpl::IsShareTexturCap()
{
return m_bUseShareTexture;
}void CDxgiCaptureImpl::UnInitDxgiCapture()
{
SAFE_RELEASE(m_pDxgiSurf);
SAFE_RELEASE(m_pDxgiOutput);
SAFE_RELEASE(m_pDeskDupl);
SAFE_RELEASE(m_pD3D11Device);
SAFE_RELEASE(m_pD3D11Context);
SAFE_RELEASE(m_pShareTexture);
SAFE_RELEASE(m_pCpuTexture);
m_hShareHanle = NULL;
}
main.cpp
#include "pch.h"
#include <iostream>
#include "CDxgiCaptureImpl.h"
int main()
{
CDxgiCaptureImpl *impl = new CDxgiCaptureImpl(FALSE);
TDxgiAdapterOutput out = impl->get(L"");
BOOL bRet = impl->InitDxgiCapture(out);
if (!bRet)
{
std::cout << " InitDxgiCapture Error \n";
return -1;
}INT nWidthPicth = 0;
void *pVideoData = NULL;
ID3D11Texture2D *pVideoTexture = NULL;
while (true)
{
pVideoTexture = NULL;
pVideoData = NULL;
nWidthPicth = 0;if (impl->CaptureScreen(&pVideoTexture, &pVideoData, nWidthPicth))
{
if (pVideoTexture)//纹理捕获成功
{
std::cout << " 捕获视频数据成功,再共享纹理上,可直接去编码或者去显示\n";
}
else if (nWidthPicth > 0)//视频数据捕获成功 pVideoData就是视频RGBA数据
{
std::cout << " 捕获视频裸流数据成功 RGBA \n";
impl->ReleaseFrame();
}
else
{
std::cout << " 捕获等待超时,但并未出错,重新等待新帧到来 \n";
}
//成功捕获一帧}
else
{
std::cout << " 视频捕获出错,重新初始化捕获即可,这边直接退出 \n";
break;
}
Sleep(50);
}
impl->UnInitDxgiCapture();
return 0;
}
工程下载地址: