一 简述
接收端发现序列号不连续,发送RTCP FB Nack包,发送端从历史队列中查找该包,再发送RTP包,但WebRTC用的RTX重发该包,ssrc和原视频流不同,pt也不同。
a=rtpmap:96 H264/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96 //表示97是96重传
a=ssrc-group:FID 1753454336 165688474
这是WebRTC中的SDP,原视频流PT是96,重传RTX PT是97, ssrc-group是原视频流ssrc和RTX ssrc。
sender ssrc是发送RTCP NACK包这端发送视频rtp包的ssrc,media ssrc是随机生成的。?
if (receiver_only_ || main_ssrc_ != nack.media_ssrc()) // Not to us.
return;
RTCP FB Nack包如上图所示:pt=205,fmt=1,PID是丢包的起始序列号-2字节,BLP表示起始序列号后面16位丢包情况,1是丢包,0是没丢包。读blp的值要反一下。BLP占2字节。这种结构体可以有多个。
二 实现
1 发送RTCP FB NACK报文
NackModule2发送RTCP FB NACK报文有两种实现方式:一种是基于序列号,另一种基于时间,周期是20ms。两种都有用到。
AddPacketsToNack(uint16_t seq_num_start,uint16_t seq_num_end),参1 !=参2,说明丢包了。
// Do not send nack for packets that are already recovered by FEC or RTX
if (recovered_list_.find(seq_num) != recovered_list_.end())
continue;
丢包了,就放到nack_list_[seq_num] = nack_info;中。
std::vector<uint16_t> NackModule2::GetNackBatch(NackFilterOptions options) {
...
//send_nack_delay_ms_可设置,默认为0
bool delay_timed_out =
now.ms() - it->second.created_at_time >= send_nack_delay_ms_;
//resend_delay是rtt,基于时间的,要判断rtt
bool nack_on_rtt_passed =
now.ms() - it->second.sent_at_time >= resend_delay.ms();
bool nack_on_seq_num_passed =
it->second.sent_at_time == -1 &&
AheadOrAt(newest_seq_num_, it->second.send_at_seq_num);
//sent_at_time == -1, 重发一次后,值变了,基于序列号的不会重发到第二次
if (delay_timed_out && ((consider_seq_num && nack_on_seq_num_passed) ||
(consider_timestamp && nack_on_rtt_passed))) {
nack_batch.emplace_back(it->second.seq_num);
++it->second.retries;
it->second.sent_at_time = now.ms();
...
}
nack_sender_->SendNack(nack_batch, /*buffering_allowed=*/true);
基于时间的:在构造函数中,起了一个定时任务, nack_sender_->SendNack(nack_batch, /*buffering_allowed=*/false);
一些参数:
const int kMaxNackPackets = 1000;//nack_list_队列的最大长度
const int kDefaultRttMs = 100; //rtt的默认值
const int kMaxNackRetries = 10; //最大重传次数10次
const int kMaxPacketAge = 10000;
丢包太多,会直接请求FIR、关键帧了。
2 发送端用RTX重发RTP包
根据序列号,从packet_history_找到该包。重发。
packet_history_默认大小是600个,可设置的,存储600个RTP包。
static const int kMinSendSidePacketHistorySize = 600;
细节1:发送端重发丢失的包,会判断RTT,时间比RTT小,不发送。
RTPSender::ReSendPacket(...)中,会从packet_history_队列中查找,
if (!VerifyRtt(*packet, clock_->TimeInMilliseconds())) {
// Packet already resent within too short a time window, ignore.
return nullptr;
}
PacingController::EnqueuePacket中,会判断优先级,音频、重传、视频。
细节2:RTX和原RTP包不同的地方:payload多了2字节的OSN即原始序列号,具体见 RTPSender::BuildRtxPacket。
3 接收到原视频包或RTX包
根据ssrc判断出是那种。在RtxReceiveStream::OnRtpPacket中,处理RTX包,然后给RtpVideoStreamReceiver2::OnRtpPacket。
三 自问自答
1 RTCP FB NACK包发送原则?发现丢包立即发送?定时发送?
答:不仅仅判断丢包,基于时间的还判断了RTT,RTT是一直在更新的。
代码见:NackModule2::GetNackBatch。不同版本有差别。
2 重发RTP包即RTX包,原则?判断了rtt时间。
发RTCP Nack包,最多重试10次?
基于序列号的,不会重发10次的。sent_at_time == -1 ,还有什么情况能赋值为-1 ?
基于时间存在重试10次的情况。
3 基于时间周期是20ms,期望RTT在20毫秒之内!!!这个值可以优化。
4 基于序列号和时间有冲突吗?没有,++retries。
5 怎么优化nack,调整那些参数?