WebRTC 之音視頻同步

在網(wǎng)絡(luò)視頻會(huì)議中, 我們常會(huì)遇到音視頻不同步的問(wèn)題, 我們有一個(gè)專(zhuān)有名詞 lip-sync 唇同步來(lái)描述這類(lèi)問(wèn)題,當(dāng)我們看到人的嘴唇動(dòng)作與聽(tīng)到的聲音對(duì)不上的時(shí)候,不同步的問(wèn)題就出現(xiàn)了

而在線會(huì)議中, 聽(tīng)見(jiàn)清晰的聲音是優(yōu)先級(jí)最高的, 人耳對(duì)于聲音的延遲是很敏感的

根據(jù) T-REC-G.114-200305 中的描述

  • 大于~280ms 有些用戶(hù)就會(huì)不滿意
  • 大于~380ms 多數(shù)用戶(hù)就會(huì)不滿意
  • 大于~500ms 幾乎所有用戶(hù)就會(huì)不滿意

我們就盡量使得聲音的延遲在 280 ms 之內(nèi),這是解決 lip-sync 問(wèn)題的前提, 聲音不好的嚴(yán)重程序超過(guò)音視頻不同步。

我們可以定義一個(gè) sync_diff 值 來(lái)表示音頻幀和視頻幀之間的時(shí)間差

  • 正值表示音頻領(lǐng)先于視頻
  • 負(fù)值表示音頻落后于視頻

ITU 對(duì)此給出以下的閾值:

  • 不可感知 Undetectability (-100ms, +25ms)
  • 可感知 Detectability: (-125ms, +45ms)
  • 可接受 Acceptability: (–185ms, +90 ms)
  • 影響用戶(hù) 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)我們?cè)诓シ乓粋€(gè)視頻幀及對(duì)應(yīng)的音頻幀的時(shí)候,要計(jì)算一下這個(gè) sync_diff

sync_diff = audio_frame_time - video_frame_time

如果這個(gè) sync_diff 大于 90ms, 也就是音頻包到得過(guò)早,就會(huì)有音視頻不同步的問(wèn)題 - 聲音聽(tīng)到了,嘴巴沒(méi)跟上.

如果這個(gè) sync_diff 小于 -185ms, 也就是視頻包到得過(guò)早,就會(huì)有音視頻不同步的問(wèn)題 - 嘴巴在動(dòng),聲音沒(méi)跟上.

不同步的原因

lip sync 1

這個(gè)問(wèn)題的原因主要在于音頻的采集, 編碼,傳輸, 解碼, 播放與視頻的采集,編碼,傳輸,解碼以及渲染一般是分開(kāi)進(jìn)行的,因?yàn)橐纛l和視頻采集自不同的設(shè)備,即它們的來(lái)源不同,在網(wǎng)絡(luò)上傳輸也會(huì)有延遲,也由不同的設(shè)備進(jìn)行播放,這樣如果在接收方不采取措施進(jìn)行時(shí)間同步,就會(huì)極有可能看到口型和聽(tīng)到的聲音對(duì)不上的情況。

由此派生出 3 個(gè)小問(wèn)題:

  1. 如何將來(lái)自同一個(gè)人或設(shè)備的多路 audio 及 video stream關(guān)聯(lián)起來(lái)?
  2. 如何將 RTP 中的時(shí)間戳 timestamp 映射到發(fā)送方的音視頻采集時(shí)間
  3. 如何調(diào)整音頻或者視頻幀的播放時(shí)間,讓它們?cè)趺粗g相對(duì)同步?

解決方案

1. 如何將來(lái)自同一個(gè)人或設(shè)備的音視頻流關(guān)聯(lián)起來(lái)?

對(duì)于多媒體會(huì)話,每種類(lèi)型的媒體(例如音頻或視頻)一般會(huì)在單獨(dú)的 RTP 會(huì)話中發(fā)送,發(fā)送方會(huì)在 RTCP SDES 消息中指明
接收方通過(guò) CNAME 項(xiàng)關(guān)聯(lián)要同步的RTP流, 而這個(gè) CNAME 包含在發(fā)送方所發(fā)送的 RTCP SDES 中

SDES 數(shù)據(jù)包包含常規(guī)包頭,有效負(fù)載類(lèi)型為 202,項(xiàng)目計(jì)數(shù)等于數(shù)據(jù)包中 SSRC/CSRC 塊的數(shù)量,后跟零個(gè)或多個(gè) SSRC/CSRC 塊,其中包含有關(guān)特定 SSRC 或 CSRC,每個(gè)都與 32 位邊界對(duì)齊。

    0               1               2               3
    0 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 項(xiàng)在每個(gè) SDES 數(shù)據(jù)包中都是必需的,而 SDES 數(shù)據(jù)包又是每個(gè)復(fù)合 RTCP 數(shù)據(jù)包中的必需部分。

與 SSRC 標(biāo)識(shí)符一樣,CNAME 必須與其他會(huì)話參與者的 CNAME 不同。 但 CNAME 不應(yīng)隨機(jī)選擇 CNAME 標(biāo)識(shí)符,而應(yīng)允許個(gè)人或程序通過(guò) CNAME 內(nèi)容來(lái)定位其來(lái)源。

    0               1               2               3
    0 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ā)送一路音頻流,一路視頻流, 這兩路流會(huì)使用不同的 SSRC, 但是在其所發(fā)送的 RTCP SDES 消息會(huì)使用相同的 CNAME.

  • RTP SSRC 1 ~ CNAME 1
  • RTP SSRC 2 ~ CNAME 1

2. 同步的時(shí)間如何計(jì)算

來(lái)自同一個(gè)終端用戶(hù)的音頻和視頻, 在編碼發(fā)送的 RTP 包中有一個(gè) timestamp, 這個(gè)時(shí)間戳表示媒體流的捕捉時(shí)間。
同時(shí), 作為發(fā)送者也會(huì)發(fā)送 RTCP Sender Report, 其中包含發(fā)送的 RTP timestamp 和 NTP timestamp 的映射關(guān)系,這樣我們?cè)诮邮辗骄涂梢园?RTP 包里的

lip sync flow

對(duì)于每個(gè) RTP 流,發(fā)送方定期發(fā)出 RTCP SR, 其中包含一對(duì)時(shí)間戳:

NTP 時(shí)間戳以及與該 RTP 流關(guān)聯(lián)的相應(yīng) RTP 時(shí)間戳。

這對(duì)時(shí)間戳傳達(dá)每個(gè)媒體流的 NTP 時(shí)間和 RTP 時(shí)間之間的關(guān)系。

先回顧一下 RTP packet 和 RTCP sender report

  • RTP 包結(jié)構(gòu)

    0                   1                   2                   3
    0 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                   3
         0 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                  |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

通過(guò) NTP timestamp 和 RTP timestamp 之間的映射, 我們可以知道 audio 包的時(shí)間和 video 包的時(shí)間。

具體的計(jì)算可以參見(jiàn) WebRTC 的 RtpToNtpEstimator 類(lèi), 它將收到的若干 SR 中的 NTP time 和 RTP timestamp 保存下來(lái),然后 應(yīng)用最小二乘法來(lái)估算后續(xù) RTP timestamp 所對(duì)應(yīng)的 NTP timestamp, 大致為用最近 N=20 個(gè) RTCP SR 包的 ntp timestamp 和 rtp timestamp 的構(gòu)造出線性關(guān)系 y = ax + b, 通過(guò)最小二乘法來(lái)計(jì)算收到的 RTP 包對(duì)應(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)整播放和渲染時(shí)間

一般我們會(huì)以 audio 為主, video 向 audio 靠攏, 兩者時(shí)間一致也就會(huì)達(dá)到 lip sync 音視頻同步

  1. audio 包先來(lái), video 包后來(lái): audio 包放在 jitter buffer 時(shí)等一會(huì)兒, 但是這個(gè)時(shí)間是有限的, 音頻的流暢是首先要保證的, 視頻跟不上可以降低視頻的碼率
  2. video 包先來(lái), audio 包后來(lái): video 包要等 audio 包來(lái), 這是為了讓音視頻同步要付出的代價(jià)

一般以音頻為主流 master stream,視頻為從流 slave stream。 一般方法是接收方維護(hù)音頻流的緩沖區(qū)的管理,并通過(guò)將視頻 RTP 時(shí)間戳轉(zhuǎn)換為正確從屬于音頻流的時(shí)間戳來(lái)調(diào)整視頻流的播放。

當(dāng)帶有RTP時(shí)間戳 RTPv的視頻幀到達(dá)接收器時(shí),接收器通過(guò)四個(gè)步驟將RTP時(shí)間戳 RTPv 映射到視頻設(shè)備時(shí)間戳VTB( Video Time Base),如圖所示。

  1. 使用 Video RTCP SR 中的 RTP/NTP 時(shí)間戳對(duì)建立的映射,將視頻 RTP 時(shí)間戳 RTPv 映射到發(fā)送方 NTP 時(shí)間。

  2. 根據(jù)該 NTP 時(shí)間戳,使用 Audio RTCP SR 中的 RTP/NTP 時(shí)間戳對(duì)建立的映射,計(jì)算來(lái)自發(fā)送方的相應(yīng)音頻 RTPa 時(shí)間戳。
    此時(shí),視頻RTP時(shí)間戳被映射到音頻RTP 包的相同時(shí)間基準(zhǔn)。

  3. 根據(jù)該音頻 RTP 時(shí)間戳,使用卡爾曼濾波的方法計(jì)算音頻設(shè)備時(shí)間基準(zhǔn)中的相應(yīng)時(shí)間戳。 結(jié)果是音頻設(shè)備時(shí)間基準(zhǔn) ATB(Audio Time Base) 中的時(shí)間戳。

  4. 根據(jù) ATB,使用偏移量 AtoV 計(jì)算視頻設(shè)備時(shí)基 VTB 中的相應(yīng)時(shí)間戳。

接收方需要確保帶有 RTP 時(shí)間戳 RTPv 的視頻幀使用所計(jì)算出的發(fā)送方視頻設(shè)備時(shí)間基準(zhǔn) VTB 播放。

      AtoV = V_time - A_Time/(audio sample rate)

注:

  • AtoV: 音頻相較視頻的偏移量
  • ATB: Audio device Time Base 音頻設(shè)備的時(shí)間基準(zhǔn)
  • VTB: Video device Time Base 視頻設(shè)備的時(shí)間基準(zhǔn)

具體方法可以參見(jiàn) https://www.ccexpert.us/video-conferencing/using-rtcp-for-media-synchronization.html)

WebRTC 的做法原理上差不多,實(shí)現(xiàn)略有不同,可以參見(jiàn) WebRTC 的源代碼 StreamSynchronization 類(lèi)和 RtpStreamsSynchronizer 類(lèi)

大致上它會(huì)計(jì)算出 video 的延遲

current_delay_ms = max(min_playout_delay_ms, jitter_delay_ms + decode_time _ms + render_delay_ms)

然后再計(jì)算視頻相對(duì)于音頻的延遲 relative_delay_ms,

  • 如果它大于0, 視頻比音頻慢,減小視頻延遲(主要是調(diào)整 jitter buffer delay),或者是增大音頻延遲, 取決于閾值 base_target_delay_ms
  • 如果它小于0, 音頻比視頻慢,減小音頻延遲,或者是增大視頻延遲, 取決于閾值base_target_delay_ms

base_target_delay_ms 的比較邏輯參見(jiàn)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

通過(guò)StreamSynchronization::ComputeDelays計(jì)算出音頻和視頻的相對(duì)延遲,如果相對(duì)延遲很小( < 30ms), 則無(wú)需調(diào)整音視頻的播放時(shí)間,如果相對(duì)延遲很大, 則以 80ms 的幅度進(jìn)行逐步調(diào)整。 與傳統(tǒng)的只調(diào)視頻延遲,不調(diào)音頻延遲, WebRTC 會(huì)兩邊都調(diào)點(diǎn),使得音視頻的時(shí)間彼此靠近,前提是音頻的延遲是在上面提到的可接受范圍之內(nèi)。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容