接收方定时把所有未收到的包序号通过反馈报文通知到发送方进行重传。
相对 ARQ带来的改进:减少的反馈包的频率和带宽占用,同时也能比较及时地通知发送方进行丢包重传。
NACK 报文的定义在 [rfc4585] 文档中定义。
RTCP 的反馈报文包头定义如下,FMT 和 PT 决定了该报文的类型,FCI 则是该类型报文的具体负载:
version (V): 2 bits 该字段标识RTP版本。当前版本是2
padding (P): 1 bit 如果设置,则填充位指示数据包包含末尾的其他填充八位位组不属于控制信息,但包含在长度字段中。
Feedback message type (FMT): 5 bits 此字段标识FB消息的类型,并且为相对于类型(传输层,有效负载特定的或应用层的反馈)。每个值 三种反馈类型在各自的部分中定义下面。
Payload type (PT): 8 bits 这是RTCP数据包类型,将数据包标识为RTCP FB消息. IANA定义了两个值:
Length: 16 bits 此数据包的长度(以32位字为单位)减去1,包括标头和任何填充。这符合定义RTCP发送方和接收方报告中使用的长度字段。
SSRC of packet sender: 32 bits 此消息的发起者的同步源标识符包。
SSRC of media source: 32 bits 媒体源的同步源标识符。
协议规定的 NACK 反馈报文的 PT= 205,FMT=1,FCI 的格式如下(可以附带多个 FCI,通过 header 的 length 字段来标示其长度):
这里的设计比较巧妙,它可以一次性携带多个连续的数据包的丢包情况:
Packet ID (PID): 16 bits PID字段用于指定丢失的数据包。PID字段表示丢失数据包的RTP序列号。
bitmask of following lost packets (BLP): 16 bits BLP允许报告紧跟在PID指示的RTP数据包之后16个RTP数据包中任何一个的丢失情况。如果接收方将位掩码的位i设置为1, 表示编号为(PID + i)的RTP数据包丢失。 否则,将位i设置为0。
NACK数据抓包
下面为RTCP数据包,红色方框内就为NACK数据。
使用wireshark解析如下:
通过丢失的RTP数据包序列号计算 PID 和 BLP
static void check_for_seq_number_gap_immediate(RtpSession *session, rtp_header_t *rtp) {
uint16_t pid;
uint16_t i;
/*don't check anything before first packet delivered*/
if (session->flags & RTP_SESSION_FIRST_PACKET_DELIVERED
&& RTP_SEQ_IS_STRICTLY_GREATER_THAN(rtp->seq_number, session->rtp.rcv_last_seq + 1)
&& RTP_SEQ_IS_STRICTLY_GREATER_THAN(rtp->seq_number, session->rtp.snd_last_nack + 1)
) {
uint16_t first_missed_seq = session->rtp.rcv_last_seq + 1;
uint16_t diff;
if (first_missed_seq <= session->rtp.snd_last_nack) {
first_missed_seq = session->rtp.snd_last_nack + 1;
}
diff = rtp->seq_number - first_missed_seq;
pid = first_missed_seq;
for (i = 0; i <= (diff / 16); i++) {
uint16_t seq;
uint16_t blp = 0;
for (seq = pid + 1; (seq < rtp->seq_number) && ((seq - pid) < 16); seq++) {
blp |= (1 << (seq - pid - 1));
}
if (session->rtp.congdetect != NULL && session->rtp.congdetect->state == CongestionStateDetected) {
/*
* Do not send NACK in IMMEDIATE_NACK mode in congestion, because the retransmission by the other party of the missing packets
* will necessarily increase or at least sustain the congestion.
* Furthermore, due to the congestion, the retransmitted packets have very few chance to arrive in time.
*/
ortp_message("Immediate NACK not sent because of congestion.");
return;
}
rtp_session_send_rtcp_fb_generic_nack(session, pid, blp);
pid = seq;
}
}
if (RTP_SEQ_IS_STRICTLY_GREATER_THAN(rtp->seq_number, session->rtp.snd_last_nack)) {
/* We update the last_nack since we received this packet we don't need a nack for it */
session->rtp.snd_last_nack = rtp->seq_number;
}
}
根据 PID 和 BLP 获取丢失的RTP数据包序列号
static void generic_nack_received(const OrtpEventData *evd, OrtpNackContext *ctx) {
if (rtcp_is_RTPFB(evd->packet) && rtcp_RTPFB_get_type(evd->packet) == RTCP_RTPFB_NACK) {
RtpTransport *rtpt = NULL;
rtcp_fb_generic_nack_fci_t *fci;
uint16_t pid, blp, seq;
mblk_t *lost_msg;
/* get RTP transport from session */
rtp_session_get_transports(ctx->session, &rtpt, NULL);
fci = rtcp_RTPFB_generic_nack_get_fci(evd->packet);
pid = rtcp_fb_generic_nack_fci_get_pid(fci);
blp = rtcp_fb_generic_nack_fci_get_blp(fci);
bctbx_mutex_lock(&ctx->sent_packets_mutex);
lost_msg = find_packet_with_sequence_number(&ctx->sent_packets, pid);
if (lost_msg != NULL) {
meta_rtp_transport_modifier_inject_packet_to_send(rtpt, ctx->rtp_modifier, lost_msg, 0);
ortp_message("OrtpNackContext [%p]: Resending missing packet with seq=%hu", ctx, pid);
} else {
ortp_warning("OrtpNackContext [%p]: Cannot find missing packet with seq=%hu", ctx, pid);
}
++pid;
for (seq = blp; seq != 0; seq >>= 1, ++pid) {
if (seq & 1) {
lost_msg = find_packet_with_sequence_number(&ctx->sent_packets, pid);
if (lost_msg != NULL) {
meta_rtp_transport_modifier_inject_packet_to_send(rtpt, ctx->rtp_modifier, lost_msg, 0);
ortp_message("OrtpNackContext [%p]: Resending missing packet with seq=%hu", ctx, pid);
} else {
ortp_warning("OrtpNackContext [%p]: Cannot find missing packet with seq=%hu", ctx, pid);
}
}
}
bctbx_mutex_unlock(&ctx->sent_packets_mutex);
}
}