성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)</h1> <p> 화면 캡처 글을 검색해 보면 다양한 글이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Various methods for capturing the screen ; <a target='tab' href='http://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen'>http://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen</a> </pre> <br /> 일단 위의 글에서는 3가지 방법을 소개하는데 그중 DirectX 예제만 따라 해 보았지만 Windows 10에서 동작하지 않았습니다. 왜냐하면 GetFrontBufferData 호출에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > hr = g_pd3dDevice->GetFrontBufferData(0, pSurface); // hr == 0x8876086c (D3DERR_INVALIDCALL) </pre> <br /> D3DERR_INVALIDCALL 오류가 발생하는데 이유를 모르겠습니다. (혹시 아시는 분 덧글 부탁드립니다.)<br /> <br /> 게다가 d3dx9tex.h 헤더 파일 및 d3dx9.lib 파일이 없다고 오류가 발생하는데 이를 정상적으로 컴파일하기 위해서는 다음의 과정을 거쳐야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DXSDK_Jun10.exe 설치 시 "Error Code: S1023" 오류 해결하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1195'>http://www.sysnet.pe.kr/2/0/1195</a> </pre> <br /> 귀찮으니 ^^; 다음 캡처 방식으로 넘어가 보겠습니다. Windows 8부터 지원하는 DXGI를 이용하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DXGI fast screen capture ; <a target='tab' href='http://www.pavelgurenko.com/2013/12/dxgi-outputs-enumeration-and-fast.html'>http://www.pavelgurenko.com/2013/12/dxgi-outputs-enumeration-and-fast.html</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> 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). <br /> </div><br /> <br /> 속도는 물론이고 일반적인 GDI API로는 할 수 없는 DirectX 게임 화면까지도 안정적으로 캡처할 수 있다고 합니다. 친절하게도 위의 블로그를 쓴 사람이 github에 소스 코드도 공개한 것이 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > pgurenko/DXGICaptureSample ; <a target='tab' href='https://github.com/pgurenko/DXGICaptureSample'>https://github.com/pgurenko/DXGICaptureSample</a> </pre> <br /> 또한 다음과 같은 식으로 사용하면, 3840 * 2160 해상도에서 30 fps로 캡처할 수 있었다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 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++) { <span style='color: blue; font-weight: bold'>capture(rcDim, buf, spWICFactory);</span> } clock_t t2 = clock(); printf("%d iterations: %0.0f fps\n", iterations, iterations / ((double)(t2 - t1) / CLOCKS_PER_SEC)); return 0; } </pre> <br /> <span style='text-decoration: line-through'>하지만 실제로 해보면 iterations 중에 capture 함수 내의 "GetOutputBits failed with hr=0x887a0027" 호출 실패가 있기 때문에 이를 감안하면 성능이 더 낮을 것입니다. 그래도 이 정도면 훌륭하죠. ^^</span><br /><br /> <a name='acquire_next_frame'></a> <br /> 여기서 한 가지 중요한 점은, GetOutputBits 함수 내에서 호출하는 IDXGIOutputDuplication::AcquireNextFrame의 동작 방식입니다.<br /> <br /> AcquireNextFrame은, 캡처 대상이 되는 화면에서 변화가 있어야 그것을 이미지 데이터로 반환합니다. 만약 지정한 시간 내에 변화가 없으면 DXGI_ERROR_WAIT_TIMEOUT 값을 반환합니다. 상당히 효율적이죠? ^^<br /> <br /> 실제로, 30fps 동영상을 바탕화면에서 재생하면서 IDXGIOutputDuplication::AcquireNextFrame을 호출하면 1초에 30번 정도 호출이 됩니다. 참고로, 바탕화면의 변화에는 (실제 캡처된 이미지에는 나오지 않지만) 마우스 커서 움직임도 포함이 됩니다.<br /> <br /> DXGIOutputDuplication::AcquireNextFrame 내의 AcquireNextFrame 호출을 좀 더 살펴볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HRESULT DXGIOutputDuplication::AcquireNextFrame(IDXGISurface1** pDXGISurface, DXGIPointerInfo*& pDXGIPointer) { DXGI_OUTDUPL_FRAME_INFO fi; CComPtr<IDXGIResource> spDXGIResource; HRESULT hr = m_DXGIOutputDuplication-><span style='color: blue; font-weight: bold'>AcquireNextFrame(50, &fi, &spDXGIResource);</span> if(FAILED(hr)) { __L_INFO("m_DXGIOutputDuplication->AcquireNextFrame failed with hr=0x%08x", hr); return hr; } // ... [생략] ... } </pre> <br /> 인자로 전달한 50은 50ms 시간 내에서 다음 화면 데이터를 얻어내라는 것입니다. 만약 그 시간 내에 데이터를 얻을 수 없었다면 hr == 0x887a0027 (The timeout value has elapsed and the resource is not yet available) 오류가 발생합니다. 따라서 저 시간 값을 너무 낮추면 (변화가 없는 화면인 경우) 호출에 대한 오류가 쓸데없이 많이 발생할 수 있습니다.<br /> <br /> 그 외에, 모니터 3대인 경우에는 CSMonitor3 스위치 문을 다음과 같이 DXGIManager::GetOutputDuplication 함수에 추가하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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; </pre> <br /> <hr style='width: 50%' /><br /> <br /> 위의 소개 글에서 <a target='tab' href='https://github.com/pgurenko/DXGICaptureSample'>pgurenko/DXGICaptureSample</a>로 공개된 캡처 예제를 버퍼 그대로 Bitmap으로 바꿔 화면에 출력하는 코드를 다음과 같이 작성해 봤습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #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; <span style='color: blue; font-weight: bold'>RGBQUAD *prgbBits;</span> g_hbm = CreateDIBSection(hdcWin, &bmi, DIB_RGB_COLORS, &reinterpret_cast<void*&>(<span style='color: blue; font-weight: bold'>prgbBits</span>), NULL, 0); DWORD dwBufSize = dwWidth*dwHeight * 4; <span style='color: blue; font-weight: bold'>vector<BYTE> buf(dwBufSize); BYTE *pBuf = buf.data();</span> MSG msg; while (1) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE) { if (msg.message == WM_QUIT) { break; } TranslateMessage(&msg); DispatchMessage(&msg); } else { <span style='color: blue; font-weight: bold'>HRESULT hr = g_DXGIManager.GetOutputBits(pBuf, rcDim);</span> if (hr != S_OK) { continue; } <span style='color: blue; font-weight: bold'>memcpy(prgbBits, pBuf, dwWidth * dwHeight * 4); ::InvalidateRect(g_hWnd, nullptr, TRUE);</span> } } 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); <span style='color: blue; font-weight: bold'>BitBlt(hDC, 0, 0, g_screenX, g_screenY, hdcMem, 0, 0, SRCCOPY);</span> SelectObject(hdcMem, hbmPrev); DeleteDC(hdcMem); ::EndPaint(hWnd, &paint); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam); } </pre> <br /> <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1189&boardid=331301885'>첨부 파일은 위의 예제 코드를 포함</a>합니다.<br /> <br /> 실제로 실행해서 프로그램을 돌려보면... 부드러운 캡처 성능에 반하실 것입니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 시간 있으시면 다음의 글도 보시면 좋겠지요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Desktop Screen Capture on Windows via Windows Desktop Duplication API with Drawing of Cursor's Image ; <a target='tab' href='https://www.codeproject.com/Tips/1116253/Desktop-Screen-Capture-on-Windows-via-Windows-Desk'>https://www.codeproject.com/Tips/1116253/Desktop-Screen-Capture-on-Windows-via-Windows-Desk</a> DXGI desktop duplication sample ; <a target='tab' href='https://code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a'>https://code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a</a> The reboot of Coding4Fun ; <a target='tab' href='https://blogs.windows.com/windowsdeveloper/2015/12/21/the-reboot-of-coding4fun/'>https://blogs.windows.com/windowsdeveloper/2015/12/21/the-reboot-of-coding4fun/</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1569
(왼쪽의 숫자를 입력해야 합니다.)