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

转载于:https://www.cnblogs.com/webrtc/p/7402558.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值