0%

live_nack

重传理论

正向反馈: ack,收到的包确认,靠发送方来得出需要重传的包
反向反馈: nack,未收到的包请求重传,靠接收方来得出需要重传的包,并请求重传

如何确定丢包? tcp通过seqnum,其他基本也得依赖序号标记;接收方来判断是否需要重传;

nack的算法几个关键要素

流程和算法:
  • 最好用单独的队列结构存储没收到的seq包信息。

  • nack重传基础:
    必须是包的seq连续的前提下,才能做这种算法。能知道本来应到的包和能判断出来哪些seq的包丢失了。

  • 重传序号检测和收集:注意回滚等情况
    排除第一个包,当收到的包比当前的包seq大于预期间隔如1,
    eg: 2,3,5,6,8,则认为4,7 可能丢失,当过了认为的晚到时间后,则以丢失对待,发起重传,
    因为流媒体的特殊性,对延迟和丢包敏感,所以需要尽快重传,不能等太久。

  • 同个seq的重传频率控制:
    这个seq已经累计重传了n次了,超过限制,则不再请求重传.

  • 同个seq的重传间隔控制:
    这个seq在t1时间时请求重传了,要在t1+xxms 后再进行判断重传。

  • 同个seq的超时控制:
    即这个seq本来应该在t1时间收到,但是没收到,要超时到t1+2s就认为不用再重传了

  • 其他:

  1. 当seq跳变时的处理:可能是向前跳1000,10000,或者向后跳1000,10000,这种,而且可能是正常的跳,如切流了,或者是异常的跳变,如前一个结点的bug

nack的协议:

简单的是

流id,缺失的seq列表,上述流程和算法中的一些控制参数。

webrtc中的nack协议。
  • rfc: RFC5104,rfc4585
    Video数据包的发送和接收为例,分析ANCK丢包重传机制的实现。
    主要内容包括:SDP协商NACK,接收端丢包判定,NACK报文构造、发送、接收和解析,RTP数据包重传。

  • sdp中的相关定义:见rfc5104

    1
    2
    3
    4
    5
    6
    7
    8
    9
    v=0
    o=alice 3203093520 3203093520 IN IP4 host.example.com
    s=Media with feedback
    t=0 0
    c=IN IP4 host.example.com
    m=audio 49170 RTP/AVPF 98
    a=rtpmap:98 H263-1998/90000
    a=rtcp-fb:98 nack pli //这里是指示用pli

  • 关于Webrtc中定义的反馈消息格式和nack协议格式:
    摘自rfc4585第六节:

6.RTCP反馈消息的格式
本节定义了低延迟RTCP反馈消息的格式。这些信息分为以下三类:

  • 传输层FB消息-负载特定FB消息-应用层FB消息
  • 传输层FB消息旨在传输通用反馈信息,即独立于特定编解码器或正在使用的应用程序的信息。信息预计将在传输/RTP层生成和处理。目前,仅定义了一般否定确认(NACK)消息。

  • 特定于有效负载的FB消息传输特定于特定有效负载类型的信息,并将在编解码器“层”生成和执行。本文档定义了一个与所有特定于有效负载的FB消息一起使用的通用标头。
    特定消息的定义留给RTP有效负载格式规范或其他反馈格式文档。

  • 应用层FB消息提供了一种将接收方的反馈透明地传递给发送方的应用程序的方法。此类消息中包含的信息预计不会在传输/RTP或编解码器层上进行操作。
    两个应用程序实例之间要交换的数据通常在应用程序协议规范中定义,因此可以由应用程序识别,因此不需要额外的外部信息。
    因此,本文档仅定义一个与所有应用层FB消息一起使用的公共头。从协议的角度来看,应用层FB消息被视为特定于有效负载的FB消息的特例。

  • 注意:在媒体发送方端正确处理某些FB消息可能需要发送方知道FB消息所指的有效负载类型。大多数情况下,这些知识可能仅使用单一有效负载类型从媒体流中获得。
    然而,如果同时使用多个编解码器(例如,与音频和DTMF一起使用),或者当编解码器发生变化时,可能需要作为FB消息的一部分显式地传送有效负载类型信息。
    这适用于所有人

特定于负载以及应用层FB消息。如何传输有效负载类型信息取决于FB消息的规范。
本文档定义了两个传输层和三个(视频)特定于负载的FB消息,以及应用层FB消息的单个容器。其他文件中可能会定义额外的传输层和负载特定的FB消息,并且必须通过IANA进行注册(参见第9节“IANA注意事项”)
以下小节描述了上述RTCP FB消息类型的一般语法和语义。
6.1. 反馈消息的通用数据包格式
所有FB消息必须使用图3所示的通用数据包格式:

1
2
3
4
5
6
7
8
9
10
11
12
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| FMT | PT | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of media source |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
: Feedback Control Information (FCI) :
: :

图3:反馈消息的通用数据包格式
RTP规范[2]中定义了字段V、P、SSRC和长度,其各自含义总结如下:
版本(V):2位此字段标识RTP版本。目前的版本是2。
padding(P):1位如果设置,则padding位表示数据包末尾包含额外的padding八位字节,这些八位字节不属于控制信息的一部分,但包含在长度字段中。
反馈消息类型(FMT):5位此字段标识FB消息的类型,并根据类型(传输层、特定负载或应用层反馈)进行解释。三种反馈类型中每种类型的值在以下各节中定义。
有效负载类型(PT):8位这是将数据包标识为RTCP FB消息的RTCP数据包类型。IANA定义了两个值:

1
2
3
4
Name   | Value | Brief Description
----------+-------+------------------------------------
RTPFB | 205 | Transport layer FB message
PSFB | 206 | Payload-specific FB message

长度:16位此数据包的长度(32位字减去1),包括标头和任何填充。这与RTCP发送方和接收方报告中使用的长度字段的定义一致[3]。
数据包发送方的SSRC:32位此数据包发起方的同步源标识符。
媒体源SSRC:32位此反馈信息相关的媒体源的同步源标识符。
反馈控制信息(FCI):可变长度以下三个部分定义了每种反馈类型的FB消息中可能包含的附加信息:传输层、特定负载或应用层反馈。请注意,进一步的FCI内容可能会在进一步的文件中指定。
每个RTCP反馈数据包必须在FCI字段中至少包含一条FB消息。第6.2节和第6.3节定义了每种FCI类型,是否可以将多条FB消息压缩到单个FCI字段中。如果是这种情况,它们必须是相同类型的,即相同的FMT。如果需要传输多种类型的反馈消息,即多个FMT,则必须生成多个RTCP FB消息,并将其连接在同一复合RTCP数据包中。

6.2. 传输层反馈消息
传输层FB消息由值RTPFB标识为RTCP消息类型。rtp报文丢失重传。
本文档中定义了一条通用传输层FB消息:通用NACK。FMT通过以下参数进行识别:
0:unassigned 1:Generic NACK 2-30:unassigned 31:保留用于将来扩展标识符编号空间
以下小节定义了此类FB消息的FCI字段格式。将来可能会定义更多的通用反馈消息。
6.2.1. 泛型NACK
通用NACK消息由PT=RTPFB和FMT=1标识。
FCI字段必须至少包含一个,并且可以包含多个通用NACK。
通用NACK用于指示一个或多个RTP分组的丢失。丢失的分组通过分组标识符和位掩码来识别。
如果基础传输协议能够向发送方提供类似的反馈信息,则不应使用通用NACK反馈(如DCCP)。
反馈控制信息(FCI)字段具有以下语法(图4):

1
2
3
4
5
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PID | BLP |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图4:通用NACK消息的语法

数据包ID(PID):16位PID字段用于指定丢失的数据包。PID字段是指丢失数据包的RTP序列号。
bitmask of following lost packets (BLP):16位BLP允许在PID指示的RTP数据包之后立即报告16个RTP数据包中的任何一个的丢失。
BLP的定义与[6]中给出的定义相同。将BLP的最低有效位表示为位1,将其最高有效位表示为位16,然后,如果接收器未接收到RTP分组号(PID+i)(模2^16)并指示该分组丢失,
则将位掩码的位i设置为1;否则,位i设置为0。请注意,发送方不能因为其位掩码设置为0而假设接收方已收到数据包。
例如,如果与PID对应的分组和以下分组已经丢失,则BLP的最低有效位将被设置为1。然而,发送方不能仅仅因为BLP的比特2到15为0而推断已经接收到分组PID+2到PID+16;
发送者只知道接收者此时没有将其报告为丢失。

FB消息的长度必须设置为2+n,n是FCI字段中包含的通用NACK数。
通用NACK消息通过序列号隐式引用有效负载类型。

6.3. 负载特定反馈消息
有效负载特定的FB消息由值PT=PSFB标识为RTCP消息类型。
到目前为止,定义了三条特定于有效负载的FB消息以及一条应用层FB消息。它们通过FMT参数识别,如下所示:
0:未分配1:图片丢失指示(PLI)2:切片丢失指示(SLI)3:参考图片选择指示(RPSI)4-14:未分配15:应用层FB(AFB)消息16-30:未分配31:保留用于序列号空间的未来扩展
即:
指定负载重传,指定负载重传里面又分如下三种:
(1) PLI(Picture Loss Indication)视频帧丢失重传。
(2) SLI(Slice Loss Indication)slice丢失重转。
(3)RPSI(Reference Picture Selection Indication)参考帧丢失重传。

以下小节定义了有效负载特定FB消息的FCI格式,第6.4节定义了应用层FB消息的FCI格式。

6.3.1. Picture Loss Indication
图像丢失指示(PLI)
PLI FB消息由PT=PSFB和FMT=1标识。
FCI字段中必须正好包含一个PLI。
6.3.1.1. 语义学
利用图片丢失指示消息,解码器将属于一个或多个图片的未定义量的编码视频数据的丢失通知编码器。当与基于画面间预测的任何视频编码方案结合使用时,接收PLI的编码器意识到预测链可能被中断。发送方可通过发送帧内图片对PLI作出反应以实现再同步(使该消息有效地类似于[6]中定义的FIR消息);然而,发送方必须考虑如第7节所述的拥塞控制,这可能限制其发送帧内的能力。

其他RTP有效负载规范(如RFC 2032[6])已经为某些特定编解码器定义了反馈机制。支持这两种方案的应用程序在发送反馈时必须使用本规范中定义的反馈机制。出于向后兼容性的原因,如果有效载荷格式需要,这样的应用程序还应该能够接收在各自的RTP有效载荷格式中定义的反馈方案并对其作出反应。

6.3.1.2. 消息格式
PLI不需要参数。因此,长度字段必须为2,并且不得有任何反馈控制信息。
此FB消息的语义与有效负载类型无关。
6.3.1.3. Timing Rules
The timing follows the rules outlined in Section 3. In systems that employ both PLI and other types of feedback, it may be advisable to follow the Regular RTCP RR timing rules for PLI, since PLI is not as delay critical as other FB types.

6.3.1.4. Remarks
PLI消息通常触发完整帧内图片的发送。帧内图片比预测(帧间)图片大几倍。它们的大小与生成它们的时间无关。在大多数环境中,特别是在使用带宽有限的链路时,使用帧内图片意味着允许的延迟,即
大量的典型帧持续时间。例如:如果发送帧速率为10 fps,并且假设帧内图片是帧间图片的10倍大,则必须接受整整一秒的延迟。在这种环境中,发送FB消息时不需要特定的短延迟。因此,根据[2],在Tmin=0的情况下等待RTCP定时规则允许的下一个可能的时隙不会对系统性能产生负面影响。

6.3.2. Slice Loss Indication (SLI)
The SLI FB message is identified by PT=PSFB and FMT=2.
FCI字段必须至少包含一个,并且可以包含多个SLI。
6.3.2.1. 语义学
利用片丢失指示,解码器可以通知编码器它已检测到一个或多个连续宏块按扫描顺序丢失或损坏(见下文)。此FB消息不得用于具有非统一、动态可变宏块大小的视频编解码器,如启用附录Q的H.263。在这种情况下,编码器无法始终识别损坏的空间区域。

6.3.2.2. Format
切片丢失指示使用一个额外的FCI字段,其内容如图6所示。FB消息的长度必须设置为2+n,n是FCI字段中包含的SLI数。

1
2
3
4
5
6
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| First | Number | PictureID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图6:切片丢失指示(SLI)的语法
第一:13位第一个丢失宏块的宏块(MB)地址。进行MB编号,使得图片左上角的宏块被视为宏块编号1,并且每个宏块的编号按照光栅扫描顺序从左到右然后从上到下增加(这样,如果图片中总共有N个宏块,则右下宏块被视为宏块编号N)。

数字:13位丢失宏块的数量,按上面讨论的扫描顺序。
PictureID:6位编解码器特定标识符的六个最低有效位,用于引用发生宏块丢失的图片。对于许多视频编解码器,PictureID与时间参考相同。
此FB消息的适用性仅限于一小部分视频编解码器;因此,没有提供明确的有效负载类型信息。
6.3.2.3. Timing Rules
当指示没有及时传输时,使用切片丢失指示的算法的效率大大降低。运动补偿传播未报告为损坏的损坏像素。因此,强烈建议使用第3节中讨论的算法。
6.3.2.4. Remarks
术语Slice在这里是按照MPEG-1定义和使用的,MPEG-1是按扫描顺序的连续宏块数。最近的视频编码标准有时对术语切片有不同的理解。例如,在H.263(1998)中,存在一个称为“矩形切片”的概念。一个矩形切片的丢失可能导致需要发送多个SLI,以便准确识别丢失/损坏MBs的区域。
FCI的第一个字段将图片的第一个宏块定义为1,而不是人们可能怀疑的0。这样做是为了使本规范与ITU-T Rec.H.245[24]中提供的类似机制保持一致。图片中的最大宏块数(2**13或8192)对应于大多数ITU-T和ISO/IEC视频编解码器的最大图片大小。如果未来的视频编解码器提供更大的图片大小和/或更小的宏块大小,则必须定义额外的FB消息。时间参考场的六个最低有效位被认为足以指示发生丢失的画面。
对SLI的反应不属于本规范的一部分。对SLI作出反应的一种典型方式是对受影响的空间区域使用帧内刷新。
报告了跟踪受运动补偿影响的区域的算法,以便允许将帧内宏块传输到所有这些区域,而与FB的定时无关(见H.263(2000)附录I[17]和[15])。尽管当使用这些算法时,FB的定时比不使用这些算法时不那么关键,但必须观察到,这些算法校正了图片的大部分,因此,在FB延迟的情况下,必须传输更高的数据量。

6.3.3. Reference Picture Selection Indication (RPSI)
The RPSI FB message is identified by PT=PSFB and FMT=3.
FCI字段中必须正好包含一个RPSI。
6.3.3.1. 语义学
现代视频编码标准,如MPEG-4 visual version 2[16]或H.263 version 2[17],允许使用比最新参考图片更旧的参考图片进行预测编码。通常,保持参考图片的先进先出队列。如果编码器了解到编码器-解码器同步性丢失,则可以使用称为正确参考图片的图像。由于该参考图片在时间上比平常更远,因此产生的预测编码图片将使用更多比特。

MPEG-4和H.263都为RPSI消息的“有效载荷”定义了二进制格式,该RPSI消息包括诸如受损图片的时间ID和受损区域的大小之类的信息。该位串通常很小(几十位)、长度可变且自包含,即包含执行参考图片选择所需的所有信息。

MPEG-4和H.263都允许使用带有正反馈信息的RPSI。也就是说,报告的图片(或切片)解码无误。请注意,在多方会话中,不得使用任何形式的正反馈(以RTCP间隔报告有关单个参考图片的正反馈无论如何都不会有多大用处)。

6.3.3.2. Format
The FCI for the RPSI message follows the format depicted in Figure 7:

1
2
3
4
5
6
7
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PB |0| Payload Type| Native RPSI bit string |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| defined per codec ... | Padding (0) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图7:参考图片选择指示(RPSI)的语法
PB:8位将RPSI消息长度填充为32位的倍数所需的未使用位数。
0:1位必须在传输时设置为零,在接收时忽略。
有效负载类型:7位表示必须在其上下文中解释本机RPSI位字符串的RTP有效负载类型。
Native RPSI bit string: variable length The RPSI information as natively defined by the video codec.
填充:#PB位设置为零的位数,用于将RPSI消息的内容填充到下一个32位边界。填充位数必须由PB字段指示。
6.3.3.3. Timing Rules
RPSI比使用SLI的算法对延迟更为关键。这是因为RPSI消息越老,编码器需要花费的比特数就越多,以重新建立编码器-解码器的同步性。有关特定比特率/帧速率/丢失率情况下RPSI开销的一些信息,请参见[15]。
因此,通常应使用第3节的算法尽快发送RPSI消息。
6.4. 应用层反馈消息
应用层FB消息是特定于有效负载的消息的特例,由PT=PSFB和FMT=15标识。FCI字段中必须仅包含一条应用层FB消息,除非应用层FB消息结构本身允许堆叠(例如,通过固定大小或显式长度指示器)。
这些消息用于将应用程序定义的数据直接从接收方的应用程序传输到发送方的应用程序。FB消息未识别传输的数据。因此,应用程序必须能够识别消息负载。
通常,应用程序定义自己的消息集,例如,MPEG-4[16]中的NEWPRED消息(根据RFC 3016[23]在RTP包中携带)或H.263/附录N,U[17]中的FB消息(根据RFC 2429[14]打包)。这些消息不需要来自RTCP消息的任何附加信息。因此,应用程序消息被简单地放置到FCI字段中,如下所示,并且相应地设置长度字段。
应用程序消息(FCI):可变长度此字段包含应从接收方传输到源的原始应用程序消息。格式取决于应用程序。此字段的长度是可变的。如果应用程序数据未32位对齐,则必须添加填充位和字节以实现32位对齐。填充标识由应用层决定,本规范中未定义。
应用层FB消息规范必须定义是否需要在特定编解码器(由RTP有效负载类型标识)的上下文中专门解释消息。如果正确处理需要对有效负载类型的引用,则应用层FB消息规范必须定义将有效负载类型信息作为应用层FB消息本身的一部分进行通信的方式。

webrtc中的nack算法流程。

  • 流程图
  • 代码位置:
    1
    2
    3
    4
    5
    6
    7
    void RtpVideoStreamReceiver2::OnReceivedPayloadData(
    if (nack_module_) {
    const bool is_keyframe =
    video_header.is_first_packet_in_frame &&
    video_header.frame_type == VideoFrameType::kVideoFrameKey;

    packet->times_nacked = nack_module_->OnReceivedPacket //这里,webrtc将nack交给单独的nack_module处理。

srs中的nack

srs上行重传:其中体现一些控参数的地方;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
srs_error_t SrsRtcPublishStream::check_send_nacks()
{
srs_error_t err = srs_success;

if (!nack_enabled_) {
return err;
}
//视频的重传
for (int i = 0; i < (int)video_tracks_.size(); ++i) {
SrsRtcVideoRecvTrack* track = video_tracks_.at(i);
if ((err = track->check_send_nacks()) != srs_success) {
return srs_error_wrap(err, "video track=%s", track->get_track_id().c_str());
}
}
//音频的重传
for (int i = 0; i < (int)audio_tracks_.size(); ++i) {
SrsRtcAudioRecvTrack* track = audio_tracks_.at(i);
if ((err = track->check_send_nacks()) != srs_success) {
return srs_error_wrap(err, "audio track=%s", track->get_track_id().c_str());
}
}

return err;
}


srs_error_t SrsRtcVideoRecvTrack::check_send_nacks()
{
srs_error_t err = srs_success;

++_srs_pps_svnack->sugar;

uint32_t timeout_nacks = 0;
if ((err = do_check_send_nacks(timeout_nacks)) != srs_success) {
return srs_error_wrap(err, "video");
}

// If NACK timeout, start PLI if not requesting.
if (timeout_nacks == 0) {
return err;
}

srs_trace2(TAG_MAYBE, "RTC: NACK timeout=%u, request PLI, track=%s, ssrc=%u", timeout_nacks,
track_desc_->id_.c_str(), track_desc_->ssrc_);

return err;
}

srs_error_t SrsRtcRecvTrack::do_check_send_nacks(uint32_t& timeout_nacks)
{
srs_error_t err = srs_success;

uint32_t sent_nacks = 0;
session_->check_send_nacks(nack_receiver_, track_desc_->ssrc_, sent_nacks, timeout_nacks);

return err;
}


void SrsRtcConnection::check_send_nacks(SrsRtpNackForReceiver* nack, uint32_t ssrc, uint32_t& sent_nacks, uint32_t& timeout_nacks)
{
++_srs_pps_snack->sugar;

SrsRtcpNack rtcpNack(ssrc);

rtcpNack.set_media_ssrc(ssrc);
nack->get_nack_seqs(rtcpNack, timeout_nacks);//去拿没收到的seqs

if(rtcpNack.empty()){
return;
}

++_srs_pps_snack2->sugar;
++_srs_pps_srtcps->sugar;

char buf[kRtcpPacketSize];
SrsBuffer stream(buf, sizeof(buf));

// TODO: FIXME: Check error.
rtcpNack.encode(&stream);

// TODO: FIXME: Check error.
int nb_protected_buf = stream.pos();
transport_->protect_rtcp(stream.data(), &nb_protected_buf);

// TODO: FIXME: Check error.
sendonly_skt->sendto(stream.data(), nb_protected_buf, 0);
}



void SrsRtpNackForReceiver::get_nack_seqs(SrsRtcpNack& seqs, uint32_t& timeout_nacks)
{
// If circuit-breaker is enabled, disable nack.
if (_srs_circuit_breaker->hybrid_high_water_level()) {
queue_.clear();
++_srs_pps_snack4->sugar;
return;
}

srs_utime_t now = srs_get_system_time();

srs_utime_t interval = now - pre_check_time_; //上次检查的时间距离现在
if (interval < opts_.nack_check_interval) {
return;
}
pre_check_time_ = now;

// 结构:seqnum -> SrsRtpNackInfo
std::map<uint16_t, SrsRtpNackInfo>::iterator iter = queue_.begin();
while (iter != queue_.end()) {
const uint16_t& seq = iter->first;
SrsRtpNackInfo& nack_info = iter->second;

int alive_time = now - nack_info.generate_time_;//老化时间
if (alive_time > opts_.max_alive_time || nack_info.req_nack_count_ > opts_.max_count) {//同个seq的最大重传次数,删除
++timeout_nacks;
rtp_->notify_drop_seq(seq);
queue_.erase(iter++);
continue;
}

// TODO:Statistics unorder packet.
if (now - nack_info.generate_time_ < opts_.first_nack_interval) {
break;
}

//同个seq前后 两次重传的时间间隔,避免太频繁
srs_utime_t nack_interval = srs_max(opts_.min_nack_interval, opts_.nack_interval / 3);
if(opts_.nack_interval < 50 * SRS_UTIME_MILLISECONDS){
nack_interval = srs_max(opts_.min_nack_interval, opts_.nack_interval);
}

if (now - nack_info.pre_req_nack_time_ >= nack_interval ) {//超过时间才能加入重传seqs
++nack_info.req_nack_count_;
nack_info.pre_req_nack_time_ = now;
seqs.add_lost_sn(seq);
}

++iter;
}
}


xx的nack:

一个亮点:结点会给其哪个序号的包没收到,这样下级结点就不会去做这个包的请求重传,这样避免了下级结点的无效重传请求。