在進入具體的方式討論前,我們先看看 Windows 桌面圖形界面的簡化架構,如下圖:
在 Windows Vista 之前,Windows 界面的復合畫面經由 Graphics Device Interface(以下簡稱 GDI)技術直接渲染到桌面上。
在 Windows Vista 以及之后的版本,Desktop Composition 的工作就交由一個新的模塊Desktop Window Manager(以下簡稱 DWM) 來完成了。
如上圖所示,應用程序畫完自己的界面后,提交給 DWM 把它合成到桌面上,而 DWM 經過一系列演進后,為了提效,基于微軟自己的 Direct3D(以下簡稱 D3D) 實現了整套技術,在 D3D 這層的下面是 Windows Display Driver Model(以下簡稱 WDDM,Windows 圖形驅動程序模型)。
? ? ? ? ? ? ? ? ? ? ? ? ? ??
所以在 Windows 下實現錄屏采集,基本上可以從最基本的 GDI 技術和 D3D 技術兩方面考慮。
GDI:第一代桌面采集
Windows 圖形設備接口(GDI)是為與設備無關的圖形設計的。基于 Windows 的應用程序不能直接訪問圖形硬件,應用程序通過 GDI 來與設備驅動程序進行交互。GDI 截圖就是通過屏幕的DC獲取到當前屏幕的位圖數據。
調用過程
GetDC(GetDesktokWindow() )獲取桌面的DC
然后使用CreateDIBSection創建一個設備無關位圖以及內存DC
使用BitBlt把桌面DC的復制到內存DC,這樣通過內存DC就能直接獲取到原始RGB數據。
基本采樣流程
在 Windows 平臺上有過圖形開發經驗的開發者,應該都知道 BitBlt 這個 API, 它為我們實現了 Windows DC 間的內容拷貝,假如將 Source DC 指定為 Window DC 或是 Destop DC,這就實現了對屏幕指定源的畫面截取。下圖說明了基于 GDI 技術錄屏采集的大致調用過程:
? ? ? ? ? ? ? ? ? ? ? ? ? ??
以整個桌面作為采集源舉例,通用做法是調用 GetDC(GetDesktokWindow()) 獲取桌面 DC,通過 CreateDIBSection 創建一個設備無關的位圖對象以及內存 DC,最后調用 BitBlt 把桌面 DC 的原始數據翻轉到內存 DC 上,這樣從內存 DC 上就能直接獲取到桌面的原始 RGB 數據。
需要注意的是,創建一個設備無關的位圖時,CreateDIBSection 的第4個參數 ppvBits 是提前分配好的位圖數據緩沖區。而在以實時視頻流的方式共享屏幕的場景下,需要以每秒十幾次甚至幾十次的頻率進行采樣,從效率的要求考慮,這里自然不可能每次都重新分配緩沖區,所以可以根據源的分辨率,在采集前就分配一個足夠大的空間。
CreateDIBSection的函數原型如下圖所示:
HBITMAP CreateDIBSection(HDC hdc,const BITMAPINFO *pbmi,UINT usage,VOID **ppvBits,HANDLE hSection,DWORD offset
);
????????經過以上基本采樣流程,得到的畫面內容是不含鼠標的,如果需要將鼠標還原到畫面中
CURSORINFO ci = { 0 };ci.cbSize = sizeof(ci);ZeroMemory(&ci, sizeof(CURSORINFO));ci.cbSize = sizeof(CURSORINFO);if (::GetCursorInfo(&ci)){POINT ptCursorPos = { 0, 0 };ptCursorPos = ci.ptScreenPos;::DrawIconEx(m_hCompDC,ptCursorPos.x , ptCursorPos.y,ci.hCursor,0, 0, 0, 0,DI_NORMAL | DI_DEFAULTSIZE | DI_COMPAT);}
優缺點
優點:GDI函數實現的通用做法,能在所有windows平臺實現
缺點:通用歸通用,截取的效率則是有點低,尤其是要達到每秒20幀以上的截取,占用CPU有點高,GDI不能獲取鼠標,需要在截取的圖像中把鼠標畫上去。
????????由于整體的運算、拷貝過程都在 CPU 中完成,導致采樣效率偏低,尤其在高頻采樣(> 20fps)時,對 CPU資源的消耗過高,而且后續還要處理為實時視頻流,經過同樣高頻的編碼、網絡包發送,多方面因素疊加,自然對機器性能有更高的要求。 GDI 則可以作為一種保底方案。
在具體實現中,還有兩點需要特別提醒:
1)在 Windows XP 下,可以通過 BitBlt 函數最后的參數,來控制是否拷貝 Layered Window。只有 SRCCPY 標識,表示拷貝內容不包含 Layered Window;如果是SRCCPY | CAPTUREBLT,則表示拷貝包括 Layered Window 在內的所有窗口。而這個標識,在 Windows Vista 之后的系統版本開啟 DWM 的情況下,已經無效,因為這種情況下所有的窗口都是 Layered Window;
2)在 Windows Vista 之后的系統版本開啟 DWM 的情況下,單次抓取速度變得非常慢(作者機器實測 30ms +)
且無法采集到使用gpu渲染的窗口,顯示黑屏:
DXGI:高性能桌面采集技術
DXGI(Microsoft DirectX Graphics Infrastructure)是微軟提供的一種可以在win8及以上系統使用的圖形設備接口。它負責枚舉圖形適配器、枚舉顯示模式、選擇緩沖區格式、在進程之間(例如,在應用程序和桌面窗口管理器(DWM)之間)共享資源,以及將呈現的幀傳給窗口或監視器以供顯示。其直接和硬件設備進行交互,具有很高的效率和性能。
基本采樣流程
除了用 GDI 技術實現錄屏,實際上在 Windows 平臺上,微軟提供了多種錄屏方案,相對 GDI 技術來說,其大多數接口的處理性能并不理想,或存在諸多限制,通用性不足。
從 Windows 8 開始,微軟引入了一套新技術叫 Desktop Duplication API,應用程序可以通過這套 API 請求桌面的圖形數據。由于 Desktop Duplication API 是通過 DirectX Graphics Infrastructure(以下簡稱 DXGI)來提供桌面圖像的,競爭的是 GPU 流水線資源,所以 CPU 占用率很低,采集性能非常高。
由于這套能力整合在 DirextX 中提供,所以與大部分 DirectX 接口的使用方式基本一致,其流程概括如下圖。
如圖所示,使用 DXGI 需要一些簡單的 DirectX 基礎知識,通過各種 DirectX COM 接口的查詢,最終獲取 IDXGIOutputDuplication 接口指針,截屏時使用其中核心的AcquireNextFrame API 獲取當前桌面圖像,此外,它還提供 GetFrameDirtyRects 等 API,可以獲取經過 GPU 計算后發生了變化的臟矩形區域。
繪制鼠標
和 GDI 面臨相同的問題,直接通過 AcquireNextFrame API 獲取到的畫面中,也是不含鼠標圖像的。想要將鼠標繪制到畫面中,我們需要和GDI相關的API配合使用。
在采集之前創建數據緩沖區時,將緩沖區關聯到設備無關的位圖上,并將位圖選入臨時的內存DC(一般由桌面DC生成的臨時內存DC),再將通過 AcquireNextFrame獲取的畫面拷貝到緩沖區后,這時可以使用GDI繪制鼠標的方法,將鼠標繪制到位圖上,這樣數據緩沖區中的圖像數據就包含了鼠標了。
優劣勢分析
在 Windows 平臺上,從現有的錄屏采集方式(包括GDI采集和放大鏡采集)來看,DXGI 是性能最好的。
其劣勢是只在 Windows 8 系統版本及以上才支持,所以在整體方案中,一般要與 GDI 共同組合提供。此外,它無法指定某個程序窗口進行采集
? 特點:Win Vista 以后支持,使用GPU直接處理紋理,效率最高;
? 缺點:根據Direct3D?版本不同,存在硬件的支持以及調用特性 的區別,且因為采集需要獲取設備的adapter,所以無法采集桌面窗口。、???????
? ? ? ? 如果用戶機器有A卡和N卡,出現了跨卡問題導致采集失效,禁用AMD顯卡后問題解決
Magnification:彎道超車的采集技術
Magnification API 使用于放大屏幕某個區域的 輔助應用技術,初衷是用于協助視力存在問題或者色弱的用戶能跟方便的看到桌面內容的api
? 特點:能實現放大縮小顏色轉換等操作,能過濾窗口
基本采樣流程
前面所述的兩種方式都可以實現錄屏采集,也是最常用的兩種方式。
但有時,我們要指定源來采集,并且希望采集到的畫面不被其他內容干擾。
在 Windows XP 時代,用 GDI 的 BitBlt API 進行采集時,指定一個 Window DC,并且對最后一個參數去掉 CAPTUREBLT 標識,即可排除掉其他 Layered Window 的干擾。但如今 Windows XP 已成過去式,這項措施無法解決現有系統的過濾問題,必須找到另一替代方案。
從 Windows Vista 開始,微軟新引入了一個新的 Magnification API(放大鏡效果),當我們將放大倍率設置成1(默認倍率就是1)亦可以用它來截取屏幕圖像。MSDN上提供了該庫的完整文檔。根據文檔,可以通過以下步驟簡單地完成錄屏采集過程:
在初始化相關模塊后,首先創建放大鏡控件的主窗口,并且將其設置為全屏不可見,因為我們要使用它來捕獲圖像,它只是一個工具,所以不能也不需要在用戶側顯示它。因此,設置窗口擴展屬性 WS_EX_LAYERED,調用 SetLayeredWindowAttributes 設置全透明。
::SetLayeredWindowAttributes(hwnd, 0 ,255, LWA_ALPHA);
接著創建放大鏡窗口作為主窗口的子窗口,窗口類名必須為“Magnifier”。如果要捕獲鼠標光標,還要設置窗口屬性為 MS_SHOWMAGNIFIEDCURSOR。
hwndMag = ::CreateWindow(WC_MAGNIFIER, TEXT("Magnifier"), WS_CHILD /*| MS_SHOWMAGNIFIEDCURSOR */| WS_VISIBLE,0, 0, m_ScreenX, m_ScreenY, hostDlg->GetSafeHwnd(), NULL, hInstance, NULL );
最關鍵的部分,利用 MagSetWindowFilterList 這個神奇的 API,它能夠指定一些窗口,在我們截取指定源目標時,從采集到的圖像中將 FilterList 中的窗口過濾掉,好像這些窗口根本沒有顯示一樣。這就是我們使用這放大鏡方案的主要原因。
那么如何獲得錄屏圖像呢?每當我們調用 MagSetWindowSource 時,都會觸發回調MagSetImageScalingCallback數據回調。原型如下:
BOOL MagImageScaling(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)
其中第二個參數 srcdata 就是指向錄屏結果圖像的原始數據,srcheader 則包含數據的長度,以及圖像長寬等元信息,至此,我們可以使用這兩個參數來構建位圖了。
Magnification
Window Graphics Capturer:新世代采集技術
WGC 全稱為 Windows Graphics Capture 是微軟目前主推的一個桌面/窗口采集技術,使用 D3D11 庫實現。該采集技術最早在Windows 10 18年3月份的更新中提供。WGC 對比放大鏡采集(Magnification Capture) 具有更高的性能、更低CPU及GPU消耗。但是在 使用方面比起其他采集方式會更復雜。
- 系統版本不低于10.0.17134.0 (Windows 10, version 1803)
- 相關接口均基于微軟的新一代運行時庫接口C++/WinRT,而且是最低要求 C++17
????????鼠標支持,自Windows 10, version 2004 (introduced in 10.0.19041.0)才開始支持捕獲鼠標。
GraphicsCaptureSession.IsCursorCaptureEnabled Property
????????黃色邊框去除,自Windows 10, version 2104 (introduced in 10.0.20348.0)才開始去除采集目標的黃色邊框。
GraphicsCaptureSession.IsBorderRequired
FFMPEG錄屏(15)---- WGC 捕獲桌面(三) WGC(Windows Graphics Capture)采集_ffmpeg采集桌面-CSDN博客
? 特點:效率高,拓展屏采集支持高,1080p采集消耗gpu達到個位數;
? 缺點:
1. 當Capture Session開始采集后,在剛開始采集的時候可能存在 HRESULT 為S_OK ,但是畫面數據為空,因為這個時候采集Engine可能處于啟動中狀態;
2. 當開始采集是,Windows會在采集源(窗口或桌面)區域增加一個黃色邊框去標識正在采集的區域,目前無法設置該邊框的樣式或者去除該邊框;
3. WGC 使用SetWindowDisplayAffinity 實現窗口過濾,但是設置的窗口必須為當前進程創建的子窗口才能設置成功,否則無法實現過濾。