web 網(wǎng)站做甘特圖教育機(jī)構(gòu)排名
在網(wǎng)絡(luò)視頻會議中, 我們常會遇到音視頻不同步的問題, 我們有一個專有名詞 lip-sync 唇同步來描述這類問題,當(dāng)我們看到人的嘴唇動作與聽到的聲音對不上的時候,不同步的問題就出現(xiàn)了
而在線會議中, 聽見清晰的聲音是優(yōu)先級最高的, 人耳對于聲音的延遲是很敏感的
根據(jù) T-REC-G.114-200305 中的描述
- 大于~280ms 有些用戶就會不滿意
- 大于~380ms 多數(shù)用戶就會不滿意
- 大于~500ms 幾乎所有用戶就會不滿意
我們就盡量使得聲音的延遲在 280 ms 之內(nèi),這是解決 lip-sync 問題的前提, 聲音不好的嚴(yán)重程序超過音視頻不同步。
我們可以定義一個 sync_diff 值 來表示音頻幀和視頻幀之間的時間差
- 正值表示音頻領(lǐng)先于視頻
- 負(fù)值表示音頻落后于視頻
ITU 對此給出以下的閾值:
- 不可感知 Undetectability (-100ms, +25ms)
- 可感知 Detectability: (-125ms, +45ms)
- 可接受 Acceptability: (–185ms, +90 ms)
- 影響用戶 Impact user experience (-∞, -185ms) ∪ (+90ms,∞)
(ITU-R BT.1359-1, Relative Timing of Sound and Vision for Broadcasting" 1998. Retrieved 30 May 2015)
當(dāng)我們在播放一個視頻幀及對應(yīng)的音頻幀的時候,要計算一下這個 sync_diff
sync_diff = audio_frame_time - video_frame_time
如果這個 sync_diff 大于 90ms, 也就是音頻包到得過早,就會有音視頻不同步的問題 - 聲音聽到了,嘴巴沒跟上.
如果這個 sync_diff 小于 -185ms, 也就是視頻包到得過早,就會有音視頻不同步的問題 - 嘴巴在動,聲音沒跟上.
不同步的原因

這個問題的原因主要在于音頻的采集, 編碼,傳輸, 解碼, 播放與視頻的采集,編碼,傳輸,解碼以及渲染一般是分開進(jìn)行的,因為音頻和視頻采集自不同的設(shè)備,即它們的來源不同,在網(wǎng)絡(luò)上傳輸也會有延遲,也由不同的設(shè)備進(jìn)行播放,這樣如果在接收方不采取措施進(jìn)行時間同步,就會極有可能看到口型和聽到的聲音對不上的情況。
由此派生出 3 個小問題:
- 如何將來自同一個人或設(shè)備的多路 audio 及 video stream關(guān)聯(lián)起來?
- 如何將 RTP 中的時間戳 timestamp 映射到發(fā)送方的音視頻采集時間
- 如何調(diào)整音頻或者視頻幀的播放時間,讓它們怎么之間相對同步?
解決方案
1. 如何將來自同一個人或設(shè)備的音視頻流關(guān)聯(lián)起來?
對于多媒體會話,每種類型的媒體(例如音頻或視頻)一般會在單獨的 RTP 會話中發(fā)送,發(fā)送方會在 RTCP SDES 消息中指明
接收方通過 CNAME 項關(guān)聯(lián)要同步的RTP流, 而這個 CNAME 包含在發(fā)送方所發(fā)送的 RTCP SDES 中
SDES 數(shù)據(jù)包包含常規(guī)包頭,有效負(fù)載類型為 202,項目計數(shù)等于數(shù)據(jù)包中 SSRC/CSRC 塊的數(shù)量,后跟零個或多個 SSRC/CSRC 塊,其中包含有關(guān)特定 SSRC 或 CSRC,每個都與 32 位邊界對齊。
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|V=2|P| SC | PT=SDES=202 | length L |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+| SSRC/CSRC_1 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| SDES items || ... |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+| SSRC/CSRC_2 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| SDES items || ... |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
CNAME 項在每個 SDES 數(shù)據(jù)包中都是必需的,而 SDES 數(shù)據(jù)包又是每個復(fù)合 RTCP 數(shù)據(jù)包中的必需部分。
與 SSRC 標(biāo)識符一樣,CNAME 必須與其他會話參與者的 CNAME 不同。 但 CNAME 不應(yīng)隨機(jī)選擇 CNAME 標(biāo)識符,而應(yīng)允許個人或程序通過 CNAME 內(nèi)容來定位其來源。
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| CNAME=1 | length | user and domain name ...+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
例如 Alice 向外發(fā)送一路音頻流,一路視頻流, 這兩路流會使用不同的 SSRC, 但是在其所發(fā)送的 RTCP SDES 消息會使用相同的 CNAME.
- RTP SSRC 1 ~ CNAME 1
- RTP SSRC 2 ~ CNAME 1
2. 同步的時間如何計算
來自同一個終端用戶的音頻和視頻, 在編碼發(fā)送的 RTP 包中有一個 timestamp, 這個時間戳表示媒體流的捕捉時間。
同時, 作為發(fā)送者也會發(fā)送 RTCP Sender Report, 其中包含發(fā)送的 RTP timestamp 和 NTP timestamp 的映射關(guān)系,這樣我們在接收方就可以把 RTP 包里的

對于每個 RTP 流,發(fā)送方定期發(fā)出 RTCP SR, 其中包含一對時間戳:
NTP 時間戳以及與該 RTP 流關(guān)聯(lián)的相應(yīng) RTP 時間戳。
這對時間戳傳達(dá)每個媒體流的 NTP 時間和 RTP 時間之間的關(guān)系。
先回顧一下 RTP packet 和 RTCP sender report
- RTP 包結(jié)構(gòu)
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|V=2|P|X| CC |M| PT | sequence number |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| timestamp |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| synchronization source (SSRC) identifier |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+| contributing source (CSRC) identifiers || .... |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- RTCP Sender Report 結(jié)構(gòu)
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+header |V=2|P| RC | PT=SR=200 | length |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| SSRC of sender |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+sender | NTP timestamp, most significant word |info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| NTP timestamp, least significant word |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| RTP timestamp |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| sender's packet count |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| sender's octet count |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+report | SSRC_1 (SSRC of first source) |block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+1 | fraction lost | cumulative number of packets lost |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| extended highest sequence number received |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| interarrival jitter |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| last SR (LSR) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| delay since last SR (DLSR) |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+report | SSRC_2 (SSRC of second source) |block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+2 : ... :+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+| profile-specific extensions |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
通過 NTP timestamp 和 RTP timestamp 之間的映射, 我們可以知道 audio 包的時間和 video 包的時間。
具體的計算可以參見 WebRTC 的 RtpToNtpEstimator 類, 它將收到的若干 SR 中的 NTP time 和 RTP timestamp 保存下來,然后 應(yīng)用最小二乘法來估算后續(xù) RTP timestamp 所對應(yīng)的 NTP timestamp, 大致為用最近 N=20 個 RTCP SR 包的 ntp timestamp 和 rtp timestamp 的構(gòu)造出線性關(guān)系 y = ax + b, 通過最小二乘法來計算收到的 RTP 包對應(yīng)的 ntp timestamp.
// Converts an RTP timestamp to the NTP domain.
// The class needs to be trained with (at least 2) RTP/NTP timestamp pairs from
// RTCP sender reports before the convertion can be done.
class RtpToNtpEstimator {public://...enum UpdateResult { kInvalidMeasurement, kSameMeasurement, kNewMeasurement };// Updates measurements with RTP/NTP timestamp pair from a RTCP sender report.UpdateResult UpdateMeasurements(NtpTime ntp, uint32_t rtp_timestamp);// Converts an RTP timestamp to the NTP domain.// Returns invalid NtpTime (i.e. NtpTime(0)) on failure.NtpTime Estimate(uint32_t rtp_timestamp) const;// Returns estimated rtp_timestamp frequency, or 0 on failure.double EstimatedFrequencyKhz() const;private:// Estimated parameters from RTP and NTP timestamp pairs in `measurements_`.// Defines linear estimation: NtpTime (in units of 1s/2^32) =// `Parameters::slope` * rtp_timestamp + `Parameters::offset`.struct Parameters {double slope;double offset;};// RTP and NTP timestamp pair from a RTCP SR report.struct RtcpMeasurement {NtpTime ntp_time;int64_t unwrapped_rtp_timestamp;};void UpdateParameters();int consecutive_invalid_samples_ = 0;std::list<RtcpMeasurement> measurements_;absl::optional<Parameters> params_;mutable RtpTimestampUnwrapper unwrapper_;
};
3. 調(diào)整播放和渲染時間
一般我們會以 audio 為主, video 向 audio 靠攏, 兩者時間一致也就會達(dá)到 lip sync 音視頻同步
- audio 包先來, video 包后來: audio 包放在 jitter buffer 時等一會兒, 但是這個時間是有限的, 音頻的流暢是首先要保證的, 視頻跟不上可以降低視頻的碼率
- video 包先來, audio 包后來: video 包要等 audio 包來, 這是為了讓音視頻同步要付出的代價
一般以音頻為主流 master stream,視頻為從流 slave stream。 一般方法是接收方維護(hù)音頻流的緩沖區(qū)的管理,并通過將視頻 RTP 時間戳轉(zhuǎn)換為正確從屬于音頻流的時間戳來調(diào)整視頻流的播放。
當(dāng)帶有RTP時間戳 RTPv的視頻幀到達(dá)接收器時,接收器通過四個步驟將RTP時間戳 RTPv 映射到視頻設(shè)備時間戳VTB( Video Time Base),如圖所示。
使用 Video RTCP SR 中的 RTP/NTP 時間戳對建立的映射,將視頻 RTP 時間戳 RTPv 映射到發(fā)送方 NTP 時間。
根據(jù)該 NTP 時間戳,使用 Audio RTCP SR 中的 RTP/NTP 時間戳對建立的映射,計算來自發(fā)送方的相應(yīng)音頻 RTPa 時間戳。
此時,視頻RTP時間戳被映射到音頻RTP 包的相同時間基準(zhǔn)。根據(jù)該音頻 RTP 時間戳,使用卡爾曼濾波的方法計算音頻設(shè)備時間基準(zhǔn)中的相應(yīng)時間戳。 結(jié)果是音頻設(shè)備時間基準(zhǔn) ATB(Audio Time Base) 中的時間戳。
根據(jù) ATB,使用偏移量 AtoV 計算視頻設(shè)備時基 VTB 中的相應(yīng)時間戳。
接收方需要確保帶有 RTP 時間戳 RTPv 的視頻幀使用所計算出的發(fā)送方視頻設(shè)備時間基準(zhǔn) VTB 播放。
AtoV = V_time - A_Time/(audio sample rate)
注:
- AtoV: 音頻相較視頻的偏移量
- ATB: Audio device Time Base 音頻設(shè)備的時間基準(zhǔn)
- VTB: Video device Time Base 視頻設(shè)備的時間基準(zhǔn)
具體方法可以參見 https://www.ccexpert.us/video-conferencing/using-rtcp-for-media-synchronization.html)

WebRTC 的做法原理上差不多,實現(xiàn)略有不同,可以參見 WebRTC 的源代碼 StreamSynchronization 類和 RtpStreamsSynchronizer 類
大致上它會計算出 video 的延遲
current_delay_ms = max(min_playout_delay_ms, jitter_delay_ms + decode_time _ms + render_delay_ms)
然后再計算視頻相對于音頻的延遲 relative_delay_ms
,
- 如果它大于0, 視頻比音頻慢,減小視頻延遲(主要是調(diào)整 jitter buffer delay),或者是增大音頻延遲, 取決于閾值 base_target_delay_ms
- 如果它小于0, 音頻比視頻慢,減小音頻延遲,或者是增大視頻延遲, 取決于閾值base_target_delay_ms
base_target_delay_ms 的比較邏輯參見StreamSynchronization::ComputeDelays,
if (diff_ms > 0) {// The minimum video delay is longer than the current audio delay.// We need to decrease extra video delay, or add extra audio delay.if (video_delay_.extra_ms > base_target_delay_ms_) {// We have extra delay added to ViE. Reduce this delay before adding// extra delay to VoE.video_delay_.extra_ms -= diff_ms;audio_delay_.extra_ms = base_target_delay_ms_;} else { // video_delay_.extra_ms > 0// We have no extra video delay to remove, increase the audio delay.audio_delay_.extra_ms += diff_ms;video_delay_.extra_ms = base_target_delay_ms_;}} else { // if (diff_ms > 0)// The video delay is lower than the current audio delay.// We need to decrease extra audio delay, or add extra video delay.if (audio_delay_.extra_ms > base_target_delay_ms_) {// We have extra delay in VoiceEngine.// Start with decreasing the voice delay.// Note: diff_ms is negative; add the negative difference.audio_delay_.extra_ms += diff_ms;video_delay_.extra_ms = base_target_delay_ms_;} else { // audio_delay_.extra_ms > base_target_delay_ms_// We have no extra delay in VoiceEngine, increase the video delay.// Note: diff_ms is negative; subtract the negative difference.video_delay_.extra_ms -= diff_ms; // X - (-Y) = X + Y.audio_delay_.extra_ms = base_target_delay_ms_;}
}
更多細(xì)節(jié)在 WebRTC 的代碼中
- class StreamSynchronization
- class RtpStreamsSynchronizer
通過StreamSynchronization::ComputeDelays計算出音頻和視頻的相對延遲,如果相對延遲很小( < 30ms), 則無需調(diào)整音視頻的播放時間,如果相對延遲很大, 則以 80ms 的幅度進(jìn)行逐步調(diào)整。 與傳統(tǒng)的只調(diào)視頻延遲,不調(diào)音頻延遲, WebRTC 會兩邊都調(diào)點,使得音視頻的時間彼此靠近,前提是音頻的延遲是在上面提到的可接受范圍之內(nèi)。
參考資料
- https://www.ciscopress.com/articles/article.asp?p=705533&seqNum=6
- https://www.ccexpert.us/video-conferencing/using-rtcp-for-media-synchronization.html
- https://testrtc.com/docs/how-do-you-find-lip-sync-issues-in-webrtc/
- https://en.wikipedia.org/wiki/Audio-to-video_synchronization
- https://www.simplehelp.net/2018/05/29/how-to-fix-out-of-sync-audio-video-in-an-mkv-mp4-or-avi/
*RFC6051: Rapid Synchronisation of RTP Flows