NACK & RTX
Tags: WebRTC NACK RTX
written by cjqu
webrtc\video\video_receive_stream.cc
webrtc\video\rtp_stream_receiver.cc
webrtc\modules\video_coding\nack_module.cc
@(NACK)[RTCP-FB, WebRTC]
概述
相關RFC:
RFC 4585 Extended RTP Profile for RTCP-BaseFeedback (RTP/AVPF)
RFC 4588 RTP Retransmission Payload Format
RFC 4585 中定義了兩種模式, "ACK mode" "NACK mode", 本文只描述 NACK mode
RTCP FeedBack Syntax
a=rtcp-fb:" rtcp-fb-pt SP rtcp-fb-val CRLF
rtcp-fb-pt = "*"; wildcard: applies to all formats
/ fmt; as defined in SDP spec
rtcp-fb-val = "ack" rtcp-fb-ack-param
/ "nack" rtcp-fb-nack-param
/ "trr-int" SP 1*DIGIT
/ rtcp-fb-id rtcp-fb-param
//NACK MODE
rtcp-fb-nack-param = SP "pli"
/ SP "sli"
/ SP "rpsi"
/ SP "app" [SP byte-string]
/ SP token [SP byte-string]
/ ; empty
//ACK MODE
rtcp-fb-ack-param = SP "rpsi"
/ SP "app" [SP byte-string]
/ SP token [SP byte-string]
/ ; empty
NACK Sender
WebRTC 源碼細節
- video::VideoReceiverStream 繼承NACK發送類
NackSender, 實現了其純虛接口SendNack - SendNack 使用VideoReceiverStream 內部的
RtpStreamReceiver對象的接口RequestPacketRetransmit發送 NACK, 其內部使用RtpRtcp對象的SendNack發送接口實現最終的 NACK 發送 - VideoReceiverStream 將 NackSender 傳入底層 RtpStreamReceiver 中, 在這里創建了
NackModule模塊.
創建NackModule需要指定 nack 和 keyframe 的sender. 會在 RtpStreamReceiver::OnReceivedPayloadData中將收到的RTP傳入NackModule 模塊中(通過調用NackModule::OnReceivedPacket), 在這里檢測是否需要發送 NACK 請求.
void VideoReceiveStream::SendNack(
const std::vector<uint16_t>& sequence_numbers) {
rtp_stream_receiver_.RequestPacketRetransmit(sequence_numbers);
}
void RtpStreamReceiver::RequestPacketRetransmit(
const std::vector<uint16_t>& sequence_numbers) {
rtp_rtcp_->SendNack(sequence_numbers);
}
void ModuleRtpRtcpImpl::SendNack(
const std::vector<uint16_t>& sequence_numbers) {
for (auto it = sequence_numbers.begin(); it != sequence_numbers.end(); it++)
{
LOG(LS_WARNING) << "[RtpRtcp] NackList SendNack seq:" << *it;
}
rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpNack, sequence_numbers.size(),
sequence_numbers.data());
}
NACK 請求發送細節
WebRTC中使用 nack_list_ 存儲所有已經或者將要發送NACK請求出去過的序號, 以及該序號請求過的次數.
list 的
在 NackModule::OnReceivedPacket 中檢測NACK的發送, 如果發現亂序的包(當前收到的包序號seq_num 在 seq_last_recv 的后面)需要檢測 nack_list 中是否已經存在該序號, 如果是則從list中刪除該序號.
如果包沒有亂序, 則將 seq_last_recv + 1 到 seq_num 之間的包加入 nack_list 中.
-
nack_list 的最大容量為 kMaxNackPackets = 1000, 如果滿了會刪除最后一個 KeyFrame 之前的所有nacked 序號, 如果刪除之后還是滿的那么清空 nack_list 並請求KeyFrame.
-
GetNackBatch接口 從nack_list 中取出需要發送 NACK 的序號列表, 如果某個 seq 請求次數超過kMaxNackRetries = 10次則會從nack_list 中刪除. -
最后在 NackModule 中觸發使用
NackSender::SednNack發送 NACK 請求 -
從
GetNackBatch中獲取需要批量發送的 nack_list 時有兩個不同的邏輯:(1) 只考慮序號 kSeqNumOnly
這種策略在OnReceivedPacket中檢測那些需要發送NACk時會觸發,
此時獲取需要發送nack的列表的條件為:- 當前序號是第一次發送(本地記錄的send_at_time == -1)
- 當前最新收到的包序號在這個需要發送NAKC的序號的前面(避免當前還在收之前沒收到的包)
比如當前最新收到100, 當前檢測是否需要發送NACK的序號為小於等於100的才滿足條件, 比如 99
如果當前檢測是否需要發送nack的序號為 103, 那么此時不滿足條件. 因為很有可能接下來會收到丟失的103.如果觸發了NACK的發送那么記錄發送時間以及重發次數, 用於后面實現發送間隔一個RTT以及超過重發此時(10)后不 再重發.
(2) 只考慮時間 kTimeOnly
這種策略在NackModule的Process()定時處理中使用
此時獲取需要發送NACK的條件為:
* 該序號上次發送NACK的時間到當前時間要超過1個RTT(該序號一次也沒發送過NACK(send_at_time == -1)也滿足 這個條件)
*
NACK Receiver
WebRTC中處理 NACK 請求流程:
- 首先是正常的 RTCP 處理流程: RTCPReceiver 中解析處理RTCP 詳細, 在
TriggerCallbacksFromRtcpPacket處理不同的RTCP消息. - 如果是 kRtcpNack 消息則觸發 RtpRtcp 對象的
ModuleRtpRtcpImpl::OnReceivedNack處理流程:
將丟包的序號 記錄到PacketLossStats, 獲取RTT后進入 RTPSedner.OnReceivedNack. - RTPSender中完成在
PacketHistory中查找需要發送的RTP seq, 並決定重發時間. 重發也需要經過重發的比特率限制的檢查. RTPSedner 初始化話時可以配置是否使用(PacedSend, 均勻發送), 最后檢查重發格式(RtxStatus() 可以獲取是否使用 RTX 封裝)后使用RTPSedner::PrepareAndSendPacket進行立即重發. 如果是使用 PacedSend, 則使用PacedSender::InsertPacket先加入發送列表中, 它的process會定時處理發送任務.
RTX
概述
RTP Retransmission Payload Format RFC 4588
RTX 結構
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OSN | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| Original RTP Packet Payload |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
RFC 4588 中定義了兩種 RTX scheme, Session-multiplexing / SSRC-multiplexing
多路會話
RTX重傳流使用與主流不一樣的RTP session來傳輸重傳流, 媒體流的目的地址應該應該不一樣, 使用相同的SSRC.
多路SSRC
主流和重傳流在同一個 RTP Session中, 使用不同的 PT 和不同的SSRC 來區分RTX流和主流.
WebRTC 中使用多路SSRC這種方式.
OSN Orignal Sequence Number, 主流對應的 SN.
- PT 使用協商RTX時使用的PT, SN 使用當前 RTP session 變化規則, 遞增.
- 其他RTP header 信息均使用重傳的包對應的信息(主流的).
CNAME
RTX流和主流使用應該使用相同的CNAME
協商
Syntax
a=fmtp:<number> apt=<apt-value>;rtx-time=<rtx-time-val>
number : RTX流的PT
apt(Associated Payload Type) RTX流對應的主流的PT
rtx-time: 單位毫秒, 可用於RTX 重傳的本地存儲的 RTP 的歷史時長, 可選, 默認值3000.
WebRTC 中的RTX特性
RTX 發送
- 在
RtcpReceiver中處理 RTCP 消息, 獲取需要重發的的包序號,RtpRtcp::OnReceiverNack中處理NACK. - 將 nack_sequence_numbers 和 RTT 送入
RTPSender::OnReceivedNack中處理. RTPSender中有重發包的接口:ReSendPacket, WebRTC中的重發包時間使用 RTT+5 ms- RTPSender 中實現了重發的比特率限制, 使用
RateLimiter *retransmission_rate_limiter_實現判斷重發某個包時是否滿足比特率限制.
以上流程與收到 NACK 重傳RTP包流程一致, 后續 在 RTPSender::PrepareAndSendPacket中重發時判斷是否使用 RTX 的封裝格式.
WebRTC中的 RTX 配置
主要跟 rtx_ssrc , rtx_pt, rtx_mode相關.
-
RtpRtcp中使用 RtpSender 中提供的接口設置RTX相關參數:
//設置RTPSender中的 RTX 使用開關模式 void ModuleRtpRtcpImpl::SetRtxSendStatus(int mode) enum RtxMode { kRtxOff = 0x0, kRtxRetransmitted = 0x1, // Only send retransmissions over RTX. kRtxRedundantPayloads = 0x2 // Preventively send redundant payloads // instead of padding. }; //設置RTPSender中的RTX發送時的Payload, 包括 **rtx_pt 和 apt** ModuleRtpRtcpImpl::SetRtxSendPayloadType(int payload_type,int associated_payload_type) //用於設置RTPSender中的 RTX SSRC ModuleRtpRtcpImpl::SetRtxSsrc(uint32_t ssrc) -
VideoSendStream 中在構造時使用VideoSendStream::Config中的配置信息設置RtpRtcp相關信息
(1) 檢查 config.rtp.rtx.ssrcs是否配置, 如果沒有則不設置 RTX 相關參數. 如果有配置則分別使用上述接口配置 SSRC, PT, RTX_MODE參數. 每個RtpRtcp 的rtx_ssrc使用不同配置, 但是 PT 和 rtx_mode 使用相同配置.
NOTE:
VideoSendStream中是根據 config.rtp.ssrcs 中存儲的ssrc個數來創建對應數量的 RtpRtcp 模塊的, 即一個SSRC對應一個 RtpRtcp而 config.rtp.rtx.ssrcs應該跟 config.rtp.ssrcs 相互對應,
即:RtpRtcp[i].SetRtxSsrc( config.rtp.rtx.ssrcs[i])
相關接口:
video_send_stream.cc
void VideoSendStreamImpl::ConfigureSsrcs()
rtp_rtcp->SetRtxSsrc(ssrc)
rtp_rtcp->SetRtxSendPayloadType(config_->rtp.rtx.payload_type,
config_->encoder_settings.payload_type);
rtp_rtcp->SetRtxSendStatus(kRtxRetransmitted | kRtxRedundantPayloads);Payloads);
-
VideoSendStream 中的配置信息 VideoSendStream::Config 是從
WebRtcVideoChannel2::WebRtcVideoSendStream中創建 VideoSendStream 時copy過來的. 這里的Config信息都是從 StreamParams(SP) 中獲取的(很多信息)(在WebRtcVideoSendStream的構造階段).比如: config.rtp.ssrcs 和 config.rtp.rtx.ssrcs 就是SP中獲取的.
rtp.ssrcs 從 ssrc-group:MID獲取(如果沒有MID則使用第一個SSRC),
再根據rtp.ssrcs從ssrc-group:FID中獲取 rtp.rtx.ssrcsrtx_pt 從codec_setting中獲取
config.rtp.rtx.payload_type = codec_setting.rtx_payload_type開啟WebRTC的RTX的SDP協商需要包括:
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=ssrc-group:FID 2736695910 239189782
其中 2736695910 是rtp.ssrcs中, 從MID中獲取到的 PrimarySSRC , 239189782 是RTX對應SSRC.這里發現WebRTC一個BUG, 在RED(ULPFEC)開啟, 本端使用 RED 封裝媒體以及FEC流的, 如果需要本端重傳
包使用RTX的格式, 那么對端的SDP不能只有 RED對應的RTX a行. 還需要其他媒體流的PT也有對應的RTX a行.
我提交的patch 點這里
NOTE
VideoEngine 中使用WebRTCVideoSendStream封裝VideoSendStream的, 也就是說在 VideoEngine 中是使用 WebRTCVideoSendStream來創建 VideoSendStream, WebRtc開頭的接口是更上層的接口
相關接口
webrtcvideoengine2.cc
stream_ = call_->CreateVideoSendStream(std::move(config),
parameters_.encoder_config.Copy());
//各種SSRC group 語義
const char kFecSsrcGroupSemantics[] = "FEC";
const char kFecFrSsrcGroupSemantics[] = "FEC-FR";
const char kFidSsrcGroupSemantics[] = "FID";
const char kSimSsrcGroupSemantics[] = "SIM";
