1. 程式人生 > >開源專案OEIP 遊戲引擎與音視訊多媒體(UE4/Unity3D)

開源專案OEIP 遊戲引擎與音視訊多媒體(UE4/Unity3D)

  現開源一個專案 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 ThirdParty\cuda 2 ThirdParty\FFmpeg\dll 3 ThirdParty\opencv4\bin 4 ThirdParty\pthread\dll).

  直播SDK環境配置:

  • 1 先啟動直播伺服器 OeipLiveServer
  • 2 啟動媒體伺服器 OeipLiveMedia
  • 3 本機註冊OeipLiveCom這個COM元件,然後就可以用了。

  相應UE4/Unity3D裡神經網路載入用的的絕對路徑,請自己修改相應路徑。

  其主要只考慮了64位,相應編譯的環境只有64位配置了,32位需要自己配置。