很多软件都需要屏幕捕捉功能,在软件中实现屏幕捕捉也不是难事,在微软Windows平台,有很多截屏的方法,例如:BitBlt、Mirror driver、 GDI hook、DirectX、DWM/Dxgi hook、Desktop Duplication与GetWindowDC 等方法,但大多效率不高,效率高的 Mirror driver技术只能用于XP等老系统,在Windows8 与Windows 10 上似乎已经失效,Windows8以后微软引入了一套新的接口,叫“Desktop Duplication API”,应用程序可以通过这套API访问桌面数据。
Desktop Duplication API是通过Microsoft DirectX Graphics Infrastructure (DXGI)来提供桌面图像的,速度非常快。DXGI是通过GPU实现的,因此cpu占用率很低,性能非常高。 Duplication API获取到的桌面数据,不管显示模式如何设置,都永远是32位RGBA数据,这就给屏幕捕捉带来了很大的方便性,不再需要考虑各种显示模式的问题了。
要实现DXGI屏幕捕捉,基本流程如下:
1)创建D3DDevice;
2)通过一系列接口获取路径,获取到IDXGIOutputDuplication接口;
3)调用AcquireNextFrame,获取当前桌面数据,保存在IDXGIResource中;
4)把数据从GPU映射到内存中拷贝需要的数据到自己的buffer里。
其中,获取到IDXGIOutputDuplication接口,是通过如下路径:
IDXGIDevice -- IDXGIAdapter -- IDXGIOutput -- IDXGIOutput1 -- IDXGIOutputDuplication
真实实现DXGI屏幕捕捉的代码如下:
virtual BOOL CaptureImage(RECT rect, void *pData, INT nLen); virtual BOOL CaptureImage(void *pData, INT nLen); virtual BOOL ResetDevice();private: BOOL AttatchToThread(VOID); BOOL QueryFrame(void *pImgData, INT nImgSize); BOOL QueryFrame(void *pImgData, INT nImgSize, int z);private: IDXGIResource *zhDesktopResource; DXGI_OUTDUPL_FRAME_INFO zFrameInfo; ID3D11Texture2D *zhAcquiredDesktopImage; IDXGISurface *zhStagingSurf;private: BOOL m_bInit; int m_iWidth, m_iHeight; ID3D11Device *m_hDevice; ID3D11DeviceContext *m_hContext; IDXGIOutputDuplication *m_hDeskDupl; DXGI_OUTPUT_DESC m_dxgiOutDesc;
#define RESET_OBJECT(obj) { if(obj) obj- Release(); obj = NULL; }static BOOL g_bAttach = FALSE;VideoDXGICaptor::VideoDXGICaptor() m_bInit = FALSE; m_hDevice = NULL; m_hContext = NULL; m_hDeskDupl = NULL; ZeroMemory( m_dxgiOutDesc, sizeof(m_dxgiOutDesc));VideoDXGICaptor::~VideoDXGICaptor() Deinit();BOOL VideoDXGICaptor::Init() HRESULT hr = S_OK; if (m_bInit) return FALSE; // Driver types supported D3D_DRIVER_TYPE DriverTypes[] = D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE, UINT NumDriverTypes = ARRAYSIZE(DriverTypes); // Feature levels supported D3D_FEATURE_LEVEL FeatureLevels[] = D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels); D3D_FEATURE_LEVEL FeatureLevel; // Create D3D device for (UINT DriverTypeIndex = 0; DriverTypeIndex NumDriverTypes; ++DriverTypeIndex) hr = D3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels, NumFeatureLevels, D3D11_SDK_VERSION, m_hDevice, FeatureLevel, m_hContext); if (SUCCEEDED(hr)) break; if (FAILED(hr)) return FALSE; // Get DXGI device IDXGIDevice *hDxgiDevice = NULL; hr = m_hDevice- QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast void** ( hDxgiDevice)); if (FAILED(hr)) return FALSE; // Get DXGI adapter IDXGIAdapter *hDxgiAdapter = NULL; hr = hDxgiDevice- GetParent(__uuidof(IDXGIAdapter), reinterpret_cast void** ( hDxgiAdapter)); RESET_OBJECT(hDxgiDevice); if (FAILED(hr)) return FALSE; // Get output INT nOutput = 0; IDXGIOutput *hDxgiOutput = NULL; hr = hDxgiAdapter- EnumOutputs(nOutput, hDxgiOutput); RESET_OBJECT(hDxgiAdapter); if (FAILED(hr)) return FALSE; // get output description struct hDxgiOutput- GetDesc( m_dxgiOutDesc); // QI for Output 1 IDXGIOutput1 *hDxgiOutput1 = NULL; hr = hDxgiOutput- QueryInterface(__uuidof(hDxgiOutput1), reinterpret_cast void** ( hDxgiOutput1)); RESET_OBJECT(hDxgiOutput); if (FAILED(hr)) return FALSE; // Create desktop duplication hr = hDxgiOutput1- DuplicateOutput(m_hDevice, m_hDeskDupl); RESET_OBJECT(hDxgiOutput1); if (FAILED(hr)) return FALSE; // 初始化成功 m_bInit = TRUE; return TRUE;// #else // 小于vs2012,此功能不能实现 return FALSE;// #endifVOID VideoDXGICaptor::Deinit() if (!m_bInit) return; m_bInit = FALSE; if (m_hDeskDupl) m_hDeskDupl- Release(); m_hDeskDupl = NULL; if (m_hDevice) m_hDevice- Release(); m_hDevice = NULL; if (m_hContext) m_hContext- Release(); m_hContext = NULL;// #endifBOOL VideoDXGICaptor::AttatchToThread(VOID) if (g_bAttach) return TRUE; HDESK hCurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL); if (!hCurrentDesktop) return FALSE; // Attach desktop to this thread BOOL bDesktopAttached = SetThreadDesktop(hCurrentDesktop); CloseDesktop(hCurrentDesktop); hCurrentDesktop = NULL; g_bAttach = TRUE; return bDesktopAttached;BOOL VideoDXGICaptor::CaptureImage(RECT rect, void *pData, INT nLen) return QueryFrame(pData, nLen);BOOL VideoDXGICaptor::CaptureImage(void *pData, INT nLen) return QueryFrame(pData, nLen);BOOL VideoDXGICaptor::ResetDevice() Deinit(); return Init();BOOL VideoDXGICaptor::QueryFrame(void *pImgData, INT nImgSize) if (!m_bInit || !AttatchToThread()) return FALSE; nImgSize = 0; IDXGIResource *hDesktopResource = NULL; DXGI_OUTDUPL_FRAME_INFO FrameInfo; HRESULT hr = m_hDeskDupl- AcquireNextFrame(0, FrameInfo, hDesktopResource); if (FAILED(hr)) // 在一些win10的系统上,如果桌面没有变化的情况下,; // 这里会发生超时现象,但是这并不是发生了错误,而是系统优化了刷新动作导致的。; // 所以,这里没必要返回FALSE,返回不带任何数据的TRUE即可; return TRUE; // query next frame staging buffer ID3D11Texture2D *hAcquiredDesktopImage = NULL; hr = hDesktopResource- QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast void ** ( hAcquiredDesktopImage)); RESET_OBJECT(hDesktopResource); if (FAILED(hr)) return FALSE; // copy old description D3D11_TEXTURE2D_DESC frameDescriptor; hAcquiredDesktopImage- GetDesc( frameDescriptor); // create a new staging buffer for fill frame image ID3D11Texture2D *hNewDesktopImage = NULL; frameDescriptor.Usage = D3D11_USAGE_STAGING; frameDescriptor.CPUAccessFlags = D3D11_CPU_ACCESS_READ; frameDescriptor.BindFlags = 0; frameDescriptor.MiscFlags = 0; frameDescriptor.MipLevels = 1; frameDescriptor.ArraySize = 1; frameDescriptor.SampleDesc.Count = 1; hr = m_hDevice- CreateTexture2D( frameDescriptor, NULL, hNewDesktopImage); if (FAILED(hr)) RESET_OBJECT(hAcquiredDesktopImage); m_hDeskDupl- ReleaseFrame(); return FALSE; // copy next staging buffer to new staging buffer m_hContext- CopyResource(hNewDesktopImage, hAcquiredDesktopImage); RESET_OBJECT(hAcquiredDesktopImage); m_hDeskDupl- ReleaseFrame(); // create staging buffer for map bits IDXGISurface *hStagingSurf = NULL; hr = hNewDesktopImage- QueryInterface(__uuidof(IDXGISurface), (void **)( hStagingSurf)); RESET_OBJECT(hNewDesktopImage); if (FAILED(hr)) return FALSE; // copy bits to user space DXGI_MAPPED_RECT mappedRect; hr = hStagingSurf- Map( mappedRect, DXGI_MAP_READ); if (SUCCEEDED(hr)) // nImgSize = GetWidth() * GetHeight() * 3; // PrepareBGR24From32(mappedRect.pBits, (BYTE*)pImgData, m_dxgiOutDesc.DesktopCoordinates); // mappedRect.pBits; // am_dxgiOutDesc.DesktopCoordinates; memcpy((BYTE*)pImgData, mappedRect.pBits, m_dxgiOutDesc.DesktopCoordinates.right * m_dxgiOutDesc.DesktopCoordinates.bottom * 4); hStagingSurf- Unmap(); RESET_OBJECT(hStagingSurf); return SUCCEEDED(hr);}