Qt中使用DXGI截取桌面图像

一、概要

DXGI(DirectX Graphics Infrastructure)图像截取是一种技术,用于从图形设备接口(Graphics Device Interface)中捕获屏幕上的图像数据。它是基于 DirectX 和 DXGI 接口的截取方法。

二、DXGI 图像截取的实现步骤

1. 获取 DXGI 设备和适配器: 首先,通过调用 CreateDXGIFactory 来创建一个 DXGI 工厂对象。然后使用工厂对象的 EnumAdapters 方法来获取系统上的适配器(显示适配器)。可以根据需求选择适配器。
2. 创建 DXGI 输出(Output)对象: 使用选定的适配器调用 EnumOutputs 方法来获取适配器上的输出(显示器)。可以选择某个输出,或者使用默认输出(通常是主显示器)。
3. 创建 DXGI 输出复制(Output Duplication)对象: 调用 IDXGIOutput1 接口的 DuplicateOutput 方法来创建一个 DXGI 输出复制对象。输出复制对象用于捕获屏幕上的图像数据。
4. 获取桌面帧: 使用输出复制对象的 AcquireNextFrame 方法来获取下一个桌面帧(Desktop Frame)。这个桌面帧包含了屏幕上的图像数据。
5. 访问桌面帧数据: 可以通过输出复制对象的 MapDesktopSurface 方法将桌面帧数据映射到内存中。然后可以使用指针来访问图像数据。
6. 释放桌面帧和输出复制对象: 使用 IDXGIOutputDuplication 接口的 ReleaseFrame 方法来释放桌面帧。最后调用 Release 方法释放输出复制对象。

三、DXGI常用接口介绍

1. D3D11CreateDevice

D3D11CreateDevice是 DirectX 11 中用于创建 D3D 设备的函数

HRESULT D3D11CreateDevice(
  IDXGIAdapter         *pAdapter,
  D3D_DRIVER_TYPE      DriverType,
  HMODULE              Software,
  UINT                 Flags,
  const D3D_FEATURE_LEVEL *pFeatureLevels,
  UINT                 FeatureLevels,
  UINT                 SDKVersion,
  ID3D11Device         **ppDevice,
  D3D_FEATURE_LEVEL    *pFeatureLevel,
  ID3D11DeviceContext  **ppImmediateContext
);
接口参数说明

pAdapter: 指向 DXGI 适配器(IDXGIAdapter)的指针,用于指定使用的图形适配器。可以为 nullptr,表示使用默认适配器。
DriverType: 指定驱动类型,如 D3D_DRIVER_TYPE_HARDWARE、D3D_DRIVER_TYPE_WARP 或 D3D_DRIVER_TYPE_REFERENCE。
Software: 指定一个可选的软件模块的句柄,用于软件驱动。一般情况下可以传入 nullptr。
Flags: 配置标志,用于配置设备创建的行为。可以是 D3D11_CREATE_DEVICE_FLAG 枚举值的组合,如D3D11_CREATE_DEVICE_DEBUG 用于启用调试模式。
pFeatureLevels: 指向 D3D 特性级别(D3D_FEATURE_LEVEL)数组的指针,指定所需的 D3D 特性级别。可以为 nullptr,表示使用默认的特性级别。
FeatureLevels: 数组 pFeatureLevels 中的特性级别个数。
SDKVersion: DirectX SDK 版本号。通常使用 D3D11_SDK_VERSION 宏。
ppDevice: 输出参数,返回创建的 D3D 设备对象的指针。
pFeatureLevel: 输出参数,返回实际创建的 D3D 设备的特性级别。
ppImmediateContext: 输出参数,返回与设备关联的即时渲染上下文(ID3D11DeviceContext)的指针。

D3D_DRIVER_TYPE常见类型

D3D_DRIVER_TYPE 枚举类型定义了在 Direct3D 中可用的驱动程序类型。下面是 D3D_DRIVER_TYPE 中所有枚举值的含义:

1.D3D_DRIVER_TYPE_HARDWARE: 表示使用硬件加速的图形驱动程序。它指示 Direct3D 使用可用的硬件加速功能来执行图形渲染操作。通常用于具有独立显卡的系统。
2.D3D_DRIVER_TYPE_WARP: 表示使用 WARP(Windows Advanced Rasterization Platform)软件渲染器进行图形渲染。WARP 是一种在不具备独立显卡的系统上进行软件渲染的选择。它可以提供基本的图形渲染功能,但性能相对较低。
3.D3D_DRIVER_TYPE_REFERENCE: 表示使用参考软件渲染器进行图形渲染。参考渲染器是一种纯软件实现的渲染器,不依赖于硬件加速,适用于调试和开发目的。它通常用于验证和测试渲染代码,但性能较低。
4.D3D_DRIVER_TYPE_NULL: 表示使用空驱动程序类型。它不执行任何图形渲染操作,主要用于模拟或测试环境中。
5.D3D_DRIVER_TYPE_SOFTWARE: 表示使用软件驱动程序进行图形渲染。这是一种在没有硬件加速的系统上进行软件渲染的选择。它通常性能较低,仅用于特殊情况。
以上是 D3D_DRIVER_TYPE 中的所有枚举值及其含义。根据具体的系统配置和需求,你可以选择适合的驱动类型来执行 Direct3D 图形渲染操作。通常情况下,硬件加速的图形驱动程序(D3D_DRIVER_TYPE_HARDWARE)是首选的,以获得最佳性能和图形质量。
D3D_FEATURE_LEVEL 枚举类型定义了在 Direct3D 中可用的功能级别(Feature Level)。它表示硬件或驱动程序所支持的图形功能和功能集合。

D3D11CreateDevice 函数的使用示例
bool ScreenCapture::InitD3D11Device()
{
    D3D_DRIVER_TYPE driverTypes[] = {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = std::extent<decltype(driverTypes)>::value;

    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 = std::extent<decltype(featureLevels)>::value;
    D3D_FEATURE_LEVEL featureLevel;

    for (UINT i = 0; i < numDriverTypes; ++i)
    {
        HRESULT hr = D3D11CreateDevice(nullptr, driverTypes[i], nullptr, 0, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &m_pDevice, &featureLevel, &m_pDeviceContext);
        if (SUCCEEDED(hr)) {
            break;
        }
    }

    if (m_pDevice == nullptr || m_pDeviceContext == nullptr) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "InitD3D11Device Failed";
        return false;
    }

    return true;

}

2.QueryInterface

在 DXGI中,QueryInterface 是一种用于查询接口支持和获取接口指针的函数

HRESULT QueryInterface(
  REFIID  riid,
  void    **ppvObject
);
接口参数说明

参数说明:
riid:一个指向所需接口的唯一标识符(IID,Interface Identifier)的引用。可以使用 __uuidof 宏获取接口的 IID。
ppvObject:一个指向指针的指针,用于接收查询到的接口指针。
返回值:
如果操作成功,函数返回 S_OK,并且 ppvObject 将指向查询到的接口指针。
如果查询的接口不可用,函数返回 E_NOINTERFACE。
其他可能的返回值包括各种错误代码,表示查询接口失败或发生其他错误。
使用 QueryInterface 函数的一般流程如下:
在合适的对象上调用 QueryInterface 函数,传递所需接口的 IID 和接收指针的地址。
检查函数的返回值,如果返回 S_OK,表示查询成功。
使用获取到的接口指针执行相应的操作。
在不再需要接口时,记得释放接口指针的引用计数。

3.GetAdapter

在DXGI中,IDXGIDevice 接口提供了 GetAdapter 方法,用于获取与设备关联的适配器(adapter)对象

HRESULT GetAdapter(
  IDXGIAdapter **ppAdapter
);

参数说明:
ppAdapter:指向 IDXGIAdapter 指针的指针,用于接收获取到的适配器对象指针。
返回值:
如果操作成功,函数返回 S_OK,并且 ppAdapter 将指向获取到的适配器对象指针。
如果获取适配器失败,函数返回错误代码。
适配器(adapter)代表了一个物理或虚拟的图形适配器,它与显示设备(如显卡)相关联。通过调用 GetAdapter 方法,可以获得与当前设备相关的适配器对象,并进一步获取适配器的属性和信息。

4.EnumOutputs

在 DXGI中,EnumOutputs 是一个函数,用于枚举与图形适配器相关联的显示输出(display output)。

HRESULT EnumOutputs(
  UINT         Output,
  IDXGIOutput **ppOutput
);

参数说明:
Output:要枚举的输出的索引,从0开始。
ppOutput:指向 IDXGIOutput 指针的指针,用于接收获取到的输出对象指针。
返回值:
如果操作成功,函数返回 S_OK,并且 ppOutput 将指向获取到的输出对象指针。
如果枚举输出失败,函数返回错误代码。

显示输出(display output)表示与适配器关联的显示设备,如显示器或投影仪。通过调用 EnumOutputs 函数,可以枚举适配器上的所有显示输出,并获取相应的输出对象。

5.DuplicateOutput

在 DXGI(DirectX Graphics Infrastructure)中,DuplicateOutput 函数用于创建一个用于复制输出的 DXGI 接口对象,即 IDXGIOutputDuplication 接口对象。通过复制输出功能,可以实现屏幕捕捉、录制或远程显示等应用场景。

HRESULT DuplicateOutput(
  IUnknown              *pDevice,
  IDXGIOutputDuplication **ppOutputDuplication
);

参数说明:
pDevice:指向用于复制输出的 Direct3D 设备(ID3D11Device 或 ID3D12Device)的指针。
ppOutputDuplication:指向 IDXGIOutputDuplication 指针的指针,用于接收创建的输出复制对象。
返回值:
如果操作成功,函数返回 S_OK,并且 ppOutputDuplication 将指向创建的输出复制对象的指针。
如果复制输出失败,函数返回错误代码。

要使用 DuplicateOutput 函数,首先需要创建一个 Direct3D 设备对象(ID3D11Device 或 ID3D12Device),并获取要复制的输出对象(IDXGIOutput1)。然后,调用 DuplicateOutput 函数来创建输出复制对象。

代码示例
bool ScreenCapture::InitDuplication()
{
    HRESULT hr = S_OK;

     /* IDXGIDevice 接口是 DXGI中的一个接口,用于表示一个设备对象,该设备对象是与 Direct3D 设备相关联的。 */
    IDXGIDevice* dxgiDevice = nullptr;
    hr = m_pDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice));
    if (FAILED(hr)) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "QueryInterface Failed:" << hr;
        return false;
    }

    if (dxgiDevice == nullptr) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "dxgiDevice is  nullptr";
        return false;
    }

    IDXGIAdapter* dxgiAdapter = nullptr;
    /* 获取与设备相关联的适配器对象 */
    hr = dxgiDevice->GetAdapter(&dxgiAdapter);
    dxgiDevice->Release();
    if (FAILED(hr)) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "GetAdapter Failed:" << hr;
        return false;
    }

    if (dxgiAdapter == nullptr) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "dxgiAdapter is  nullptr";
        return false;
    }

    UINT output = 0;
    IDXGIOutput* dxgiOutput = nullptr;
    while (true)
    {
        hr = dxgiAdapter->EnumOutputs(output++, &dxgiOutput);
        if (hr == DXGI_ERROR_NOT_FOUND) {
            return false;
        } else {
            if (dxgiOutput == nullptr) {
                qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "dxgiOutput is  nullptr";
                break;
            }

            DXGI_OUTPUT_DESC desc;
            dxgiOutput->GetDesc(&desc);
            m_screenWidth = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
            m_screenHeight = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;
            break;
        }
    }
    dxgiAdapter->Release();

    /* IDXGIOutput1 接口是 DXGI中的一个接口,它是 IDXGIOutput 接口的扩展版本。IDXGIOutput1 提供了一些额外的方法和功能,
     * 用于与显示输出(display output)进行交互和控制。 */
    IDXGIOutput1* dxgiOutput1 = nullptr;
    hr = dxgiOutput->QueryInterface(__uuidof(IDXGIOutput1), reinterpret_cast<void**>(&dxgiOutput1));
    dxgiOutput->Release();
    if (FAILED(hr)) {
        return false;
    }

    if (dxgiOutput1 == nullptr) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "dxgiOutput1 is  nullptr";
        return false;
    }

    hr = dxgiOutput1->DuplicateOutput(m_pDevice, &m_pDuplication);
    dxgiOutput1->Release();
    if (FAILED(hr)) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "DuplicateOutput Failed";
        return false;
    }

    return true;
}

6.AcquireNextFrame

在 DXGI中,AcquireNextFrame 函数用于从 IDXGIOutputDuplication 接口获取下一帧的图像数据。

HRESULT AcquireNextFrame(
  UINT                  TimeoutInMilliseconds,
  DXGI_OUTDUPL_FRAME_INFO *pFrameInfo,
  IDXGIResource          **ppDesktopResource
);

参数说明:
TimeoutInMilliseconds:超时时间(以毫秒为单位),指定等待新帧的最大时间。
pFrameInfo:指向 DXGI_OUTDUPL_FRAME_INFO 结构的指针,用于接收获取到的帧信息。
ppDesktopResource:指向 IDXGIResource 指针的指针,用于接收获取到的桌面资源。
返回值:
如果操作成功,函数返回 S_OK,并且 pFrameInfo 将包含帧信息,ppDesktopResource 将指向获取到的桌面资源。
如果获取帧失败或超时,函数返回错误代码。

要使用 AcquireNextFrame 函数,首先需要创建一个 IDXGIOutputDuplication 接口对象,通过 DuplicateOutput 方法获取。然后,在循环中调用 AcquireNextFrame 函数来获取每一帧的图像数据。

代码示例
bool ScreenCapture::GetDesktopFrame(QString fileName)
{
    HRESULT hr = S_OK;
    DXGI_OUTDUPL_FRAME_INFO frameInfo;
    IDXGIResource* resource = nullptr;
    ID3D11Texture2D* acquireFrame = nullptr;
    ID3D11Texture2D* texture = nullptr;

    if (m_pDuplication == nullptr) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "m_pDuplication is  nullptr";
        return false;
    }

    hr = m_pDuplication->AcquireNextFrame(0, &frameInfo, &resource);
    if (FAILED(hr)) {
        return false;
    }

    if (resource == nullptr) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "resource is  nullptr";
        return false;
    }

    hr = resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&acquireFrame));
    resource->Release();
    if (FAILED(hr)) {
        return false;
    }

    if (acquireFrame == nullptr) {
        qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "acquireFrame is  nullptr";
        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;
    m_pDevice->CreateTexture2D(&desc, NULL, &texture);
    if (texture) {
        /* 将图像数据从显存中拷贝到内存中 */
        m_pDeviceContext->CopyResource(texture, acquireFrame);
        QImage image = this->CopyDesktopImage(texture);
        image.save(fileName, "PNG");
    }
    acquireFrame->Release();

    hr = m_pDuplication->ReleaseFrame();
    if (FAILED(hr)) {
        return false;
    }

    return true;
}

四.部分代码示例

QImage ScreenCapture::CopyDesktopImage(ID3D11Texture2D* texture)
{
    D3D11_TEXTURE2D_DESC desc;
    texture->GetDesc(&desc);
    D3D11_MAPPED_SUBRESOURCE mapped_resource;
    m_pDeviceContext->Map(texture, 0, D3D11_MAP_READ, 0, &mapped_resource);

    QImage image(static_cast<uchar *>(mapped_resource.pData), m_screenWidth, m_screenHeight, QImage::Format_ARGB32);

    texture->Release();
    texture = nullptr;

    return image;
}

开始截图(截图之前,需要加载d3d11.lib和dxgi.lib,另外还需要包含头文件d3d11.h和dxgi1_2.h)

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    std::shared_ptr<ScreenCapture> screen_capture = std::make_shared<ScreenCapture>();
    if (!screen_capture.get()->InitD3D11Device()) {
        return  -1;
    }

    if (!screen_capture.get()->InitDuplication()) {
        return -1;
    }

    int counts = 0;
    /* 每隔1秒获取一次图像 */
    while (counts < 5) {
        QString fileName = QString("%1.png").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss"));
        screen_capture.get()->GetDesktopFrame(fileName);

        counts++;
        Sleep(1000);
    }

    return a.exec();
}

完整demo示例下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值