WebRtc Video Receiver(七)-基于Kalman filter模型的平滑渲染時(shí)間估計(jì)

1)前言

  • 前一篇文章分析了FrameBuffer模塊對(duì)視頻幀的插入原理,以及出隊(duì)(送到解碼隊(duì)列)的機(jī)制。
  • 在出隊(duì)的過程中涉及到了很多和延遲相關(guān)的信息,沒有分析,諸如渲染時(shí)間的計(jì)算、幀延遲的計(jì)算、抖動(dòng)的計(jì)算等都未進(jìn)行相應(yīng)的分析。
  • 同時(shí)經(jīng)過上文的分析,在實(shí)際的測(cè)試過程中發(fā)現(xiàn),在視頻接收的過程中如果不出現(xiàn)丟幀的現(xiàn)象,那么從組幀到送入到FrameBuffer的緩存隊(duì)列的耗時(shí)是十分小的,那么實(shí)際測(cè)試過程中的延遲究竟是怎么來的,經(jīng)過上文的分析,初步得出,首先是在出隊(duì)前從緩存列表中獲取待解碼的幀的時(shí)候會(huì)根據(jù)期望渲染時(shí)間計(jì)算延遲,這個(gè)延遲直接會(huì)作用到延遲重復(fù)任務(wù)的調(diào)度時(shí)間。
  • 其次就是在實(shí)際解碼過程中不同的硬件平臺(tái)能力不一樣硬件解碼器的原理也有區(qū)別,比如有些解碼器本身就會(huì)緩存視頻幀也是導(dǎo)致實(shí)際播放延遲的一個(gè)原因。
  • 再者就是音頻和視頻之間的同步也不會(huì)是導(dǎo)致播放延遲的因素之一。
  • 通過前面幾篇文章的一系列分析,不難看出如VideoReceiveStream2模塊、RtpVideoStreamReceiver2模塊、FrameBuffer模塊在整個(gè)工作的過程中都復(fù)用了同一個(gè)VCMTiming模塊,在Call模塊創(chuàng)建VideoReceiveStream2模塊的時(shí)候被實(shí)例化,之后在其他模塊中被引用。
  • 接下來按照實(shí)際函數(shù)調(diào)用棧的流程對(duì)視頻接收模塊涉及到的時(shí)序相關(guān)信息進(jìn)行一一闡述。

2)PlayoutDelay更新

  • 顧名思義,叫做播放延遲,該值可在發(fā)送端通過RTP頭擴(kuò)展進(jìn)行攜帶,如果未攜帶,默認(rèn)值為{-1,-1}
  • 首先看其定義如下:
#common_types.h
struct PlayoutDelay {
  PlayoutDelay(int min_ms, int max_ms) : min_ms(min_ms), max_ms(max_ms) {}
  int min_ms;//最小播放延遲
  int max_ms;//做到播放延遲
  ....  
}    
#rtp_video_header.h
struct RTPVideoHeader {
  ....  
  PlayoutDelay playout_delay = {-1, -1};
  ....
}
# encoded_image.h
class RTC_EXPORT EncodedImage {
 public:
  ...  
  // When an application indicates non-zero values here, it is taken as an
  // indication that all future frames will be constrained with those limits
  // until the application indicates a change again.
  PlayoutDelay playout_delay_ = {-1, -1};
  ...
}
RtpFrameObject::RtpFrameObject(
   ......
    : first_seq_num_(first_seq_num),
      last_seq_num_(last_seq_num),
      last_packet_received_time_(last_packet_received_time),
      times_nacked_(times_nacked) {

  // Setting frame's playout delays to the same values
  // as of the first packet's.
  SetPlayoutDelay(rtp_video_header_.playout_delay);
  ...
}
  • PacketBuffer模塊組幀過程中,每組幀成功后會(huì)執(zhí)行發(fā)現(xiàn)幀的處理,此時(shí)會(huì)創(chuàng)建對(duì)應(yīng)的RtpFrameObject
  • 而在RtpFrameObject的構(gòu)造函數(shù)會(huì)通過SetPlayoutDelay函數(shù)為當(dāng)前幀設(shè)置播放延遲時(shí)間,由此可看出如果發(fā)送端未擴(kuò)展該RTP頭的話,那么默認(rèn)值為{-1,-1}。
  • 默認(rèn)是未擴(kuò)展的如果需要擴(kuò)展需要SDP支持如下:
"http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"
  • 接下來介紹,在何時(shí)會(huì)將該延遲作用到其他模塊。


    WebRtc_Video_Stream_Receiver_07_01.png
  • 接上文的分析,待解碼視頻幀的插入驅(qū)動(dòng)是由VideoReceiveStream2:: OnCompleteFrame()函數(shù)來驅(qū)動(dòng)的,在插入視頻幀前首先會(huì)將播放延遲信息作用到VCMTiming模塊,上面有介紹VideoReceiveStream2模塊、RtpVideoStreamReceiver2模塊、FrameBuffer模塊在整個(gè)工作的過程中都復(fù)用了同一個(gè)VCMTiming實(shí)例(同一路流)。

void VideoReceiveStream2::OnCompleteFrame(
    std::unique_ptr<video_coding::EncodedFrame> frame) {
  ....
  //拿到PlayoutDelay引用    
  const PlayoutDelay& playout_delay = frame->EncodedImage().playout_delay_;
    
  if (playout_delay.min_ms >= 0) {
    frame_minimum_playout_delay_ms_ = playout_delay.min_ms;
    UpdatePlayoutDelays();
  }

  if (playout_delay.max_ms >= 0) {
    frame_maximum_playout_delay_ms_ = playout_delay.max_ms;
    UpdatePlayoutDelays();
  }
  ....
}
  • 如果playout_delay.min_ms >= 0或者playout_delay.max_ms >= 0都會(huì)調(diào)用UpdatePlayoutDelays函數(shù)將該播放延遲作用到VCMTiming模塊。
void VCMTiming::set_min_playout_delay(int min_playout_delay_ms) {
  rtc::CritScope cs(&crit_sect_);
  min_playout_delay_ms_ = min_playout_delay_ms;
}

void VCMTiming::set_max_playout_delay(int max_playout_delay_ms) {
  rtc::CritScope cs(&crit_sect_);
  max_playout_delay_ms_ = max_playout_delay_ms;
}
  • VCMTiming模塊記錄當(dāng)前幀的min_playout_delay_ms_max_playout_delay_ms_供后續(xù)延遲估計(jì)使用。

3)RenderTimeMs設(shè)置流程

  • 回顧上文的分析,FindNextFrame函數(shù)在找待解碼幀的時(shí)候會(huì)通過VCMTiming模塊獲取期望渲染時(shí)間。
  • 本節(jié)內(nèi)容著重介紹RenderTimeMs的設(shè)置業(yè)務(wù)流程以及大致的原理,涉及到Kalman filter的實(shí)現(xiàn)原理在第4節(jié)內(nèi)容將進(jìn)行詳細(xì)分析。本節(jié)涉及的大致流程如下:
    WebRtc_Video_Stream_Receiver_07_02.png
  • 從上圖左側(cè)部分可知,在當(dāng)前幀插入到frame_的時(shí)候如果當(dāng)前幀不是重傳幀的話,會(huì)使用VCMTiming模塊調(diào)用TimestampExtrapolator模塊的Update()函數(shù)依據(jù)當(dāng)前幀的rtp時(shí)間戳來估計(jì)當(dāng)前幀的期望接收時(shí)間,并對(duì)Kalman gain進(jìn)行校準(zhǔn),其原理將在第4節(jié)內(nèi)容進(jìn)行詳細(xì)分析。
int64_t FrameBuffer::FindNextFrame(int64_t now_ms) {
  ....
  //默認(rèn)該函數(shù)調(diào)用到這里的時(shí)候期望渲染時(shí)間都還未賦值的    
  if (frame->RenderTime() == -1) {
      //首先調(diào)用VCMTiming獲取期望渲染時(shí)間,然后將其設(shè)置到Frame中,供后續(xù)使用
      frame->SetRenderTime(timing_->RenderTimeMs(frame->Timestamp(), now_ms));
  }
  ...  
  //得出最大等待時(shí)間    
  wait_ms = timing_->MaxWaitingTime(frame->RenderTime(), now_ms);
  ....
  //取最小時(shí)間,如果在一次調(diào)度時(shí)間(未超時(shí)范圍內(nèi))的話,返回wait_ms    
  wait_ms = std::min<int64_t>(wait_ms, latest_return_time_ms_ - now_ms);
    
  wait_ms = std::max<int64_t>(wait_ms, 0);  
  return wait_ms;
}    
  • 以上函數(shù)返回的wait_ms值會(huì)直接決定重復(fù)延遲隊(duì)列的執(zhí)行時(shí)間(也就是等多節(jié)執(zhí)行),如果wait_ms等于0,則說明重復(fù)延遲隊(duì)列會(huì)立即執(zhí)行。優(yōu)化該值趨向0會(huì)節(jié)省一定的延遲。

3.1)VCMTiming模塊獲取期望渲染時(shí)間

int64_t VCMTiming::RenderTimeMs(uint32_t frame_timestamp,
                                int64_t now_ms) const {
  rtc::CritScope cs(&crit_sect_);
  return RenderTimeMsInternal(frame_timestamp, now_ms);
}
//frame_timestamp為當(dāng)前幀的時(shí)間戳以1/90k為單位,now_ms為當(dāng)前Clock時(shí)間
int64_t VCMTiming::RenderTimeMsInternal(uint32_t frame_timestamp,
                                        int64_t now_ms) const {
  //如果min_playout_delay_ms_=0并且max_playout_delay_ms_=0則表示立即渲染
 // 不建議賦值0,若賦值0的話jitterDelay就失效了
  if (min_playout_delay_ms_ == 0 && max_playout_delay_ms_ == 0) {
    // Render as soon as possible.
    return 0;
  }
  //傳入當(dāng)前幀的時(shí)間戳,來得到一個(gè)平滑渲染時(shí)間,TimestampExtrapolator通過卡爾曼濾波負(fù)責(zé)期望接收時(shí)間的產(chǎn)生
  int64_t estimated_complete_time_ms =
      ts_extrapolator_->ExtrapolateLocalTime(frame_timestamp);
  if (estimated_complete_time_ms == -1) {
    estimated_complete_time_ms = now_ms;
  }

  // Make sure the actual delay stays in the range of |min_playout_delay_ms_|
  // and |max_playout_delay_ms_|.
  // 和min_playout_delay_ms_取最大值,min_playout_delay_ms_默認(rèn)值-1,  
  int actual_delay = std::max(current_delay_ms_, min_playout_delay_ms_);
  //和max_playout_delay_ms_求最小值,max_playout_delay_ms_默認(rèn)值-1  
  actual_delay = std::min(actual_delay, max_playout_delay_ms_);
  return estimated_complete_time_ms + actual_delay;
}
  • 該函數(shù)首先判斷min_playout_delay_ms_max_playout_delay_ms_是否同時(shí)為0,如果同時(shí)為0則表示會(huì)理解發(fā)送到解碼隊(duì)列并解碼后后立即渲染。
  • 若上述條件不滿足,也就是說什么時(shí)候渲染依據(jù)系統(tǒng)框架的估計(jì)來做。
  • 首先調(diào)用TimestampExtrapolator模塊的ExtrapolateLocalTime函數(shù)來估計(jì)出一個(gè)期望接收時(shí)間,最后將該時(shí)間和actual_delay實(shí)際延遲相加得到最終的期望渲染時(shí)間
  • 其中actual_delay的值是通過VCMTiming::SetJitterDelayVCMTiming::UpdateCurrentDelay兩個(gè)函數(shù)來進(jìn)行更新的,這兩個(gè)函數(shù)在下面進(jìn)行分析。
  • 最終得出視頻幀的最終期望渲染時(shí)間 = 平滑渲染時(shí)間 + 當(dāng)前實(shí)際播放延遲(它的原理有是什么)

3.2)VCMTiming模塊獲取調(diào)度等待時(shí)間

  • FrameBuffer模塊的FindNextFrame函數(shù)通過該函數(shù)返回一個(gè)最大等待時(shí)間,也就是說如果找到了一幀數(shù)據(jù),但是FrameBuffer模塊并不會(huì)立即將其發(fā)送到解碼隊(duì)列,而是要等待一段時(shí)間,再發(fā)送到解碼器,該函數(shù)的作用就是得到等待多長(zhǎng)時(shí)間發(fā)送到解碼隊(duì)列進(jìn)行解碼
int64_t VCMTiming::MaxWaitingTime(int64_t render_time_ms,
                                  int64_t now_ms) const {
  rtc::CritScope cs(&crit_sect_);
  const int64_t max_wait_time_ms =
      render_time_ms - now_ms - RequiredDecodeTimeMs() - render_delay_ms_;

  return max_wait_time_ms;
}
  • 上述代碼顯示最大等待時(shí)間 (何時(shí)發(fā)到解碼隊(duì)列)= 期望渲染時(shí)間 - 當(dāng)前時(shí)間 - 解碼所需要的的時(shí)間 - 渲染延遲的時(shí)間
  • 其中正常情況下期望渲染時(shí)間是根據(jù)卡爾曼濾波理論估計(jì)出來的。
  • render_delay_ms_默認(rèn)為10ms,可通過VCMTiming::set_render_delay進(jìn)行設(shè)置,默認(rèn)在初始化階段實(shí)例化VideoReceiveStream2模塊的時(shí)候在其構(gòu)造函數(shù)中有調(diào)用該函數(shù),可通過修改webrtc::VideoReceiveStream::Config::render_delay_ms變量進(jìn)行設(shè)置。
  • 經(jīng)過以上的分析最大的困難之處就是平滑渲染時(shí)間estimated_complete_time_ms的估計(jì)過程,在后續(xù)將專門分析卡爾曼的原理。
  • 按照正常的流程如果卡爾曼估計(jì)出來平滑渲染時(shí)間比較大,然后解碼所需要的的時(shí)間已知的情況下,那么優(yōu)化就必須放在卡爾曼濾波器的身上。
  • 通過上述的分析,期望渲染時(shí)間的延遲會(huì)直接影響到FrameBuffer模塊重復(fù)延遲隊(duì)列的調(diào)度,也就決定了當(dāng)前幀播放的延遲。
  • 用時(shí)間軸來描述當(dāng)前時(shí)間,進(jìn)入解碼隊(duì)列時(shí)間,解碼延遲時(shí)間,渲染等待時(shí)間的關(guān)系,如下:


    image-20210614150723557.png

4)TimestampExtrapolator Kalman filter期望渲染時(shí)間估計(jì)

  • 本節(jié)內(nèi)容從四個(gè)方面來進(jìn)行介紹。
  • 首先介紹卡爾曼理論5大公式以及基于rtp時(shí)間戳和當(dāng)前實(shí)際接收時(shí)間的理論模型
  • 其次基于理論模型建立狀態(tài)轉(zhuǎn)移方程以及觀測(cè)方程
  • 再次介紹TimestampExtrapolator::ExtrapolateLocalTime函數(shù)期望接收時(shí)間的計(jì)算。
  • 最后結(jié)合TimestampExtrapolator::Update函數(shù)分析狀態(tài)變量的計(jì)算原理和Kalman Ganin的更新。

4.1)TimestampExtrapolator模塊Kalman模型

  • 為后續(xù)的方便分析首先介紹卡爾曼濾波的5大核心公式,本文不做理論推導(dǎo)


    image-20210614000341875.png
  • 根據(jù)卡爾曼濾波的5大核心公式,首先需要建立狀態(tài)轉(zhuǎn)移方程和觀測(cè)方程

  • 為建立狀態(tài)轉(zhuǎn)移方程和觀測(cè)方程,需要先了解一下rtp time stamp和實(shí)際時(shí)間的相應(yīng)關(guān)系

  // Local time in webrtc time base.
  int64_t current_time_us = clock_->TimeInMicroseconds();
  int64_t current_time_ms = current_time_us / rtc::kNumMicrosecsPerMillisec;

  // Capture time may come from clock with an offset and drift from clock_.
  int64_t capture_ntp_time_ms = current_time_ms + delta_ntp_internal_ms_;
  // Convert NTP time, in ms, to RTP timestamp.
  const int kMsToRtpTimestamp = 90;
  uint32_t timestamp_rtp =
      kMsToRtpTimestamp * static_cast<uint32_t>(capture_ntp_time_ms);

  • webrtc 在發(fā)送每一幀視頻數(shù)據(jù)前通過上述代碼來設(shè)置每幀的rtp時(shí)間戳,以90KHZ為采樣率,也就是每秒鐘被劃分成了90000個(gè)時(shí)間塊,假設(shè)是60fps每秒的幀率,那么每幀的rtp時(shí)間戳理論上是相隔90000 / 60 = 1500個(gè)時(shí)間塊,也就是每幀數(shù)據(jù)之間RTP時(shí)間戳的增量為1500,如果將該時(shí)間增量換算成ms數(shù)如下:
 fps = 60fps
 samplerate = 90000    
 timestampDiff(k) = rtpTimeStamp(k) - rtpTimeStamp(0)                        (4.1.1)
 timestampDiffToMs(k) = timestampDiff(k) * 1000 / samplerate                 (4.1.2)
  • timestampDiff(k)表示第(k)幀視頻和第一幀視頻的rtpTimeStamp之差

  • timestampDiffToMs(k)表示第(k)幀視頻和第一幀視頻之間所經(jīng)歷的毫秒數(shù)

  • 基于此,建立如下到達(dá)傳輸模型


    WebRtc_Video_Stream_Receiver_07_06.png
  • 以第一幀(t0,T0)作為基準(zhǔn),來估計(jì)接收到第k幀的期望接收時(shí)間

  • 假設(shè)上述模型沒有任何誤差和干擾,那么在已知_startMs的情況下,上述的傳輸曲線應(yīng)該都是藍(lán)色實(shí)現(xiàn)所示,并很容易就能得到如下計(jì)算

 t(0) = _startMs
 t(k) = timestampDiffToMs(k) + t(0)                                     (4.1.3)
  • 但事實(shí)上不上這樣,傳輸過程有很多的不確定性諸如網(wǎng)絡(luò)延遲抖動(dòng)等,且由于采集幀率可能也具有誤差,也就是sample_rate可能大于90KHZ,最終每幀數(shù)據(jù)的到達(dá)模型可能就變成了上述的紅色虛線部分所示,這樣timestampDiffToMs(k)就會(huì)比第(k)幀達(dá)到接收端實(shí)際所經(jīng)歷的時(shí)間要大或者要小,從而使得t(k)第(k)幀的接收時(shí)間變得不準(zhǔn),這樣較為準(zhǔn)確的t(k)應(yīng)該用如下公式來描述
 t(k) = timestampDiffToMs(k) + t(0) + error(k)                          (4.1.4)
  • 其中error(k)表示傳輸過程中采集噪聲和網(wǎng)絡(luò)噪聲以及其他噪聲的總集合,如果將網(wǎng)絡(luò)延遲導(dǎo)致的誤差和因采集噪聲所導(dǎo)致的延遲誤差提取出來,就可以將上述公司進(jìn)行如下變換
 t(k) = (timestampDiff(k) - jitterTimestamp(k))  / sampleratePermillage(k) + t(0)   (4.1.5)
  • 其中jitterTimestamp(k)就是因網(wǎng)絡(luò)波動(dòng)導(dǎo)致的第(k)幀和第1幀數(shù)據(jù)之間的rtpTimeStamp時(shí)延抖動(dòng)
  • sampleratePermillage(k)表示第k幀的千分之采樣率
  • 基于此我們可以建立如下狀態(tài)轉(zhuǎn)移方程,并使用卡爾曼濾波,通過迭代和更新使得jitterTimestamp(k)sampleratePermillage(k)的誤差盡可能的小從而使得第(k)幀的期望接收時(shí)間更加的準(zhǔn)確
 w(k) = w(k-1) + u(k-1)                                 P(u) ~ (0,Q)    (4.1.6)      
 w_bar(k) = [sampleratePermillage(k) jitterTimestamp(k)]^    
  • 定義目標(biāo)二維向量w_bar(k)

  • u(k-1)為過程噪聲服從正太分布,由于樣本samplerate_permillage(k)jitterTimestamp(k)完全獨(dú)立,所以其協(xié)方差矩陣Q似乎可以取0

  • 狀態(tài)轉(zhuǎn)移方程如果用矩陣的表示方式如下:


    image-20210614190609595.png
  • 同時(shí)建立如下觀測(cè)方程

 timestampDiff(k) = t_bar(k)^ * w_bar(k) + v(k)             P(v) ~ (0,R)    (4.1.7)
 t_bar(k) = [recvTimeMsDiff(k) 1]^    
  • v(k)為測(cè)量噪聲服從正太分布,其協(xié)方差矩陣為R,取值為1

  • t_bar(i)為第(k)幀觀測(cè)方程系數(shù)矩陣

  • recvTimeMsDiff(k)表示第(k)幀和第一幀的本地接收時(shí)間之差

  • 觀測(cè)方程如果用矩陣的表示方式如下:


    image-20210614190704946.png
  • 網(wǎng)絡(luò)殘差公式

residual(k) = timestampDiff(k) - t_bar(k)^ * w_hat(k-1)                 (4.1.8)
  • 網(wǎng)絡(luò)殘差體現(xiàn)了噪聲的大小,使用第(k)幀的觀測(cè)值 - 第(k-1)的估計(jì)計(jì)算值

4.2)TimestampExtrapolator模塊計(jì)算期望接收時(shí)間

int64_t TimestampExtrapolator::ExtrapolateLocalTime(uint32_t timestamp90khz) {
  ReadLockScoped rl(*_rwLock);
  int64_t localTimeMs = 0;
  CheckForWrapArounds(timestamp90khz);
  double unwrapped_ts90khz =
      static_cast<double>(timestamp90khz) +
      _wrapArounds * ((static_cast<int64_t>(1) << 32) - 1);
  if (_packetCount == 0) {
    localTimeMs = -1;
  } else if (_packetCount < _startUpFilterDelayInPackets) {
    localTimeMs =
        _prevMs +
        static_cast<int64_t>(
            static_cast<double>(unwrapped_ts90khz - _prevUnwrappedTimestamp) /
                90.0 +
            0.5);
  } else {
    if (_w[0] < 1e-3) {
      localTimeMs = _startMs;
    } else {
      double timestampDiff =
          unwrapped_ts90khz - static_cast<double>(_firstTimestamp);
      localTimeMs = static_cast<int64_t>(static_cast<double>(_startMs) +
                                         (timestampDiff - _w[1]) / _w[0] + 0.5);
    }
  }
  return localTimeMs;
}
  • 在3.1節(jié)中有介紹到獲取當(dāng)前幀的期望渲染時(shí)間 = 期望接收時(shí)間 + 實(shí)際延遲時(shí)間
  • timestampDiff = (第k幀的rtp時(shí)間戳 - 第一幀時(shí)間戳 )
  • localTimeMs = (timestampDiff - _w[1]) / _w[0] + (第一幀的實(shí)際接收時(shí)間),使用timestampDiff (發(fā)送端決定的) - 由于網(wǎng)絡(luò)延遲或波動(dòng)造成的延遲所對(duì)應(yīng)的rtp時(shí)間戳的大小得出第k幀和第一幀最優(yōu)的時(shí)間戳只差
  • _w[0] = sampleratePermillage(k)表示第(k)幀的千分之采樣率
  • _w[1] = jitterTimestamp(k)表示第(k)幀的抖動(dòng)rtp timestamp 時(shí)延

4.3)TimestampExtrapolator模塊Kalman預(yù)測(cè)及校正

  • 對(duì)于未接收到的未丟過包的視頻幀,每幀數(shù)據(jù)插入到FrameBuffer緩存后,通過當(dāng)前幀的rtp 時(shí)間戳以及接收時(shí)間來更新TimestampExtrapolator的卡爾曼濾波器,進(jìn)行迭代和校正
//參數(shù)tMs為當(dāng)前幀實(shí)際接收時(shí)間
//參數(shù)ts90khz為當(dāng)前幀的rtp時(shí)間戳
void TimestampExtrapolator::Update(int64_t tMs, uint32_t ts90khz) {
   _rwLock->AcquireLockExclusive();
  //1)第一幀初始賦值  
  if (tMs - _prevMs > 10e3) {//第一幀或者10秒鐘內(nèi)未收到任何完整的幀則重置
    // Ten seconds without a complete frame.
    // Reset the extrapolator
    _rwLock->ReleaseLockExclusive();
    Reset(tMs);
    _rwLock->AcquireLockExclusive();
  } else {
    _prevMs = tMs;
  } 
    
  //2)根據(jù)當(dāng)前幀的本地接收時(shí)間計(jì)算detalRecvTimeMs(k)  
  // Remove offset to prevent badly scaled matrices
  // 將當(dāng)前幀接收時(shí)間 - 第一幀的接收時(shí)間得當(dāng)前幀和第一幀的本地接收時(shí)間差
  // 此處記為detalRecvTimeMs =  tMs - _startMs  
  int64_t recvTimeMsDiff = tMs - _startMs;

  CheckForWrapArounds(ts90khz);

  int64_t unwrapped_ts90khz =
      static_cast<int64_t>(ts90khz) +
      _wrapArounds * ((static_cast<int64_t>(1) << 32) - 1);

  if (_firstAfterReset) {//重置后賦值初值
    // Make an initial guess of the offset,
    // should be almost correct since tMs - _startMs
    // should about zero at this time.
    _w[1] = -_w[0] * tMs;
    _firstTimestamp = unwrapped_ts90khz;
    _firstAfterReset = false;
  }
  /*3)使用上一次最優(yōu)估計(jì)計(jì)算網(wǎng)絡(luò)殘差為計(jì)算驗(yàn)估計(jì)做準(zhǔn)備,對(duì)應(yīng)5大核心公式的公式(4) 以及4.1.8
      用當(dāng)前幀真實(shí)的rtp時(shí)間戳 - 第一幀的時(shí)間戳 - detalRecvTimeMs * _w[0] - _w[1]
      detalRecvTimeMs * _w[0](上一次的最優(yōu)采樣率) 得出detalRtpTimeStamp 
  */  
  double residual = (static_cast<double>(unwrapped_ts90khz) - _firstTimestamp) -
                    static_cast<double>(recvTimeMsDiff) * _w[0] - _w[1];
    
  if (DelayChangeDetection(residual) &&
      _packetCount >= _startUpFilterDelayInPackets) {
    // A sudden change of average network delay has been detected.
    // Force the filter to adjust its offset parameter by changing
    // the offset uncertainty. Don't do this during startup.
    _pP[1][1] = _pP11;
  }

  if (_prevUnwrappedTimestamp >= 0 &&
      unwrapped_ts90khz < _prevUnwrappedTimestamp) {
    // Drop reordered frames.
    _rwLock->ReleaseLockExclusive();
    return;
  }

  // T = [t(k) 1]';
  // that = T'*w;
  // K = P*T/(lambda + T'*P*T);
  // 4)計(jì)算卡爾曼增益  
  double K[2];
  // 對(duì)應(yīng)5大公式,公式3中的分子部分  
  K[0] = _pP[0][0] * recvTimeMsDiff + _pP[0][1];
  K[1] = _pP[1][0] * recvTimeMsDiff + _pP[1][1];
  // 對(duì)應(yīng)5大公式,公式3中的分母部分   
  double TPT = _lambda + recvTimeMsDiff * K[0] + K[1];
  K[0] /= TPT;
  K[1] /= TPT;
  //5) 根據(jù)最優(yōu)卡爾曼因子進(jìn)行校正,計(jì)算后驗(yàn)估計(jì)值  
  // w = w + K*(ts(k) - that);
  _w[0] = _w[0] + K[0] * residual;
  _w[1] = _w[1] + K[1] * residual;
  //6)更新誤差協(xié)方差  
  // P = 1/lambda*(P - K*T'*P);
  double p00 =
      1 / _ * (_pP[0][0] - (K[0] * recvTimeMsDiff * _pP[0][0] + K[0] * _pP[1][0]));
  double p01 =
      1 / _lambda * (_pP[0][1] - (K[0] * recvTimeMsDiff * _pP[0][1] + K[0] * _pP[1][1]));
  _pP[1][0] =
      1 / _lambda * (_pP[1][0] - (K[1] * recvTimeMsDiff * _pP[0][0] + K[1] * _pP[1][0]));
  _pP[1][1] =
      1 / _lambda * (_pP[1][1] - (K[1] * recvTimeMsDiff * _pP[0][1] + K[1] * _pP[1][1]));
  _pP[0][0] = p00;
  _pP[0][1] = p01;
  _prevUnwrappedTimestamp = unwrapped_ts90khz;
  if (_packetCount < _startUpFilterDelayInPackets) {
    _packetCount++;
  }
  _rwLock->ReleaseLockExclusive();
}
  • 以上首先是根據(jù)圖WebRtc_Video_Stream_Receiver_07_05中的公式(3)計(jì)算卡爾曼增量因子,計(jì)算的過程中先求其分子部分,然后再求分母部分
  • 以上觀測(cè)噪聲的協(xié)方差R等于lambda,默認(rèn)為1。
  • 到此就能得出一個(gè)期望的接收時(shí)間,根據(jù)期望接收時(shí)間就能得出一個(gè)期望渲染時(shí)間從而得到期望渲染時(shí)間
expectRenderTime = expectRecvTime + actual_delay
  • 接下來開始分析actual_delay是如何求取的?
  • actual_delay指的是前面已解碼的幀的實(shí)際的延遲時(shí)間

5)計(jì)算期望渲染時(shí)間

  • 首先再回顧FrameBuffer模塊取幀和將幀發(fā)送到解碼隊(duì)列的流程如下圖

    image-20210614175550223.png

  • 解碼任務(wù)隊(duì)列,每次輪詢通過FrameBuffer::FindNextFrame()找到一幀完整的幀,并為其設(shè)置期望最優(yōu)渲染時(shí)間,然后再通過MaxWaitingTime函數(shù)返回一個(gè)延遲時(shí)間,告知重復(fù)任務(wù)隊(duì)列,讓該任務(wù)等待wait_ms之后開始執(zhí)行,將該幀數(shù)據(jù)發(fā)送到解碼任務(wù)隊(duì)列進(jìn)行解碼,解碼完畢后又重新輪詢新的幀

  • 其中在計(jì)算渲染時(shí)間的時(shí)候有用到上一幀數(shù)據(jù)的平滑actual_delay,也就是current_delay_ms_,基于此開始分析current_delay_ms_的計(jì)算過程

void VCMTiming::SetJitterDelay(int jitter_delay_ms) {
  rtc::CritScope cs(&crit_sect_);
  if (jitter_delay_ms != jitter_delay_ms_) {
    jitter_delay_ms_ = jitter_delay_ms;
    // When in initial state, set current delay to minimum delay.
    if (current_delay_ms_ == 0) {
      current_delay_ms_ = jitter_delay_ms_;
    }
  }
}
  • jitter_delay_ms是在每一幀數(shù)據(jù)送到解碼隊(duì)列之前使用VCMJitterEstimator模塊估計(jì)出來的,對(duì)于第一幀數(shù)據(jù)current_delay_ms_的值就等于該幀的jitter_delay_ms_
void VCMTiming::UpdateCurrentDelay(int64_t render_time_ms,
                                   int64_t actual_decode_time_ms) {
  rtc::CritScope cs(&crit_sect_);
  uint32_t target_delay_ms = TargetDelayInternal();//目標(biāo)延遲
  //計(jì)算實(shí)際延遲  
  int64_t delayed_ms =
      actual_decode_time_ms -
      (render_time_ms - RequiredDecodeTimeMs() - render_delay_ms_);
  if (delayed_ms < 0) {
    return;
  }
  if (current_delay_ms_ + delayed_ms <= target_delay_ms) {
    current_delay_ms_ += delayed_ms;
  } else {
    current_delay_ms_ = target_delay_ms;
  }
}
int VCMTiming::TargetDelayInternal() const {
  //計(jì)算出目標(biāo)延遲 =  jitter_delay_ms_ + 解碼耗時(shí) +  渲染延遲
  return std::max(min_playout_delay_ms_,
                  jitter_delay_ms_ + RequiredDecodeTimeMs() + render_delay_ms_);
}
  • 以上函數(shù)的作用就是在當(dāng)前幀順利發(fā)送到解碼隊(duì)列之后盡量確保current_delay_ms_的值逼近jitter_delay_ms_ + RequiredDecodeTimeMs() + render_delay_ms_
  • 然后在后續(xù)幀獲取期望渲染時(shí)間的時(shí)候,盡量保證所有幀的延遲間隔趨向于平滑
  current_delay_ms_ = 幀間抖動(dòng)延時(shí) + 解碼耗時(shí) + 渲染延遲 
  • 最后再回顧3.1)節(jié)中的VCMTiming模塊獲取期望渲染時(shí)間
expectRenderTime = expectRecvTime + actual_delay
                 = 期望接收時(shí)間 + 幀間抖動(dòng)延時(shí) + 解碼耗時(shí) + 渲染延遲 
  • 已知默認(rèn)情況下渲染延遲一般都是默認(rèn)值10ms

  • 解碼耗時(shí)依據(jù)硬解的性能而定,一般都會(huì)比較均勻

  • 然而期望接收時(shí)間和幀間抖動(dòng)延時(shí)都會(huì)因?yàn)榫W(wǎng)絡(luò)的千變?nèi)f化以及發(fā)送端的各種不確定性存在一定的波動(dòng)

  • 其實(shí)在調(diào)試中發(fā)現(xiàn)不管用不用卡爾曼濾波,對(duì)于期望接收時(shí)間,如果網(wǎng)絡(luò)不是很差,基本上估計(jì)出來的曲線和實(shí)際曲線是基本一致的,當(dāng)然也有可能是我測(cè)試的條件比較好


    image-20210614185054732.png
  • 其中藍(lán)色曲線直接試用timestampDiff / 90 + _startMs所得,當(dāng)我設(shè)置接收端5%丟包的時(shí)候兩條曲線完全重合了

6)總結(jié)

  • 通過本文的分析,首先明確webrtc視頻接收過程中的邏輯處理是十分復(fù)雜的,同時(shí)通過兩大卡爾曼濾波對(duì)每幀的期望接收和每幀的幀間抖動(dòng)進(jìn)行濾波處理
  • 其中對(duì)幀間抖動(dòng)的濾波可以使得每幀數(shù)據(jù)進(jìn)入解碼隊(duì)列的時(shí)機(jī)變得相對(duì)平滑,這樣可以有效的緩解因網(wǎng)絡(luò)丟包等情況導(dǎo)致視頻的卡頓等問題
  • 而期望渲染時(shí)間的估計(jì)使得渲染過程趨向于平滑,同時(shí)也明確本文中使用的卡爾曼濾波的作用是為每幀產(chǎn)生一個(gè)最優(yōu)的接收時(shí)間,改時(shí)間最終決定了該幀的渲染時(shí)間,這對(duì)音視頻的同步是非常有作用的
  • 同時(shí)通過學(xué)習(xí)本文深入學(xué)習(xí)卡爾曼濾波的應(yīng)用場(chǎng)景
最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

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

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