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;
}
// ...
}