WebRTC服务质量(07)- 重传机制(04) 接收NACK消息
一、前言:
首先我们要明白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!!!