当前位置: 首页 > article >正文

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!!!


http://www.kler.cn/a/447588.html

相关文章:

  • 分布式协同 - 分布式事务_TCC解决方案
  • 关于使用拓扑排序算法实现解析勾稽关系优先级的研究和实现
  • 【Vue.js 3.0】provide 、inject 函数详解
  • AI的进阶之路:从机器学习到深度学习的演变(三)
  • 【LeetCode】394、字符串解码
  • LabVIEW深海气密采水器测控系统
  • 基于微信小程序的消防隐患在线举报系统
  • Tensor
  • sh cmake-linux.sh -- --skip-license --prefix = $MY_INSTALL_DIR
  • Javascript元编程
  • 2024年CCF 非专业级软件能力认证CSP-J/S 第二轮( 提高组) 染色(color)
  • HDR视频技术之八:色域映射
  • Kafka学习篇
  • 如何使用Python编写有效的网页抓取脚本以获取和处理数据?
  • blender生成城市白膜数据
  • 云技术基础
  • cisp-pte培训需要多久
  • JAVA开发Erp时日志报错:SQL 当 IDENTITY_INSERT 设置为 OFF 时,不能为表 ‘***‘ 中的标识列插入显式值
  • 【5G】5G 无线协议 Radio Protocols(一)
  • 【大语言模型】ACL2024论文-30 探索语言模型在文本分类中的伪相关性:概念层面的分析
  • clickhouse-题库
  • VSCode中的Black Formatter没有生效的解决办法
  • 云计算赋能:TSP 问题求解与创新定价机制的全景剖析
  • MFC/C++学习系列之简单记录10——定时器
  • 基于SpringBoot+Vue的音乐网站-无偿分享 (附源码+LW+调试)
  • LSTM实现天气模型训练与预测