admin管理员组

文章数量:1602103

目录

一、DXGI流程

1、官方资料

2、官方示例

3、采集桌面流程

1)、获得D3D设备 

2)、获得DXGI设备 

3)、获取DXGI Adapter

4)、获取DXGI Output

5)、获取DXGI Output1

6)、获取Output Duplication

7)、获取Output Desc

8)、获取桌面图像

9)、创建纹理

10)、拷贝图像数据


一、DXGI流程

DXGI全称是Microsoft DirectX Graphics Infrastructure,即微软图形设备基础架构。

它的工作,就是为图形库提供底层的硬件设备的接口支持。

正是由于它的存在,也使得不同图形库之间能进行交互,因为在DXGI中,资源的接口是IDXGIResource,它可以转换成上层的图形库的接口,如D3D的纹理。

本文章,主要是介绍使用DXGI 采集桌面,比起传统的GDI方式,要强大很多。

1、官方资料

DXGI

Desktop Duplication API - Win32 apps | Microsoft Docs

2、官方示例

Windows-classic-samples/Samples/DXGIDesktopDuplication at main · microsoft/Windows-classic-samples · GitHub

也可从这里下载:https://download.csdn/download/shuilan0066/86260425

不过这个例子很复杂,

下面这个例子简单些,从github上下载来,原地址忘了

https://download.csdn/download/shuilan0066/86260562

3、采集桌面流程

最关键的是 要获取 IDXGIOutputDuplication 接口,然后通过这个接口获得桌面数据

1)、获得D3D设备 

typedef struct _DX_RESOURCES
{
	ID3D11Device* Device;
	ID3D11DeviceContext* Context;
	ID3D11VertexShader* VertexShader;
	ID3D11PixelShader* PixelShader;
	ID3D11InputLayout* InputLayout;
	ID3D11SamplerState* SamplerLinear;
} DX_RESOURCES;

DUPL_RETURN  InitializeDx()
{

	HRESULT hr = S_OK;

	// Driver types supported
	D3D_DRIVER_TYPE DriverTypes[] =
	{
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE,
	};
	UINT NumDriverTypes = ARRAYSIZE(DriverTypes);

	// Feature levels supported
	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;

	// Create device
	for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
	{
		hr = D3D11CreateDevice(nullptr, DriverTypes[DriverTypeIndex], nullptr, 0, FeatureLevels, NumFeatureLevels,
			D3D11_SDK_VERSION, &m_DxRes->Device, &FeatureLevel, &m_DxRes->Context);
		if (SUCCEEDED(hr))
		{
			// Device creation success, no need to loop anymore
			break;
		}
	}
	if (FAILED(hr))
	{

		return ProcessFailure(nullptr, L"Failed to create device in InitializeDx", hr);
	}

	return DUPL_RETURN_SUCCESS;
}
		DX_RESOURCES *m_DxRes;

	m_DxRes = new (std::nothrow) DX_RESOURCES;
	RtlZeroMemory(m_DxRes, sizeof(DX_RESOURCES));
	DUPL_RETURN Ret = InitializeDx(); 
	if (Ret != DUPL_RETURN_SUCCESS)
	{
		fprintf_s(log_file, "DX_RESOURCES couldn't be initialized.");
		return Ret;
	}

2)、获得DXGI设备 

    // Get DXGI device
    IDXGIDevice* DxgiDevice = nullptr;
    HRESULT hr = m_DxRes->Device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&DxgiDevice));
    if (FAILED(hr))
    {
        return ProcessFailure(nullptr, L"Failed to QI for DXGI Device", hr);
    }

3)、获取DXGI Adapter

 Adapter可以理解为我们显卡的抽象层。当我们创建D3D设备时,其实参数中已经指定好了哪块显卡了,我填的NULL,而且指定是硬件类型的,所以它会为我们指定一块默认的(当然,我就一块显卡)。除此之外,还可以通过获取IDXGIFactory对象,然后调用EnumAdapters方法来获取某个Adapter。代码如下

    // Get DXGI adapter
    IDXGIAdapter* DxgiAdapter = nullptr;
    hr = DxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&DxgiAdapter));
    DxgiDevice->Release();
    DxgiDevice = nullptr;
    if (FAILED(hr))
    {
        return ProcessFailure(m_DxRes->Device, L"Failed to get parent DXGI Adapter", hr, SystemTransitionsExpectedErrors);
    }

4)、获取DXGI Output

  指定好显卡之后,我们就要指定它的输出了。我们知道,有时候我们可能不止使用一块显示器,显然,显卡是支持多显示设备进行输出的,因此我们要指定好哪一个输出设备,我们需要调用IDXGIAdapter里的EnumOutputs方法,我就一块显示器,第一个参数直接填0就完事了。

    // Get output
    IDXGIOutput* DxgiOutput = nullptr;
    hr = DxgiAdapter->EnumOutputs(0, &DxgiOutput);
    DxgiAdapter->Release();
    DxgiAdapter = nullptr;
    if (FAILED(hr))
    {
        return ProcessFailure(m_DxRes->Device, L"Failed to get specified output in DUPLICATIONMANAGER", hr, EnumOutputsExpectedErrors);
    }

5)、获取DXGI Output1

这时看到这个标题的读者可能又要问了,output1是什么鬼,刚才不是已经获取了output了么。当然,output1也并不是代表第一个显示器,它可以说是output的扩展,它是在DXGI 1.2之后推出的,里面包含了我们Desktop Duplication API接口。可能微软为了与之前的接口兼容,让我们先获取output,然后再通过output来获取到output1。获取DXGI output1的代码如下:

    DxgiOutput->GetDesc(&m_OutputDesc);

    // QI for Output 1
    IDXGIOutput1* DxgiOutput1 = nullptr;
    hr = DxgiOutput->QueryInterface(__uuidof(DxgiOutput1), reinterpret_cast<void**>(&DxgiOutput1));
    DxgiOutput->Release();
    DxgiOutput = nullptr;
    if (FAILED(hr))
    {
        return ProcessFailure(nullptr, L"Failed to QI for DxgiOutput1 in DUPLICATIONMANAGER", hr);
    }

6)、获取Output Duplication

  在我们刚才获取到的output1中,就可以调用DuplicateOutput方法来获取Output Duplication,然后我们就可以通过它来采集桌面图像了

    IDXGIOutputDuplication* m_DeskDupl;


    // Create desktop duplication
    hr = DxgiOutput1->DuplicateOutput(m_DxRes->Device, &m_DeskDupl);
    DxgiOutput1->Release();
    DxgiOutput1 = nullptr;
    if (FAILED(hr))
    {
        if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)
        {
            MessageBoxW(nullptr, L"There is already the maximum number of applications using the Desktop Duplication API running, please close one of those applications and then try again.", L"Error", MB_OK);
            return DUPL_RETURN_ERROR_UNEXPECTED;
        }
        return ProcessFailure(m_DxRes->Device, L"Failed to get duplicate output in DUPLICATIONMANAGER", hr, CreateDuplicationExpectedErrors);
    }

7)、获取Output Desc

 在采集桌面前,我们需要获得显示设备相关的信息,这样才方便我们后续去做编码和渲染等工作,如显示器的宽高,刷新率,像素格式,扫描方式,缩放格式等等。调用GetDesc方法,我们可以获得与这些信息有关的一个结构体对象,便可以通过它来读取,代码如下:

	DXGI_OUTDUPL_DESC lOutputDuplDesc;
	m_DeskDupl->GetDesc(&lOutputDuplDesc);

8)、获取桌面图像

到了这步,我们就可以获取桌面图像了,直接调用AcquireNextFrame即可,代码如下:

    IDXGIResource* DesktopResource = nullptr;
    DXGI_OUTDUPL_FRAME_INFO FrameInfo;

    // Get new frame
    HRESULT hr = m_DeskDupl->AcquireNextFrame(0, &FrameInfo, &DesktopResource);
    if (hr == DXGI_ERROR_WAIT_TIMEOUT)
    {
        return DUPL_RETURN_SUCCESS;
    }

    if (FAILED(hr))
    {
        return ProcessFailure(m_DxRes->Device, L"Failed to acquire next frame in DUPLICATIONMANAGER", hr, FrameInfoExpectedErrors);
    }

IDXGIResource便是我们的图像了。我们并不能直接访问里面的数据,这些数据是存在于显存中的。

对于英伟达的硬编码,肯定是在显卡中的,所以理论上我们可以直接将他扔给它来帮我们做编码了。然而,目前英伟达并不支持IDXGIResource形式的图像输入,但支持DirectX 的纹理输入(dx版本最低是9,最高是11,12在官方文档中是不支持的),因此,我们需要将其转成dx纹理,才能扔进去进行编码。

9)、创建纹理


	D3D11_TEXTURE2D_DESC desc; 
	DXGI_OUTDUPL_DESC lOutputDuplDesc;
	m_DeskDupl->GetDesc(&lOutputDuplDesc);
	desc.Width = lOutputDuplDesc.ModeDesc.Width;
	desc.Height = lOutputDuplDesc.ModeDesc.Height;
	desc.Format = lOutputDuplDesc.ModeDesc.Format;
	desc.ArraySize = 1;
	desc.BindFlags = 0;
	desc.MiscFlags = 0;
	desc.SampleDesc.Count = 1;
	desc.SampleDesc.Quality = 0;
	desc.MipLevels = 1;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
	desc.Usage = D3D11_USAGE_STAGING;

	hr = m_DxRes->Device->CreateTexture2D(&desc, NULL, &m_DestImage);

	if (FAILED(hr))
	{
		ProcessFailure(nullptr, L"Creating cpu accessable texture failed.", hr);
		return DUPL_RETURN_ERROR_UNEXPECTED;
	}

	if (m_DestImage == nullptr)
	{
		ProcessFailure(nullptr, L"Creating cpu accessable texture failed.", hr);
		return DUPL_RETURN_ERROR_UNEXPECTED;
	}

  ID3D11Texture2D* m_AcquiredDesktopImage;


    // If still holding old frame, destroy it
    if (m_AcquiredDesktopImage)
    {
        m_AcquiredDesktopImage->Release();
        m_AcquiredDesktopImage = nullptr;
    }

    // QI for IDXGIResource
    hr = DesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&m_AcquiredDesktopImage));
    DesktopResource->Release();
    DesktopResource = nullptr;
    if (FAILED(hr))
    {
        return ProcessFailure(nullptr, L"Failed to QI for ID3D11Texture2D from acquired IDXGIResource in DUPLICATIONMANAGER", hr);
    }

10)、拷贝图像数据

void  CopyImage(BYTE* ImageData)
{
	m_DxRes->Context->CopyResource(m_DestImage, m_AcquiredDesktopImage);
	D3D11_MAPPED_SUBRESOURCE resource;
	UINT subresource = D3D11CalcSubresource(0, 0, 0);
	m_DxRes->Context->Map(m_DestImage, subresource, D3D11_MAP_READ, 0, &resource);

	BYTE* sptr = reinterpret_cast<BYTE*>(resource.pData);

	//Store Image Pitch
	m_ImagePitch = resource.RowPitch;

	int height = GetImageHeight();
	memcpy_s(ImageData, resource.RowPitch*height, sptr, resource.RowPitch*height);

	m_DxRes->Context->Unmap(m_DestImage, subresource);
	DoneWithFrame();
}

参考资料:

Windows直播软件开发——DXGI - 掘金

【技术分享】Windows桌面端录屏采集实现教程

使用 DirectX 设备资源 - Win32 apps | Microsoft Docs

本文标签: 流程方式DXGI