技术宝典 | WebRTC 技术解析之 Android VDM
導讀:WebRTC 中的Android VDM(Video Device Manager)技術模塊,是指 WebRTC 基于 Android 系統,對視頻數據采集、編碼、 解碼和渲染的管理。當你拿到一部Android 手機,通過網易云信 SDK 進行 RTC 通信時,你是否好奇, Android 系統的 VDM 是如何實現的?WebRTC 又是如何使用 Android VDM 的?本文對 WebRTC 中 Android VDM 的實現進行了分解和梳理。
文|Iven? 網易云信資深音視頻客戶端開發工程師
01 Android 圖形系統介紹
視頻是由一幅幅圖像組成的序列, 在 Android 系統中, 圖像的載體是 Surface。Surface 可以理解為 Android 系統內存中的一段繪圖緩沖區。無論開發者使用什么渲染 API,一切內容都會渲染到 Surface 上。Android 的采集、編解碼、渲染,都是基于對 Surface 的處理。Surface 表示緩沖區隊列中的生產方,而緩沖區隊列通常會被 SurfaceFlinger 或顯示 OpenGL ES 消耗。在 Android 平臺上創建的每個窗口都由 Surface 提供支持。所有被渲染的可見 Surface 都被 SurfaceFlinger 合成到屏幕,最后顯示到手機屏幕上。
?Android 圖形系統中的生產者和消費者模型?
前面提到,Surface 表示緩沖區隊列中的生產方,對應下圖的 Producer。BufferQueues 是 Android 圖形組件之間的粘合劑,它是一個隊列,將可生成圖形數據緩沖區的組件(生產方)連接到接收數據以便進行顯示或進一步處理的組件(消費方)。一旦生產方移交其緩沖區,消費方便會負責將生產出來的內容進行處理。
圖像流生產方可以是生成圖形緩沖區以供消耗的任何內容。例如 Canvas 2D 和 mediaserver 視頻解碼器。圖像流的最常見消耗方是 SurfaceFlinger,該系統服務會消耗當前可見的 Surface,并使用窗口管理器中提供的信息將它們合成到屏幕。
整個 Android 圖像的數據流管線很長,所以生產者和消費者的角色其實是相對的,同一個模塊對應的可能既是生產者,也是消費者。OpenGL ES 既可以作為生產方提供相機采集圖像流,也可以作為消費方消耗視頻解碼器解碼出來的圖像流。
?Android 圖形顯示 pipeline?
SurfaceFlinger 是整個 Android 屏幕顯示的核心。SurfaceFlinger 進程由 init 進程創建,把系統中所有應用程序的最終繪圖結果合并成一張,然后統一顯示到物理屏幕上。
一個應用程序在送給 SurfaceFlinger 之前,是多個生產者在同時工作的,也就是有多個 Surface 通過 BufferQueue 向 SurfaceFlinger 輸送數據。如下圖的 Status Bar、System Bar、Icons/Widgets ?等;但無論有多少個生產者,最終都在 SurfaceFlinger 這個消費者這里,合并成一個 Surface。
值得一提的是 SurfaceFlinger 的硬件加速功能,如果所有的合成都交給 SurfaceFlinger 進行處理, 對 GPU 的負擔會加重,所以現在大部分手機都支持硬件加速進行合成,也就是 HWComposer。HWComposer?通過專門的硬件加速減輕 GPU 負擔,幫助 Surfaceflinger 高效快速地進行 Surface 的合成。
如下圖, 以打開系統 Camera 為例, 可以看到下圖屏幕截圖的顯示其實對應了6個 Surface, 圖中標注了4個 Surface, 另外的2個在圖中不可見,所以沒有標注出來。Camera 數據的顯示作為其中一個 Surface(SurfaceView Layer), 占用大部分的 SurfaceFlinger 合成計算, 由于 Camera 數據內容會不斷的變化,所以 GPU 需要重新繪制。
那么另外兩個不可見的 Surface 是哪兩個呢?其實就是下圖對應的 NavigationBar,因為隱藏了,所以圖中看不到。
另外一個是 Dim Layer, 因為“USB 用于”這個窗口置頂了,使他后面的窗口產生了一個變暗的透明效果,這也是一個單獨的 Surface。
02?WebRTC 中 Android 端 VDM
講完 Android 系統的 VDM 模塊, 那么 WebRTC 是如何管理使用Capturer、Encoder、Decoder、Render 這四個模塊的呢?還是按照生產者消費者模型,我們分為:?
- 生產者(綠色):Capturer、Decoder;
Capturer 采集到數據、Decoder 解碼到數據后,不斷往 SurfaceTexture 的 Surface 中送數據。 SurfaceTexture 通過 OnFrameAvailableListener 通知消費者進行處理。WebRTC 中 Capturer使用Camera1/Camera2 實現,Decoder 使用 MediaCodec 實現。
- 消費者(藍色):Render、Encoder;
Render 和 Encoder 各自的 Surface 通過 eglCreateWindowSurface() 跟 EGLSurface 進行關聯,而 EGLSurface 跟 SurfaceTexture 又是共用同一個 EGLContext。這樣 EGLSurface 就打通了 SurfaceTexture 跟 render/Encoder 的數據通道。EGLSurface 通過讀取 SurfaceTexture 的 Surface 數據,進行 shader 語言的圖形繪制。最終通過 eglSwapBuffers() 來提交當前幀,數據最終繪制到 Render 和 Encoder 的 Surface 上。
WebRTC 中 Render 使用 SurfaceView,不過 TextureView 在開源的 WebRTC 代碼中并沒有實現,感興趣的小伙伴可以自行實現。Encoder 使用 MediaCodec 實現。
?采集?
Android 系統的采集在 WebRTC 中主要使用的是 Camera 采集和屏幕采集。WebRTC 中還有外部采集,比如從外部輸入紋理數據和 buffer 數據,但是外部采集不依賴 Android 原生系統功能, 所以不在本文討論范圍內。
- Camera 采集
經歷了多個系統相機架構迭代。目前提供 Camera1/Camera2/CameraX 三種使用方式。
Camera1 在5.0以前系統上使用,使用方法比較簡單。開發者可以設置的參數有限。可以通過 SurfaceTexure 獲取紋理數據。如果視頻前處理或者軟件編碼需要獲取 buffer 數據,可通過設置攝像頭采集視頻流格式和 Nv21 數據。
Camera2 是5.0時,谷歌針對攝像頭新推出的一套 API。開發使用上比 Camera1 復雜,有更多的 Camera 控制參數,可以通過 SurfaceTexure 獲取紋理數據。如果視頻前處理或者軟件編碼需要獲取 buffer 數據,可通過 ImageReader 設置監聽,拿到 i420/rgba 等數據。
CameraX 是 jetpack 的一個支持庫提供的方法。使用方法比 Camera2 ?更簡單,從源碼看主要是封裝了 Camera1/Camera2 的實現,讓用戶不必去考慮什么時候使用 Camera1,什么時候使用 Camera2。CameraX 讓使用者更關注采集數據本身,而不是繁雜的調用方式和頭疼的兼容性/穩定性問題。CameraX 在 WebRTC 源碼中沒有實現,感興趣同學可以自行研究。
- 屏幕采集
5.0 以后,Google 開放了屏幕共享 API:MediaProjection,但是會彈出錄屏權限申請框,用戶同意后才能開始錄屏。在 targetSdkVersion 大于等于29時,系統加強了對屏幕采集的限制,必須先啟動相應的前臺 Service,才能正常調用 getMediaProjection 方法。對于數據的采集,跟 Camera2 的數據采集方式類似,也是通過 SurfaceTexure 獲取紋理數據,或者通過 ImageReader 獲取 i420/rgba 數據。筆者嘗試在屏幕共享時獲取 i420,沒有成功,看起來大部分手機是不支持在屏幕共享時輸出 i420 數據的。屏幕共享的采集幀率沒法控制,主要規律是在屏幕靜止時,采集幀率降低。如果運動畫面,采集幀率可以達到最高的 60fps。屏幕共享 Surface 的長寬設置如果跟屏幕比例不一致,在部分手機上可能存在黑邊問題。
編解碼?
說起 Android MediaCodec, 這張圖一定會被反復提及。MediaCodec 的作用是處理輸入的數據生成輸出數據。首先生成一個輸入數據緩沖區,將數據填入緩沖區提供給 Codec,Codec 會采用異步的方式處理這些輸入的數據,然后將填滿輸出緩沖區提供給消費者,消費者消費完后將緩沖區返還給 Codec。
在編碼的時候,如果輸入的數據是 texture,需要從 MediaCodec 獲取一個 Surface,通過 EGLSurface 將 Texture 數據繪制到這個 Surface 上。這種方式全程基于 Android 系統 Surface 繪制管線,認為是最高效的。
在解碼的時候,如果想要輸出到 texture,需要將 SurfaceTexture 的 Surface 設置給 MediaCodec,MediaCodec 作為生產者源源不斷地將解碼后的數據傳遞給 SurfaceTexture。這種方式全程基于 Android 系統 Surface 繪制管線,認為是最高效的。
除了高效的基于 texture 的操作,MediaCodec 可以對壓縮編碼后的視頻數據進行解碼得到 NV12 數據,也支持對 i420/NV12 數據進行編碼。
WebRTC 源碼除了基于 MediaCodec 的硬件編解碼,還實現了軟件編解碼。通過軟硬件的切換策略,很好的考慮了性能和穩定性的平衡。
MediaCodec 其實也有軟件編解碼的實現。MediaCodec 的底層實現是基于開源 OpenMax 框架,集成了多個軟硬件編解碼器。不過一般在實際使用過程中,并沒有使用 Android 系統自帶的軟件編解碼,我們更多的是使用硬件編解碼。?
在使用 MediaCodec 硬件編解碼時,可以獲取 Codec 相關信息。如下以“OMX.MTK.VIDEO.ENCODER.AVC”編碼器為例,可通過 MediaCodecInfo 提供編碼器名字、支持的顏色格式、編碼 profile/level、可創建的最大實例個數等。
渲染?
SurfaceView:從 Android 1.0(API level 1) 時就有。與普通 View 不同,SurfaceView?有自己的 Surface,通過 SurfaceHolder 進行管理。視頻內容可以單獨在這個 Surface上進行單獨線程渲染,不會影響主線程對事件的響應,但是不能進行移動、旋轉、縮放、動畫等變化。
TextureView:從 Android 4.0 中引入,可以跟普通 View 一樣進行移動、旋轉、縮放、動畫等變化。TextureView 必須在硬件加速的窗口中,當有其他 View 在 TextureView 頂部時,更新 TextureView 內容時,會觸發頂部 View 進行重繪,這無疑會增加性能方面消耗。在 RTC 場景中, 大部分時候都會在視頻播放窗口上面增加一些控制按鈕,這時候使用 SurfaceView 無疑性能上更有優勢。
03 VDM 的跨平臺工程實現
說到 WebRTC,不得不說它的跨平臺特點。那么 Android VDM 是如何通過跨平臺這個框架進行工作的呢??
根據筆者的理解,將 Android VDM 在 WebRTC 中的實現分為4層。從上到下分為:Android Java Application、Java API、C++ Wrapper、All In One API。
- All In One API :
了解 WebRTC 的同學都知道,跨平臺的代碼都是 C/C++ 實現的,因為 C/C++ 語言在各平臺具有良好的通用性。WebRTC 通過對各平臺,包括 Android/IOS/Windows/MAC、Encoder/Decoder/Capturer/Render 模塊的抽象,形成了 All In One API。各平臺基于這些 API,各自基于不同操作系統去實現對應功能。這里不得不贊嘆 C++ 的多態性的厲害之處。通過 All In One API,WebRTC 在 PeerConnection 建立后的媒體數據傳輸、編解碼器的策略控制、大小流、主輔流的切換等功能,才能順利搭建, All In One API 是整個音視頻通訊建立的基礎。
- C++ Wrapper:
這一層,是 Android 對應 Java 模塊在 native 層的封裝,并且繼承自 All In One API 層的對應模塊,由 C++ 實現。通過 Wrapper 使C++層可以無感知的訪問 Android 的 Java 對象。技術上,通過 Android 的 JNI 來實現。以 VideoEncoderWrapper 為例,VideoEncoderWrapper 封裝了 Java 的 VideoEncoder 對象,VideoEncoderWrapper 又繼承自 All In One API 的 VideoEncoder。這樣通過調用 All In One API 的VideoEncoder,實際上也就是執行到了 Android Java 的具體實現。除了 Android 平臺,其他平臺也可以通過同樣的方法在這一層進行封裝,這一層可以說是一個大熔爐,Android/IOS/Windows/MAC 的平臺屬性都可以得到封裝。C++ Wrapper 這一層,真是 WebRTC 跨平臺層和各平臺具體實現的完美橋梁。
- Java API?
這一層提供了 WebRTC 在 Java 實現的 API 接口, 通過繼承這些 API,使得 Android SDK Application 的實現具有更好的擴展性。比如 CameraVideoCapturer 和 ScreenCapturerAndroid 通過繼承 VideoCapturer, 實現了 Camera 和 Screen 的采集。當后續開發維護者想要添加其他視頻采集方式時, 通過繼承 VideoCapturer,可以實現良好擴展性。再比如圖中的 SurfaceViewRender 繼承自 VideoSink,如果開發者想要實現基于 TextureView 的 Render,同樣的通過繼承 VideoSink, 即可快速實現。
- Android SDK Application
這一層是真正Android VDM 實現的地方,是基于 Android SDK API ?對 Encode/Decode/Capture/Render功能的具體實現。這是離 Android 系統最近的一層。在這一層的實現中值得注意的是:Capturer/Encode/Decode 是由跨平臺層觸發對象的創建和銷毀,而 Render 是從 Java 創建對象,然后主動傳遞到跨平臺層的。所以對于 Render 的創建/銷毀,需要格外注意,防止野指針的出現。
04 RTC 場景中的 VDM 參數適配優化
上一章提到了 Android Java Application 層是具體功能實現的地方,而對于 All In One API 這一層,是對所有平臺的抽象。所以在調用的時候,并不關心平臺相關的一些兼容性問題。而對于 Android 系統,繞不開的也是兼容性的問題。所以如果想要 Android VDM 功能在基于 All In One API 層的復雜調用下,保持穩定運行,兼容性適配問題的解決是不可忽視的,需要有個比較完善的兼容適配框架,通過線上下發、本地配置讀取、代碼層面的邏輯處理等手段,對不同的設備機型、不同 CPU 型號、不同 Android 系統版本、不同業務場景等進行全方位的是適配優化。下圖是對兼容性問題的下發配置方式框架圖。通過維護一份兼容配置參數,通過 Compat 設置到 VDM 各模塊,以解決兼容性問題。
05 總結
本文通過對 Android 顯示系統的介紹,進而引出 WebRTC 在 Android 平臺上的 VDM 實現,并且深入 WebRTC 源碼,將 Android VDM 在 WebRTC 中的實現剖解為4層,從上到下分為:Android SDK Application、Java API、C++ Wrapper、All In One API。
同時對于 Android 不能忽視的兼容性問題的工程實現,做了簡單介紹。通過分析 WebRTC 在 Android VDM 上的實現,我們可以更加深入了解 WebRTC 的視頻系統的實現架構,以及跨平臺實現的架構思維。
?作者介紹?
Iven ,網易云信資深音視頻客戶端開發工程師,主要負責視頻工程,Android VDM 相關工作。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的技术宝典 | WebRTC 技术解析之 Android VDM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 快上车!2021次“网易云信号”回顾列车
- 下一篇: 资讯|WebRTC M96 更新