開源項目OEIP 遊戲引擎與音影片多媒體(UE4/Unity3D)
- 2020 年 3 月 26 日
- 筆記
現開源一個項目 OEIP 項目實現的功能Demo展示
這個項目演示了在UE4中,接入攝像機通過OEIP直接輸出到UE4紋理上,並直接把UE4里的RenderTarget當做輸入源通過OEIP里GPU管線處理後推流出去,而另一邊Unity3D也是把RenderTarget當做輸入,用OEIP處理後推流,經過OEIP封裝signalR技術的直播SDK通知,二邊各自拉另一邊的流並通過OEIP相應管線直接輸出到Texture2D並顯示出來。演示的機器配置是i5-7500,8G記憶體,有二個推1080P,拉1080P流的處理,再加上生成截屏影片和yolov3-tiny神經網路識別,所以CPU有點吃不消。
這是我個人驗證一些技術所搭建的DEMO級方案,接入了基本的普通攝影機處理,也沒有提供穩定的直播供應商的實現,一些基本的影像處理,推拉流也只支援422P/420P格式。但是我自己還是花了大量業餘時間在這方案上,並以及大熱情來完善,不過業餘時間畢竟有限,測試不完善,加上本人C++不是太熟悉,所以肯定有很多隱藏問題,歡迎指出問題,更歡迎提交修改。
本項目重點主要在影像處理並與遊戲引擎的對接上,主要實現與遊戲引擎對接更少的性能消耗,方便引入各種影像處理,包括相關神經網路影像處理,餘下處理都是結合網上程式碼加上測試完善邏輯。畢竟這個項目開始只是想驗證DX11比CUDA的GPGPU計算資源佔用高是不是因為執行緒組的分配方式,後來想著用神經網路層的做法來搭建相關邏輯,方便用來做測試一些演算法。雙十一騰訊的雲伺服器打折,一時手癢就買了台,現在不是直播很火嗎,再加上對雲遊戲的概念感興趣,本人在工作過程也接入過二個商業的直播SDK,通過接入SDK自己思考下流程,發現做一個技術驗證性的DEMO還是比較容易的,所以也就有了這個項目。
本項目暫時只考慮WIN平台,但是框架從開始就考慮從多平台擴展,後面熟悉別的平台相關知識後,會把相應功能補起。
特點:
- 1 與遊戲引擎UE4/Unity3D方便接入,引擎里的紋理可以直接傳入傳出。
- 2 影像處理現支援CUDA/DX11,影像處理管線可以直接輸入輸出DX11紋理,可以做到不需要CPU/記憶體做影像中轉,提高效率。
- 3 影像處理管線類似神經網路框架的影像處理層設計,並且可以動態打開與關閉某層,方便組合。
- 4 方便接入各種神經網路框架處理,項目上面集成darknet,可以方便對比別的神經網路框架接入。
- 5 使用Media Foundation採集影像設備,WASAPI採集麥與音效卡。
- 6 用signalR搭建直播SDK,配合nginx管理推拉流,使用ffmpeg編碼解碼推流拉流,設計支援多推流多拉流。
- 7 有一些同學找我要過我原來寫的CUDA grabcut實現,我是感覺效果不好也沒有商用價值,這次也集成在上面,要的可以去找相應實現自己改進。
- 8 結合後面5G,有4K,8K影像處理的,這種所有計算都用GPGPU來完成的應該有更多可能。
大致內容如下。
- 1 OEIP框架設計
- 2 GPGPU影像處理
- 3 採集音影片數據
- 4 FFmpeg編解碼與推拉流
- 5 直播伺服器設計
- 6 Unity3D插件
- 7 UE4插件
OEIP框架設計
和一般直播SDK類似,分為設備採集,影像/音頻處理,編碼,推流,伺服器通知與分發,拉流,解碼,影像顯示這幾步。
方案中,核心項目oeip定義上面模組的各個功能介面,插件模組化,影像處理層的設計。
影像處理層採用類神經網路實現,層之間可以互相結合,層支援多輸入與多輸出,可以方便擴展成別的GPGPU方案,現在主要是CUDA與DX11實現,CUDA模版添加與神經網路Darknet的集成,後續會引入別的神經網路框架集成影像處理。
關聯項目:oeip
GPGPU影像處理
在遊戲引擎里,想設計各種影像處理說方便也方便,說麻煩也很麻煩,說方便就是因為如果你想實現的功能在這個框架下,那很簡單,嗯,UE4下如果要集成自己的Compute shader還是有點麻煩,複雜點我想引入摳圖相關演算法,會發現各種麻煩,以及如果想引入 神經網路框架的處理更是複雜,由此我想實現一個能支援CPU數據輸入,也支援引擎里GPU數據直接輸入,支援CPU數據輸出,也支援直接把處理的GPU顯示記憶體結果返回給遊戲引擎,脫離實際遊戲環境,只關注本身的邏輯實現。
最開始,並沒有輸入層與輸出層的設計,但是有幾個問題,如在DX11中,讓所有層以紋理流通,而傳入與傳出的CPU數據與紋理長度不一定對應是其一,其二封裝記憶體/顯示記憶體處理,顯示記憶體外部上下文與Oeip處理的上下文不同執行緒切換等,三是並不好處理多輸入與多輸出,中間層輸出等各種問題,所以加入輸入與輸出層,這二層本身並沒任何邏輯,專門用來解決上面的問題。
在GPU演算法中,一是善用一些多執行緒的演算法,如跨執行緒組步長的循環,以及執行緒組內二分操作,盡可以同時多利用執行緒組內所有執行緒。二是多利用共享顯示記憶體,注意這個大小有限制,如果你把太多數據放進去,可能會起反作用。三是GPGPU執行緒組的劃分也比較重要,如果出現幾個執行緒同時訪問或是讀取某個顯示記憶體地址,不管需要同步不,都不算太好的方式,情願一個執行緒讀寫多個顯示記憶體地址。四是可以在CPU確認判斷可以先編譯成不同GPU程式碼,如HLSL可以通過加入宏定義編譯,而CUDA可以利用模版。五減少與CPU的數據交互,如1080P的數據下,上傳與下載到顯示記憶體的時間大約是你做一次基本影像處理的十倍左右,我認為的理想方法,要麼是從CPU數據讀入,然後所有處理在GPU,並通過引擎顯示,或是數據就在GPU上,影像處理最後一步交給CPU傳輸用,或是從GPU來,GPU處理後再還給GPU,中間但凡出現多次CPU-GPU的交互不如考慮方案的合理性。
關聯項目:oeip-win,oeip-win-cuda,oeip-win-dx11
CUDA版Grabcut的實現 整合Yolov3到UE4/Unity3D
採集音影片數據
這個沒什麼好說的,採集影像影片用的是Media Foundation技術,大約有幾點,一是讀不管非同步還是同步,數據讀取都應該放在非主執行緒中,用非同步讀自己不需要開,用同步自己管理執行緒,但是需要注意設備關閉時,確保相應數據流執行緒最好同步調用執行緒關閉,免的數據狀態不正確。二是避免CPU處理數據,直接讀取設備所支援的原生格式,如NV12(YUV420SP),YUV422I,我們在GPGPU影像處理層里有相應的YUV/RGB層,層里採集設備常用的NV12,YUV420I,BGR,YUV422I等都支援,當然傳輸用的YUV422P,YUV420P也是支援的,相應的CUDA/HLSL程式碼都有.三是我以前採過的坑,採集設備就是採集數據,他本身不應該和數據處理綁在一起。
音頻採集用的WASAPI技術,處理沒用Media Foundation,重取樣,混音用的FFmpeg,音頻採集主要是麥與音效卡這二部分,麥還好,音效卡處理需要注意靜音的處理,別的跟著網上的程式碼來就行。
關聯項目:oeip-win-mf,oeip-ffmpeg
FFmpeg編解碼與推拉流
現在直播相關比較火,並且結合現在網路情況可以做很多原來想不到的事情,雲遊戲這種原概念產品感覺有完善的可能了,我今年也學了些FFmpeg相關知識用來儲備。
推流前,數據處理後需要編碼,主要用來壓縮數據,可以說是超強的壓縮率,在這隻結合網上程式碼完善了H264與AAC這二種影片與音頻編碼方式,推拉流使用RTMP協議。
而拉流就是把上來的拉到的H264/AAC數據解碼得到YUV/PCM固定格式後固定大小的數據,然後自己處理。
主要程式碼都是參照網上部分,然後整合,其中感覺主要是FFmpeg各種資源的銷毀比較麻煩,比如要動態更新編碼格式,重取樣混音都有FFmpeg中間重用的資源,結合std::unique_ptr可以自定義銷毀函數與模板,寫出C#的感覺,省了我不少腦力與程式碼。
關聯項目:oeip-ffmpeg
直播伺服器設計
直播伺服器簡單來說,就是通知一組成員之間消息流通,比如張三李四王五,張三上來了,李四推流了,王五關閉推流了等等這些消息,都需要及時通知這組裡的所有成員,每個成員根據需求來對各種消息做各種處理。
直播伺服器通訊方案我選擇的signarR,我對C#相關的技術熟悉點。
這只是一個非常簡單的設計,主要分為三方,一是SDK調用方,也就是上面的張三李四王五他們,二是直播伺服器,管理上面的各種通知,三是媒體伺服器,管理推拉流的音影片數據。三方是可以分開放的,不過現沒有丟楨方案,SDK調用方最好和媒體伺服器在同一區域網效果會比較好。
相關流程簡單來說,先打開直播伺服器,然後打開媒體伺服器,這樣直播伺服器就知道了所有的媒體伺服器,然後SDK調用方連接直播伺服器後,直播伺服器返回給SDK調用方相應的媒體伺服器地址,這樣SDK調用方推流後就知道向那個媒體伺服器的地址推流並記錄下來,然後別的用戶進來,就通知別的用戶已經有別的用戶推流了,並返回相應的推流地址,然後就可以拉流的,當然這個用戶推流了,也需要返回相應推流地址給前一個用戶。
注意事項,signalR 現在也是類似ASP net core里的一個中間件,在這為了直播伺服器是否成功打開,我也寫了個簡單的中間件驗證是否能成功連接伺服器,打開伺服器就會返回結果,在這中間件處理的是每個請求,每次請求都會生成一個HUB對象,這樣導致相應的HUB裡面綁定事件話,會累加,所以並不是一個好的選擇,可以用GlobalHost返回這個HUB邏輯上的所有鏈接用戶。
SDK調用方,我最開始找的是signalR的C++實現,可惜,一個是老版概念signalR 實現的,幾年沒更新了,最新的在asp net core下有份C++ 實現,這個還沒BATA版,故客戶端SDK調用方與直播伺服器通訊用C#完成,我們知道,與播伺服器通訊主要是二個部分,一個是我們主動提交的資訊,如我們登陸了,我們推流了,還有一個是直播伺服器的通知,比如通知你別的用戶上線,別的用戶推流了。第一個部分我們主動發起通知,表現就是我們從C++調用相關C#的實現,而第二部分是伺服器通知回調,需要從C#端通知到C++端,這個算是不常用調用方法,綜合考慮了下,把相應的C#客戶端封裝成COM介面,方便一是C++調用相關C#的實現,二是把相應的C++介面實現傳入到C#環境中去執行。需要注意的,這個C++客戶端事實上包含相應的CLI環境,所以如果銷毀資源,如unity3D/UE4里的每次play/endplay間,要確認引用的C++DLL所關聯的CLI環境已經清理乾淨,我反正是在對應銷毀時調用GC.WaitForPendingFinalizers()才搞定關閉時不掛起的現象。
需要注意的是,客戶端C#使用COM封裝,那麼每台機器需要註冊相應的COM組件,如果你是用的VS,直接開管理員,編譯相應的OeipLiveCom項目就行。
當然這個等asp net core signalR的C++實現完善後,會把相應C#+COM/C++調用方案改成全C++低層實現。
關聯項目:OeipLiveServer,OeipLiveMedia,OeipLiveCom,oeip-live,oeip-live-ffmpeg
Unity3D插件
因為在驗證各項功能前,我已經用WinForm+SharpDx做了驗證項目,包含DX11紋理的傳入傳出驗證,Unity3D的大部分程式碼和這部分共用,注意事項就一點,在Unity3D C#中我們拿不到DX11設備與上下文,我們需要編寫一個Unity3D的非託管插件,在這插件里我們能拿到Unity3D的DX11設備與上下文,結合OEIP原來介面再封裝一層。
注意事項,更新Unity3D的RHI資源,需要用到Unity3D的非託管插件特定的寫法,保證在渲染執行緒中更新資源,而OEIP回調大部分在非主執行緒中,所以回調里要用到Unity3D遊戲執行緒里的資源里,請轉到遊戲執行緒去執行。
關聯項目:oeip-unity3d,OeipWrapper,OeipUnity3D
更詳細說明請看 UE4/Unity3D中同時捕獲多高清攝影機的高效插件
UE4插件
基本和Unity3D插件思路一樣,相應數據處理編寫相應管線,設備數據處理管線,拉流管線,推流管線,直播SDK的再封裝都是差不多的,就連注意事項也是差不多,回調里用到UE4資源的,請轉到遊戲執行緒,用到RHI資源的,請轉到渲染執行緒。
關聯項目:OeipUE4
更詳細說明請看 UE4/Unity3D中同時捕獲多高清攝影機的高效插件
最後說下項目編譯相關
我主要環境在VS2017上開發。
第三方庫:
CUDA 10.1安裝:https://developer.nvidia.com/cuda-downloads
CUDNN 10.1安裝:https://developer.nvidia.com/cudnn
下載https://github.com/xxxzhou/oeip-thridparty在Oeip項目下,新建一個ThirdParty文件夾,把oeip-thridparty里的文件全部複製到這。 二種引用DLL方式。 一是把相應的DLL複製到對應oeip dll目錄下。 二是在環境變數里把上面的幾個文件夾的BIN目錄寫入,推薦第二種。(1 ThirdPartycuda 2 ThirdPartyFFmpegdll 3 ThirdPartyopencv4bin 4 ThirdPartypthreaddll).
直播SDK環境配置:
- 1 先啟動直播伺服器 OeipLiveServer
- 2 啟動媒體伺服器 OeipLiveMedia
- 3 本機註冊OeipLiveCom這個COM組件,然後就可以用了。
相應UE4/Unity3D里神經網路載入用的的絕對路徑,請自己修改相應路徑。
其主要只考慮了64位,相應編譯的環境只有64位配置了,32位需要自己配置。