NACK & RTX


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.ssrcs

    rtx_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";


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM