Windows通过DXGI进行屏幕捕获(代码解析及源码配套)

使用场景     

        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;
}
 

工程下载地址:

        https://download.csdn.net/download/2403_83063732/88857552

很抱歉,作为AI语言模型,我无法为您提供编写代码的实际指导。但是,我可以为您提供参考资源和示例代码,供您参考学习和应用。 以下是用Qt和DXGI进行屏幕截图并保存的示例代码: ```c++ // Qt 5.x and later with DirectX 11 QPixmap grab() { ID3D11Device* device = nullptr; ID3D11DeviceContext* context = nullptr; IDXGIOutputDuplication* duplication = nullptr; IDXGIResource* resource = nullptr; QPixmap pixmap; QPixmap screenshot; RECT rect = { 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) }; if (SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &device, nullptr, &context))) { duplication = GetDuplication(device, context); if (duplication == nullptr) { return pixmap; } DXGI_OUTDUPL_FRAME_INFO frame_info; ID3D11Texture2D* texture = nullptr; D3D11_TEXTURE2D_DESC texture_desc; ZeroMemory(&texture_desc, sizeof(texture_desc)); texture_desc.Width = rect.right - rect.left; texture_desc.Height = rect.bottom - rect.top; texture_desc.MipLevels = 1; texture_desc.ArraySize = 1; texture_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; texture_desc.SampleDesc.Count = 1; texture_desc.Usage = D3D11_USAGE_STAGING; texture_desc.BindFlags = 0; texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; texture_desc.MiscFlags = 0; if (FAILED(device->CreateTexture2D(&texture_desc, nullptr, &texture))) { duplication->Release(); device->Release(); context->Release(); return pixmap; } POINT offset = { rect.left, rect.top }; duplication->GetFramePointer(&frame_info, &texture, &offset); if (FAILED(texture->QueryInterface(IID_PPV_ARGS(&resource)))) { duplication->ReleaseFrame(); duplication->Release(); device->Release(); context->Release(); texture->Release(); return pixmap; } D3D11_MAPPED_SUBRESOURCE map_info; ZeroMemory(&map_info, sizeof(map_info)); if (FAILED(context->Map(texture, 0, D3D11_MAP_READ, 0, &map_info))) { duplication->ReleaseFrame(); duplication->Release(); device->Release(); context->Release(); texture->Release(); resource->Release(); return pixmap; } screenshot = QPixmap::fromImage(QImage(static_cast<uchar*>(map_info.pData), texture_desc.Width, texture_desc.Height, QImage::Format_ARGB32)); pixmap = screenshot.copy(rect); context->Unmap(texture, 0); duplication->ReleaseFrame(); duplication->Release(); device->Release(); context->Release(); texture->Release(); resource->Release(); } return pixmap; } IDXGIOutputDuplication* GetDuplication(ID3D11Device* device, ID3D11DeviceContext* context) { IDXGIOutputDuplication* duplication = nullptr; IDXGIAdapter* adapter = nullptr; IDXGIOutput* output = nullptr; DXGI_OUTPUT_DESC output_desc; ZeroMemory(&output_desc, sizeof(output_desc)); output_desc.Flags = DXGI_ENUMERATION_FLAG_PRIMARY; if (FAILED(CreateDXGIFactory(IID_PPV_ARGS(&factory)))) return nullptr; if (FAILED(factory->EnumAdapters(0, &adapter))) return nullptr; if (FAILED(adapter->EnumOutputs(0, &output))) return nullptr; if (FAILED(output->GetDesc(&output_desc))) return nullptr; D3D11_RENDER_TARGET_VIEW_DESC target_desc; ZeroMemory(&target_desc, sizeof(target_desc)); target_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; target_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; ID3D11Texture2D* texture = nullptr; if (FAILED(device->CreateTexture2D(&GetTargetDesc(output_desc.DesktopCoordinates), nullptr, &texture))) return nullptr; ID3D11RenderTargetView* target = nullptr; ID3D11Texture2D* backbuffer = nullptr; D3D11_TEXTURE2D_DESC desc; backbuffer->GetDesc(&desc); target_desc.Texture2D.MipSlice = 0; if (FAILED(device->CreateRenderTargetView(backbuffer, &target_desc, &target))) return nullptr; ID3D11Texture2D* stage_texture = nullptr; if (FAILED(device->CreateTexture2D(&desc, nullptr, &stage_texture))) return nullptr; ID3D11Device* device2 = nullptr; if (FAILED(device->QueryInterface(IID_PPV_ARGS(&device2)))) return nullptr; ID3D11DeviceContext* context2 = nullptr; if (FAILED(device2->CreateDeferredContext(0, &context2))) return nullptr; context2->CopyResource(stage_texture, backbuffer); context2->Flush(); DXGI_OUTPUT_DESC output_desc2; ZeroMemory(&output_desc2, sizeof(output_desc2)); output_desc2.Flags = DXGI_ENUMERATION_FLAG_PRIMARY; IDXGIOutput5* output2 = nullptr; adapter->EnumOutputs(0, (IDXGIOutput**)&output2); output2->DuplicateOutput(context, &duplication); return duplication; } ``` 此代码是用 C++ 和 Qt 框架实现的。如果您非常熟悉Qt和DXGI,那么您可能会发现此代码可能有些错误或漏洞,但这个示例应该可以为您提供一个很好的起点。请确保在尝试使用这段代码之前认真阅读并理解它,并仔细检查以确保它适合您的具体用例。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值