dxgi截屏,多次调用报错

gg22g2
  • 122

我想使用window上dxgi方式截屏,做成dll,并在java端调用。
但是现在只能截图一次,第一次截图没有问题,获取到的也是当前整个桌面的图像。但是之后每次调用都会失败,并且报错。我看了下官网这个错是DXGI_ERROR_INVALID_CALL。

DXGI_ERROR_INVALID_CALL 0x887A0001
The application provided invalid parameter data; this must be debugged and fixed before the application is released.

下边是我的C++代码

#include "pch.h"

#include <tchar.h>
#include <memory>
#include <windows.h>
#include <atlbase.h>
#include <dxgi.h>
#include <DXGI1_2.h>
#include <d3d11.h>

using namespace std;

IDXGIFactory1* m_spDXGIFactory1;
IDXGIAdapter1* spAdapter;
IDXGIOutput* spDXGIOutput;
IDXGIOutputDuplication* spDXGIOutputDuplication;

//设备接口代表一个虚拟适配器;用于创建资源。
ID3D11Device* spD3D11Device;
//ID3D11DeviceContext接口表示生成渲染命令的设备上下文
ID3D11DeviceContext* spD3D11DeviceContext;

void init() {
    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&m_spDXGIFactory1));
    if (m_spDXGIFactory1->EnumAdapters1(0, &spAdapter) == DXGI_ERROR_NOT_FOUND) {
        return;
    }
    if ((spAdapter)->EnumOutputs(0, &spDXGIOutput) == DXGI_ERROR_NOT_FOUND) {
        return;
    }

    //描述Direct3D设备的一组特性,这里的值没意义,调用D3D11CreateDevice后,会重新给这个指针赋值
    D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_11_0;
    // 参阅 https://docs.microsoft.com/zh-cn/windows/win32/api/d3d11/nf-d3d11-d3d11createdevice
    // 创建ID3D11Device等一些东西
    hr = D3D11CreateDevice(
        spAdapter,                        /***/
        D3D_DRIVER_TYPE_UNKNOWN,        /***/
        NULL,                            /** 软件栅格化的回调地址,这里不需要*/
        0,                                /** 0代表单线程模式*/
        NULL,                            /** pFeatureLevels ,使用默认特性数组*/
        0,                                /** 上一个参数的长度,这里传0,不知道有啥用*/
        D3D11_SDK_VERSION,                /** 使用D3D11_SDK_VERSION版本*/
        &spD3D11Device,                    /** 返回ID3D11Device的地址给这个指针*/
        &fl,                            /** 返回从pFeatureLevels中,最终使用的特性,赋值给这个指针,测试返回的为D3D_FEATURE_LEVEL_11_0*/
        &spD3D11DeviceContext);            /** 返回ID3D11DeviceContext赋值给这个指针*/


    IDXGIOutput1* spDXGIOutput1 = (IDXGIOutput1*)spDXGIOutput;
    IDXGIDevice1* spDXGIDevice = (IDXGIDevice1*)spD3D11Device;

    //关键点,获取IDXGIOutputDuplication,IDXGIOutputDuplication接口用来访问和操作被复制的桌面图像。
    // DuplicateOutput函数,从spDXGIOutput1创建一个桌面图像复制接口,
    // 根据官方文档,可以通过IDXGIOutput5::DuplicateOutput1 提高性能,待测试
    hr = spDXGIOutput1->DuplicateOutput(spDXGIDevice, &spDXGIOutputDuplication);

    if (FAILED(hr))
    {
        return;
    }

    DXGI_OUTDUPL_DESC dxgi_des;
    spDXGIOutputDuplication->GetDesc(&dxgi_des);
    if (dxgi_des.DesktopImageInSystemMemory) {

        //new DxgiTextureMapping(spDXGIOutputDuplication);
    }
    else {


    }
}

long capture(unsigned char* buffer) {

    DXGI_OUTDUPL_FRAME_INFO frame_info;
    //IDXGIResource接口允许资源共享,并标识资源所在的内存。
    IDXGIResource* desktop_resource;
    HRESULT hr = spDXGIOutputDuplication->AcquireNextFrame(0, &frame_info, &desktop_resource);

    // Timeout will return when desktop has no chane
    if (hr == DXGI_ERROR_WAIT_TIMEOUT) return hr;

    if (hr == DXGI_ERROR_INVALID_CALL) return -100;

    if (FAILED(hr))
        return hr;


    ID3D11Texture2D* image;

    desktop_resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&image));

    desktop_resource->Release();
    desktop_resource = nullptr;

    D3D11_TEXTURE2D_DESC frame_desc;
    image->GetDesc(&frame_desc);


    ID3D11Texture2D* new_image = NULL;

    frame_desc.Usage = D3D11_USAGE_STAGING;
    frame_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    frame_desc.BindFlags = 0;
    frame_desc.MiscFlags = 0;
    frame_desc.MipLevels = 1;
    frame_desc.ArraySize = 1;
    frame_desc.SampleDesc.Count = 1;
    hr = spD3D11Device->CreateTexture2D(&frame_desc, NULL, &new_image);

    spD3D11DeviceContext->CopyResource(new_image, image);

    //将图像从GPU映射到内存中
    IDXGISurface* dxgi_surface = NULL;
    hr = new_image->QueryInterface(__uuidof(IDXGISurface), (void**)(&dxgi_surface));
    new_image->Release();

    // DXGI_MAPPED_RECT的Pitch代表图像的宽度,假如1920像素,每个像素4通道,则Pitch等于7680
    // pBits指向表面的图像缓冲区的指针。
    DXGI_MAPPED_RECT rect;
    hr = dxgi_surface->Map(&rect, DXGI_MAP_READ);



    //    int dst_rowpitch = frame_desc.Width * 4;
        //for (int h = 0; h < frame_desc.Height; h++) {
        //    memcpy_s(buffer + h * dst_rowpitch, dst_rowpitch, (BYTE*)rect.pBits + h * rect.Pitch, min(mapped_rect.Pitch, dst_rowpitch));
    //    }

    memcpy(buffer, rect.pBits, 1920 * 1080 * 4);
    dxgi_surface->Unmap();
    return 123456789;
}

void Release() {
    spAdapter->Release();
    spDXGIOutput->Release();
}

int main() {
    init();
    unsigned char* buffer = new unsigned char[1920 * 1080 * 4];
    capture(buffer);
    Release();
}

JAVA代码使用的jdk16的调用方式

import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.LibraryLookup;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.nio.file.Path;

public class CallScreenShotDLL{
    public static void main(String[] args) {
        Path paths = Path.of("C:\\Users\\h6706\\source\\repos\\screenShot\\x64\\Debug\\screenShot.dll");

        MethodHandle init = CLinker.getInstance().downcallHandle(
                LibraryLookup.ofPath(paths).lookup("init").get(),
                MethodType.methodType(void.class),
                FunctionDescriptor.ofVoid()
        );

        MethodHandle release = CLinker.getInstance().downcallHandle(
                LibraryLookup.ofPath(paths).lookup("Release").get(),
                MethodType.methodType(void.class),
                FunctionDescriptor.ofVoid()
        );

        MethodHandle capture = CLinker.getInstance().downcallHandle(
                LibraryLookup.ofPath(paths).lookup("capture").get(),
                MethodType.methodType(int.class, MemoryAddress.class),
                FunctionDescriptor.of(CLinker.C_INT, CLinker.C_POINTER)
        );

        try {
            init.invokeExact();

            for (int i = 0; i < 3; i++) {
                MemorySegment segment = MemorySegment.allocateNative(1920 * 1080 * 4);
                byte[] bytes = segment.toByteArray();
                try {
                    Thread.sleep(1000);
                    int result = (int) capture.invokeExact(segment.address());
                    System.out.println(result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            
            release.invokeExact();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

先调用init()初始化,然后调用capture函数获取图片,第一次调用capture没问题。我也测试了调用capture后返回的内容,最终生成的图片就是我当时的屏幕内容。但是从第二次调用开始就一直报错了。

我也尝试过每次调用capture时都去调用init函数,也同样报错了。这份C代码,是我根据网上的例子改出来的,也不知道是不是那些细节没考虑到,

回复
阅读 835
1 个回答
✓ 已被采纳

解决了,在调用AcquireNextFrame前,应该调用ReleaseFrame方法。

The application must release the frame before it acquires the next frame. After the frame is released, the surface that contains the desktop bitmap becomes invalid; you will not be able to use the surface in a DirectX graphics operation.
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
你知道吗?

宣传栏