視訊直播:Windows中各類畫面源的擷取和合成方法總結

網易雲信發表於2018-08-15
當今,視訊直播技術和實時音視訊技術已經是很多行業必備,典型的應用場景有教育直播、遠端視訊會議、網際網路娛樂等。在移動端發起直播,其畫面源的種類是十分有限的,無非是取攝像頭、截圖等。PC端由於其系統資源充足,應用程式豐富,畫面源種類多樣,更適合作為主播程式執行的平臺。在實際應用中,經常有一些場景是需要將不同的畫面源合在一起,然後推流出去的。本文粗淺介紹一些網易雲信在開發過程中總結的一些獲取不同畫面源的畫面並將其合併的方法。

相關閱讀推薦

如何快速實現移動端短視訊功能?

視訊私有云實戰:基於Docker構建點播私有云平臺

各類畫面源的擷取

1. 攝像頭畫面

Windows下采集攝像頭畫面,DShow是最常用的方法之一。通過DShow採集攝像頭資料,建立視訊採集Filter,將其加入到圖表IGraphBuilder中,用IMediaControl介面來控制流媒體在Filter Graph中的流動,再通過Render來獲取視訊的原始資料。以上流程封裝在了我們的SDK中,使用者可以直接呼叫SDK介面。

2. 桌面取屏及應用程式視窗擷取

在Windows系統中,桌面和所有應用程式視窗一樣,本身也是一個HWND視窗,因此可以放在一起討論。獲取一個視窗的點陣圖資料,最常用的方法是:建立一個用來接收視窗畫面的HBITMAP點陣圖物件以及一個HDC裝置上下文物件,用SelectObject將兩者繫結,然後用BitBlt從被擷取視窗的HDC將資料拷貝到目標HDC。下面列出關鍵程式碼:

視訊直播:Windows中各類畫面源的擷取和合成方法總結

3. 其他截圖/截視窗方法

教育直播中,PPT分享是非常重要的一個場景。但是據我考查,自從Microsoft Office 2013之後,BitBlt就取不到Word、Excel、PPT視窗的內容了,截到的是一片白色。但是用PrintWindow這個Windows API卻可以取到。呼叫PrintWindow的程式會收到WM_PRINT或WM_PRINTCLIENT訊息。PrintWindow的效率比BitBlt低,但當BitBlt無法取到時,可以用PrintWindow。

越來越多的程式的畫面是在視訊記憶體中的,此時,BitBlt和PrintWindow都不管用(得到的都是一塊黑色的點陣圖)。可以考慮用DirectX的方法。而且DirectX方法由於使用了GPU,所以相較前面兩種方法效率更高。以下是DirectX截圖的程式碼:

視訊直播:Windows中各類畫面源的擷取和合成方法總結

4. 獲取本地圖片的點陣圖資料

將本地圖片(jpg、bmp、png、gif等格式)載入到記憶體,並取得其點陣圖控制程式碼或畫素首地址的方法有很多種。這裡列舉幾種最常見的。

GdiPlus方法比較簡單。首先是通過圖片路徑建立一個Gdiplus::Bitmap物件,通過Gdiplus::Bitmap::LockBits()方法可以得到圖片的資料,存放在一個Gdiplus::BitmapData結構中。Gdiplus::BitmapData::Scan0就是圖片畫素資料的首地址。如果想得到該圖片的HBITMAP控制程式碼,只需調Gdiplus::Bitmap::GetHBITMAP()即可。

另一種方法是使用Windows API LoadImage來載入一個本地bmp圖片得到HBITMAP控制程式碼,但這種方法似乎只能載入點陣圖檔案(.bmp格式)。使用ATL的CImage只需要3行程式碼即可得到一個圖片檔案的HBITMAP控制程式碼。

視訊直播:Windows中各類畫面源的擷取和合成方法總結

畫面合成

主播常常希望同時將自己的攝像頭畫面和桌面內容或者某個程式的畫面共享給觀眾,有時甚至需要同一時刻分享10個以上的畫面源。這時候,需要將多個畫面貼上到一個目標畫面上,我們稱這個過程為畫面合成。合成的畫面通常還要支援改變各個畫面的尺寸、位置等操作。這樣一來,程式效能成了瓶頸問題。

首先,對於各種畫面源的擷取應該儘量採用高效的方式,其次,畫面的拉伸壓縮是比較耗效能的地方。在1秒鐘需要合成20幀畫面的要求下,應該避免直接強行壓縮HBITMAP,而是採用一些有加速的方案。

1. LibYuv方案

我們找到一個一個yuv庫(LibYuv Project),支援圖形資料從rgb格式到各種yuv格式之間的互相轉換(定義在libyuv/convert.h中)。比較重要的一點是,它對yuv格式圖形的拉伸和壓縮以及其他各種變換(定義在libyuv/scale.h中)是有加速的。正好我們最終要推流的格式也是yuv格式的,所以我們方案的流程是:取得各個畫面源的畫面之後,先將它們各自轉化為yuv格式,然後把這些yuv畫面按照我們制定的方式貼上到一個目標yuv畫面上,最後將目標yuv畫面資料推流出去。另外,由於主播的視窗上也要顯示合併畫面,所以還要把目標畫面轉成rgb格式渲染到視窗HDC上。

當然,由於存在rgb格式和yuv格式之間反覆的轉換以及頻繁的scale,而且yuv加速畢竟是軟體方式,程式的CPU佔用率還是有點高。如果能採用DirectX、OpenGL等硬體加速解決方案,對程式效能以及使用者體驗的提升應該是比較明顯的。

2. DirectX 9方案

在DirectX 9方案中,我們的每個畫面源以及最終的目標合成畫面,都對應一個表面(IDirect3DSurface9)和一個紋理(IDirect3DTexture9)。

由於畫面源的顏色記憶體可能會被頻繁訪問和修改,所以建立其表面或紋理時,應該將其建立在系統記憶體或AGP中(D3DPOOL_MANAGED)而不是視訊記憶體中。對於yuv格式的攝像頭資料或網路視訊幀,DirectX可以建立能直接接受yuv資料的紋理(D3DFMT_UYVY)。合成的時候,呼叫IDirect3DDevice9::DrawPrimitive()來將每個畫面源繪製到目標畫面上。

而最終合成畫面是要顯示到視窗上的,所以應該建立在視訊記憶體中(D3DPOOL_DEFAULT)。渲染的時候,呼叫IDirect3DDevice9::DrawPrimitive()將目標畫面的紋理繪製到視窗的渲染目標紋理上,或者呼叫IDirect3DDevice9::StretchRect()將目標畫面的表面貼上到視窗的back buffer上。

另外,由於要取得目標畫面的資料用於推流,我們還要呼叫IDirect3DDevice9::CreateOffscreenPlainSurface()在系統記憶體中(D3DPOOL_SYSTEMMEM)建立一個離屏表面,用IDirect3DDevice9::GetRenderTargetData()將目標畫面取到離屏表面上,然後IDirect3DSurface9::LockRect()就能得到目標畫面的rgb格式資料了,將其轉化為yuv格式就可以推流出去了。

總 結

直播產品由於需要對每一幀畫面做處理,畫面的清晰度要高,幀率還不能太低,所以通常會存在消耗系統資源過多的問題。無論是取畫面還是合成畫面,方法有很多,不僅限於上面幾種。Win API效率一般,如果對程式效能要求高,就要在其他方面去想法設法減少資源消耗。而DirectX雖然對2D圖形加速不如3D加速那麼顯著,但還是勝過Win API的。需要注意的是,使用DirectX時要非常清楚各個引數的意義,比如裝置型別(D3DDEVTYPE)、記憶體池型別(D3DPOOL)、用途型別(D3DUSAGE)等等。引數用錯,可能導致其效能還不如Win API。

以上就是視訊直播中Windows中各類畫面源的擷取和合成方法總結。


另外,想要獲取更多產品乾貨、技術乾貨,記得關注網易雲信部落格



相關文章