百度APP視訊播放中的解碼優化

語言: CN / TW / HK

背景

在全民視訊的時代,百度APP中視訊播放是十分重要的業務。隨著 5G 的到來,視訊播放已經不滿足以前的標清/高清,超清乃至於 4K 已經是舊時王謝堂前燕飛入尋常百姓家。越來越清晰的視訊源,越來越複雜的視訊編碼,對 APP 的視訊解碼能力也有越來越高的要求。
與此同時,大家的手機效能越來越好,很多手機都逐步提供了強悍的硬體解碼能力;而軟體解碼發展多年,也有其不可替代的優勢。所以,如何合理利用手機的軟/硬體解碼能力,充分發揮其各自優勢,為使用者們提供更加優質的視訊播放體驗,就成為了我們重點優化的方向。

丨軟/硬體解碼優缺點

解碼器有兩種模式:軟體解碼與硬體解碼。
軟體解碼目前業界有比較成熟的 FFmpeg ,利用 CPU 進行解碼。
硬體解碼發展起步較晚,在 Android 手機上,利用專用解碼晶片進行解碼。系統提供 MediaCodec ,用於訪問底層硬體解碼器。

事物都有兩面性,兩種解碼模式各有優缺點,在很多播放器中,兩種模式並存。軟硬解碼的優缺點,對音視訊開發者其實算老生常談了。

說明:在 Android 上,MediaCodec 更加具體來說,是 Google 提供的一套框架,因為各個晶片廠商,手機廠商實現差異,所以經常出現相容性問題。另外 MediaCodec 的初始化流程長,且一些手機上,需要內部快取多個幀後才對外輸出第一個幀,這兩個因素導致硬解在首幀解碼速度上明顯比軟解慢。
丨效率對比

  1. 軟體解碼:使用 FFmpeg ,解碼後得到 YUV 資料,需要通過 libyuv 轉換為 RGB ,渲染上屏。

  2. 硬解碼 buffer 模式:使用 MediaCodec ,解碼後從 buffer 中得到 YUV 資料,需要通過 libyuv 轉換為 RGB ,渲染上屏。

  3. 硬解碼 surface 模式:使用 MediaCodec ,官方說明中 surface 模式為最高效的模式:解碼時繫結 surface ,解碼後可通過系統 API 直接上屏到 surface。

首幀解碼耗時線上統計:

圖片.jpg

⬆️(百度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 如下圖:
圖片.jpg

由此可以看出,在視訊播放上,MediaCodec surface 模式是效率最高的模式,既充分利用了硬解碼的優勢,又因為系統直接上屏降低了資料拷貝和 YUV 轉換 RGB 的耗時,有效降低了 CPU 負載和對記憶體的消耗。

丨痛點

綜上所述,在視訊播放中,理想狀態是儘可能地去用硬解碼 surface 模式,其次是使用硬解碼 buffer 模式,最後再考慮軟解碼。但同時需要兼顧首屏解碼速度,硬解碼的機型相容性,在這些場景下需要優先使用軟解碼。

對此,我們需要解決以下痛點:

1. 怎麼完善硬解碼的相容性判斷?

2. 怎麼在保證首屏解碼速度的情況下,儘可能使用硬體解碼?

我們的方案

痛點 1:怎麼完善硬解碼的相容性判斷?

主流做法:線下測試各種機型硬體解碼相容性,維護靜態硬體解碼黑名單。

劣勢:測試人力成本高,且線下測試很難 cover 線上多種機型;手機型號不斷迭代,這種方式,無法保證新的異常機型及時拉黑。

我們的方案 1:在靜態硬體解碼黑名單機制上,增加解碼器監控。

痛點 2:怎麼在保證首屏解碼速度的情況下,儘可能使用硬體解碼?

主流做法:需要保障解碼效率的播放場景選擇硬體解碼,需要保證首屏解碼速度則選擇軟體解碼。

劣勢:選擇軟體解碼的場景,無法充分發揮手機硬體解碼的優勢。

我們的方案 2:

劃定首屏解碼耗時閾值,例如 200ms 。

從解碼器監控模組中獲取歷史硬解碼首屏耗時進行預測,若低於閾值,直接使用 MediaCodec surface 模式;高於閾值,使用軟解起播,中途無縫切換為 MediaCodec buffer 模式。

如上述,下文具體介紹這 2 個方案。

丨方案 1:解碼器監控

  1. 解碼器監控模組設計

圖片.jpg

解碼監控模組通過編碼型別(H264/HEVC)& profile & level作為一個ID,記錄各種編碼方式源的軟硬體解碼情況;

說明:profile 指定視訊的壓縮率;level 指定解析度、幀率和位元速率的。兩者都是視訊編碼的重要特徵。同一編碼型別,解碼器對不同級別的 profile/level 支援可能不同。

記錄該編碼方式中軟硬體解碼的首屏解碼速度和平均解碼速度。

針對硬體解碼,還記錄了硬體解碼器是否存在崩潰;執行次數及執行期間出現異常的次數(包括解碼介面拋異常;解碼 block 等)。

剛安裝百度APP的一段時間內,視訊播放會隨機使用軟/硬體播放,用於採集該機器的解碼器執行情況。

  1. 流程
    起播時,在 prepared 階段,先通過靜態硬解碼黑名單,再細分到 編碼型別 & profile & level,從解碼監控模組看視訊源編碼方式硬解是否崩潰—>硬解是否異常過多,判斷硬解碼相容性是否滿足。

從解碼監控模組獲取當前視訊源編碼方式使用軟硬解碼首幀耗時進行首屏耗時的預測,硬解碼滿足特定首屏耗時時,優先使用硬解碼,反之則選軟體解碼起播。

視訊播放後,將本次的首幀解碼耗時、解碼器執行情況(是否崩潰、是否有異常、每幀平均耗時)更新到監控模組,用於下次播放預測。

圖片.jpg

對於硬體解碼有崩潰、異常過多的情況,我們判定硬體解碼存在相容性問題,用軟體解碼播放完整個視訊。

對於硬體解碼首屏耗時超過閾值的,其實相容性是OK的,那麼在用軟體解碼快速起播後,我們可以用方案2進一步優化。

丨方案 2:軟硬體解碼器無縫切換

  1. 解碼通路的統一
    為什麼要統一?工欲善其事,必先利其器。

如果有一個統一的解碼模組,封裝軟/硬解碼器(包括三種解碼方式),對外提供統一接入介面,那麼對於 Player 仍然像使用一個普通解碼器一樣使用。在整個架構實現上更加合理,維護擴充套件也方便。

模組內部維護前後臺解碼器,內部狀態,切換追幀等邏輯,對外無感。而I幀標識,可切換標識等,均可攜帶在pkt中傳入,這樣也不需要對解碼模組增加一些解碼無關的介面,介面設計更加合理。

  1. 解碼器切換邏輯
    兩種時機可以切換:1)播放解碼到第二個 GOP;2)Player 發生 seek。

播放到第二個 GOP 切換:

圖片.jpg

播放開始時,解碼模組內開啟軟解碼器作為前臺解碼器;同時建立後臺硬解執行緒,處於等待狀態,並不會阻塞住前臺解碼任務。

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 模式即可。

圖片.jpg

  1. 保證解碼器無縫切換

圖片.jpg

追幀和解碼器切換過程中有兩種情況:

(左圖)GOP2 硬解碼解碼N幀後,才追上軟解碼,那麼這些重複的 frame (灰色部分),需要進行丟棄,避免畫面重複和回跳。

(右圖)GOP2 硬解碼解碼第一幀,即已經追上軟解碼,那麼必須填入空 pkt包,將軟解碼器內部快取全部輸出,避免畫面跳變。

結語

目前百度APP Android端,在保障首屏速度和解碼錯誤率沒有退化的前提下,視訊播放中硬體解碼佔比已達到 87%,如下:
圖片.jpg

在目前視訊業務百花齊放的時代,編解碼也在不斷髮展進步,各種新的編碼方式層出不窮,端上也在這個方向上不斷強化自身解碼能力。解碼作為視訊播放中重要的一環,可以預見的是,後續我們仍會在端上解碼不斷進行探索、優化,為使用者提供更優的體驗。