背景
在全民視頻的時代,百度APP中視頻播放是十分重要的業務。隨着 5G 的到來,視頻播放已經不滿足以前的標清/高清,超清乃至於 4K 已經是舊時王謝堂前燕飛入尋常百姓家。越來越清晰的視頻源,越來越複雜的視頻編碼,對 APP 的視頻解碼能力也有越來越高的要求。
與此同時,大家的手機性能越來越好,很多手機都逐步提供了強悍的硬件解碼能力;而軟件解碼發展多年,也有其不可替代的優勢。所以,如何合理利用手機的軟/硬件解碼能力,充分發揮其各自優勢,爲用戶們提供更加優質的視頻播放體驗,就成爲了我們重點優化的方向。
丨軟/硬件解碼優缺點
解碼器有兩種模式:軟件解碼與硬件解碼。
軟件解碼目前業界有比較成熟的 FFmpeg ,利用 CPU 進行解碼。
硬件解碼發展起步較晚,在 Android 手機上,利用專用解碼芯片進行解碼。系統提供 MediaCodec ,用於訪問底層硬件解碼器。
事物都有兩面性,兩種解碼模式各有優缺點,在很多播放器中,兩種模式並存。軟硬解碼的優缺點,對音視頻開發者其實算老生常談了。
模式 | 優點 | 缺點 |
軟件解碼 | 支持格式多,兼容性強 | CPU負載重,功耗高,性能較差(相對硬解) |
硬件解碼 | 性能好,CPU 負載/功耗/內存消耗都更優 | 支持格式少,兼容性差。解碼出首幀速度慢(相對軟解) |
說明:在 Android 上,MediaCodec 更加具體來說,是 Google 提供的一套框架,因爲各個芯片廠商,手機廠商實現差異,所以經常出現兼容性問題。另外 MediaCodec 的初始化流程長,且一些手機上,需要內部緩存多個幀後纔對外輸出第一個幀,這兩個因素導致硬解在首幀解碼速度上明顯比軟解慢。
丨效率對比
1. 軟件解碼:使用 FFmpeg ,解碼後得到 YUV 數據,需要通過 libyuv 轉換爲 RGB ,渲染上屏。
2. 硬解碼 buffer 模式:使用 MediaCodec ,解碼後從 buffer 中得到 YUV 數據,需要通過 libyuv 轉換爲 RGB ,渲染上屏。
3. 硬解碼 surface 模式:使用 MediaCodec ,官方說明中 surface 模式爲最高效的模式:解碼時綁定 surface ,解碼後可通過系統 API 直接上屏到 surface。
-
首幀解碼耗時線上統計:
(百度APP版本V11.20.0.14,數據日期:2020年03月20日)
-
解碼幀率和 CPU 佔用統計需要進行壓測(不進行音視頻同步,完全放開解碼性能),以下這兩項採用線下測試數據。測試源:4K HEVC ,測試機魅族 16th。
說明:解碼幀率越高,表示1秒內解碼幀數越多,單幀解碼耗時更少,性能更高。
模式 | 軟解碼 | 硬解碼 buffer 模式 | 硬解碼 surface 模式 |
幀率 | 29.4fps | 55.0fps | 58.8fps |
CPU 佔用(峯值) | 79% | 23% | 12% |
CPU 如下圖:
由此可以看出,在視頻播放上,MediaCodec surface 模式是效率最高的模式,既充分利用了硬解碼的優勢,又因爲系統直接上屏降低了數據拷貝和 YUV 轉換 RGB 的耗時,有效降低了 CPU 負載和對內存的消耗。
丨痛點
綜上所述,在視頻播放中,理想狀態是儘可能地去用硬解碼 surface 模式,其次是使用硬解碼 buffer 模式,最後再考慮軟解碼。但同時需要兼顧首屏解碼速度,硬解碼的機型兼容性,在這些場景下需要優先使用軟解碼。
對此,我們需要解決以下痛點:
1. 怎麼完善硬解碼的兼容性判斷?
2. 怎麼在保證首屏解碼速度的情況下,儘可能使用硬件解碼?
我們的方案
痛點 1:怎麼完善硬解碼的兼容性判斷?
-
主流做法:線下測試各種機型硬件解碼兼容性,維護靜態硬件解碼黑名單。
-
劣勢:測試人力成本高,且線下測試很難 cover 線上多種機型;手機型號不斷迭代,這種方式,無法保證新的異常機型及時拉黑。
-
我們的方案 1:在靜態硬件解碼黑名單機制上,增加解碼器監控。
痛點 2:怎麼在保證首屏解碼速度的情況下,儘可能使用硬件解碼?
-
主流做法:需要保障解碼效率的播放場景選擇硬件解碼,需要保證首屏解碼速度則選擇軟件解碼。
-
劣勢:選擇軟件解碼的場景,無法充分發揮手機硬件解碼的優勢。
-
我們的方案 2:
-
劃定首屏解碼耗時閾值,例如 200ms 。
-
從解碼器監控模塊中獲取歷史硬解碼首屏耗時進行預測,若低於閾值,直接使用 MediaCodec surface 模式;高於閾值,使用軟解起播,中途無縫切換爲 MediaCodec buffer 模式。
-
如上述,下文具體介紹這 2 個方案。
丨方案 1:解碼器監控
1. 解碼器監控模塊設計
-
解碼監控模塊通過編碼類型(H264/HEVC)& profile & level作爲一個ID,記錄各種編碼方式源的軟硬件解碼情況;
說明:profile 指定視頻的壓縮率;level 指定分辨率、幀率和碼率的。兩者都是視頻編碼的重要特徵。同一編碼類型,解碼器對不同級別的 profile/level 支持可能不同。
-
記錄該編碼方式中軟硬件解碼的首屏解碼速度和平均解碼速度。
-
針對硬件解碼,還記錄了硬件解碼器是否存在崩潰;運行次數及運行期間出現異常的次數(包括解碼接口拋異常;解碼 block 等)。
-
剛安裝百度APP的一段時間內,視頻播放會隨機使用軟/硬件播放,用於採集該機器的解碼器運行情況。
2. 流程
-
起播時,在 prepared 階段,先通過靜態硬解碼黑名單,再細分到 編碼類型 & profile & level,從解碼監控模塊看視頻源編碼方式硬解是否崩潰—>硬解是否異常過多,判斷硬解碼兼容性是否滿足。
-
從解碼監控模塊獲取當前視頻源編碼方式使用軟硬解碼首幀耗時進行首屏耗時的預測,硬解碼滿足特定首屏耗時時,優先使用硬解碼,反之則選軟件解碼起播。
-
視頻播放後,將本次的首幀解碼耗時、解碼器運行情況(是否崩潰、是否有異常、每幀平均耗時)更新到監控模塊,用於下次播放預測。
對於硬件解碼有崩潰、異常過多的情況,我們判定硬件解碼存在兼容性問題,用軟件解碼播放完整個視頻。
對於硬件解碼首屏耗時超過閾值的,其實兼容性是OK的,那麼在用軟件解碼快速起播後,我們可以用方案2進一步優化。
丨方案 2:軟硬件解碼器無縫切換
1. 解碼通路的統一
爲什麼要統一?工欲善其事,必先利其器。
如果有一個統一的解碼模塊,封裝軟/硬解碼器(包括三種解碼方式),對外提供統一接入接口,那麼對於 Player 仍然像使用一個普通解碼器一樣使用。在整個架構實現上更加合理,維護擴展也方便。
模塊內部維護前後臺解碼器,內部狀態,切換追幀等邏輯,對外無感。而I幀標識,可切換標識等,均可攜帶在pkt中傳入,這樣也不需要對解碼模塊增加一些解碼無關的接口,接口設計更加合理。
2. 解碼器切換邏輯
兩種時機可以切換:1)播放解碼到第二個 GOP;2)Player 發生 seek。
播放到第二個 GOP 切換:
-
播放開始時,解碼模塊內打開軟解碼器作爲前臺解碼器;同時創建後臺硬解線程,處於等待狀態,並不會阻塞住前臺解碼任務。
-
Player(播放器)開始播放,把第一個 GOP 的 pkt(視頻包)給解碼模塊,利用前臺解碼器(軟解)的優勢,快速解碼首個視頻幀用於渲染顯示,實現快速起播。
-
4-5秒後,第二個 GOP 到來,pkt(視頻包)攜帶可切換的flag通知解碼模塊,同時輸入 GOP2 的多個 pkt 給硬解碼器在後臺解碼,進入追幀狀態。前臺軟解碼器解碼保持不變,輸入一個 pkt ,解碼一個幀。
-
當後臺硬解碼器 PTS 追上軟解碼器的 PTS ,即可關閉硬解碼線程,前後臺解碼器切換。此過程需保證幀的連續,到達無縫切換,用戶無感。
-
GOP2 的後續的 pkt 和後續的 GOP3/4/5……都會使用 MediaCodec buffer 模式。這樣在利用軟解保證首幀解碼速度的同時,也最大限度的利用了 MediaCodec 的解碼優勢。
Player 發生 seek 切換:
這種場景邏輯比較簡單,在 Player seek 時,需要調用 decoder 的 flush,我們趁此機會把前臺解碼器切換爲硬解碼器,後續一直用 MediaCodec buffer 模式即可。
3. 保證解碼器無縫切換
追幀和解碼器切換過程中有兩種情況:
-
(左圖)GOP2 硬解碼解碼N幀後,才追上軟解碼,那麼這些重複的 frame (灰色部分),需要進行丟棄,避免畫面重複和回跳。
-
(右圖)GOP2 硬解碼解碼第一幀,即已經追上軟解碼,那麼必須填入空 pkt包,將軟解碼器內部緩存全部輸出,避免畫面跳變。
結語
目前百度APP Android端,在保障首屏速度和解碼錯誤率沒有退化的前提下,視頻播放中硬件解碼佔比已達到 87%,如下:
在目前視頻業務百花齊放的時代,編解碼也在不斷髮展進步,各種新的編碼方式層出不窮,端上也在這個方向上不斷強化自身解碼能力。解碼作爲視頻播放中重要的一環,可以預見的是,後續我們仍會在端上解碼不斷進行探索、優化,爲用戶提供更優的體驗。