webrtc 源码-获取 RTT 和丢包率
webrtc 版本:2021-04-23 master
1 主要内容
发送端需要得到 rtt 和 丢包率来作发送端带宽调整的依据 (注意区分发送端带宽调整与 SendSide-BWE 带宽估计算法不是同一回事) 。
实际上接收端也需要 rtt 来作为 nack 发送间隔的依据,但是接收端 rtt 被设置成了固定值。
2 SR 与 RR 包
具体结构与参数参考 rfc3550。
2.1 包结构
SR 包结构如下:
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
其中 report-block:
- fraction lost 两次 SR/RR 包之间的丢包率,原本是小数,但是这里乘以了 255 以便使用 8 位空间来存储
- cumulative number of packets lost 到发送当前 SR/RR 包时刻为止,累计丢包个数
- extended highest sequence number received 到发送当前 SR/RR 包时刻为止,收到的最高序列号,低 16 位存储最高序列号,高 16 位存储序列号回绕次数
- interarrival jitter 两次 SR/RR 包之间的平均抖动
- LSR 最近一次收到的对端发送的 SR 包中携带的 ntp-timestamp(高 16 位来自 most significant word 的低位,低 16 位来自 least significant word 的高位),如果还没有收到过,则为 0
- DLSR 到发送当前 SR/RR 包时刻为止,距离上次收到 SR 包的 ntp 时间间隔
一般来说,SR 包不会携带 report-block,RR 包只携带一个对应 source ssrc 的 report-block,例如接收端收一路音频和一路视频,那么这两路媒体流发送 RR 包时,只单独发送自己对应的 report-block。
3 接收端统计
StreamStatisticianImpl 类是主要的收包信息统计类,每收到一个 rtp 包,如果不是被 nack,fec 恢复的包,都会送入此类进行统计。至于不统计被 nack,fec 恢复的包,是因为统计模块需要排除丢包恢复算法的影响。
---> /video/rtp_video_stream_receiver.cc::OnRtpPacket()
void RtpVideoStreamReceiver::OnRtpPacket(const RtpPacketReceived& packet) {
// ...
// Update receive statistics after ReceivePacket.
// Receive statistics will be reset if the payload type changes (make sure
// that the first packet is included in the stats).
// 如果不是 nack/fec 恢复的包, 则需要进行收包统计
// 因为收包统计模块关系到拥塞控制, 所以这里为了避开丢包恢复算法的影响, 需要统计原始的收包网络状况
if (!packet.recovered()) {
rtp_receive_statistics_->OnRtpPacket(packet);
}
}
另外,每一路拥有独立 ssrc 的流,都进行独立的统计,即每一个 ssrc 对应一个 StreamStatisticianImpl 类对象。
StreamStatisticianImpl 类主要会统计三种数据:
- 收包码率
- 累计丢包数量
- 累计收包数量,累计负载大小,累计填充大小,当前收包时刻,最高收包序列号等
---> /modules/rtp_rtcp/source/receive_statistics_impl.h
class StreamStatisticianImpl : public StreamStatistician {
// ...
RateStatistics incoming_bitrate_ RTC_GUARDED_BY(&stream_lock_);
uint32_t jitter_q4_ RTC_GUARDED_BY(&stream_lock_);
int64_t last_receive_time_ms_ RTC_GUARDED_BY(&stream_lock_);
uint32_t last_received_timestamp_ RTC_GUARDED_BY(&stream_lock_);
int64_t received_seq_first_ RTC_GUARDED_BY(&stream_lock_);
int64_t received_seq_max_ RTC_GUARDED_BY(&stream_lock_);
int32_t cumulative_loss_ RTC_GUARDED_BY(&stream_lock_);
StreamDataCounters receive_counters_ RTC_GUARDED_BY(&stream_lock_);
};
其中,incoming_bitrate_ 是计算码率的模块。jitter_q4_ 是计算的抖动值,后面 3.1 节会描述。接着是记录收包时刻和包时间戳的两个 time 变量。再接着是记录包序列号的两个 seq 变量。cumulative_loss_ 记录累计丢包数量。receive_counters_ 是记录收包数量,累计负载等数据的模块。
统计的入口函数是:
---> /modules/rtp_rtcp/source/receive_statistics_impl.cc::UpdateCounters()
StreamStatisticianImpl::UpdateCounters()
3.1 计算抖动
假设上一个 rtp 包为 r_last,当前 rtp 包为 r_current,计算公式为 (参考 rfc3550 6.4节):
jitter = (r_current_recv_time - r_last_recv_time) - (r_current_rtp_ts - r_last_rtp_ts)
即 抖动 = 两个包的接收时间差 - 发送时间差,其中接收时间在 UpdateCounters() 函数内赋值,发送时间即为 rtp 包时间戳。
---> /modules/rtp_rtcp/source/receive_statistics_impl.cc::UpdateJitter()
void StreamStatisticianImpl::UpdateJitter(const RtpPacketReceived& packet,
int64_t receive_time_ms) {
int64_t receive_diff_ms = receive_time_ms - last_receive_time_ms_;
// packet.payload_type_frequency() 一般返回 90000
// 除以 1000 的目的是为了将 ms 转化为 rtp 时间戳的 s
uint32_t receive_diff_rtp = static_cast<uint32_t>(
(receive_diff_ms * packet.payload_type_frequency()) / 1000);
int32_t time_diff_samples =
receive_diff_rtp - (packet.Timestamp() - last_received_timestamp_);
time_diff_samples = std::abs(time_diff_samples);
// lib_jingle sometimes deliver crazy jumps in TS for the same stream.
// If this happens, don't update jitter value. Use 5 secs video frequency
// as the threshold.
// 过滤异常
// 同时需要计算平均抖动, 过滤噪声 (参看 rfc3550-6.4.1 章节)
if (time_diff_samples < 450000) {
// Note we calculate in Q4 to avoid using float.
int32_t jitter_diff_q4 = (time_diff_samples << 4) - jitter_q4_;
jitter_q4_ += ((jitter_diff_q4 + 8) >> 4);
}
}
注意,接收时间差需要先转化为 rtp 时间戳后再与发送时间差相减。同时,接收时间是接收端本地时间,发送时间是发送端本地时间,时间不同步,所以数学意义上来说不能变换计算公式为同一个包的接收时间减去发送时间。
抖动的计算理论上是每接收到一个 rtp 包就要更新计算一次,但是如果接收包长期乱序,则不计算,待收包平稳些后再计算。
3.2 计算 SR/RR 需要的丢包率等统计参数
入口函数是:
---> /modules/rtp_rtcp/source/receive_statistics_impl.cc::CalculateRtcpStatistics()
RtcpStatistics StreamStatisticianImpl::CalculateRtcpStatistics() {
// Calculate fraction lost.
// received_seq_max_ 始终为收到的最新 rtp 包的序列号
// last_report_seq_max_ 初始化为 -1, 当收到第一个 rtp 包后被设置为第一个包序列号减 1
int64_t exp_since_last = received_seq_max_ - last_report_seq_max_;
// cumulative_loss_ 为从收到流累计到现在的丢包个数
// last_report_cumulative_loss_ 初始化为 0
int32_t lost_since_last = cumulative_loss_ - last_report_cumulative_loss_;
// 计算丢包率, 即 rate = lost/received, 这里做了定点化处理
if (exp_since_last > 0 && lost_since_last > 0) {
// Scale 0 to 255, where 255 is 100% loss.
stats.fraction_lost =
static_cast<uint8_t>(255 * lost_since_last / exp_since_last);
} else {
stats.fraction_lost = 0;
}
// ...
// Only for report blocks in RTCP SR and RR.
last_report_cumulative_loss_ = cumulative_loss_;
last_report_seq_max_ = received_seq_max_;
return stats;
}
即每次调用此函数,计算两次调用间的丢包率等参数。
4 构造 Report-Block
ReportBlock 类负责 report-block 的构造:
---> /modules/rtp_rtcp/source/rtcp_packet/report_block.h
// A ReportBlock represents the Sender Report packet from
// RFC 3550 section 6.4.1.
class ReportBlock {
// ...
uint32_t source_ssrc_; // 32 bits
uint8_t fraction_lost_; // 8 bits representing a fixed point value 0..1
int32_t cumulative_lost_; // Signed 24-bit value
uint32_t extended_high_seq_num_; // 32 bits
uint32_t jitter_; // 32 bits
uint32_t last_sr_; // 32 bits
uint32_t delay_since_last_sr_; // 32 bits, units of 1/65536 seconds
};
并且由 ReceiveStatisticsImpl 类创建并初始化多个 ReportBlock 类对象 (ReceiveStatisticsImpl 类也持有多个 StreamStatisticianImpl 类对象,每个收流 ssrc 对应一个):
---> /modules/rtp_rtcp/source/receive_statistics_impl.cc::RtcpReportBlocks()
std::vector<rtcp::ReportBlock> ReceiveStatisticsImpl::RtcpReportBlocks(
size_t max_blocks) {
std::map<uint32_t, StreamStatisticianImpl*> statisticians;
{
rtc::CritScope cs(&receive_statistics_lock_);
statisticians = statisticians_;
}
std::vector<rtcp::ReportBlock> result;
result.reserve(std::min(max_blocks, statisticians.size()));
auto add_report_block = [&result](uint32_t media_ssrc,
StreamStatisticianImpl* statistician) {
// Do we have receive statistics to send?
RtcpStatistics stats;
// 获得接收统计数据 (如丢包率, 抖动, 收包数等)
if (!statistician->GetActiveStatisticsAndReset(&stats))
return;
result.emplace_back();
rtcp::ReportBlock& block = result.back();
block.SetMediaSsrc(media_ssrc);
block.SetFractionLost(stats.fraction_lost);
if (!block.SetCumulativeLost(stats.packets_lost)) {
RTC_LOG(LS_WARNING) << "Cumulative lost is oversized.";
result.pop_back();
return;
}
block.SetExtHighestSeqNum(stats.extended_highest_sequence_number);
block.SetJitter(stats.jitter);
};
// ...
}
还有 LSR,DLSR 没有填充,这两个参数由 RtcpTransceiverImpl 类进行填充:
---> /modules/rtp_rtcp/source/rtcp_transceiver_impl.cc::CreateReportBlocks()
std::vector<rtcp::ReportBlock> RtcpTransceiverImpl::CreateReportBlocks(
int64_t now_us) {
if (!config_.receive_statistics)
return {};
// TODO(danilchap): Support sending more than
// |ReceiverReport::kMaxNumberOfReportBlocks| per compound rtcp packet.
// ---> /modules/rtp_rtcp/source/receive_statistics_impl.cc::RtcpReportBlocks()
std::vector<rtcp::ReportBlock> report_blocks =
config_.receive_statistics->RtcpReportBlocks(
rtcp::ReceiverReport::kMaxNumberOfReportBlocks);
uint32_t last_sr = 0;
uint32_t last_delay = 0;
for (rtcp::ReportBlock& report_block : report_blocks) {
// 校验 ssrc
auto it = remote_senders_.find(report_block.source_ssrc());
if (it == remote_senders_.end() ||
!it->second.last_received_sender_report) {
continue;
}
// 得到本端上一次收到对端发送的 SR 包的时间
// 系统初始化时, 可能本端还没有接收到过 SR 包
const SenderReportTimes& last_sender_report =
*it->second.last_received_sender_report;
// 根据 rfc3550-6.4 章节, lsr 和 dlsr 都必须是 ntp 时间戳
last_sr = CompactNtp(last_sender_report.remote_sent_time);
// dlsr 等于当前时间减去收到 SR 包的时间, 再转化为 ntp 时间
last_delay = SaturatedUsToCompactNtp(
now_us - last_sender_report.local_received_time_us);
// 为当前 ssrc-report-block 设置 lsr 和 dlsr
report_block.SetLastSr(last_sr);
report_block.SetDelayLastSr(last_delay);
}
return report_blocks;
}
其中,remote_sent_time、local_received_time_us 等变量的赋值:
---> /modules/rtp_rtcp/source/rtcp_transceiver_impl.cc::HandleSenderReport()
void RtcpTransceiverImpl::HandleSenderReport(
const rtcp::CommonHeader& rtcp_packet_header,
int64_t now_us) {
//
// 每当收到一个 SR 包, 调用此
//
rtcp::SenderReport sender_report;
if (!sender_report.Parse(rtcp_packet_header))
return;
RemoteSenderState& remote_sender =
remote_senders_[sender_report.sender_ssrc()];
absl::optional<SenderReportTimes>& last =
remote_sender.last_received_sender_report;
last.emplace();
// remote_sent_time 赋值为收到对端 SR 包时的时刻
last->local_received_time_us = now_us;
// remote_sent_time 赋值为对端 SR 包中的 ntp 时间戳(注意remote_sent_time是uint64_t类型的)
last->remote_sent_time = sender_report.ntp();
// ...
}
即每次收到 SR 包,都会更新一次这两个变量,在构造 report-block 时,再取出来。
到此,多个 report-block 就填充好了,然后嵌入到 SR/RR 包中即可。
5 接收端解析
rtt 计算公式:
rtt = now_ntp - LSR - DLSR
即 rtt = 当前时刻 - LSR发送时刻 - LSR在对端停留的间隔
RTCPReceiver 类负责接收解析所有收到的 RTCP 包,同时也包括计算 rtt:
---> /modules/rtp_rtcp/source/rtcp_receiver.cc::HandleReportBlock()
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.
// |registered_ssrcs_|集合存储的是本端发送数据的ssrc
// 只有对端发过来的SR/RR, ssrc才能匹配|registered_ssrcs_|集合
if (registered_ssrcs_.count(report_block.source_ssrc()) == 0)
return;
// 当前时刻
const Timestamp now = clock_->CurrentTime();
last_received_rb_ms_ = now.ms();
// ...
// 得到抖动等参数
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;
// 获取LSR (注意, 这是本端 ntp 时间)
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.
// 当对端还没有收到过本端发送的SR包时, LSR会被设置为0
if (send_time_ntp != 0) {
// 获取DLSR (注意, 这是对端ntp时间)
uint32_t delay_ntp = report_block.delay_since_last_sr();
// Local NTP time.
// 得到当前时刻本端的ntp时间
uint32_t receive_time_ntp = CompactNtp(TimeMicrosToNtp(now.us()));
// RTT in 1/(2^16) seconds.
//
// 计算rtt, 注意receive_time_ntp与send_time_ntp应当都为realtime或monotonic_time中的一种
//
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, 所以一个SR/RR会计算出多个rtt, 这里需要调用AddRoundTripTimeSample()函数来取平均
report_block_data->AddRoundTripTimeSample(rtt_ms);
packet_information->rtt_ms = rtt_ms;
}
// ...
}