使用Qt在Windows下进行截屏的一些尝试

1 篇文章 0 订阅
1 篇文章 0 订阅

【写在前面】

        最近在尝试做一个简单的屏幕控制,本以为截屏应该是最简单的部分,没想到在 windows 下有很大的问题。

        其中主要的问题在于截屏效率,查了很多资料以后发现巨多坑。

        本篇只讲几种截屏的方式,不讲屏幕控制( 因为比起截屏反而是很简单的部分 )。

        注意:后面我专门实现了一个采集库集成了本篇的技术,并且简单易于使用,地址:

Qt 实现的万能采集库( 屏幕/相机/扬声器/麦克风采集)_qt 麦克风声音采集-CSDN博客文章浏览阅读1.2k次,点赞11次,收藏14次。之前应公司需要,给公司写过一整套直播的库( 推拉流,编解码),类似于 libobs。结果后来因为没有相关项目,便停止开发&维护了。不过里面很多有用的组件,然后也挺好用的,遂开源出来一部分。因此,本篇就简单讲一下用法。_qt 麦克风声音采集https://blog.csdn.net/u011283226/article/details/137244131?spm=1001.2014.3001.5502


  【正文开始】

        1、首先,因为使用的 Qt,立即能想到的截屏方式是:

    QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(0);

        这种方法在我的电脑上平均花费30ms的时间截取一帧,当然我的电脑很辣鸡,所以一般来说这个性能已经足够了,但是一个屏幕控制,也不仅仅是截屏这一部分,还包括其他诸如比对、压缩、分片、传输等等,所以实际上花费时间远不止30ms。

        但是截屏的时间仍是大头。

        2、使用 GDI 进行截屏:

        头文件:windows.h

        pro文件需要加入:LIBS += -lGdi32

        这种方法在我的机器上面平均花费30ms。

    int width = GetSystemMetrics(SM_CXSCREEN);
    int height = GetSystemMetrics(SM_CYSCREEN);

    HWND hwnd = GetDesktopWindow();
    HDC display_dc = GetDC(nullptr);
    HDC bitmap_dc = CreateCompatibleDC(display_dc);
    HBITMAP bitmap = CreateCompatibleBitmap(display_dc, width, height);
    HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);

    // copy data
    HDC window_dc = GetDC(hwnd);
    BitBlt(bitmap_dc, 0, 0, width, height, window_dc, 0, 0, SRCCOPY | CAPTUREBLT);

    // clean up all but bitmap
    ReleaseDC(hwnd, window_dc);
    SelectObject(bitmap_dc, null_bitmap);
    DeleteDC(bitmap_dc);

    screen = QtWin::fromHBITMAP(bitmap);

    DeleteObject(bitmap);
    ReleaseDC(nullptr, display_dc);

        其中:

QtWin::fromHBITMAP(),Qt 提供的 windows 下特有的便利函数,可以将 HBITMAP 转换成 QPixmap。

头文件 #include <QtWin>

pro文件: QT += winextras

        这段代码是从 Qt 源码中截取下来的,也就是说,Qt 在 windows 下使用 GDI 方式进行截图,性能确实不错了,但听说使用 DirectX 可能更快,然后我就去找了一些方法尝试,结果。。

        3、使用 D3D 进行截屏:

        头文件:d3d9.h d3dx9tex.h

        pro文件需要加入:LIBS += -ld3d9 -lD3dx9

        这种方法在我的渣机上面平均花费60ms。

    LPDIRECT3D9 lpD3D = nullptr;
    LPDIRECT3DDEVICE9 lpDevice = nullptr;
    D3DDISPLAYMODE ddm;
    
    lpD3D = Direct3DCreate9(D3D_SDK_VERSION);
    lpD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &ddm);
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));
    d3dpp.Windowed = true;
    d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    d3dpp.BackBufferFormat = ddm.Format;
    d3dpp.BackBufferHeight = ddm.Height;
    d3dpp.BackBufferWidth = ddm.Width;
    d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = GetDesktopWindow();
    d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
    d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
    
    lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, GetDesktopWindow(),
                        D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &lpDevice);
    
    if (lpDevice)
    {
        LPDIRECT3DSURFACE9 surface;
        lpDevice->CreateOffscreenPlainSurface(ddm.Width, ddm.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &surface, nullptr);
        lpDevice->GetFrontBufferData(0, surface);
        LPD3DXBUFFER bufferedImage = nullptr;
        D3DXSaveSurfaceToFileInMemory(&bufferedImage, D3DXIFF_JPG, surface, nullptr, nullptr);
        screen.loadFromData((uchar *)bufferedImage->GetBufferPointer(), bufferedImage->GetBufferSize(), "JPG");
        surface->Release();
    }

        一开始我使用的是后备缓冲,GetBackBufferData(),但这个获取到的一直都是黑屏,查了查发现说是 windows 桌面不使用后备缓冲,只能使用前置缓冲,但是性能简直不能看(我电脑太辣鸡也是很大的原因,用法上应该是没问题的)。

        D3DXSaveSurfaceToFileInMemory() 将表面保存至文件存储在内存,D3DXSaveSurfaceToFile() 则存储于本地。

        4、使用 DXGI 进行截屏:

        注意:DXGI 在 Windows 8 及以上提供

        头文件:windows.h dxgi1_6.h d3d11.h

        pro 文件需要加入:LIBS += -lD3D11 -lDXGI

        这种方法在我的机器上面平均花费10ms。

        DXGI 使用起来比较麻烦,分三个步骤:

        1、初始化各种接口,设备:

bool DxgiManager::init()
{
    ID3D11Device *d3dDevice = nullptr;
    ID3D11DeviceContext *d3dContext = nullptr;
    D3D_FEATURE_LEVEL feat = D3D_FEATURE_LEVEL_11_0;
    HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &d3dDevice, &feat, &d3dContext);
    if (FAILED(hr)) {
        m_lastError = "Failed to D3D11CreateDevice ErrorCode = " + QString::number(uint(hr), 16);
        return false;
    }

    IDXGIDevice *dxgiDevice = nullptr;
    hr = d3dDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice));
    if(FAILED(hr)) {
        m_lastError = "Failed to QueryInterface IDXGIOutput6 ErrorCode = " + QString::number(uint(hr), 16);
        return false;
    }

    IDXGIAdapter *dxgiAdapter = nullptr;
    hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&dxgiAdapter));
    dxgiDevice->Release();
    if (FAILED(hr)) {
        m_lastError = "Failed to Get IDXGIAdapter ErrorCode = " + QString::number(uint(hr), 16);
        return false;
    }

    IDXGIOutput *dxgiOutput = nullptr;
    QVector<IDXGIOutput *> outputs;
    for(uint i = 0; dxgiAdapter->EnumOutputs(i, &dxgiOutput) != DXGI_ERROR_NOT_FOUND; ++i) {
        outputs.push_back(dxgiOutput);
    }
    dxgiAdapter->Release();
    if (outputs.size() > 0) dxgiOutput = outputs.at(0);
    else {
        m_lastError = "Failed to IDXGIOutput is Empty!";
        return false;
    }

    IDXGIOutput6 *dxgiOutput6 = nullptr;
    hr = dxgiOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast<void**>(&dxgiOutput6));
    dxgiOutput->Release();
    if (FAILED(hr)) {
        m_lastError = "Failed to QueryInterface IDXGIOutput6 ErrorCode = " + QString::number(uint(hr), 16);
        return false;
    }

    hr = dxgiOutput6->DuplicateOutput(d3dDevice, &m_duplication);
    dxgiOutput6->Release();
    if (FAILED(hr)) {
        m_lastError = "Failed to DuplicateOutput ErrorCode = " + QString::number(uint(hr), 16);
        return false;
    }

    DXGI_OUTDUPL_DESC desc;
    m_duplication->GetDesc(&desc);
    m_texture = new DxgiTextureStaging(d3dDevice, d3dContext);
    if (desc.DesktopImageInSystemMemory) {
        qDebug() << "Desc: CPU shared with GPU";
    } else {
        qDebug() << "Desc: CPU not shared with GPU";
    }

    return true;
}

        2、释放 & 请求下一帧:

QPixmap DxgiManager::grabScreen()
{
    IDXGIResource *desktopRes;
    DXGI_OUTDUPL_FRAME_INFO frameInfo;
    m_duplication->ReleaseFrame();
    HRESULT hr = m_duplication->AcquireNextFrame(100, &frameInfo, &desktopRes);
    if (FAILED(hr)) {
        m_lastError = "Failed to AcquireNextFrame ErrorCode = " + QString::number(uint(hr), 16);
        return QPixmap();
    }

    return m_texture->copyToImage(desktopRes);
}

        3、复制纹理 & 像素映射:

QPixmap DxgiTextureStaging::copyToImage(IDXGIResource *res)
{
    D3D11_TEXTURE2D_DESC desc;
    ID3D11Texture2D *textrueRes = nullptr;
    HRESULT hr = res->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&textrueRes));
    if (FAILED(hr)) {
       qDebug() << "Failed to ID3D11Texture2D result =" << hex << uint(hr);
       return QPixmap();
    }
    textrueRes->GetDesc(&desc);

    D3D11_TEXTURE2D_DESC texDesc;
    ZeroMemory(&texDesc, sizeof(texDesc));
    texDesc.Width = desc.Width;
    texDesc.Height = desc.Height;
    texDesc.MipLevels = 1;
    texDesc.ArraySize = 1;
    texDesc.SampleDesc.Count = 1;
    texDesc.SampleDesc.Quality = 0;
    texDesc.Usage = D3D11_USAGE_STAGING;
    texDesc.Format = desc.Format;
    texDesc.BindFlags = 0;
    texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    texDesc.MiscFlags = 0;
    m_device->CreateTexture2D(&texDesc, nullptr, &m_texture);
    m_context->CopyResource(m_texture, textrueRes);

    IDXGISurface1 *surface = nullptr;
    hr = m_texture->QueryInterface(__uuidof(IDXGISurface1), reinterpret_cast<void **>(&surface));
    if (FAILED(hr)) {
       qDebug() << "Failed to QueryInterface IDXGISurface1 ErrorCode =" << hex << uint(hr);
       return QPixmap();
    }

    DXGI_MAPPED_RECT map;
    surface->Map(&map, DXGI_MAP_READ);
    QPixmap pixmap = QPixmap::fromImage(QImage(static_cast<uchar *>(map.pBits),
                                       int(desc.Width), int(desc.Height), QImage::Format_ARGB32));
    surface->Unmap();
    surface->Release();
    m_texture->Release();

    return pixmap;
}

【结语】

        好了,四个方法都讲完了,其中关键部分的代码量已经是最少的了。

        其实说起来,只要不是特别追求性能的 ,Qt 提供的方式已经足够了。

        最后,附上完整项目链接(多多star呀..⭐_⭐):

        Github的:https://github.com/mengps/RemoteControl

  • 13
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦起丶

您的鼓励和支持是我创作最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值