c/c++语言开发共享【webrtc】webrtc的rtp重传代码分析

pgm不太能用,没有想象中的可靠,重传机制貌似仍然使用组播重传,丢包率80%的网络感觉没啥改进,如果有所好转延迟估计也是个不小的问题。 后听说rtp也有nack机制,webrtc基于rtp实现了重传在一定程度上保证可靠性。 在各路大神的指引下找到了rfc4585,看到了这么一段 RTCP扩展反馈报文 …

pgm不太能用,没有想象中的可靠,重传机制貌似仍然使用组播重传,丢包率80%的网络感觉没啥改进,如果有所好转延迟估计也是个不小的问题。

后听说rtp也有nack机制,webrtc基于rtp实现了重传在一定程度上保证可靠性。

在各路大神的指引下找到了rfc4585,看到了这么一段
rtcp扩展反馈报文,有一种nack报文
【webrtc】webrtc的rtp重传代码分析

当fmt=1并且pt=205时,代表此报文是个nack报文

name value brief description
rtpfb 205 transport layer fb message
psfb 206 pyload-specific fb message
0:    unassigned 1:    generic nack 2-30: unassigned 31:   reserved for future expansion of the identifier number space

the generic nack message is identified by pt=rtpfb and fmt=1.

fci字段会有如下图所示的数据
【webrtc】webrtc的rtp重传代码分析

pid:表示packet id,用于表明当前接收端丢失的数据包的序号,是接收端期待收到的下一个数据包
blp:表示bitmask of following lost lost packets,占两个字节,16位,表示接着pid后面的16个数据包的丢包情况。

rtp协议本身不会帮你重传。应用应该自己解析rtcp做处理

webrtc关于nack的实现

我突然想起来,我入职的时候下过webrtc的源码,还没删除(可能是太大了,删太慢了就没删),于是就把源码拿出来看了看webrtc对于这个部分的实现

这个部分的代码量也不多,很好懂,大概就是发送端的rtcp receiver接收到rtcp数据包,解析发现是个nack,告诉rtp发送端重新发送接收端请求重传的数据包

bool rtcpreceiver::incomingpacket(const uint8_t* packet, size_t packet_size) {   if (packet_size == 0) {     log(ls_warning) << "incoming empty rtcp packet";     return false;   }    packetinformation packet_information;   if (!parsecompoundpacket(packet, packet + packet_size, &packet_information))     return false;   triggercallbacksfromrtcppacket(packet_information);   return true; }

上述代码是rtcp receiver接收到rtcp数据包后的初步判断,parsecompoundpacket函数用于解析rtcp数据包,将关键信息摘出储存到packetinformation结构体中传递给触发回调,triggercallbacksfromrtcppacket函数用于触发收到rtcp数据包回调。

下面是parsecompoundpacket结构体的实现

struct rtcpreceiver::packetinformation {   uint32_t packet_type_flags = 0;  // rtcppackettypeflags bit field.    uint32_t remote_ssrc = 0;   std::vector<uint16_t> nack_sequence_numbers;   reportblocklist report_blocks;   int64_t rtt_ms = 0;   uint8_t sli_picture_id = 0;   uint64_t rpsi_picture_id = 0;   uint32_t receiver_estimated_max_bitrate_bps = 0;   std::unique_ptr<rtcp::transportfeedback> transport_feedback; };

nack_sequence_numbers已经是解析过后的接收端没有收到的数据包的序号了,解析过程也很简单,是个拆包过的成就不再展开描述了。

void rtcpreceiver::triggercallbacksfromrtcppacket(     const packetinformation& packet_information) { ...   if (!receiver_only_ && (packet_information.packet_type_flags & krtcpnack)) {     if (!packet_information.nack_sequence_numbers.empty()) {       log(ls_verbose) << "incoming nack length: "                       << packet_information.nack_sequence_numbers.size();       _rtprtcp.onreceivednack(packet_information.nack_sequence_numbers);     } ... }

triggercallbacksfromrtcppacket函数会根据解析的数据包信息判断出当前rtcp数据包类型是nack,触发回调,该回调并不会直接到rtp sender而是到rtp-rtcp module由这个module调用rtp sender,这个module是rtp和rtcp的中心组件(和webrtc结构有关),也起到了解耦的作用

这个中间调用的代码量不多

void modulertprtcpimpl::onreceivednack(     const std::vector<uint16_t>& nack_sequence_numbers) {   for (uint16_t nack_sequence_number : nack_sequence_numbers) {     send_loss_stats_.addlostpacket(nack_sequence_number);   }   if (!rtp_sender_.storepackets() ||       nack_sequence_numbers.size() == 0) {     return;   }   // use rtt from rtcprttstats class if provided.   int64_t rtt = rtt_ms();   if (rtt == 0) {     rtcp_receiver_.rtt(rtcp_receiver_.remotessrc(), null, &rtt, null, null);   }   rtp_sender_.onreceivednack(nack_sequence_numbers, rtt); }

一开始做了一些记录,记录丢包情况,然后rtt是用来做流控的,收到nack当次并不一定会重传,会用到rtt做判断。

下面是rtp sender的代码用于重传数据包

void rtpsender::onreceivednack(     const std::vector<uint16_t>& nack_sequence_numbers,     int64_t avg_rtt) {   trace_event2(trace_disabled_by_default("webrtc_rtp"),                "rtpsender::onreceivednack", "num_seqnum",                nack_sequence_numbers.size(), "avg_rtt", avg_rtt);   for (uint16_t seq_no : nack_sequence_numbers) {     const int32_t bytes_sent = resendpacket(seq_no, 5 + avg_rtt);     if (bytes_sent < 0) {       // failed to send one sequence number. give up the rest in this nack.       log(ls_warning) << "failed resending rtp packet " << seq_no                       << ", discard rest of packets";       break;     }   } }

trace_event是google调试使用的机制,不用管它,这个函数会循环重发丢失队列中的数据包,但是不一定发送成功,数据包缓存是有限制的,如果要重新发送的数据包已经不再缓存中了,总不能变出来吧?

int32_t rtpsender::resendpacket(uint16_t packet_id, int64_t min_resend_time) {   std::unique_ptr<rtppackettosend> packet =       packet_history_.getpacketandsetsendtime(packet_id, min_resend_time, true);   if (!packet) {     // packet not found.     return 0;   }    // check if we're overusing retransmission bitrate.   // todo(sprang): add histograms for nack success or failure reasons.   rtc_dcheck(retransmission_rate_limiter_);   if (!retransmission_rate_limiter_->tryuserate(packet->size()))     return -1;    if (paced_sender_) {     // convert from ticktime to clock since capture_time_ms is based on     // ticktime.     int64_t corrected_capture_tims_ms =         packet->capture_time_ms() + clock_delta_ms_;     paced_sender_->insertpacket(rtppacketsender::knormalpriority,                                 packet->ssrc(), packet->sequencenumber(),                                 corrected_capture_tims_ms,                                 packet->payload_size(), true);      return packet->size();   }   bool rtx = (rtxstatus() & krtxretransmitted) > 0;   int32_t packet_size = static_cast<int32_t>(packet->size());   if (!prepareandsendpacket(std::move(packet), rtx, true,                             packetinfo::knotaprobe))     return -1;   return packet_size; }
  • 重发数据包操作会先检查历史缓存中有没有数据包,如果没有,继续外层循环,重发下一个包。
  • 如果有带宽限制,需要看当前分给重发机制的带宽是否已经被用完,用完了就停止循环重发操作。
  • min_resend_time时间用于检测。如果之前有请求过重传同样序号的数据包,在短时间内是不会再重传的

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/602345.html

(0)
上一篇 2021年5月11日
下一篇 2021年5月11日

精彩推荐