Home > database >  How to use IDXGIOutputDuplication to capture multiple screens?
How to use IDXGIOutputDuplication to capture multiple screens?

Time:07-15

Is there a way to screen capture multi monitor setups with windows duplication api (IDXIOutputDuplication) rather than gdi? Windows duplication is now the preferred (faster and more efficient) way of screen capturing on Windows machines. This is a question on the Microsoft forum but the link to the code is broken and I can't find it anywhere on their website 1. Examples of multi monitor screen casting with GDI can be found but they use a separate API 2,3.

I have successfully captured one monitor in C using the links for Windows duplication api found here 4. The problem is that the api requires an adapter and an output. I could possibly create two threads and run the windows api on each for separate adapters. I was just wondering if there was a more elegant/better performance solution.

P.S. I have had trouble posting questions on StackOverflow in the past. Before closing this question, please let me know how I can change the content to better suit the rules:)

In response to the request for code, here is a snippet from the header file

ID3D11Device*           D3DDevice        = nullptr;
ID3D11DeviceContext*    D3DDeviceContext = nullptr;
IDXGIOutputDuplication* DeskDupl         = nullptr;
DXGI_OUTPUT_DESC        OutputDesc;

from the cpp file (there are two parts, initializing and capuring):

From the initializing function:

// Create device
for (size_t i = 0; i < numDriverTypes; i  ) {
    hr = D3D11CreateDevice(nullptr, driverTypes[i], nullptr, 0, featureLevels, (UINT) numFeatureLevels,
                           D3D11_SDK_VERSION, &D3DDevice, &featureLevel, &D3DDeviceContext);
    if (SUCCEEDED(hr))
        break;
}
if (FAILED(hr))
    return tsf::fmt("D3D11CreateDevice failed: %v", hr);

// Initialize the Desktop Duplication system
//m_OutputNumber = Output;

// Get DXGI device
IDXGIDevice* dxgiDevice = nullptr;
hr                      = D3DDevice->QueryInterface(__uuidof(IDXGIDevice), (void**) &dxgiDevice);
if (FAILED(hr))
    return tsf::fmt("D3DDevice->QueryInterface failed: %v", hr);

// Get DXGI adapter
IDXGIAdapter* dxgiAdapter = nullptr;
hr                        = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**) &dxgiAdapter);
dxgiDevice->Release();
dxgiDevice = nullptr;
if (FAILED(hr)) {
    return tsf::fmt("dxgiDevice->GetParent failed: %v", hr);
    //return ProcessFailure(m_Device, L"Failed to get parent DXGI Adapter", L"Error", hr, SystemTransitionsExpectedErrors);
}

// Get output
IDXGIOutput* dxgiOutput = nullptr;
hr                      = dxgiAdapter->EnumOutputs(OutputNumber, &dxgiOutput);
dxgiAdapter->Release();
dxgiAdapter = nullptr;
if (FAILED(hr)) {
    return tsf::fmt("dxgiAdapter->EnumOutputs failed: %v", hr);
    //return ProcessFailure(m_Device, L"Failed to get specified output in DUPLICATIONMANAGER", L"Error", hr, EnumOutputsExpectedErrors);
}

dxgiOutput->GetDesc(&OutputDesc);

// QI for Output 1
IDXGIOutput1* dxgiOutput1 = nullptr;
hr                        = dxgiOutput->QueryInterface(__uuidof(dxgiOutput1), (void**) &dxgiOutput1);
dxgiOutput->Release();
dxgiOutput = nullptr;
if (FAILED(hr))
    return tsf::fmt("dxgiOutput->QueryInterface failed: %v", hr);

// Create desktop duplication
hr = dxgiOutput1->DuplicateOutput(D3DDevice, &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 "Too many desktop recorders already active";
    }
    return tsf::fmt("DuplicateOutput failed: %v", hr);
    //return ProcessFailure(m_Device, L"Failed to get duplicate output in DUPLICATIONMANAGER", L"Error", hr, CreateDuplicationExpectedErrors);
}

From the capturing function:

IDXGIResource*          deskRes = nullptr;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
hr = DeskDupl->AcquireNextFrame(0, &frameInfo, &deskRes);
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
    // nothing to see here
    return false;
}
if (FAILED(hr)) {
    // perhaps shutdown and reinitialize
    auto msg = tsf::fmt("Acquire failed: %x\n", hr);
    OutputDebugStringA(msg.c_str());
    return false;
}

HaveFrameLock = true;

ID3D11Texture2D* gpuTex = nullptr;
hr                      = deskRes->QueryInterface(__uuidof(ID3D11Texture2D), (void**) &gpuTex);
deskRes->Release();
deskRes = nullptr;
if (FAILED(hr)) {
    // not expected
    return false;
}

bool ok = true;

D3D11_TEXTURE2D_DESC desc;
gpuTex->GetDesc(&desc);
desc.CPUAccessFlags     = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
desc.Usage              = D3D11_USAGE_STAGING;
desc.BindFlags          = 0;
desc.MiscFlags          = 0; // D3D11_RESOURCE_MISC_GDI_COMPATIBLE ?
ID3D11Texture2D* cpuTex = nullptr;
hr                      = D3DDevice->CreateTexture2D(&desc, nullptr, &cpuTex);
if (SUCCEEDED(hr)) {
    D3DDeviceContext->CopyResource(cpuTex, gpuTex);
} else {
    // not expected
    ok = false;
}

//UINT                     subresource = D3D11CalcSubresource(0, 0, 0);
D3D11_MAPPED_SUBRESOURCE sr;
hr = D3DDeviceContext->Map(cpuTex, 0, D3D11_MAP_READ, 0, &sr);

DeskDupl is tied to a specific adapter and output.

CodePudding user response:

You will need to obtain an IDXGIOutputDuplication interface for each IDXGIOutput you wish to have duplicated. Whether those outputs are on one adapter or multiple doesn't matter. I can't speak to whether you will need to run each duplication loop on a dedicated thread.

  • Related