搞音視頻開發好些年,分享過許多博客文章,比如:前幾年發布的《FFmpeg Tips》系列,《Android 音頻開發》系列,《直播疑難雜症排查》系列等等。最近想把多年來開發和優化播放器的經驗也分享出來,同時也考慮把自己業餘時間開發的基於 ffmpeg 的播放器內核開源出來,希望能幫助到音視頻領域的初學者。第一期文章要推出的內容主要涉及到播放器比較核心的幾個技術點,大概的目錄如下:

1. 播放器技術分享(1):架構設計

2. 播放器技術分享(2):緩衝區管理3. 播放器技術分享(3):音畫同步4. 播放器技術分享(4):首開時間

5. 播放器技術分享(5):延時優化

本篇是系列文章的第二篇,主要聊一聊播放器的緩衝區管理。

1 概述

在上一篇文章中,我們有提到利用緩衝區把單線程模型的數據流改造為多線程模型,從而可以有效抵抗網路和解碼的抖動,防止頻繁卡頓,同時也能充分利用多核 CPU 的計算能力,如下圖所示:

播放器的讀線程,將 IO 和 Parser 模塊輸出的」未解碼「的音視頻數據包放到」幀緩衝區「隊列中,將解碼後的數據,存放到」顯示緩衝區「隊列中。

2 緩衝區的作用

我們深挖一下,這個 「幀緩衝區」 和 「顯示緩衝區」 究竟起到了一個什麼作用 ?

2.1 幀緩衝區

幀緩衝區,作為「讀線程」和「解碼線程」之間的緩衝池,它主要起到了三個作用:

1. 抵抗網路抖動

2. 抵抗解碼抖動3. 避免被動丟幀導致花屏

假設沒有「幀緩衝區」,即:IO -> Parser -> Decoder 整個流程是串列的,那麼會有如下潛在問題:

1. IO 網路抖動的時候(比如:短暫擁塞,無法讀到數據),那麼整個數據鏈條都會被卡住,Decoder 只能幹等著 IO 恢復

2. Decoder 同樣會出現「抖動」,因為解碼某些複雜的視頻幀,會耗時比較久,如果 Decoder 卡住,同樣 IO 模塊也只能幹等著3. 因為整個流程是串列的,每一幀都必須 IO -> Parser -> Decoder 走完才會讀取和處理下一幀,那麼,當網路抖動的時候,會出現服務端的 TCP 協議棧緩存了較多的數據,在網路恢復的時候,下發到客戶端的時候,因為接收不及時,導致 TCP 發送隊列爆滿而產生被動丟幀,從而使得後續因為數據不完整導致解碼花屏

2.2 顯示緩衝區

顯示緩衝區,作為「解碼線程」和「顯示線程」之間的緩衝池,它主要起到了三個作用:

1. 實現 「音畫同步」 的必要條件

2. 抵抗渲染抖動

假設沒有「顯示緩衝區」,即:Decoder -> Renderer 整個流程是串列的,那麼會有如下潛在問題:

1. 無論是視頻幀還是音頻數據,都是解碼完了就立馬送入了渲染模塊,無法添加音畫同步的邏輯處理

2. 如果渲染模塊出現「抖動」,會直接阻塞解碼器,無法非同步去解碼幀緩衝區中的數據,降低了效率

3 「主動緩衝」 與 「被動緩衝」

瞭解了緩衝區的作用,我們再看看緩衝區的數據是怎麼被填充的 ?

緩衝區的數據填充,主要分為 2 種情況,第一種叫 「主動緩衝」,另一種叫做 「被動緩衝」

主動緩衝:是指播放器主動暫停緩衝區的數據消費,等待數據生產者逐漸填充數據,直到達到某種條件再恢復

被動緩衝:是指數據的消費速度趕不上生產速度,從而被動滯留了數據在緩衝區中

主動緩衝,多用於點播場景,為了降低頻繁卡頓,在開始播放視頻之前,會主動 buffering 一段時間(比如:10s)的數據,再開始播放。當緩衝區內的數據因為網路抖動等原因消耗完了,會再次啟動 buffering,如此循環。

如圖所示,假設播放器緩衝區內的數據低於 Low 這個水位點後,會主動暫停播放,啟動 buffering 過程直到緩衝區中的數據達到 M 水位值。

被動緩衝,多出現在直播場景,可能有 2 種原因:

1. 手機等設備的解碼性能不足,比如軟解 1080P 的高清視頻,導致視頻的解碼和渲染的速度趕不上視頻的讀取速度,導致數據堆積在「幀緩衝區」

2. 網路的頻繁抖動,導致客戶端無法及時拿到數據進行解碼渲染,當網路恢復後,數據會迅速下發下來,但播放器已沒有辦法再快速消費掉(因為播放的速率是固定的,除非添加追幀的邏輯,後續文章會詳細介紹)

4 緩衝區的大小怎麼定 ?

理解了緩衝區的作用,那這兩個緩衝區的大小如何制定呢 ?首先,我們需要知道這兩個緩衝區大小究竟影響或者決定了什麼 ?

1. 緩衝區越大 -> 抗抖動能力越強

2. 緩衝區越大 -> 內存佔用越高3. 緩衝區越大 -> 播放延時越大

由此可見,緩衝區也不是越大越好,需要根據實際的使用場景來決定。

「顯示緩衝區」 其實是 解碼線程 和 渲染線程 之間的橋樑,由於解碼和渲染的抖動並不頻繁,所以並不需要特別大的緩衝區,最低 3 幀左右即可,一幀在生產,一幀在消費,還有一幀在緩衝區中待命。

而 「幀緩衝區」 是用來抵抗網路抖動的,網路抖動往往是比較頻繁的,抖動的時間也有時會比較久一些,所以 「幀緩衝區」 相對要設置得大一點,但以不過於影響內存和播放延時為前提。

對於直播場景,為了防止 「被動丟幀」,往往 「幀緩衝區」 默認是設置為 「無限大」 的,當檢測到緩衝區達到一定閾值後,啟動一些諸如主動丟幀或者倍數播放的方式,來快速消耗掉緩衝的內容,從而降低內存和延時。

5 緩衝區何時會主動清空 ?

有如下幾種場景,播放器會主動清空緩衝區內的數據:

1. 播放器重置

2. 播放進度條被拖動3. 消除累積延時4. 系統內存告警

6 IJKPlayer 的緩衝區管理

ijkplayer 使用非常廣泛,這裡以它為例看看播放器緩衝區的真實案例是怎麼樣的 ?

1. 播放器打開後,緩衝 100ms,再開始播放

2. 如果遇到了卡頓(緩衝區為空),則暫停播放,緩衝到 1000ms,再開始播放

3. 如果再次遇到卡頓,則緩衝到 2000ms 再播,依次類推,直到 5000ms

可以看出,它的緩衝區的最大閾值是逐步遞增上去的,這是一個非常棒的用戶體驗優化,因為如果用戶網路不是那麼差的話,不用第一次緩衝就等 5s 了

7 總結

播放器的緩衝區管理,就分享到這裡了,如有疑問的小夥伴歡迎來信 [email protected]交流。另外,也歡迎大家關注我的新浪微博 @盧_俊 或者 微信公眾號 @Jhuster 獲取最新的文章和資訊。


推薦閱讀:
相關文章