在windows系統(tǒng)中,抓取當(dāng)前桌面的屏幕有很多方法,比較常用的是GDI和mirror兩種方式,除此以外,利用ddraw和dxgi(windows7以上系統(tǒng)支持)方式也可以抓取屏幕。由于mirror的方式牽扯到驅(qū)動(dòng),并且也不是所有系統(tǒng)都支持,本文不會(huì)介紹這種抓屏方式,這里將著重介紹GDI, DDRAW和DXGI這三種抓屏方式,并給出其相應(yīng)的實(shí)現(xiàn)代碼。
GDI抓屏
這種方式比較通用,所有的windows版本都支持此方式抓屏,而且不依賴其他API,僅僅使用user32中的API即可完成抓屏;但是這種抓屏方式相對(duì)比較慢,抓取一幀1080p的桌面需要5~8ms左右的時(shí)間。單純的從抓屏來(lái)說(shuō),這個(gè)時(shí)間還是可以接受的,但是抓屏后往往需要進(jìn)行很多圖像處理,所以這個(gè)用時(shí)就顯得不那么友好了。而且在Vista以后的系統(tǒng)上,如果啟用Aero特效的話,抓取一幀的用時(shí)會(huì)增加到300~500ms,基本上屬于不可用的級(jí)別。
下面給出實(shí)現(xiàn)代碼:
HDC m_hMemDC, m_hRootDC;HBITMAP m_hBitmapMem;DEVMODE m_origDevMode;BITMAPINFO m_bitmapInfo;void *m_pvBits;int m_iWidth = 1920,m_iHeight = 1080;BOOL InitBitmapInfo(void){ if (m_hRootDC) { return FALSE; } m_hRootDC = CreateDC(("DISPLAY"), NULL, NULL, NULL); if (!m_hRootDC) { return FALSE; } m_hMemDC = CreateCompatibleDC(m_hRootDC); if (!m_hMemDC) { return FALSE; } m_hBitmapMem = CreateCompatibleBitmap(m_hRootDC, m_iWidth, m_iHeight); if (!m_hBitmapMem) { return FALSE; } return TRUE;}BOOL CreateDIBBuffers(void){ m_bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); m_bitmapInfo.bmiHeader.biBitCount = 0; if (GetDIBits(m_hMemDC, m_hBitmapMem, 0, 1, NULL, &m_bitmapInfo, DIB_RGB_COLORS) == 0) { return FALSE; } m_bitmapInfo.bmiHeader.biComPRession = BI_RGB; m_bitmapInfo.bmiHeader.biHeight = -abs(m_bitmapInfo.bmiHeader.biHeight); // 因?yàn)樽ト〉膱D片是上下顛倒的,所以這里需要將高度顛倒一下 HBITMAP hBitmapTmp = CreateDIBSection(m_hMemDC, &m_bitmapInfo, DIB_RGB_COLORS, &m_pvBits, NULL, 0); if (!hBitmapTmp) { ("Can not use fast blit!/n"); } if (m_hBitmapMem != NULL) { DeleteObject(m_hBitmapMem); m_hBitmapMem = NULL; } m_hBitmapMem = hBitmapTmp; return TRUE;}void ByteAlign(RECT &rect){ int nWidth = rect.right - rect.left; int nHeight = rect.bottom - rect.top; // // 對(duì)齊4字節(jié)邊界 // 因?yàn)镚DI的原因,分辨率必須要是4字節(jié)的邊界,否則抓出來(lái)的圖像重新渲染的話, // 會(huì)出現(xiàn)花屏現(xiàn)象,這里處理的目的就是為了保證是4字節(jié)的邊界。——yshen on 2012-11-25 // int nMod = nWidth % 4; if (nMod) { rect.left += (nMod / 2); rect.right -= (nMod / 2); } nMod = nHeight % 4; if (nMod) { rect.top += (nMod / 2); rect.bottom -= (nMod / 2); }}int CaptureScreen(const RECT &rect){ ByteAlign(rect); // 檢測(cè)和啟動(dòng)時(shí)的分辨率是否一致,如果不一致,則停止抓屏 INT nWidth = GetSystemMetrics(SM_CXSCREEN); INT nHeight = GetSystemMetrics(SM_CYSCREEN); if ((m_iWidth != nWidth) && (m_iHeight != nHeight)) { return -1; } GdiFlush(); HBITMAP hOldBitmap = (HBITMAP)SelectObject(m_hMemDC, m_hBitmapMem); if (hOldBitmap) { BitBlt(m_hMemDC, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, m_hRootDC, rect.left, rect.top, SRCCOPY | CAPTUREBLT); SelectObject(m_hMemDC, hOldBitmap); } return 0;}BOOL Init(void){ if (!InitBitmapInfo()) { return FALSE; } if (!CreateDIBBuffers()) { return FALSE; } return TRUE;}調(diào)用示例:
// 初始化抓屏 init(); // 抓取一幀 RECT rect(0, 0, 1920, 1080); CaptureScreen(rect); DDRAW抓屏
這種方式抓屏速度比GDI要快一些,抓取一幀圖像大概需要4~5ms左右,但是缺點(diǎn)也和GDI一樣,如果啟用了特效,速度也非常慢,基本也在400ms以上。
實(shí)現(xiàn)代碼:
LPDIRECTDRAW m_lpDDraw;LPDIRECTDRAWSURFACE m_lpDDSPrime;LPDIRECTDRAWSURFACE m_lpDDSBack;DDSURFACEDESC m_DDSdesc;BOOL Init(){ HMODULE hDll = LoadLibrary("ddraw.dll"); if (hDll == NULL) { ("無(wú)法載入ddraw.dll/n"); return FALSE; } // 載入ddraw的導(dǎo)入函數(shù) PFN_DirectDrawCreate DirectDrawCreateFunc = (PFN_DirectDrawCreate)GetProcAddress(hDll, "DirectDrawCreate"); if (DirectDrawCreateFunc == NULL) { ("無(wú)法找到訪問(wèn)點(diǎn):DirectDrawCreate/n"); return FALSE; } HRESULT hr = DirectDrawCreateFunc(NULL, &m_lpDDraw, NULL); if (FAILED(hr)) { ("DirectDrawCreate失敗/n"); return FALSE; } hr = m_lpDDraw->SetCoOperativeLevel(NULL, DDSCL_NORMAL); if (FAILED(hr)) { ("SetCooperativeLevel失敗/n"); return FALSE; } DDSURFACEDESC DDSdesc; ZeroMemory(&DDSdesc, sizeof(DDSdesc)); DDSdesc.dwSize = sizeof(DDSdesc); DDSdesc.dwFlags = DDSD_CAPS; DDSdesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; hr = m_lpDDraw->CreateSurface(&DDSdesc, &m_lpDDSPrime, NULL); if (FAILED(hr)) { ("CreateSurface 主表面失敗/n"); return FALSE; } ZeroMemory(&DDSdesc, sizeof(DDSdesc)); DDSdesc.dwSize = sizeof(DDSdesc); DDSdesc.dwFlags = DDSD_ALL; hr = m_lpDDSPrime->GetSurfaceDesc(&DDSdesc); if (FAILED(hr)) { ("GetSurfaceDesc失敗/n"); return FALSE; } // 備份描述信息 memcpy(&m_DDSdesc, &DDSdesc, sizeof(DDSdesc)); DDSdesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH; DDSdesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; hr = m_lpDDraw->CreateSurface(&DDSdesc, &m_lpDDSBack, 0); if (FAILED(hr)) { ("CreateSurface 后備表面失敗/n"); return FALSE; } return TRUE;}BOOL CaptureImage(RECT &rect, void *pData, INT &nLen){ if (m_lpDDSBack == NULL) { ("DDraw對(duì)象未初始化/n"); return FALSE; } HRESULT hr = m_lpDDSBack->BltFast(rect.left, rect.top, m_lpDDSPrime, &rect, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT); if (FAILED(hr)) { ("BltFast失敗/n"); return FALSE; } DDSURFACEDESC surfDesc; ZeroMemory(&surfDesc, sizeof(surfDesc)); surfDesc.dwSize = sizeof(surfDesc); hr = m_lpDDSBack->Lock(&rect, &surfDesc, DDLOCK_READONLY | DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR , NULL); if (FAILED(hr)) { ("Lock失敗/n"); return FALSE; } // 這里拷貝的是32位數(shù)據(jù) memcpy(pBuf, (BYTE*)surfDesc.lpSurface, surfDesc.dwWidth * surfDesc.dwHeight * surfDesc.ddpfPixelFormat.dwRGBBitCount / 8); m_lpDDSBack->Unlock(surfDesc.lpSurface); return TRUE;} DXGI抓屏
這種抓屏方式,速度非常快,通常一幀圖像能夠在2~3ms內(nèi)完成,而且即使啟用了Aero特效,抓屏效率也一樣。尤其在windows10以后的系統(tǒng)上,當(dāng)桌面沒(méi)有變化時(shí),你是抓取不到任何圖像的,只有在桌面有變化時(shí),你才能抓取到圖像。這會(huì)帶來(lái)更高的抓屏效率和更少的系統(tǒng)開(kāi)銷(xiāo)。當(dāng)然,它也不是完美的,它只能在vista以上的系統(tǒng)上才可以使用,老舊的xp是不支持這種新技術(shù)的。
實(shí)現(xiàn)代碼:
ID3D11Device *m_hDevice;ID3D11DeviceContext *m_hContext;IDXGIOutputDuplication *m_hDeskDupl;DXGI_OUTPUT_DESC m_dxgiOutDesc;BOOL Init(){ HRESULT hr = S_OK; // 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; } return TRUE;}BOOL AttatchToThread(VOID){ HDESK hCurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL); if (!hCurrentDesktop) { return FALSE; } // Attach desktop to this thread BOOL bDesktopAttached = SetThreadDesktop(hCurrentDesktop); CloseDesktop(hCurrentDesktop); hCurrentDesktop = NULL; return bDesktopAttached;}BOOL VideoDXGICaptor::QueryFrame(void *pImgData, INT &nImgSize){ if (!AttatchToThread()) { return FALSE; } nImgSize = 0; IDXGIResource *hDesktopResource = NULL; DXGI_OUTDUPL_FRAME_INFO FrameInfo; HRESULT hr = m_hDeskDupl->AcquireNextFrame(500, &FrameInfo, &hDesktopResource); if (FAILED(hr)) { // // 在一些win10的系統(tǒng)上,如果桌面沒(méi)有變化的情況下, // 這里會(huì)發(fā)生超時(shí)現(xiàn)象,但是這并不是發(fā)生了錯(cuò)誤,而是系統(tǒng)優(yōu)化了刷新動(dòng)作導(dǎo)致的。 // 所以,這里沒(méi)必要返回FALSE,返回不帶任何數(shù)據(jù)的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); hStagingSurf->Unmap(); } RESET_OBJECT(hStagingSurf); return SUCCEEDED(hr);}DXGI只能使用vs2012以上的IDE才可以編譯。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注