WebRTC GCC基于丢包的码率估计原理



1)前言

  • WebRtc基于发送端的动态码率调控主要分成两大块,其中一部分是基于丢包率的码率控制,另一部分是基于延迟的码率控制。
  • 本文主要分析WebRtc中基于丢包率的码率控制。
  • WebRtc中基于丢包率的码率控制的实现原理是基于发送端接收对端反馈过来的RR或SR报文,并对报文的发送者报告块进行解析,解析其RTT和丢包率。
  • 如果丢包率比较大说明网络状态不大好,将丢包信息和RTT更新到GoogCcNetworkController模块,评估新的发送码率。
  • 最后在RtpTransportControllerSend模块中将新评估出的码率作用到pacer模块。

2)RTCP报文接收大致流程

  • WebRTC RTP/RTCP协议分析(一)一文中有分析RTCP报文的接收流程,那篇文章是基于m76版本的分支进行分析的,而本文是基于m79版本进行分析,在分析过程中发现函数调用栈有些出入。
  • 其调用流程大致如下:
rtcp_接收流程-001.png
  • 上图忽略从网络部分得到RTCP包的业务逻辑,直接从Call模块说起。
  • 同时在Call模块收到RTCP报文后会进行一系列的处理,本文业务逻辑图也未画出。
  • 对于音频流RTCP报文的处理逻辑在第三步,有一些变化如下图:
rtcp_接收流程-002.png
  • 从上图可以看出对于音频流RCPReceiver模块在调用TriggerCallbacksFromRtcpPacket函数触发回调的时候首先是将报文送给VoERtcpObserver块进行处理。
  • 而根据上图得知VoERtcpObserverRtpTransportControllerSend都是RtcpBandwidthObserver的子类。
  • 最终在VoERtcpObserver中先调用OnReceivedRtcpReceiverReport将报告块作用到GoogCcNetworkController模块,后经过相应处理作用到编码器。

3)RTCP报文RTT计算和丢包统计

  • SR或RR报文的RTT信息和丢包信息包含在发送者报告块,定义在RFC3550中,以RR报文为例,如下:
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                  |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 报告块的解析代码如下:
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);
}
  • 该函数的核心的作用是求得RTT时间,单位为1/(2^16) seconds.,计算公式为receive_time_ntp(当前ntp) - delay_ntp(对端收到SR后发送SR或RR之间的延迟) - send_time_ntp(对端在发送SR或者RR之前收到发送者报告的时间)。
  • 另外获取上一次和本次之间的丢包率,以及总的丢包数。
  • RTT计算公式如下:
[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结构当中,然后调用RTCPReceiver::TriggerCallbacksFromRtcpPacket函数进行回调处理。

4)RTCP触发回调

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);
  }
}
  • 保留相关的,改函数通过调用RtcpBandwidthObserver模块的OnReceivedRtcpReceiverReport函数,来传递信息。
  • 根据上面的业务图,RtcpBandwidthObserver模块的最终实现为RtpTransportControllerSend,在创建RTCPReceiver实例的时候会实例化rtcp_bandwidth_observer_,
class RTCPReceiver {
 public: 
    .....
 private:
    RtcpBandwidthObserver* const rtcp_bandwidth_observer_;
}
  • RtpTransportControllerSend模块的OnReceivedRtcpReceiverReport函数对RTCP 反馈信息的处理主要分成两个步骤
  • 其一是调用OnReceivedRtcpReceiverReportBlocks函数封装TransportLossReport结构消息,并最后调用PostUpdates(controller_->OnTransportLossReport(msg)),首先将TransportLossReport消息传递给GoogCcNetworkController模块进行码率估计,其次是调用PostUpdates来将新估计得码率值作用到pacer模块
  • 其二是封装RoundTripTimeUpdate消息,并调用PostUpdates(controller_->OnRoundTripTimeUpdate(report));,GoogCcNetworkController模块接收消息后先计算基于RTT时间延迟的码率,最后调用PostUpdates来刷新码率,作用到发送模块

5)TransportLossReport和RoundTripTimeUpdate结构封装

  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));
  });
  • 传递过来的now_ms参数为当前ntp时间,也就是收到该rtcp sr或rr报文的时间。
  • 同时这两个步骤使用的是同一个task_queue_任务队列,这说明先进行基于丢包的码率估计,然后再进行基于延迟的码率估计。

5.1)TransportLossReport封装

  • 通过OnReceivedRtcpReceiverReportBlocks函数对report_blocks消息进行再封装,将其封装成TransportLossReport格式,其定义如下
#network_types.h
struct TransportLossReport {
  /*表示当前接收到该消息的ntp时间*/
  Timestamp receive_time = Timestamp::PlusInfinity();
  /*上一次接收到SR或者RR报文的时间*/
  Timestamp start_time = Timestamp::PlusInfinity();
  /*当前接收到该消息的ntp时间*/  
  Timestamp end_time = Timestamp::PlusInfinity();
  /*在上一次处理和本次处理时间差范围内也就是end_time - start_time 之间的丢包数量*/  
  uint64_t packets_lost_delta = 0;
  /*在上一次处理和本次处理时间差范围内也就是end_time - start_time 之间实际发送成功的包数*/  
  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;
}
  • 首先根据报告块得出,上一次和本次时间戳之内总共的发包数量number_of_packets,并对不同SSR进行累加得出total_packets_delta。

  • 计算total_packets_lost_delta,上一次总丢包数量减去本次总丢包数量,从而可知total_packets_lost_delta表示的是msg.end_time - msg.start_time时间差之间的丢包数量。

  • 根据total_packets_delta计算packets_received_delta,使用total_packets_delta - total_packets_lost_delta获得,在msg.end_time - msg.start_time时间差之间的总发包数量减去本段时间差之内总丢包数,最终得出的就是实际发送成功的包数量。

  • 封装TransportLossReport并触发OnTransportLossReport将TransportLossReport消息传递给GoogCcNetworkController模块。

  • 为使分析逻辑清晰,在后面再确切分析GoogCcNetworkController模块对TransportLossReport消息的处理和对码率的估计。

5.2)RoundTripTimeUpdate封装

struct RoundTripTimeUpdate {
  /*表示当前接收到该消息的ntp时间*/  
  Timestamp receive_time = Timestamp::PlusInfinity();
  /*rtt时间*/  
  TimeDelta round_trip_time = TimeDelta::PlusInfinity();
  bool smoothed = false;
};
  • 封装RoundTripTimeUpdate结构
  • 调用controller_->OnRoundTripTimeUpdate(report),将消息传递给GoogCcNetworkController模块,进行码率估计并得出新码率。
  • 调用PostUpdates更新pacer模块的实时发送码率。

6)丢包对GoogCcNetworkController模块的影响

NetworkControlUpdate GoogCcNetworkController::OnTransportLossReport(
    TransportLossReport msg) {
  if (packet_feedback_only_)//默认为false,这里可以通过配置该变量让码率估计只依据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为实际发送成功(对端收到的包) + 丢包数 。

  • 调用SendSideBandwidthEstimation::UpdatePacketsLost更新当前时间差范围内的总发包数和丢包信息。

  • 返回NetworkControlUpdate(),从这里可以看出在此时只是直接new 了一个NetworkControlUpdate,并未对其中的成员进行赋值,所以由此可知,经由RR或者SR报文的丢包情况对GoogCcNetworkController模块的真实作用是更新了SendSideBandwidthEstimation模块中的发包数量和丢包数量,而在RtpTransportControllerSend模块该阶段的最后调用栈中调用的PostUpdates函数实际上会直接返回,不会做任何事情。

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_表示上截止上一次基于丢包的回调的丢包数量,这两将上一次处理RR或者SR报告时的丢包数和本次接收到的丢包数进行累加。
  • expected_packets_since_last_loss_update_期望发送成功的总包数,发送多少对方收到多少,这里其实就是自上一次处理基于丢包码率估计到本次处理时间差之间总共的发包数量。
  • 判断expected_packets_since_last_loss_update_ < kLimitNumPackets(默认20),从这可以看出,如果在上一次处理丢包码率估计到本次处理丢包估计之间的时间差内如果总发包数小于20个,则直接返回,如果大于20个则求出last_fraction_loss_ = 上一次处理到本次处理时间差内的总丢包数 * 256 / 本段时间内总发包数,然后和255 取最小值。
  • 假设本段时间内丢包率为1,那肯定大于255,理论上丢包率肯定是小于1的,所以这里得到的丢包率是2^8 * 丢包数 / 发包数。
  • 如果发包数大于20的话计算出丢包率后需要进行清除。
  • 由以上可知,这段代码的时间差为 大于发送20个数据包的时间差,可以理解成每相隔20个数据包的时间差会进行一次丢包统计。
  • 调用UpdateEstimate进行码率估计。
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延迟相关部分,保留和基于RR或SR丢包统计相关部分代码。

  • 获取本次处理丢包统计和上次处理丢包统计的时间差time_since_loss_packet_report = at_time - last_loss_packet_report_;

  • 首先调用UpdateMinHistory以当前时间做为参数来更新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_是一个以时间为key,码率为值得一个键值对的容器。

  • 删除历史旧的数据。以当前时间和队列的第一个元素的时间差进行比较,删除1s以前保存的码率。

  • 删除历史旧的数据。以码率为查询条件,如果当前的码率比历史数据中的码率还要小则删除。

  • 以当前时间和当前码率构建std::make_pair,将其插入到容器尾部,现假设每次处理的时间间隔为100ms,那么从时间的角度来看,该队列能保存前10个码率。

  • bitrate_threshold_默认情况下为0,可以通过“WebRTC-BweLossExperiment”进行配置,low_loss_threshold_的默认值为0.02f,high_loss_threshold_的默认值为0.1f。

  • 首先判断time_since_loss_packet_report也就是两次评估处理的时间间隔小于1.2*5000等于6000ms的情况下,分成两种情况进行码率估计,一种是当前的丢包率小于等于2%的时候,那么会以历史最小码率以8%的幅度进行递增。额外再加上1kbps。

  • 如果当前的丢包率介于2% - 10%之间则维持当前码率。

  • 如果丢包率大于10%,说明网络比较差,需要降低码率,码率下降的公式为 新码率 = (当前码率 * (512 - 256 * 丢包率)) / 512,当然这个计算是有条件的,条件就是(当前时间 - 上一次开始递减的时间) > 上一次的rtt时间 + 300ms ,也就是说得到递减的码率在一定的时间范围内有效。has_decreased_since_last_fraction_loss_的值在UpdatePacketsLost回调中被设置成false,在码率递减后设置成true,每次处理RR或者SR丢包统计时进行置false,在码率递减后设置成true。

  • 最终码率新增或递减得到新码率,调用UpdateTargetBitrate对码率进行更新。从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_。
  • 调用LinkCapacityTracker::OnRateUpdate进行更新,后续进行分析,此文不进行分析。

7)RTT对GoogCcNetworkController模块的影响

  • GoogCcNetworkController模块在收到rtt的回调后其处理逻辑如下图:
rtcp_接收流程-004.png
  • 一方面将RTT信息作用到DelayBasedBwe模块。
  • 另一方面讲RTT信息作用到SendSideBandwidthEstimation模块。
  • 本文主要分析SendSideBandwidthEstimation相关的信息。
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_的值,该值在上面码率递减的分析过程中有用到,其来源就是基于此。

8)总结

  • 本文意在分析webrtc基于丢包率的动态码率调控原理和其实现的业务逻辑,包含对应的函数调用栈,以及基于丢包率对码率控制(上调或者下降)的核心算法原理。
  • 根据上文的分析得出,当网络环境良好的情况下,webrtc给出的指标是丢包率小于2%10%之间,当丢包率小于2%的时候可以适当的增加码率,其增加的公式为以码率的8%进行递增同时附加1kbps,而当丢包率在2%10%之间的时候,保持码率平稳不增加也不减少。
  • 当丢包率超过10%的时候,对码率以 (当前码率 * (512 - 256 * 丢包率)) / 512的幅度进行递减,并且确保递减的新码率在一定的时间范围内有效。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350