WebRTC之RTP封装与解封装

1 前言

rtp_rtcp模块作为Webrtc组件中非常重要的组成部分,首先是对应rtp和rtcp的封装与解封装,第二部分是对QOS各种策略的支持都离不开rtcp的控制协议。这里首先进行协议封装的探讨。

2 RTP协议解析

各个音视频的大佬对下面这张RTP协议图应该并不陌生,这就是RTP头部协议,解析rtp主要就是为了解析头部信息,并且获取到准确的音视频数据部分。整个rtp头部至少包含12个字节,此时CSRC只有一个的情况,同时不包含扩展头部。

rtp解析核心函数是RtpPacket::ParseBuffer,这里完成的rtp头部各个字段的解析。rtp头部解析的方式在各种开源项目不尽相同,有采用数据结构强转的方式,也有webrtc采用字节解析的方式,但是要注意的是转换过程中的网络字节序与主机字节序的一个转换关系。

bool RtpPacket::ParseBuffer(const uint8_t* buffer, size_t size) {
//rtp头大小检查,最小12个字节
  if (size < kFixedHeaderSize) {
    return false;
  }
  //版本检查,v = 2
  const uint8_t version = buffer[0] >> 6;
  if (version != kRtpVersion) {
    return false;
  }
  //填充字段
  const bool has_padding = (buffer[0] & 0x20) != 0;
  //扩展字段
  const bool has_extension = (buffer[0] & 0x10) != 0;
  //csrc数量获取
  const uint8_t number_of_crcs = buffer[0] & 0x0f;
  //标志位,对于视频一般是标识视频最后一个包
  marker_ = (buffer[1] & 0x80) != 0;
  //荷载类型0-95固定,96-125动态协商
  payload_type_ = buffer[1] & 0x7f;
  //序列号
  sequence_number_ = ByteReader<uint16_t>::ReadBigEndian(&buffer[2]);
  //时间戳
  timestamp_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[4]);
  //源
  ssrc_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[8]);
  if (size < kFixedHeaderSize + number_of_crcs * 4) {
    return false;
  }
  payload_offset_ = kFixedHeaderSize + number_of_crcs * 4;

  extensions_size_ = 0;
  extension_entries_.clear();
  //扩展解析
  if (has_extension) {
    /* RTP header extension, RFC 3550.
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |      defined by profile       |           length              |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                        header extension                       |
    |                             ....                              |
    */
  	//此处省略...
  }
  //填充解析,填充大小放在最后一个字节
  if (has_padding && payload_offset_ < size) {
    padding_size_ = buffer[size - 1];
    if (padding_size_ == 0) {
      RTC_LOG(LS_WARNING) << "Padding was set, but padding size is zero";
      return false;
    }
  } else {
    padding_size_ = 0;
  }

  if (payload_offset_ + padding_size_ > size) {
    return false;
  }
  //最终得到荷载大小
  payload_size_ = size - payload_offset_ - padding_size_;
  return true;
}

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

3 RTP协议封装

RTP设置比较简单都是获取data数据后与上对于位置进行设置。

4 RTCP协议解析

webrtc中RTCP解析统一入口是在RtcpPacketParser::Parse接口内完成,根据不同的RTCP类型分发到对应的类型类中进行解析。这里需要注意的点一个是for循环主要是针对一个udp数据包可能携带多个RTCP包,主要根据公共头部进行分割。第二是数据分发,目前已经支持的类型包括app,bye,xr,fir,pli,rr,remb,nack,tmmbr等类型.

bool RtcpPacketParser::Parse(const void* data, size_t length) {
  ++processed_rtcp_packets_;

  const uint8_t* const buffer = static_cast<const uint8_t*>(data);
  const uint8_t* const buffer_end = buffer + length;

  rtcp::CommonHeader header;
  for (const uint8_t* next_packet = buffer; next_packet != buffer_end;
       next_packet = header.NextPacket()) {
    RTC_DCHECK_GT(buffer_end - next_packet, 0);
    if (!header.Parse(next_packet, buffer_end - next_packet)) {
      RTC_LOG(LS_WARNING)
          << "Invalid rtcp header or unaligned rtcp packet at position "
          << (next_packet - buffer);
      return false;
    }
    switch (header.type()) {
      case rtcp::App::kPacketType:
        app_.Parse(header);
        break;
      case rtcp::Bye::kPacketType:
        bye_.Parse(header, &sender_ssrc_);
        break;
      case rtcp::ExtendedReports::kPacketType:
        xr_.Parse(header, &sender_ssrc_);
        break;
      case rtcp::ExtendedJitterReport::kPacketType:
        ij_.Parse(header);
        break;
      case rtcp::Psfb::kPacketType:
        switch (header.fmt()) {
          case rtcp::Fir::kFeedbackMessageType:
            fir_.Parse(header, &sender_ssrc_);
            break;
          case rtcp::Pli::kFeedbackMessageType:
            pli_.Parse(header, &sender_ssrc_);
            break;
          case rtcp::Psfb::kAfbMessageType:
            if (!loss_notification_.Parse(header, &sender_ssrc_) &&
                !remb_.Parse(header, &sender_ssrc_)) {
              RTC_LOG(LS_WARNING) << "Unknown application layer FB message.";
            }
            break;
          default:
            RTC_LOG(LS_WARNING)
                << "Unknown rtcp payload specific feedback type "
                << header.fmt();
            break;
        }
        break;
      case rtcp::ReceiverReport::kPacketType:
        receiver_report_.Parse(header, &sender_ssrc_);
        break;
      case rtcp::Rtpfb::kPacketType:
        switch (header.fmt()) {
          case rtcp::Nack::kFeedbackMessageType:
            nack_.Parse(header, &sender_ssrc_);
            break;
          case rtcp::RapidResyncRequest::kFeedbackMessageType:
            rrr_.Parse(header, &sender_ssrc_);
            break;
          case rtcp::Tmmbn::kFeedbackMessageType:
            tmmbn_.Parse(header, &sender_ssrc_);
            break;
          case rtcp::Tmmbr::kFeedbackMessageType:
            tmmbr_.Parse(header, &sender_ssrc_);
            break;
          case rtcp::TransportFeedback::kFeedbackMessageType:
            transport_feedback_.Parse(header, &sender_ssrc_);
            break;
          default:
            RTC_LOG(LS_WARNING)
                << "Unknown rtcp transport feedback type " << header.fmt();
            break;
        }
        break;
      case rtcp::Sdes::kPacketType:
        sdes_.Parse(header);
        break;
      case rtcp::SenderReport::kPacketType:
        sender_report_.Parse(header, &sender_ssrc_);
        break;
      default:
        RTC_LOG(LS_WARNING) << "Unknown rtcp packet type " << header.type();
        break;
    }
  }
  return true;
}

4.1 接收者报告

这里重点介绍接收者报文和发送者报文,其他rtcp将根据实际用途在相关策略篇进行讲解。

首先是RR报文的解析在ReceiverReport类中进行解析解析函数是ReceiverReport::Parse,主要进行RR报文中头部解析和接收报告块解析。接收报告块会进一步进入ReportBlock类解析,

接收报告块协议部分:丢包率,期望序列号,抖动,上次发送sr时间等等这部分数据主要是用于报告当前的丢包情况和网络的往返时间计算等等。

 

报告块解析采用的是位操作逐个解析的方式,整体比较简单。

 

4.2 发送者报告

SR报告解析在SenderReport类中进行,核心就是解析相关的字段,主要包括NTP时间用于计算当前的发送时间,当前发送的RTP的时间戳,后续的音视频时间同步需要用到相关字段,发送包的数量和发送的字节数,这些信息主要是统计当前的发送者的流量统计。

解析部分也是采用根据位运算进行逐个字段解析的过程。需要注意的是发送报告一般也会携带接收这报告块。

 

4.3 Sdes报文解析

sdes就是指源的描述名称,为了解决当ssrc发生变化后通知其他会话成员更新源操作。基本每次会话开始都会先发送sdes报文,来通知对方CNAME相关信息的数据。主要包括源id和描述,描述中存放用户会话名称。

chunks解析名称,主要是获取chunks数量,然后根据CNAME类型为1的进行cname名称解析。
在这里插入图片描述 

4.4 Bye报文

Bye报文一般会在会话结束时发送,用于终止会话。主要携带两个参数一个是长度,一个是退出的原因。

由于对方已经下线因此需要移除一个csrc源,同时获取退出的原因。

 

4.5 app报文

这个是自定义报文也是RFC3550定义,用于用户自定义协议使用。在App类中实现。

解析由三部分组成,子协议类型,名称,和数据体。

5 RTCP协议封装

RTCP封装分为两部分一部分是公共头部封装实现在RtcpPacket类中,其实就是设置RTCP类型。weirtcp采用的是对每个数据位进行操作。

void RtcpPacket::CreateHeader(
    size_t count_or_format,  // Depends on packet type.
    uint8_t packet_type,
    size_t length,
    bool padding,
    uint8_t* buffer,
    size_t* pos) {
  RTC_DCHECK_LE(length, 0xffffU);
  RTC_DCHECK_LE(count_or_format, 0x1f);
  constexpr uint8_t kVersionBits = 2 << 6;
  uint8_t padding_bit = padding ? 1 << 5 : 0;
  buffer[*pos + 0] =
      kVersionBits | padding_bit | static_cast<uint8_t>(count_or_format);
  buffer[*pos + 1] = packet_type;
  buffer[*pos + 2] = (length >> 8) & 0xff;
  buffer[*pos + 3] = length & 0xff;
  *pos += kHeaderLength;
}

各个协议的封装过程其实是解析的逆过程,也是分布在各个具体的协议类中实现。

6 RTP/RTCP协议总结

RTP和RTCP协议解析和封装其实是对于RFC3350的协议实现,旨在对于利用RTP报文完成对于媒体数据的一个封装,同时利用RTCP对媒体数据流的控制信息,来保证数据的传输的可靠性和实时性。两者之间需要平衡从而衍生出来了各种QOS策略来保证媒体数据流的实时性,同时又有较高的数据质量。

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/126180733