WebRTC GCC基于丟包的碼率估計(jì)原理



1)前言

  • WebRtc基于發(fā)送端的動(dòng)態(tài)碼率調(diào)控主要分成兩大塊纫塌,其中一部分是基于丟包率的碼率控制肛真,另一部分是基于延遲的碼率控制盾碗。
  • 本文主要分析WebRtc中基于丟包率的碼率控制苇倡。
  • WebRtc中基于丟包率的碼率控制的實(shí)現(xiàn)原理是基于發(fā)送端接收對(duì)端反饋過(guò)來(lái)的RR或SR報(bào)文扔涧,并對(duì)報(bào)文的發(fā)送者報(bào)告塊進(jìn)行解析,解析其RTT和丟包率近弟。
  • 如果丟包率比較大說(shuō)明網(wǎng)絡(luò)狀態(tài)不大好缅糟,將丟包信息和RTT更新到GoogCcNetworkController模塊,評(píng)估新的發(fā)送碼率祷愉。
  • 最后在RtpTransportControllerSend模塊中將新評(píng)估出的碼率作用到pacer模塊窗宦。

2)RTCP報(bào)文接收大致流程

  • WebRTC RTP/RTCP協(xié)議分析(一)一文中有分析RTCP報(bào)文的接收流程,那篇文章是基于m76版本的分支進(jìn)行分析的二鳄,而本文是基于m79版本進(jìn)行分析赴涵,在分析過(guò)程中發(fā)現(xiàn)函數(shù)調(diào)用棧有些出入。
  • 其調(diào)用流程大致如下:
rtcp_接收流程-001.png
  • 上圖忽略從網(wǎng)絡(luò)部分得到RTCP包的業(yè)務(wù)邏輯订讼,直接從Call模塊說(shuō)起髓窜。
  • 同時(shí)在Call模塊收到RTCP報(bào)文后會(huì)進(jìn)行一系列的處理,本文業(yè)務(wù)邏輯圖也未畫(huà)出欺殿。
  • 對(duì)于音頻流RTCP報(bào)文的處理邏輯在第三步寄纵,有一些變化如下圖:
rtcp_接收流程-002.png
  • 從上圖可以看出對(duì)于音頻流RCPReceiver模塊在調(diào)用TriggerCallbacksFromRtcpPacket函數(shù)觸發(fā)回調(diào)的時(shí)候首先是將報(bào)文送給VoERtcpObserver塊進(jìn)行處理鳖敷。
  • 而根據(jù)上圖得知VoERtcpObserverRtpTransportControllerSend都是RtcpBandwidthObserver的子類。
  • 最終在VoERtcpObserver中先調(diào)用OnReceivedRtcpReceiverReport將報(bào)告塊作用到GoogCcNetworkController模塊程拭,后經(jīng)過(guò)相應(yīng)處理作用到編碼器哄陶。

3)RTCP報(bào)文RTT計(jì)算和丟包統(tǒng)計(jì)

  • SR或RR報(bào)文的RTT信息和丟包信息包含在發(fā)送者報(bào)告塊,定義在RFC3550中,以RR報(bào)文為例哺壶,如下:
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=RR=201   |             length            |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                     SSRC of packet sender                     |
       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
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                  |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 報(bào)告塊的解析代碼如下:
void RTCPReceiver::HandleReportBlock(const ReportBlock& report_block,
                                     PacketInformation* packet_information,
                                     uint32_t remote_ssrc) {
  // This will be called once per report block in the RTCP packet.
  // We filter out all report blocks that are not for us.
  // Each packet has max 31 RR blocks.
  //
  // We can calc RTT if we send a send report and get a report block back.

  // |report_block.source_ssrc()| is the SSRC identifier of the source to
  // which the information in this reception report block pertains.

  // Filter out all report blocks that are not for us.
  if (registered_ssrcs_.count(report_block.source_ssrc()) == 0)
    return;

  last_received_rb_ms_ = clock_->TimeInMilliseconds();

  ReportBlockData* report_block_data =
      &received_report_blocks_[report_block.source_ssrc()][remote_ssrc];
  RTCPReportBlock rtcp_report_block;
  rtcp_report_block.sender_ssrc = remote_ssrc;
  rtcp_report_block.source_ssrc = report_block.source_ssrc();
  rtcp_report_block.fraction_lost = report_block.fraction_lost();
  rtcp_report_block.packets_lost = report_block.cumulative_lost_signed();
  if (report_block.extended_high_seq_num() >
      report_block_data->report_block().extended_highest_sequence_number) {
    // We have successfully delivered new RTP packets to the remote side after
    // the last RR was sent from the remote side.
    last_increased_sequence_number_ms_ = clock_->TimeInMilliseconds();
  }
  rtcp_report_block.extended_highest_sequence_number =
      report_block.extended_high_seq_num();
  rtcp_report_block.jitter = report_block.jitter();
  rtcp_report_block.delay_since_last_sender_report =
      report_block.delay_since_last_sr();
  rtcp_report_block.last_sender_report_timestamp = report_block.last_sr();
  report_block_data->SetReportBlock(rtcp_report_block, rtc::TimeUTCMicros());

  int64_t rtt_ms = 0;
  uint32_t send_time_ntp = report_block.last_sr();
  // RFC3550, section 6.4.1, LSR field discription states:
  // If no SR has been received yet, the field is set to zero.
  // Receiver rtp_rtcp module is not expected to calculate rtt using
  // Sender Reports even if it accidentally can.

  // TODO(nisse): Use this way to determine the RTT only when |receiver_only_|
  // is false. However, that currently breaks the tests of the
  // googCaptureStartNtpTimeMs stat for audio receive streams. To fix, either
  // delete all dependencies on RTT measurements for audio receive streams, or
  // ensure that audio receive streams that need RTT and stats that depend on it
  // are configured with an associated audio send stream.
  if (send_time_ntp != 0) {
    uint32_t delay_ntp = report_block.delay_since_last_sr();
    // Local NTP time.
    uint32_t receive_time_ntp =
        CompactNtp(TimeMicrosToNtp(clock_->TimeInMicroseconds()));

    // RTT in 1/(2^16) seconds.
    uint32_t rtt_ntp = receive_time_ntp - delay_ntp - send_time_ntp;
    // Convert to 1/1000 seconds (milliseconds).
    rtt_ms = CompactNtpRttToMs(rtt_ntp);
    report_block_data->AddRoundTripTimeSample(rtt_ms);

    packet_information->rtt_ms = rtt_ms;
  }

  packet_information->report_blocks.push_back(
      report_block_data->report_block());
  packet_information->report_block_datas.push_back(*report_block_data);
}
  • 該函數(shù)的核心的作用是求得RTT時(shí)間屋吨,單位為1/(2^16) seconds.,計(jì)算公式為receive_time_ntp(當(dāng)前ntp) - delay_ntp(對(duì)端收到SR后發(fā)送SR或RR之間的延遲) - send_time_ntp(對(duì)端在發(fā)送SR或者RR之前收到發(fā)送者報(bào)告的時(shí)間)山宾。
  • 另外獲取上一次和本次之間的丟包率至扰,以及總的丟包數(shù)。
  • RTT計(jì)算公式如下:
[10 Nov 1995 11:33:25.125 UTC]       [10 Nov 1995 11:33:36.5 UTC]
   n                 SR(n)              A=b710:8000 (46864.500 s)
   ---------------------------------------------------------------->
                      v                 ^
   ntp_sec =0xb44db705 v               ^ dlsr=0x0005:4000 (    5.250s)
   ntp_frac=0x20000000  v             ^  lsr =0xb705:2000 (46853.125s)
     (3024992005.125 s)  v           ^
   r                      v         ^ RR(n)
   ---------------------------------------------------------------->
                          |<-DLSR->|
                           (5.250 s)

   A     0xb710:8000 (46864.500 s)
   DLSR -0x0005:4000 (    5.250 s)
   LSR  -0xb705:2000 (46853.125 s)
   -------------------------------
   delay 0x0006:2000 (    6.125 s)
  • 最后是將信息封裝到PacketInformation結(jié)構(gòu)當(dāng)中资锰,然后調(diào)用RTCPReceiver::TriggerCallbacksFromRtcpPacket函數(shù)進(jìn)行回調(diào)處理敢课。

4)RTCP觸發(fā)回調(diào)

void RTCPReceiver::TriggerCallbacksFromRtcpPacket(
    const PacketInformation& packet_information) {
  ....
  if (rtcp_bandwidth_observer_) {
    RTC_DCHECK(!receiver_only_);
    if ((packet_information.packet_type_flags & kRtcpSr) ||
        (packet_information.packet_type_flags & kRtcpRr)) {
      int64_t now_ms = clock_->TimeInMilliseconds();
      rtcp_bandwidth_observer_->OnReceivedRtcpReceiverReport(
          packet_information.report_blocks, packet_information.rtt_ms, now_ms);
    }
  }
  .....  
  if ((packet_information.packet_type_flags & kRtcpSr) ||
      (packet_information.packet_type_flags & kRtcpRr)) {
    rtp_rtcp_->OnReceivedRtcpReportBlocks(packet_information.report_blocks);
  }
}
  • 保留相關(guān)的,改函數(shù)通過(guò)調(diào)用RtcpBandwidthObserver模塊的OnReceivedRtcpReceiverReport函數(shù),來(lái)傳遞信息。
  • 根據(jù)上面的業(yè)務(wù)圖,RtcpBandwidthObserver模塊的最終實(shí)現(xiàn)為RtpTransportControllerSend绷杜,在創(chuàng)建RTCPReceiver實(shí)例的時(shí)候會(huì)實(shí)例化rtcp_bandwidth_observer_直秆,
class RTCPReceiver {
 public: 
    .....
 private:
    RtcpBandwidthObserver* const rtcp_bandwidth_observer_;
}
  • RtpTransportControllerSend模塊的OnReceivedRtcpReceiverReport函數(shù)對(duì)RTCP 反饋信息的處理主要分成兩個(gè)步驟
  • 其一是調(diào)用OnReceivedRtcpReceiverReportBlocks函數(shù)封裝TransportLossReport結(jié)構(gòu)消息,并最后調(diào)用PostUpdates(controller_->OnTransportLossReport(msg))鞭盟,首先將TransportLossReport消息傳遞給GoogCcNetworkController模塊進(jìn)行碼率估計(jì)圾结,其次是調(diào)用PostUpdates來(lái)將新估計(jì)得碼率值作用到pacer模塊
  • 其二是封裝RoundTripTimeUpdate消息,并調(diào)用PostUpdates(controller_->OnRoundTripTimeUpdate(report));齿诉,GoogCcNetworkController模塊接收消息后先計(jì)算基于RTT時(shí)間延遲的碼率筝野,最后調(diào)用PostUpdates來(lái)刷新碼率,作用到發(fā)送模塊

5)TransportLossReport和RoundTripTimeUpdate結(jié)構(gòu)封裝

  task_queue_.PostTask([this, report_blocks, now_ms]() {
    RTC_DCHECK_RUN_ON(&task_queue_);
    OnReceivedRtcpReceiverReportBlocks(report_blocks, now_ms);
  });

  task_queue_.PostTask([this, now_ms, rtt_ms]() {
    RTC_DCHECK_RUN_ON(&task_queue_);
    RoundTripTimeUpdate report;
    report.receive_time = Timestamp::ms(now_ms);
    report.round_trip_time = TimeDelta::ms(rtt_ms);
    report.smoothed = false;
    if (controller_ && !report.round_trip_time.IsZero())
      PostUpdates(controller_->OnRoundTripTimeUpdate(report));
  });
  • 傳遞過(guò)來(lái)的now_ms參數(shù)為當(dāng)前ntp時(shí)間粤剧,也就是收到該rtcp sr或rr報(bào)文的時(shí)間歇竟。
  • 同時(shí)這兩個(gè)步驟使用的是同一個(gè)task_queue_任務(wù)隊(duì)列,這說(shuō)明先進(jìn)行基于丟包的碼率估計(jì)抵恋,然后再進(jìn)行基于延遲的碼率估計(jì)焕议。

5.1)TransportLossReport封裝

  • 通過(guò)OnReceivedRtcpReceiverReportBlocks函數(shù)對(duì)report_blocks消息進(jìn)行再封裝,將其封裝成TransportLossReport格式弧关,其定義如下
#network_types.h
struct TransportLossReport {
  /*表示當(dāng)前接收到該消息的ntp時(shí)間*/
  Timestamp receive_time = Timestamp::PlusInfinity();
  /*上一次接收到SR或者RR報(bào)文的時(shí)間*/
  Timestamp start_time = Timestamp::PlusInfinity();
  /*當(dāng)前接收到該消息的ntp時(shí)間*/  
  Timestamp end_time = Timestamp::PlusInfinity();
  /*在上一次處理和本次處理時(shí)間差范圍內(nèi)也就是end_time - start_time 之間的丟包數(shù)量*/  
  uint64_t packets_lost_delta = 0;
  /*在上一次處理和本次處理時(shí)間差范圍內(nèi)也就是end_time - start_time 之間實(shí)際發(fā)送成功的包數(shù)*/  
  uint64_t packets_received_delta = 0;
};
void RtpTransportControllerSend::OnReceivedRtcpReceiverReportBlocks(
    const ReportBlockList& report_blocks,
    int64_t now_ms) {
  if (report_blocks.empty())
    return;

  int total_packets_lost_delta = 0;
  int total_packets_delta = 0;

  // Compute the packet loss from all report blocks.
  for (const RTCPReportBlock& report_block : report_blocks) {
    auto it = last_report_blocks_.find(report_block.source_ssrc);
    if (it != last_report_blocks_.end()) {
      auto number_of_packets = report_block.extended_highest_sequence_number -
                               it->second.extended_highest_sequence_number;
      total_packets_delta += number_of_packets;
      auto lost_delta = report_block.packets_lost - it->second.packets_lost;
      total_packets_lost_delta += lost_delta;
    }
    last_report_blocks_[report_block.source_ssrc] = report_block;
  }
  // Can only compute delta if there has been previous blocks to compare to. If
  // not, total_packets_delta will be unchanged and there's nothing more to do.
  if (!total_packets_delta)
    return;
  int packets_received_delta = total_packets_delta - total_packets_lost_delta;
  // To detect lost packets, at least one packet has to be received. This check
  // is needed to avoid bandwith detection update in
  // VideoSendStreamTest.SuspendBelowMinBitrate

  if (packets_received_delta < 1)
    return;
  Timestamp now = Timestamp::ms(now_ms);
  TransportLossReport msg;
  msg.packets_lost_delta = total_packets_lost_delta;
  msg.packets_received_delta = packets_received_delta;
  msg.receive_time = now;
  msg.start_time = last_report_block_time_;
  msg.end_time = now;
  if (controller_)
    PostUpdates(controller_->OnTransportLossReport(msg));
  last_report_block_time_ = now;
}
  • 首先根據(jù)報(bào)告塊得出盅安,上一次和本次時(shí)間戳之內(nèi)總共的發(fā)包數(shù)量number_of_packets,并對(duì)不同SSR進(jìn)行累加得出total_packets_delta梯醒。

  • 計(jì)算total_packets_lost_delta宽堆,上一次總丟包數(shù)量減去本次總丟包數(shù)量腌紧,從而可知total_packets_lost_delta表示的是msg.end_time - msg.start_time時(shí)間差之間的丟包數(shù)量茸习。

  • 根據(jù)total_packets_delta計(jì)算packets_received_delta,使用total_packets_delta - total_packets_lost_delta獲得壁肋,在msg.end_time - msg.start_time時(shí)間差之間的總發(fā)包數(shù)量減去本段時(shí)間差之內(nèi)總丟包數(shù)号胚,最終得出的就是實(shí)際發(fā)送成功的包數(shù)量籽慢。

  • 封裝TransportLossReport并觸發(fā)OnTransportLossReport將TransportLossReport消息傳遞給GoogCcNetworkController模塊。

  • 為使分析邏輯清晰猫胁,在后面再確切分析GoogCcNetworkController模塊對(duì)TransportLossReport消息的處理和對(duì)碼率的估計(jì)箱亿。

5.2)RoundTripTimeUpdate封裝

struct RoundTripTimeUpdate {
  /*表示當(dāng)前接收到該消息的ntp時(shí)間*/  
  Timestamp receive_time = Timestamp::PlusInfinity();
  /*rtt時(shí)間*/  
  TimeDelta round_trip_time = TimeDelta::PlusInfinity();
  bool smoothed = false;
};
  • 封裝RoundTripTimeUpdate結(jié)構(gòu)
  • 調(diào)用controller_->OnRoundTripTimeUpdate(report),將消息傳遞給GoogCcNetworkController模塊弃秆,進(jìn)行碼率估計(jì)并得出新碼率届惋。
  • 調(diào)用PostUpdates更新pacer模塊的實(shí)時(shí)發(fā)送碼率。

6)丟包對(duì)GoogCcNetworkController模塊的影響

NetworkControlUpdate GoogCcNetworkController::OnTransportLossReport(
    TransportLossReport msg) {
  if (packet_feedback_only_)//默認(rèn)為false,這里可以通過(guò)配置該變量讓碼率估計(jì)只依據(jù)packet_feedback_only_
    return NetworkControlUpdate();
  int64_t total_packets_delta =
      msg.packets_received_delta + msg.packets_lost_delta;
  bandwidth_estimation_->UpdatePacketsLost(
      msg.packets_lost_delta, total_packets_delta, msg.receive_time);
  return NetworkControlUpdate();
}
  • total_packets_delta為實(shí)際發(fā)送成功(對(duì)端收到的包) + 丟包數(shù) 菠赚。

  • 調(diào)用SendSideBandwidthEstimation::UpdatePacketsLost更新當(dāng)前時(shí)間差范圍內(nèi)的總發(fā)包數(shù)和丟包信息脑豹。

  • 返回NetworkControlUpdate(),從這里可以看出在此時(shí)只是直接new 了一個(gè)NetworkControlUpdate,并未對(duì)其中的成員進(jìn)行賦值衡查,所以由此可知瘩欺,經(jīng)由RR或者SR報(bào)文的丟包情況對(duì)GoogCcNetworkController模塊的真實(shí)作用是更新了SendSideBandwidthEstimation模塊中的發(fā)包數(shù)量和丟包數(shù)量,而在RtpTransportControllerSend模塊該階段的最后調(diào)用棧中調(diào)用的PostUpdates函數(shù)實(shí)際上會(huì)直接返回拌牲,不會(huì)做任何事情俱饿。

void SendSideBandwidthEstimation::UpdatePacketsLost(int packets_lost,
                                                    int number_of_packets,
                                                    Timestamp at_time) {
  last_loss_feedback_ = at_time;
  if (first_report_time_.IsInfinite())
    first_report_time_ = at_time;

  // Check sequence number diff and weight loss report
  if (number_of_packets > 0) {
    // Accumulate reports.
    lost_packets_since_last_loss_update_ += packets_lost;
    expected_packets_since_last_loss_update_ += number_of_packets;

    // Don't generate a loss rate until it can be based on enough packets.
    if (expected_packets_since_last_loss_update_ < kLimitNumPackets)
      return;

    has_decreased_since_last_fraction_loss_ = false;
    int64_t lost_q8 = lost_packets_since_last_loss_update_ << 8;
    int64_t expected = expected_packets_since_last_loss_update_;
    last_fraction_loss_ = std::min<int>(lost_q8 / expected, 255);

    // Reset accumulators.

    lost_packets_since_last_loss_update_ = 0;
    expected_packets_since_last_loss_update_ = 0;
    last_loss_packet_report_ = at_time;
    UpdateEstimate(at_time);
  }
  UpdateUmaStatsPacketsLost(at_time, packets_lost);
}
  • lost_packets_since_last_loss_update_表示上截止上一次基于丟包的回調(diào)的丟包數(shù)量,這兩將上一次處理RR或者SR報(bào)告時(shí)的丟包數(shù)和本次接收到的丟包數(shù)進(jìn)行累加塌忽。
  • expected_packets_since_last_loss_update_期望發(fā)送成功的總包數(shù)拍埠,發(fā)送多少對(duì)方收到多少,這里其實(shí)就是自上一次處理基于丟包碼率估計(jì)到本次處理時(shí)間差之間總共的發(fā)包數(shù)量土居。
  • 判斷expected_packets_since_last_loss_update_ < kLimitNumPackets(默認(rèn)20)械拍,從這可以看出,如果在上一次處理丟包碼率估計(jì)到本次處理丟包估計(jì)之間的時(shí)間差內(nèi)如果總發(fā)包數(shù)小于20個(gè)装盯,則直接返回坷虑,如果大于20個(gè)則求出last_fraction_loss_ = 上一次處理到本次處理時(shí)間差內(nèi)的總丟包數(shù) * 256 / 本段時(shí)間內(nèi)總發(fā)包數(shù),然后和255 取最小值埂奈。
  • 假設(shè)本段時(shí)間內(nèi)丟包率為1迄损,那肯定大于255,理論上丟包率肯定是小于1的账磺,所以這里得到的丟包率是2^8 * 丟包數(shù) / 發(fā)包數(shù)芹敌。
  • 如果發(fā)包數(shù)大于20的話計(jì)算出丟包率后需要進(jìn)行清除。
  • 由以上可知垮抗,這段代碼的時(shí)間差為 大于發(fā)送20個(gè)數(shù)據(jù)包的時(shí)間差氏捞,可以理解成每相隔20個(gè)數(shù)據(jù)包的時(shí)間差會(huì)進(jìn)行一次丟包統(tǒng)計(jì)。
  • 調(diào)用UpdateEstimate進(jìn)行碼率估計(jì)冒版。
constexpr TimeDelta kBweIncreaseInterval = TimeDelta::Millis<1000>();
constexpr TimeDelta kMaxRtcpFeedbackInterval = TimeDelta::Millis<5000>();
constexpr TimeDelta kBweDecreaseInterval = TimeDelta::Millis<300>();

constexpr float kDefaultLowLossThreshold = 0.02f;
constexpr float kDefaultHighLossThreshold = 0.1f;
constexpr DataRate kDefaultBitrateThreshold = DataRate::Zero();
void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {
   .........
  UpdateMinHistory(at_time);
  .......
  /*是否啟用WebRTC-Bwe-LossBasedControl,正常情況除非用戶自己配置否則不啟用*/
  if (loss_based_bandwidth_estimation_.Enabled()) {
    loss_based_bandwidth_estimation_.Update(
        at_time, min_bitrate_history_.front().second, last_round_trip_time_);
    DataRate new_bitrate = MaybeRampupOrBackoff(current_target_, at_time);
    UpdateTargetBitrate(new_bitrate, at_time);
    return;
  }

  TimeDelta time_since_loss_packet_report = at_time - last_loss_packet_report_;
  if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) {
    // We only care about loss above a given bitrate threshold.
    //上面分析到last_fraction_loss_位丟包率*2^8
    float loss = last_fraction_loss_ / 256.0f;
    // We only make decisions based on loss when the bitrate is above a
    // threshold. This is a crude way of handling loss which is uncorrelated
    // to congestion.
    if (current_target_ < bitrate_threshold_ || loss <= low_loss_threshold_) {
      // Loss < 2%: Increase rate by 8% of the min bitrate in the last
      // kBweIncreaseInterval.
      // Note that by remembering the bitrate over the last second one can
      // rampup up one second faster than if only allowed to start ramping
      // at 8% per second rate now. E.g.:
      //   If sending a constant 100kbps it can rampup immediately to 108kbps
      //   whenever a receiver report is received with lower packet loss.
      //   If instead one would do: current_bitrate_ *= 1.08^(delta time),
      //   it would take over one second since the lower packet loss to achieve
      //   108kbps.
      DataRate new_bitrate =
          DataRate::bps(min_bitrate_history_.front().second.bps() * 1.08 + 0.5);

      // Add 1 kbps extra, just to make sure that we do not get stuck
      // (gives a little extra increase at low rates, negligible at higher
      // rates).
      new_bitrate += DataRate::bps(1000);
      UpdateTargetBitrate(new_bitrate, at_time);
      return;
    } else if (current_target_ > bitrate_threshold_) {
      if (loss <= high_loss_threshold_) {
        // Loss between 2% - 10%: Do nothing.
      } else {
        // Loss > 10%: Limit the rate decreases to once a kBweDecreaseInterval
        // + rtt.
        if (!has_decreased_since_last_fraction_loss_ &&
            (at_time - time_last_decrease_) >=
                (kBweDecreaseInterval + last_round_trip_time_)) {
          time_last_decrease_ = at_time;

          // Reduce rate:
          //   newRate = rate * (1 - 0.5*lossRate);
          //   where packetLoss = 256*lossRate;
          DataRate new_bitrate =
              DataRate::bps((current_target_.bps() *
                             static_cast<double>(512 - last_fraction_loss_)) /
                            512.0);
          has_decreased_since_last_fraction_loss_ = true;
          UpdateTargetBitrate(new_bitrate, at_time);
          return;
        }
      }
    }
  }
  // TODO(srte): This is likely redundant in most cases.
  ApplyTargetLimits(at_time);
}
  • 以上代碼刪除和TWCC延遲相關(guān)部分液茎,保留和基于RR或SR丟包統(tǒng)計(jì)相關(guān)部分代碼。

  • 獲取本次處理丟包統(tǒng)計(jì)和上次處理丟包統(tǒng)計(jì)的時(shí)間差time_since_loss_packet_report = at_time - last_loss_packet_report_;

  • 首先調(diào)用UpdateMinHistory以當(dāng)前時(shí)間做為參數(shù)來(lái)更新min_bitrate_history_集合。

void SendSideBandwidthEstimation::UpdateMinHistory(Timestamp at_time) {
  // Remove old data points from history.
  // Since history precision is in ms, add one so it is able to increase
  // bitrate if it is off by as little as 0.5ms.
  while (!min_bitrate_history_.empty() &&
         at_time - min_bitrate_history_.front().first + TimeDelta::ms(1) >
             kBweIncreaseInterval) {
    min_bitrate_history_.pop_front();
  }

  // Typical minimum sliding-window algorithm: Pop values higher than current
  // bitrate before pushing it.
  while (!min_bitrate_history_.empty() &&
         current_target_ <= min_bitrate_history_.back().second) {
    min_bitrate_history_.pop_back();
  }

  min_bitrate_history_.push_back(std::make_pair(at_time, current_target_));
}
std::deque<std::pair<Timestamp, DataRate> > min_bitrate_history_;
  • min_bitrate_history_是一個(gè)以時(shí)間為key,碼率為值得一個(gè)鍵值對(duì)的容器捆等。

  • 刪除歷史舊的數(shù)據(jù)滞造。以當(dāng)前時(shí)間和隊(duì)列的第一個(gè)元素的時(shí)間差進(jìn)行比較,刪除1s以前保存的碼率栋烤。

  • 刪除歷史舊的數(shù)據(jù)谒养。以碼率為查詢條件,如果當(dāng)前的碼率比歷史數(shù)據(jù)中的碼率還要小則刪除明郭。

  • 以當(dāng)前時(shí)間和當(dāng)前碼率構(gòu)建std::make_pair买窟,將其插入到容器尾部,現(xiàn)假設(shè)每次處理的時(shí)間間隔為100ms,那么從時(shí)間的角度來(lái)看薯定,該隊(duì)列能保存前10個(gè)碼率蔑祟。

  • bitrate_threshold_默認(rèn)情況下為0,可以通過(guò)“WebRTC-BweLossExperiment”進(jìn)行配置沉唠,low_loss_threshold_的默認(rèn)值為0.02f,high_loss_threshold_的默認(rèn)值為0.1f疆虚。

  • 首先判斷time_since_loss_packet_report也就是兩次評(píng)估處理的時(shí)間間隔小于1.2*5000等于6000ms的情況下,分成兩種情況進(jìn)行碼率估計(jì)满葛,一種是當(dāng)前的丟包率小于等于2%的時(shí)候径簿,那么會(huì)以歷史最小碼率以8%的幅度進(jìn)行遞增。額外再加上1kbps嘀韧。

  • 如果當(dāng)前的丟包率介于2% - 10%之間則維持當(dāng)前碼率篇亭。

  • 如果丟包率大于10%,說(shuō)明網(wǎng)絡(luò)比較差锄贷,需要降低碼率译蒂,碼率下降的公式為 新碼率 = (當(dāng)前碼率 * (512 - 256 * 丟包率)) / 512,當(dāng)然這個(gè)計(jì)算是有條件的谊却,條件就是(當(dāng)前時(shí)間 - 上一次開(kāi)始遞減的時(shí)間) > 上一次的rtt時(shí)間 + 300ms ,也就是說(shuō)得到遞減的碼率在一定的時(shí)間范圍內(nèi)有效柔昼。has_decreased_since_last_fraction_loss_的值在UpdatePacketsLost回調(diào)中被設(shè)置成false,在碼率遞減后設(shè)置成true,每次處理RR或者SR丟包統(tǒng)計(jì)時(shí)進(jìn)行置false,在碼率遞減后設(shè)置成true炎辨。

  • 最終碼率新增或遞減得到新碼率捕透,調(diào)用UpdateTargetBitrate對(duì)碼率進(jìn)行更新。從GoogCcNetworkController模塊到SendSideBandwidthEstimation的大致流程如下:

rtcp_接收流程-003.png
void SendSideBandwidthEstimation::UpdateTargetBitrate(DataRate new_bitrate,
                                                      Timestamp at_time) {
  new_bitrate = std::min(new_bitrate, GetUpperLimit());
  if (new_bitrate < min_bitrate_configured_) {
    MaybeLogLowBitrateWarning(new_bitrate, at_time);
    new_bitrate = min_bitrate_configured_;
  }
  current_target_ = new_bitrate;
  MaybeLogLossBasedEvent(at_time);
  link_capacity_.OnRateUpdate(acknowledged_rate_, current_target_, at_time);
}
  • 最終記錄新碼率為current_target_碴萧。
  • 調(diào)用LinkCapacityTracker::OnRateUpdate進(jìn)行更新乙嘀,后續(xù)進(jìn)行分析,此文不進(jìn)行分析破喻。

7)RTT對(duì)GoogCcNetworkController模塊的影響

  • GoogCcNetworkController模塊在收到rtt的回調(diào)后其處理邏輯如下圖:
rtcp_接收流程-004.png
  • 一方面將RTT信息作用到DelayBasedBwe模塊虎谢。
  • 另一方面講RTT信息作用到SendSideBandwidthEstimation模塊。
  • 本文主要分析SendSideBandwidthEstimation相關(guān)的信息曹质。
void SendSideBandwidthEstimation::UpdateRtt(TimeDelta rtt, Timestamp at_time) {
  // Update RTT if we were able to compute an RTT based on this RTCP.
  // FlexFEC doesn't send RTCP SR, which means we won't be able to compute RTT.
  if (rtt > TimeDelta::Zero())
    last_round_trip_time_ = rtt;

  if (!IsInStartPhase(at_time) && uma_rtt_state_ == kNoUpdate) {
    uma_rtt_state_ = kDone;
    RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitialRtt", rtt.ms<int>(), 0, 2000, 50);
  }
}
  • UpdateRtt的核心作用就是更新last_round_trip_time_的值婴噩,該值在上面碼率遞減的分析過(guò)程中有用到擎场,其來(lái)源就是基于此。

8)總結(jié)

  • 本文意在分析webrtc基于丟包率的動(dòng)態(tài)碼率調(diào)控原理和其實(shí)現(xiàn)的業(yè)務(wù)邏輯讳推,包含對(duì)應(yīng)的函數(shù)調(diào)用棧顶籽,以及基于丟包率對(duì)碼率控制(上調(diào)或者下降)的核心算法原理玩般。
  • 根據(jù)上文的分析得出银觅,當(dāng)網(wǎng)絡(luò)環(huán)境良好的情況下,webrtc給出的指標(biāo)是丟包率小于2%10%之間坏为,當(dāng)丟包率小于2%的時(shí)候可以適當(dāng)?shù)脑黾哟a率究驴,其增加的公式為以碼率的8%進(jìn)行遞增同時(shí)附加1kbps,而當(dāng)丟包率在2%10%之間的時(shí)候匀伏,保持碼率平穩(wěn)不增加也不減少洒忧。
  • 當(dāng)丟包率超過(guò)10%的時(shí)候,對(duì)碼率以 (當(dāng)前碼率 * (512 - 256 * 丟包率)) / 512的幅度進(jìn)行遞減够颠,并且確保遞減的新碼率在一定的時(shí)間范圍內(nèi)有效熙侍。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者履磨。
  • 序言:七十年代末蛉抓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子剃诅,更是在濱河造成了極大的恐慌巷送,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矛辕,死亡現(xiàn)場(chǎng)離奇詭異笑跛,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)聊品,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)飞蹂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人翻屈,你說(shuō)我怎么就攤上這事晤柄。” “怎么了妖胀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵芥颈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我赚抡,道長(zhǎng)爬坑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任涂臣,我火速辦了婚禮盾计,結(jié)果婚禮上售担,老公的妹妹穿的比我還像新娘。我一直安慰自己署辉,他們只是感情好族铆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著哭尝,像睡著了一般哥攘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上材鹦,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天逝淹,我揣著相機(jī)與錄音,去河邊找鬼桶唐。 笑死栅葡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尤泽。 我是一名探鬼主播欣簇,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼坯约!你這毒婦竟也來(lái)了熊咽?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鬼店,失蹤者是張志新(化名)和其女友劉穎网棍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體妇智,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滥玷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巍棱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惑畴。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖航徙,靈堂內(nèi)的尸體忽然破棺而出如贷,到底是詐尸還是另有隱情,我是刑警寧澤到踏,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布杠袱,位于F島的核電站,受9級(jí)特大地震影響窝稿,放射性物質(zhì)發(fā)生泄漏楣富。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一伴榔、第九天 我趴在偏房一處隱蔽的房頂上張望纹蝴。 院中可真熱鬧庄萎,春花似錦、人聲如沸塘安。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)兼犯。三九已至忍捡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間免都,已是汗流浹背锉罐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工帆竹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绕娘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓栽连,卻偏偏與公主長(zhǎng)得像险领,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秒紧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355