WebRTC服务质量(01)- Qos概述
WebRTC服务质量(02)- RTP协议
WebRTC服务质量(03)- RTCP协议
WebRTC服务质量(04)- 重传机制(01) RTX NACK概述
WebRTC服务质量(05)- 重传机制(02) NACK判断丢包
WebRTC服务质量(06)- 重传机制(03) NACK找到真正的丢包
WebRTC服务质量(07)- 重传机制(04) 接收NACK消息
WebRTC服务质量(08)- 重传机制(05) RTX机制
WebRTC服务质量(09)- Pacer机制(01) 流程概述
WebRTC服务质量(10)- Pacer机制(02) RoundRobinPacketQueue
WebRTC服务质量(11)- Pacer机制(03) IntervalBudget
WebRTC服务质量(12)- Pacer机制(04) 向Pacer中插入数据
一、前言:
首先我们要明白NACK是一种RTCP包,而RTCP在WebRtc当中是基于UDP的,所以,我们接收数据包的源头肯定是传输层的UDP包,然后,一次经历几个关键的模块到达应用层。
二、收包流程:
- PhysicalSocketServer: 这是底层网络的抽象。它负责创建和管理底层网络套接字(通常是 UDP 套接字),为 WebRTC 的其他组件提供网络 I/O 功能。 它处理网络事件,例如数据包到达和发送错误。
- UDPPort: 这个类代表一个具体的 UDP 端口,用于发送和接收媒体数据和 RTCP 控制信息。它通常由
PhysicalSocketServer
创建和管理。 - P2PTransportChannel: 这层建立在
UDPPort
之上,它负责在两个 WebRTC 对等体之间可靠地传输数据包。它可能包含重传机制、拥塞控制以及其他优化网络传输的机制。 它负责将数据从应用程序层传递到网络层,反之亦然。 - PeerConnection: 这是 WebRTC 的核心类。它管理 WebRTC 会话的整个生命周期,包括建立连接、协商编解码器和传输参数,以及处理媒体流的发送和接收。 它负责协调所有其他组件以实现 P2P 通信。
- Call: 这个类通常在更高级别的应用程序中使用,它代表一个正在进行的 WebRTC 通信会话。 它可能是
PeerConnection
的一个包装器,提供更高级别的 API 来管理通话,例如发起、应答和结束通话。 - VideoSendStream: 此类专门处理视频数据的发送。它负责编码视频帧,打包数据,并将它们通过
P2PTransportChannel
发送给远程对等体。 - RTCPReceiver: RTCP (RTP 控制协议) 用于传输媒体流的控制信息,例如丢包率、延迟和带宽估计。
RTCPReceiver
负责接收和处理来自远程对等体的 RTCP 数据包,并将这些信息提供给其他 WebRTC 组件。 - ModuleRtpRtcpImpl: 这是一个更底层的类,它负责处理 RTP (实时传输协议) 数据包的发送和接收。 它处理 RTP 包的封装和解封装,并与 RTCP 紧密集成。
我们发现其实就是:从传输层获取RTP包 —> Call模块分发(RTP或者RTCP包) —> 具体的音视频Stream处理自己的RTCP包 —> 会调用Rtp/Rtcp模块处理。
三、处理收到的RTCP包:
PacketReceiver::DeliveryStatus Call::DeliverPacket(MediaType media_type,rtc::CopyOnWriteBuffer packet,int64_t packet_time_us) {RTC_DCHECK_RUN_ON(worker_thread_);// 如果是rtcp包if (IsRtcp(packet.cdata(), packet.size()))return DeliverRtcp(media_type, packet.cdata(), packet.size());// 否则就是rtp包return DeliverRtp(media_type, std::move(packet), packet_time_us);
}
发现判断是Rtcp包就走DeliverRtcp
,那么怎么判断是Rtcp包呢?
其实就是根据RTCP协议头里面的payload type,看看是否属于RTCP包,比如FIR\BYE\NACK这些都有自己的PT值。
PacketReceiver::DeliveryStatus Call::DeliverRtcp(MediaType media_type,const uint8_t* packet,size_t length) {// ...bool rtcp_delivered = false;if (media_type == MediaType::ANY || media_type == MediaType::VIDEO) {for (VideoReceiveStream2* stream : video_receive_streams_) {if (stream->DeliverRtcp(packet, length))rtcp_delivered = true;}}// ...return rtcp_delivered ? DELIVERY_OK : DELIVERY_PACKET_ERROR;
}
发现Call是将RTCP包转给了对应Stream,我们这儿就是ReceiveStream。
bool VideoReceiveStream2::DeliverRtcp(const uint8_t* packet, size_t length) {return rtp_video_stream_receiver_.DeliverRtcp(packet, length);
}
进去看看:
bool RtpVideoStreamReceiver2::DeliverRtcp(const uint8_t* rtcp_packet,size_t rtcp_packet_length) {// ...// 将RTP/RTCP包传给 ModuleRtpRtcpImpl2 处理rtp_rtcp_->IncomingRtcpPacket(rtcp_packet, rtcp_packet_length);// ...
}
发现转给了ModuleRtpRtcpImpl2模块:
void ModuleRtpRtcpImpl::IncomingRtcpPacket(const uint8_t* rtcp_packet,const size_t length) {rtcp_receiver_.IncomingPacket(rtcp_packet, length);
}
注意,对上上面的收包流程图,始终记住自己在哪儿:
void RTCPReceiver::IncomingPacket(rtc::ArrayView<const uint8_t> packet) {// ... PacketInformation packet_information;// 解析网络包,因为发送端是将多个包串成一个包发送的。// 因此,解析过程中,就是先解析RTCP头,再通过RTCP头里面的长度解析数据。以此类推if (!ParseCompoundPacket(packet, &packet_information))return;// 将上面解析包得到的 packet_information 作为参数,传给下一步继续处理TriggerCallbacksFromRtcpPacket(packet_information);
}
其实我注释写了很多,其实就是解析CompoundPacket
,这种包我前面讲过,就是RTCP包比较小,就将多个一次发过来了。
bool RTCPReceiver::ParseCompoundPacket(rtc::ArrayView<const uint8_t> packet,PacketInformation* packet_information) {MutexLock lock(&rtcp_receiver_lock_);CommonHeader rtcp_block;// 先获取每个rtcp包的header,然后,解析for (const uint8_t* next_block = packet.begin(); next_block != packet.end();next_block = rtcp_block.NextPacket()) { // 读取一个RTCP包// remaining_blocks_size 不为0说明后面还有数据,继续解析;ptrdiff_t remaining_blocks_size = packet.end() - next_block;RTC_DCHECK_GT(remaining_blocks_size, 0);// 开始解析if (!rtcp_block.Parse(next_block, remaining_blocks_size)) {if (next_block == packet.begin()) {// Failed to parse 1st header, nothing was extracted from this packet.RTC_LOG(LS_WARNING) << "Incoming invalid RTCP packet";return false;}++num_skipped_packets_;break;}// 。。。// 根据 RTCP 包的类型 (rtcp_block.type()) 调用相应的处理函数switch (rtcp_block.type()) {case rtcp::SenderReport::kPacketType:HandleSenderReport(rtcp_block, packet_information);break;case rtcp::ReceiverReport::kPacketType:HandleReceiverReport(rtcp_block, packet_information);break;// 。。。}}return true;
}
总而言之,这个函数是一个 RTCP 包解析器,它高效地处理复合 RTCP 数据包,并根据包类型进行分发,处理各种 RTCP 消息。
四、总结:
本文主要介绍了接收Nack包的流程,其实也就是接收一般RTCP包的流程,并且从Call模块开始走读了一下代码,走读代码过程中千万要记住自己大概处于流程的什么位置,要么会lost yourself!!!