Microsoft MVP성태의 닷넷 이야기
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++) [링크 복사], [링크+제목 복사]
조회: 7028
글쓴 사람
홈페이지
첨부 파일

DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)

화면 캡처 글을 검색해 보면 다양한 글이 나옵니다.

Various methods for capturing the screen
; http://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen

일단 위의 글에서는 3가지 방법을 소개하는데 그중 DirectX 예제만 따라 해 보았지만 Windows 10에서 동작하지 않았습니다. 왜냐하면 GetFrontBufferData 호출에서,

hr = g_pd3dDevice->GetFrontBufferData(0, pSurface);
// hr == 0x8876086c (D3DERR_INVALIDCALL)

D3DERR_INVALIDCALL 오류가 발생하는데 이유를 모르겠습니다. (혹시 아시는 분 덧글 부탁드립니다.)

게다가 d3dx9tex.h 헤더 파일 및 d3dx9.lib 파일이 없다고 오류가 발생하는데 이를 정상적으로 컴파일하기 위해서는 다음의 과정을 거쳐야 합니다.

DXSDK_Jun10.exe 설치 시 "Error Code: S1023" 오류 해결하는 방법
; https://www.sysnet.pe.kr/2/0/1195

귀찮으니 ^^; 다음 캡처 방식으로 넘어가 보겠습니다. Windows 8부터 지원하는 DXGI를 이용하면,

DXGI fast screen capture 
; http://www.pavelgurenko.com/2013/12/dxgi-outputs-enumeration-and-fast.html

The second way of performing capture is DirectX Graphics Infrastructure - you command video card to store the whole screen or one/several monitors's contents inside separate part of its memory (surface).


속도는 물론이고 일반적인 GDI API로는 할 수 없는 DirectX 게임 화면까지도 안정적으로 캡처할 수 있다고 합니다. 친절하게도 위의 블로그를 쓴 사람이 github에 소스 코드도 공개한 것이 있고,

pgurenko/DXGICaptureSample 
; https://github.com/pgurenko/DXGICaptureSample

또한 다음과 같은 식으로 사용하면, 3840 * 2160 해상도에서 30 fps로 캡처할 수 있었다고 합니다.

// https://github.com/pgurenko/DXGICaptureSample/issues/2

#include "stdafx.h"
#include "DXGIManager.h"
#include <time.h>

DXGIManager g_DXGIManager;

int capture(RECT& rcDim, vector<BYTE>& buf, CComPtr<IWICImagingFactory>& spWICFactory) {
  DWORD dwWidth = rcDim.right - rcDim.left;
  DWORD dwHeight = rcDim.bottom - rcDim.top;
  DWORD dwBufSize = buf.size();

  HRESULT hr = g_DXGIManager.GetOutputBits(buf.data(), rcDim);
  if (FAILED(hr))
  {
    printf("GetOutputBits failed with hr=0x%08x\n", hr);
    return hr;
  }
  return 0;
}

int _tmain(int argc, _TCHAR* argv[]) {
  CoInitialize(NULL);

  g_DXGIManager.SetCaptureSource(CSDesktop);

  RECT rcDim;
  g_DXGIManager.GetOutputRect(rcDim);

  DWORD dwWidth = rcDim.right - rcDim.left;
  DWORD dwHeight = rcDim.bottom - rcDim.top;

  printf("dwWidth=%d dwHeight=%d\n", dwWidth, dwHeight);

  DWORD dwBufSize = dwWidth*dwHeight * 4;

  vector<BYTE> buf(dwBufSize);

  CComPtr<IWICImagingFactory> spWICFactory = NULL;
  HRESULT hr = spWICFactory.CoCreateInstance(CLSID_WICImagingFactory);
  if (FAILED(hr))
    return hr;

  clock_t t1 = clock();
  int i;
  int iterations = 100;  
  for (i = 0; i < iterations; i++) {
    capture(rcDim, buf, spWICFactory);
  }
  clock_t t2 = clock();
  printf("%d iterations: %0.0f fps\n", iterations, iterations / ((double)(t2 - t1) / CLOCKS_PER_SEC));

  return 0;
}

하지만 실제로 해보면 iterations 중에 capture 함수 내의 "GetOutputBits failed with hr=0x887a0027" 호출 실패가 있기 때문에 이를 감안하면 성능이 더 낮을 것입니다. 그래도 이 정도면 훌륭하죠. ^^




참고로, 대충 보니까 중요한 코드는 DXGIOutputDuplication::AcquireNextFrame 내의 AcquireNextFrame 호출이었습니다.

HRESULT DXGIOutputDuplication::AcquireNextFrame(IDXGISurface1** pDXGISurface, DXGIPointerInfo*& pDXGIPointer)
{
    DXGI_OUTDUPL_FRAME_INFO fi;
    CComPtr<IDXGIResource> spDXGIResource;

    HRESULT hr = m_DXGIOutputDuplication->AcquireNextFrame(50, &fi, &spDXGIResource);
    if(FAILED(hr))
    {
        __L_INFO("m_DXGIOutputDuplication->AcquireNextFrame failed with hr=0x%08x", hr);
        return hr;
    }

    // ... [생략] ...
}

인자로 전달한 50은 50ms 시간 내에서 다음 화면 데이터를 얻어내라는 것입니다. 만약 그 시간 내에 데이터를 얻을 수 없었다면 hr == 0x887a0027 (The timeout value has elapsed and the resource is not yet available) 오류가 발생합니다. 따라서 저 시간 값을 너무 낮추면 호출에 대한 오류가 쓸데없이 많이 발생할 수 있습니다.

그 외에, 모니터 3대인 경우에는 CSMonitor3 스위치 문을 다음과 같이 DXGIManager::GetOutputDuplication 함수에 추가하면 됩니다.

case CSMonitor3:
{
    int secondary = 0;
    // Return the first with !IsPrimary
    for (vector<DXGIOutputDuplication>::iterator iter = m_vOutputs.begin();
        iter != m_vOutputs.end();
        iter++)
    {
        DXGIOutputDuplication& out = *iter;
        if (!out.IsPrimary())
        {
            if (secondary == 0)
            {
                secondary = 1;
                continue;
            }

            outputs.push_back(out);
            break;
        }
    }
}
break;




위의 소개 글에서 pgurenko/DXGICaptureSample로 공개된 캡처 예제를 버퍼 그대로 Bitmap으로 바꿔 화면에 출력하는 코드를 다음과 같이 작성해 봤습니다.

#include "stdafx.h"
#include "DXGIManager.h"
#include <time.h>

DXGIManager g_DXGIManager;

LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

WNDCLASSEX g_wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"Foo", NULL };
HWND g_hWnd;
HBITMAP g_hbm;
int hDC;
int g_screenX, g_screenY;

int _tmain(int argc, _TCHAR* argv[])
{
    CoInitialize(NULL);

    g_DXGIManager.SetCaptureSource(CaptureSource::CSMonitor1);

    RECT rcDim;
    g_DXGIManager.GetOutputRect(rcDim);

    DWORD dwWidth = rcDim.right - rcDim.left;
    DWORD dwHeight = rcDim.bottom - rcDim.top;

    g_screenX = dwWidth;
    g_screenY = dwHeight;

    RegisterClassEx(&g_wc);
    g_hWnd = CreateWindow(L"Foo", L"Foo", WS_OVERLAPPEDWINDOW, 0, 0, dwWidth, dwHeight, NULL, NULL, g_wc.hInstance, NULL);
    ::ShowWindow(g_hWnd, SW_SHOW);

    printf("dwWidth=%d dwHeight=%d\n", dwWidth, dwHeight);

    HDC hdcWin = GetDC(g_hWnd);
    RECT rc = { 0, 0, dwWidth, dwHeight };
    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
    bmi.bmiHeader.biWidth = dwWidth;
    bmi.bmiHeader.biHeight = -dwHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    RGBQUAD *prgbBits;
    g_hbm = CreateDIBSection(hdcWin, &bmi, DIB_RGB_COLORS, &reinterpret_cast<void*&>(prgbBits), NULL, 0);

    DWORD dwBufSize = dwWidth*dwHeight * 4;

    vector<BYTE> buf(dwBufSize);
    BYTE *pBuf = buf.data();

    MSG msg;

    while (1)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE)
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            HRESULT hr = g_DXGIManager.GetOutputBits(pBuf, rcDim);

            if (hr != S_OK)
            {
                continue;
            }

            memcpy(prgbBits, pBuf, dwWidth * dwHeight * 4);
            ::InvalidateRect(g_hWnd, nullptr, TRUE);
        }
    }

    CoUninitialize();

    return msg.wParam;
}

LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_CREATE)
    {
        return 0;
    }

    if (msg == WM_PAINT)
    {
        PAINTSTRUCT paint;
        HDC hDC = ::BeginPaint(hWnd, &paint);

        HDC hdcMem = CreateCompatibleDC(hDC);
        HBITMAP hbmPrev = (HBITMAP)::SelectObject(hdcMem, g_hbm);

        BitBlt(hDC, 0, 0, g_screenX, g_screenY, hdcMem, 0, 0, SRCCOPY);
        
        SelectObject(hdcMem, hbmPrev);
        DeleteDC(hdcMem);

        ::EndPaint(hWnd, &paint);
        return 0;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

첨부 파일은 위의 예제 코드를 포함합니다.

실제로 실행해서 프로그램을 돌려보면... 부드러운 캡처 성능에 반하실 것입니다. ^^




시간 있으시면 다음의 글도 보시면 좋겠지요. ^^

Desktop Screen Capture on Windows via Windows Desktop Duplication API with Drawing of Cursor's Image
; https://www.codeproject.com/Tips/1116253/Desktop-Screen-Capture-on-Windows-via-Windows-Desk

DXGI desktop duplication sample
; https://code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 12/14/2017 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 



2018-06-17 05시05분
[지팡이] 잘 읽고갑니당
[손님]
2019-03-27 04시08분
[여우] DXGIManager에서 형변환 오류가 뜨는데 어떻게 해야하나요..?
2017 사용중입니다
[손님]
2019-03-27 09시27분
DXGIManager의 어느 코드에서 형 변환 오류가 뜬다는 거죠? 그리고 이 글에 첨부한 예제 코드로 실행했을 때 발생하는 건가요?
정성태

... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
11421정성태1/2/20182558오류 유형: 443. Visual Studio - nuget configuration is invalid
11420정성태12/30/20174186.NET Framework: 722. C# - Windows 10 운영체제의 데스크톱 앱에서 음성인식(SpeechRecognizer) 사용하는 방법 [2]파일 다운로드1
11419정성태12/23/20174742.NET Framework: 721. WebClient 타입의 ...Async 메서드 호출은 왜 await + 동기 호출 시 hang 현상이 발생할까요? [2]파일 다운로드1
11418정성태1/10/20186006.NET Framework: 720. 비동기 메서드 내에서 await 시 ConfigureAwait 호출 의미파일 다운로드1
11417정성태12/22/20173322.NET Framework: 719. Task를 포함하는 async 메서드의 동작 방식
11416정성태12/21/20173168.NET Framework: 718. AsyncTaskMethodBuilder.Create() 메서드 동작 방식
11415정성태12/22/20173993.NET Framework: 717. Task를 포함하지 않는 async 메서드의 동작 방식 [1]
11414정성태1/10/20184192.NET Framework: 716. async 메서드의 void 반환 타입 사용에 대하여파일 다운로드1
11413정성태12/20/20174838개발 환경 구성: 344. 윈도우 10 - TTS 및 음성 인식을 위한 환경 설정
11412정성태12/20/20174817.NET Framework: 715. C# - Windows 10 운영체제의 데스크톱 앱에서 TTS(SpeechSynthesizer) 사용하는 방법 [1]파일 다운로드1
11411정성태8/9/20183918사물인터넷: 15. 라즈베리 파이 용 C++ 프로젝트에 SSL Socket 적용
11410정성태12/20/20179636.NET Framework: 714. SSL Socket 예제 - C/C++ 서버, C# 클라이언트파일 다운로드1
11409정성태6/6/201912595VC++: 122. 오픈 소스 라이브러리를 쉽게 빌드해 주는 "C++ Package Manager for Windows: vcpkg" [4]
11408정성태12/18/20173548.NET Framework: 713. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV (2)파일 다운로드1
11407정성태12/18/20174676.NET Framework: 712. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV [1]파일 다운로드1
11406정성태12/17/201713035.NET Framework: 711. C# - OpenCvSharp의 Mat 데이터 조작 방법 [5]파일 다운로드1
11405정성태12/17/20178502.NET Framework: 710. C# - OpenCvSharp을 이용한 Webcam 영상 처리 + Direct2D파일 다운로드1
11404정성태12/16/20176842.NET Framework: 709. C# - OpenCvSharp을 이용한 동영상(avi, mp4, ...) 처리 + Direct2D파일 다운로드1
11403정성태12/16/20178548.NET Framework: 708. C# - OpenCvSharp을 이용한 동영상(avi, mp4, ...) 처리 [3]파일 다운로드1
11402정성태12/16/201712934.NET Framework: 707. OpenCV 응용 프로그램을 C#으로 구현 - OpenCvSharp [2]파일 다운로드1
11401정성태12/15/20175599.NET Framework: 706. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력파일 다운로드1
11400정성태12/14/20175552.NET Framework: 705. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 [3]파일 다운로드1
11399정성태12/13/20172589.NET Framework: 704. Win32 API의 UnionRect를 닷넷 BCL의 Rectangle.Union으로 바꿀 때 주의 사항
11398정성태12/13/20172985오류 유형: 442. ASP.NET Core Web Application (on .NET Framework) 프로젝트에서 외부 라이브러리 동적 로드 시 런타임 버전 문제파일 다운로드1
11397정성태12/12/20174051.NET Framework: 703. 양자 컴퓨팅을 위한 마이크로소프트의 Q# 언어
11396정성태12/8/201712616개발 환경 구성: 343. Visual Studio - 리눅스 용 프로젝트의 인텔리센스를 위한 헤더 파일 처리 방법 [3]
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...