WebRTC 音視頻同步原理與實現

全部的基於網絡傳輸的音視頻採集播放系統都會存在音視頻同步的問題,做爲現代互聯網實時音視頻通訊系統的表明,WebRTC 也不例外。本文將對音視頻同步的原理以及 WebRTC 的實現作深刻分析。html

時間戳 (timestamp)

同步問題就是快慢的問題,就會牽扯到時間跟音視頻流媒體的對應關係,就有了時間戳的概念。網絡

時間戳用來定義媒體負載數據的採樣時刻,從單調線性遞增的時鐘中獲取,時鐘的精度由 RTP 負載數據的採樣頻率決定。音頻和視頻的採樣頻率是不同的,通常音頻的採樣頻率有 16KHz、44.1KHz、48KHz 等,而視頻反映在採樣幀率上,通常幀率有 25fps、29.97fps、30fps 等。tcp

習慣上音頻的時間戳的增速就是其採樣率,好比 16KHz 採樣,每 10ms 採集一幀,則下一幀的時間戳,比上一幀的時間戳,從數值上多 16 x10=160,即音頻時間戳增速爲 16/ms。而視頻的採樣頻率習慣上是按照 90KHz 來計算的,就是每秒 90K 個時鐘 tick,之因此用 90K 是由於它正好是上面所說的視頻幀率的倍數,因此就採用了 90K。因此視頻幀的時間戳的增加速率就是 90/ms。ide

時間戳的生成

音頻幀時間戳的生成

WebRTC 的音頻幀的時間戳,從第一個包爲 0,開始累加,每一幀增長 = 編碼幀長 (ms) x 採樣率 / 1000,若是採樣率 16KHz,編碼幀長 20ms,則每一個音頻幀的時間戳遞增 20 x 16000/1000 = 320。這裏只是說的未打包以前的音頻幀的時間戳,而封裝到 RTP 包裏面的時候,會將這個音頻幀的時間戳再累加上一個隨機偏移量(構造函數裏生成),而後做爲此 RTP 包的時間戳,發送出去,以下面代碼所示,注意,這個邏輯一樣適用於視頻包。
函數

視頻幀時間戳的生成

WebRTC 的視頻幀,生成機制跟音頻幀徹底不一樣。視頻幀的時間戳來源於系統時鐘,採集完成後至編碼以前的某個時刻(這個傳遞鏈路很是長,不一樣配置的視頻幀,走不一樣的邏輯,會有不一樣的獲取位置),獲取當前系統的時間 timestamp_us_ ,而後算出此係統時間對應的 ntp_time_ms_ ,再根據此 ntp 時間算出原始視頻幀的時間戳 timestamp_rtp_ ,參看下面的代碼,計算邏輯也在 OnFrame 這個函數中。

爲何視頻幀採用了跟音頻幀不一樣的時間戳計算機制呢?個人理解,通常狀況音頻的採集設備的採樣間隔和時鐘精度更加準確,10ms 一幀,每秒是 100 幀,通常不會出現大的抖動,而視頻幀的幀間隔時間較大采集精度,每秒 25 幀的話,就是 40ms 一幀。若是還採用音頻的按照採樣率來遞增的話,可能會出現跟實際時鐘對不齊的狀況,因此就直接每取一幀,按照取出時刻的系統時鐘算出一個時間戳,這樣能夠再現真實視頻幀跟實際時間的對應關係。ui

跟上面音頻同樣,在封裝到 RTP 包的時候,會將原始視頻幀的時間戳累加上一個隨機偏移量(此偏移量跟音頻的並非同一個值),做爲此 RTP 包的時間戳發送出去。值得注意的是,這裏計算的 NTP 時間戳根本就不會隨着 RTP 數據包一塊兒發送出去,由於 RTP 包的包頭裏面沒有 NTP 字段,即便是擴展字段裏,咱們也沒有放這個值,以下面視頻的時間相關的擴展字段。
阿里雲

音視頻同步核心依據

從上面能夠看出,RTP 包裏面只包含每一個流的獨立的、單調遞增的時間戳信息,也就是說音頻和視頻兩個時間戳徹底是獨立的,沒有關係的,沒法只根據這個信息來進行同步,由於沒法對兩個流的時間進行關聯,咱們須要一種映射關係,將兩個獨立的時間戳關聯起來。編碼

這個時候 RTCP 包裏面的一種發送端報告分組 SR (SenderReport) 包就上場了,詳情請參考 RFC3550

SR 包的其中一個做用就是來告訴咱們每一個流的 RTP 包的時間戳和 NTP 時間的對應關係的。靠的就是上邊圖片中標出的 NTP 時間戳和 RTP 時間戳,經過 RFC3550 的描述,咱們知道這兩個時間戳對應的是同一個時刻,這個時刻表示此 SR 包生成的時刻。這就是咱們對音視頻進行同步的最核心的依據,全部的其它計算都是圍繞這個核心依據來展開的。code

SR 包的生成

由上面論述可知,NTP 時間和 RTP 時間戳是同一時刻的不一樣表示,只是精度和單位不同。NTP 時間是絕對時間,以毫秒爲單位,而 RTP 時間戳則和媒體的採樣頻率有關,是一個單調遞增數值。生成 SR 包的過程在 RTCPSender::BuildSR(const RtcpContext& ctx) 函數裏面,老版本里面有 bug,寫死了採樣率爲 8K,新版本已經修復,下面截圖是老版本的代碼:
視頻

計算的思路以下

首先,咱們要獲取當前時刻(即 SR 包生成時刻)的 NTP 時間。這個直接從傳過來的參數 ctx 中就能夠得到:

其次,咱們要計算當前時刻,應該對應的 RTP 的時間戳是多少。根據最後一個發送的 RTP 包的時間戳 last_rtp_timestamp_ 和它的採集時刻的系統時間 last_frame_capture_time_ms_,和當前媒體流的時間戳的每 ms 增加速率 rtp_rate ,以及從 last_frame_capture_time_ms_ 到當前時刻的時間流逝,就能夠算出來。注意,last_rtp_timestamp_ 是媒體流的原始時間戳,不是通過隨機偏移的 RTP 包時間戳,因此最後又累加了偏移量 timestamp_offset_ 。其中最後一個發送的 RTP 包的時間信息是經過下面的函數進行更新的:

音視頻同步的計算

由於同一臺機器上音頻流和視頻流的本地系統時間是同樣的,也就是系統時間對應的 NTP 格式的時間也是同樣的,是在同一個座標系上的,因此能夠把 NTP 時間做爲橫軸 X,單位是 ms,而把 RTP 時間戳的值做爲縱軸 Y,畫在一塊兒。下圖展現了計算音視頻同步的原理和方法,其實很簡單,就是使用最近的兩個 SR 點,兩點肯定一條直線,以後給任意一個 RTP 時間戳,均可以求出對應的 NTP 時間,又由於視頻和音頻的 NTP 時間是在同一基準上的,因此就能夠算出二者的差值。

上圖以音頻的兩個 SR 包爲例,肯定出了 RTP 和 NTP 對應關係的直線,而後給任意一個 rtp_a,就算出了其對應的 NTP_a,同理也能夠求任意視頻包 rtp_v 對應的 NTP_v 的時間點,兩個的差值就是時間差。

下面是 WebRTC 裏面計算直線對應的係數 rate 和偏移 offset 的代碼:

在 WebRTC 中計算的是最新收到的音頻 RTP 包和最新收到的視頻 RTP 包的對應的 NTP 時間,做爲網絡傳輸引入的不一樣步時長,而後又根據當前音頻和視頻的 JitterBuffer 和播放緩衝區的大小,獲得了播放引入的不一樣步時長,根據兩個不一樣步時長,獲得了最終的音視頻不一樣步時長,計算過程在 StreamSynchronization::ComputeRelativeDelay() 函數中,以後又通過了 StreamSynchronization::ComputeDelays() 函數對其進行了指數平滑等一系列的處理和判斷,得出最終控制音頻和視頻的最小延時時間,分別經過 syncable_audio_->SetMinimumPlayoutDelay(target_audio_delay_ms)syncable_video_->SetMinimumPlayoutDelay(target_video_delay_ms) 應用到了音視頻的播放緩衝區。

這一系列操做都是由定時器調用 RtpStreamsSynchronizer::Process() 函數來處理的。

另外須要注意一下,在知道採樣率的狀況下,是能夠經過一個 SR 包來計算的,若是沒有 SR 包,是沒法進行準確的音視頻同步的

WebRTC 中實現音視頻同步的手段就是 SR 包,核心的依據就是 SR 包中的 NTP 時間和 RTP 時間戳。最後的兩張 NTP 時間-RTP 時間戳 座標圖若是你能看明白(其實很簡單,就是求解出直線方程來計算 NTP),那麼也就真正的理解了 WebRTC 中音視頻同步的原理。若是有什麼遺漏或者錯誤,歡迎你們一塊兒交流!

「視頻雲技術」你最值得關注的音視頻技術公衆號,每週推送來自阿里雲一線的實踐技術文章,在這裏與音視頻領域一流工程師交流切磋。

相關文章
相關標籤/搜索