RTP/RTCP 基本知识

RTP/RTCP 基本知识

    • [一 概述](#一 概述)
      • [1.1 RTP](#1.1 RTP)
        • [1.1.1 协议概述](#1.1.1 协议概述)
        • [1.1.2 RTP 工作机制](#1.1.2 RTP 工作机制)
      • [1.2 RTCP](#1.2 RTCP)
        • [1.2.1 协议概述](#1.2.1 协议概述)
        • [1.2.2 工作机制](#1.2.2 工作机制)
    • [二 协议](#二 协议)
      • [2.1 RTP 协议](#2.1 RTP 协议)
        • [2.1.1 RTP Header](#2.1.1 RTP Header)
        • [2.1.2 RTP Header Extension](#2.1.2 RTP Header Extension)
          • [2.1.2.1 格式](#2.1.2.1 格式)
          • [2.1.2.2 One-Byte Header](#2.1.2.2 One-Byte Header)
          • [2.1.2.3 two-Byte Header](#2.1.2.3 two-Byte Header)
        • [2.1.3 RTP填充数据](#2.1.3 RTP填充数据)
      • [2.2 RTCP 协议](#2.2 RTCP 协议)
        • [2.2.1 RTCP 格式](#2.2.1 RTCP 格式)
        • [2.2.2 常用PT类型](#2.2.2 常用PT类型)
    • [三 webrtc 应用](#三 webrtc 应用)

参考文档:

深入解析RTP RTCP协议报文结构与抓包分析-开发者社区-阿里云

WebRTC(十):RTP和SRTP_rtp的header的长度-CSDN博客

2,RTP扩展头(RFC5285)-CSDN博客

14. RTCP 协议_rtcp协议-CSDN博客

一 概述

1.1 RTP

1.1.1 协议概述

RTP(实时传输协议)是专为互联网音视频流设计的传输协议,它与控制协议RTCP配合使用。RTP的主要任务是在一对一或一对多的网络环境中,为实时媒体提供时间同步和流式传输能力。

从协议定位看,RTP运行在UDP之上,利用了UDP低延迟的优势。它的工作方式与传统文件下载协议完全不同:RTP采用持续不断的流式传输,数据实时发送、实时播放,不支持暂停回放,天生适合直播、视频通话等实时场景

RTP 本身只保证实时数据的传输,并不能为按顺序传送数据包提供可靠的传送机制,也不提供流量控制或拥塞控制,它依靠 RTCP 提供这些服务。

1.1.2 RTP 工作机制

rtp 本身并不负责同步,rtp 只是传输层协议,为了简化运输层处理,提高该层的效率。 将部分运输层协议功能(比如流量控制)上移到应用层完成。同步就是属于应用层协议完成的。它没有运输层协议的完整功能,不提供任何机制来保证实时地传输数据,不支持资源预留,也不保证服务质量。rtp 报文甚至不包括长度和报文边界的描述。同时 rtp 协议的数据报文和控制报文的使用相邻的不同端口,这样大大提高了协议的灵活性和处理的简单性。

1.2 RTCP

1.2.1 协议概述

RTCP(Real-time Transport Control Protocol 或 RTP Control Protocol 或简写 RTCP),实时传输控制协议,是实时传输协议(RTP)的一个姐妹协议。

1.2.2 工作机制

当应用程序开始一个 rtp 会话时将使用两个端口:一个给 rtp,一个给 rtcp。rtp 本身并不能为按顺序传送数据包提供可靠的传送机制,也不提供流量控制或拥塞控制,它依靠 rtcp 提供这些服务。

RTCP 负责管理传输质量在当前应用进程之间交换控制信息, 在 RTP 会话期间,各参与者周期性地传送 RTCP 包,包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料。因此,服务器可以利用这些信息动态地改变传输速率,甚至改变有效载荷类型。

RTP 和 RTCP 配合使用,能以有效的反馈和最小的开销使传输效率最佳化,故特别适合传送网上的实时数据。根据用户间的数据传输反馈信息,可以制定流量控制的策略,而会话用户信息的交互,可以制定会话控制的策略

二 协议

2.1 RTP 协议

RTP 报文由以下部分组成:

css 复制代码
RTP Header (12 字节起始) + 可选扩展头 + Payload(负载部分)+ 可选 Padding
2.1.1 RTP Header

(1)示图:

bash 复制代码
  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
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |V=2|P|X|  CC   |M|     PT      |       sequence number         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                           timestamp                           |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           synchronization source (SSRC) identifier            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  contributing source (CSRC) identifiers (可选, 每个4字节)   |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

(2)含义

css 复制代码
0-1       v(version)         含义:RTP 版本号,占2位,当前为2
2	      P (Padding)	     含义:是否有填充位,最后一个字节表明填充长度, 为1时表示有填充位,填充以字节为单位,一般数据加密时需要固定大小的数据块, 此时需要将该位置置1
3	      X (Extension)	     含义:是否有扩展头,如果有扩展头,扩展头会放在CSRC之后。扩展头主要用于携带一些附加信息
4-7	      CC (CSRC Count)    含义:CSRC 的数量(最多15个),每个4字节
8	      M (Marker)	     含义:标志位,具体含义由应用定义(如帧结束标志)比如:一帧H264视频数据被分成多个包发送, 那么最后一个包的m就会被置位, 表示这一帧数据结束。
9-15	  PT (Payload Type)	 含义:有效负载类型,7位,如 96 表示动态类型。 比如:opus:111 h264:100 
16-31	  Sequence Number	 含义:序列号,每发一个 RTP 包加1,接收方可用于重排和丢包检测. 备注:sequence Number是与SSRC关联在一起的,也就是说, 每个ssrc所代表的数据流的Sequence number都是单独计数
32-63	 Timestamp	         含义:时间戳,标识采样时刻,用于同步和延时计算
64-95	 SSRC	             含义:同步源标识符,用于区分不同源(一个媒体流)
96-...	 CSRC List	0~15个    含义:标识贡献源
2.1.2 RTP Header Extension

如果RTP Header 中 X=1,表示存在扩展头,描述如下:

2.1.2.1 格式
bash 复制代码
 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                       |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

扩展头由三部分组成,分别为profile、length、header extension

profile: 用于区分不用的配置, 在RFC5285中定义了两种profile, 分别是one-byte-header{0xBE, 0xDE} 和 two-byte-header{0x10, 0x0X},(X 是任意值) 解析rtp报文是通过profile字段区分header extension 如何解析

length: 表示扩展头所携带的header extension个数。 如果 length 为4, 则 head extension 是4个。

header extension: 扩展信息, 4个字节为一个单位

2.1.2.2 One-Byte Header

描述 :以0xBEDE开头,后面用两个字节标识扩展头的个数(注:扩展头最后以4字节对齐)。后续每个扩展头都有一个起始字节,格式如下:

bash 复制代码
 0
 0 1 2 3 4 5 6 7 
+-+-+-+-+-+-+-+-+
|  ID   |  len  |
+-+-+-+-+-+-+-+-+

举例说明

4位ID是该元素的本地标识符,取值范围为1-14。在信令部分,这被称为有效范围。本地标识符值15是为将来的扩展保留的,绝对不能用作标识符。如果遇到ID值15,则应该忽略其长度字段,整个扩展的处理应该在该点终止,并且只考虑ID为15的元素之前出现的扩展元素。len代表该扩展头后续字节的长度,所以len为0时,后面会有 1 个字节,为15时,后面会有16个字节。即:至少带 1 个字节至多带16个字节。

bash 复制代码
 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       0xBE    |    0xDE       |           length=3            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  ID   | L=0   |     data      |  ID   |  L=1  |   data...     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      ...data   |  ID   | L=3   |            data...            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         ...data               |      0(pad)   |    0(pad)     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.1.2.3 two-Byte Header

描述 :开头以0x100X(X为appbit,占4bit)。RFC5285并未规定appbits的意义。后面同样跟两个字节标识扩展头的个数。

bash 复制代码
 0                   1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         0x100         |appbits|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

举例说明

扩展头的起始字节和One-Byte Header也不一样,ID和L分别用了一个字节来表示,且L表示的就是扩展头的长度(One-Byte Header中的L表示扩展头长度减1)。例子如下:

bash 复制代码
 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       0x10    |    0x00       |           length=3            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      ID       |     L=0       |     ID        |     L=1       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       data    |       ID      |      L=4      |    data...    |      
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   ...data                     |     0(pad)    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

备注:

在这两种形式中,填充字节的值都是0(零)。如果需要对齐,它们可以放在扩展元素之间;如果需要填充,它们可以放在最后一个扩展元素之后。填充字节不提供元素的ID,也不提供长度字段。当找到填充字节时,它将被忽略,解析器继续解释下一个字节。

2.1.3 RTP填充数据

RTP头中的P位用于表示RTP包中是否有填充数据, 如果P位为1,说明RTP包中含有填充数据。

当RTP包中包含有填充数据时, 其数据包的最后一个字节记录着包中填充字节的个数, 即图中的Padding Size 部分, 如果Pading Size 为5, 说明RTP包中共有5个填充字节, 其中包括他自己, 这些填充数据不属于RTP Payload的部分, 因此在解析RTP Payload 部分之前, 应将填充部分去掉

bash 复制代码
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
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |V=2|P|X|  CC   |M|     PT      |       sequence number         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                           timestamp                           |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           synchronization source (SSRC) identifier            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  contributing source (CSRC) identifiers (可选, 每个4字节)   |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |      defined by profile       |           length              |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                        header extension                       |
 |                             ....                              |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |     Payload Data (可变长度) .....                               |
 |                           :pading.....                         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |               Padding                         |Padding size   |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                 

2.2 RTCP 协议

如果想详细了解,可以看下面博客:

14. RTCP 协议_rtcp协议-CSDN博客

2.2.1 RTCP 格式
bash 复制代码
 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|   RC    |   PT=SR=200   |             length            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         SSRC of sender                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

含义

css 复制代码
```css
0-1       v(version)    含义:协议版本号,占2位,当前为2
2	      P (Padding)	含义:是否有填充位,最后一个字节表明填充长度, 为1时表示有填充位,填充以字节为单位,一般数据加密时需要固定大小的数据块, 此时需要将该位置置1
3-7	      RC (5 bits):  Reception report count(接收报告计数)
8-15	  PT (Payload Type)	Packet Type(包类型)
16-31     length        含义:表示整个RTCP包的大小, 包含RTCP头, RTCP负载以及填充字节, 需要注意Length字段是以4字节为单位。
32-61     ssrc          含义:发送者的同步源标识符
```
2.2.2 常用PT类型
PT 值 类型 英文全称 主要用途 是否必须
200 SR Sender Report 发送者报告,包含发送统计 发送者必须
201 RR Receiver Report 接收者报告,质量反馈 接收者必须
202 SDES Source Description 源描述信息(CNAME等) 必须包含
203 BYE Goodbye 离开会话通知 可选
204 APP Application-defined 应用自定义数据 可选
205 RTPFB RTP Feedback RTP层反馈消息 扩展
206 PSFB Payload-specific FB 负载特定反馈 扩展
207 XR Extended Report 扩展报告 扩展

三 webrtc 应用

在webrtc RTPSenderAudio 和 RTPSenderVideo 类中实现了音视频数据的rtp数据组装

bash 复制代码
webrtc 实现rtp数据组装的具体类和函数
modules/rtp_rtcp/source/rtp_sender_audio.h
RTPSenderAudio::SendAudio

modules/rtp_rtcp/source/rtp_sender_video.h
RTPSenderVideo::SendVideo

在这个函数中, 首先创建RtpPacketToSend对象single_packet、first_packet、middle_packet,last_packet并填充rtp头, 然后使用rtp分包器对h264视频数据进行分包, 将rtp头和每一个包数据进行组合,形成rtp数据包。
备注:RtpPacketToSend 对象set每一项rtp头数据时,都设置进了数据buffer
cpp 复制代码
bool RTPSenderVideo::SendVideo(
    int payload_type,
    absl::optional<VideoCodecType> codec_type,
    uint32_t rtp_timestamp,
    int64_t capture_time_ms,
    rtc::ArrayView<const uint8_t> payload,
    const RTPFragmentationHeader* fragmentation,
    RTPVideoHeader video_header,
    absl::optional<int64_t> expected_retransmission_time_ms) {
// 性能追踪,标记视频发送的开始,记录帧类型。
#if RTC_TRACE_EVENTS_ENABLED
  TRACE_EVENT_ASYNC_STEP1("webrtc", "Video", capture_time_ms, "Send", "type",
                          FrameTypeToString(video_header.frame_type));
#endif
  // 确保函数在同一线程中串行执行,避免并发问题。
  RTC_CHECK_RUNS_SERIALIZED(&send_checker_);
  // 空帧直接返回成功
  if (video_header.frame_type == VideoFrameType::kEmptyFrame)
    return true;
  // 无有效载荷,返回失败
  if (payload.empty())
    return false;
 
  // 重传设置
  // kRetransmitBaseLayer      基础层重传:开启(保证最低可观看质量)
  // kRetransmitHigherLayers   增强层重传:开启(尽量提高视频质量)
  int32_t retransmission_settings = retransmission_settings_;
  if (codec_type == VideoCodecType::kVideoCodecH264) {
    // Backward compatibility for older receivers without temporal layer logic.
    retransmission_settings = kRetransmitBaseLayer | kRetransmitHigherLayers;
  }
  
  // 帧标记设置
  // 判断是否为 H.264 且 需要设置时间层标记
  bool set_frame_marking =
      video_header.codec == kVideoCodecH264 &&
      video_header.frame_marking.temporal_id != kNoTemporalIdx;

  // 获取需要发送的播放延迟信息
  const absl::optional<PlayoutDelay> playout_delay =
      playout_delay_oracle_->PlayoutDelayToSend(video_header.playout_delay);

  // According to
  // http://www.etsi.org/deliver/etsi_ts/126100_126199/126114/12.07.00_60/
  // ts_126114v120700p.pdf Section 7.4.5:
  // The MTSI client shall add the payload bytes as defined in this clause
  // onto the last RTP packet in each group of packets which make up a key
  // frame (I-frame or IDR frame in H.264 (AVC), or an IRAP picture in H.265
  // (HEVC)). The MTSI client may also add the payload bytes onto the last RTP
  // packet in each group of packets which make up another type of frame
  // (e.g. a P-Frame) only if the current value is different from the previous
  // value sent.
  // Set rotation when key frame or when changed (to follow standard).
  // Or when different from 0 (to follow current receiver implementation).
  // 旋转角度设置
  // 决定是否需要在 RTP 扩展头中包含视频旋转信息
  bool set_video_rotation =
      video_header.frame_type == VideoFrameType::kVideoFrameKey ||
      video_header.rotation != last_rotation_ ||
      video_header.rotation != kVideoRotation_0;
  last_rotation_ = video_header.rotation;

  // Send color space when changed or if the frame is a key frame. Keep
  // sending color space information until the first base layer frame to
  // guarantee that the information is retrieved by the receiver.
  // 智能管理颜色空间信息的发送:
  // 颜色空间改变时立即发送
  // 关键帧必须发送
  // 非基础层需要持续发送直到基础层
  bool set_color_space;
  if (video_header.color_space != last_color_space_) {
    last_color_space_ = video_header.color_space;
    set_color_space = true;
    transmit_color_space_next_frame_ = !IsBaseLayer(video_header);
  } else {
    set_color_space =
        video_header.frame_type == VideoFrameType::kVideoFrameKey ||
        transmit_color_space_next_frame_;
    transmit_color_space_next_frame_ =
        transmit_color_space_next_frame_ ? !IsBaseLayer(video_header) : false;
  }
  // 为关键帧和普通帧配置不同的 FEC 保护级别
  if (flexfec_enabled() || ulpfec_enabled()) {
    rtc::CritScope cs(&crit_);
    // FEC settings.
    const FecProtectionParams& fec_params =
        video_header.frame_type == VideoFrameType::kVideoFrameKey
            ? key_fec_params_
            : delta_fec_params_;
    if (flexfec_enabled())
      flexfec_sender_->SetFecParameters(fec_params);
    if (ulpfec_enabled())
      ulpfec_generator_.SetFecParameters(fec_params);
  }

  // Maximum size of packet including rtp headers.
  // Extra space left in case packet will be resent using fec or rtx.
  // 计算包容量 
  // MaxRtpPacketSize():最大 RTP 包大小(通常 1200-1500 字节)
  // FecPacketOverhead():FEC 头部开销
  // kRtxHeaderSize:重传头大小如果有 RTX
  int packet_capacity = rtp_sender_->MaxRtpPacketSize() - FecPacketOverhead() -
                        (rtp_sender_->RtxStatus() ? kRtxHeaderSize : 0);

  // 分配rtp模版
  std::unique_ptr<RtpPacketToSend> single_packet =
      rtp_sender_->AllocatePacket();
  RTC_DCHECK_LE(packet_capacity, single_packet->capacity());
  single_packet->SetPayloadType(payload_type);
  single_packet->SetTimestamp(rtp_timestamp);
  single_packet->set_capture_time_ms(capture_time_ms);
    
  // 根据包的位置(第一个/中间/最后一个)添加不同的扩展头。
  auto first_packet = std::make_unique<RtpPacketToSend>(*single_packet);
  auto middle_packet = std::make_unique<RtpPacketToSend>(*single_packet);
  auto last_packet = std::make_unique<RtpPacketToSend>(*single_packet);
  // Simplest way to estimate how much extensions would occupy is to set them.
  AddRtpHeaderExtensions(video_header, playout_delay, set_video_rotation,
                         set_color_space, set_frame_marking,
                         /*first=*/true, /*last=*/true, single_packet.get());
  AddRtpHeaderExtensions(video_header, playout_delay, set_video_rotation,
                         set_color_space, set_frame_marking,
                         /*first=*/true, /*last=*/false, first_packet.get());
  AddRtpHeaderExtensions(video_header, playout_delay, set_video_rotation,
                         set_color_space, set_frame_marking,
                         /*first=*/false, /*last=*/false, middle_packet.get());
  AddRtpHeaderExtensions(video_header, playout_delay, set_video_rotation,
                         set_color_space, set_frame_marking,
                         /*first=*/false, /*last=*/true, last_packet.get());

  RTC_DCHECK_GT(packet_capacity, single_packet->headers_size());
  RTC_DCHECK_GT(packet_capacity, first_packet->headers_size());
  RTC_DCHECK_GT(packet_capacity, middle_packet->headers_size());
  RTC_DCHECK_GT(packet_capacity, last_packet->headers_size());
  // 不同位置的包头部大小不同(扩展头可能不同),需要相应调整载荷大小
  RtpPacketizer::PayloadSizeLimits limits;
  limits.max_payload_len = packet_capacity - middle_packet->headers_size();

  RTC_DCHECK_GE(single_packet->headers_size(), middle_packet->headers_size());
  limits.single_packet_reduction_len =
      single_packet->headers_size() - middle_packet->headers_size();

  RTC_DCHECK_GE(first_packet->headers_size(), middle_packet->headers_size());
  limits.first_packet_reduction_len =
      first_packet->headers_size() - middle_packet->headers_size();

  RTC_DCHECK_GE(last_packet->headers_size(), middle_packet->headers_size());
  limits.last_packet_reduction_len =
      last_packet->headers_size() - middle_packet->headers_size();

  // 检查帧描述符扩展的版本冲突
  rtc::ArrayView<const uint8_t> generic_descriptor_raw_00 =
      first_packet->GetRawExtension<RtpGenericFrameDescriptorExtension00>();
  rtc::ArrayView<const uint8_t> generic_descriptor_raw_01 =
      first_packet->GetRawExtension<RtpGenericFrameDescriptorExtension01>();

  if (!generic_descriptor_raw_00.empty() &&
      !generic_descriptor_raw_01.empty()) {
    RTC_LOG(LS_WARNING) << "Two versions of GFD extension used.";
    return false;
  }

  // Minimiazation of the vp8 descriptor may erase temporal_id, so save it.
  // 时间层 ID 保存和描述符优化
  const uint8_t temporal_id = GetTemporalId(video_header); // 保存时间层ID
  rtc::ArrayView<const uint8_t> generic_descriptor_raw =
      !generic_descriptor_raw_01.empty() ? generic_descriptor_raw_01
                                         : generic_descriptor_raw_00;
  if (!generic_descriptor_raw.empty()) {
    // // 优化描述符,减少不必要信息
    MinimizeDescriptor(&video_header);
  }

  // RTC_LOG(LS_INFO)<<"frame_encryptor_ before";
  // TODO(benwright@webrtc.org) - Allocate enough to always encrypt inline.
  
  // 如果需要,对视频载荷进行端到端加密。
  rtc::Buffer encrypted_video_payload;
  if (frame_encryptor_ != nullptr) {
    if (generic_descriptor_raw.empty() &&
        !field_trial::IsEnabled("WebRTC-E2EE")) {
      RTC_LOG(LS_ERROR)
          << "[lxy] RTPSenderVideo::SendVideo generic_descriptor_raw is";
      return false;
    }

    const size_t max_ciphertext_size =
        frame_encryptor_->GetMaxCiphertextByteSize(cricket::MEDIA_TYPE_VIDEO,
                                                   payload.size());
    encrypted_video_payload.SetSize(max_ciphertext_size);

    size_t bytes_written = 0;

    // Only enable header authentication if the field trial is enabled.
    rtc::ArrayView<const uint8_t> additional_data;
    if (generic_descriptor_auth_experiment_) {
      additional_data = generic_descriptor_raw;
    }

    if (frame_encryptor_->Encrypt(
            cricket::MEDIA_TYPE_VIDEO, first_packet->Ssrc(), additional_data,
            payload, encrypted_video_payload, &bytes_written) != 0) {
      RTC_DLOG(LS_ERROR) << "[lxy] RTPSenderVideo::SendVideo Encrypt failed";
      return false;
    }

    encrypted_video_payload.SetSize(bytes_written);
    payload = encrypted_video_payload;
  } else if (require_frame_encryption_) {
    RTC_LOG(LS_WARNING)
        << "No FrameEncryptor is attached to this video sending stream but "
        << "one is required since require_frame_encryptor is set";
  }

  // 根据编码类型创建相应的分包器(H.264Packetizer, VP8Packetizer等)
  std::unique_ptr<RtpPacketizer> packetizer = RtpPacketizer::Create(
      codec_type, payload, limits, video_header, fragmentation);

  // TODO(bugs.webrtc.org/10714): retransmission_settings_ should generally be
  // replaced by expected_retransmission_time_ms.has_value(). For now, though,
  // only VP8 with an injected frame buffer controller actually controls it.
  // 根据时间层和预期重传时间判断是否允许重传
  const bool allow_retransmission =
      expected_retransmission_time_ms.has_value()
          ? AllowRetransmission(temporal_id, retransmission_settings,
                                expected_retransmission_time_ms.value())
          : false;
  // 获取分包数量
  const size_t num_packets = packetizer->NumPackets();

  // const auto& h264 =
  // absl::get<RTPVideoHeaderH264>(video_header.video_type_header);
  // RTC_LOG(LS_ERROR) << "[lxy] RTPSenderVideo::SendVideo
  // h264.packetization_mode:"<<h264.packetization_mode <<
  // "num_packets:"<<num_packets;

  // 计算未分包前的载荷大小
  size_t unpacketized_payload_size;
  if (fragmentation && fragmentation->fragmentationVectorSize > 0) {
    unpacketized_payload_size = 0;
    for (uint16_t i = 0; i < fragmentation->fragmentationVectorSize; ++i) {
      unpacketized_payload_size += fragmentation->fragmentationLength[i];
    }
  } else {
    unpacketized_payload_size = payload.size();
  }
  // 如果分包失败,则返回
  if (num_packets == 0)
    return false;
  // 循环分包
  uint16_t first_sequence_number;
  bool first_frame = first_frame_sent_();
  std::vector<std::unique_ptr<RtpPacketToSend>> rtp_packets;
  for (size_t i = 0; i < num_packets; ++i) {
    std::unique_ptr<RtpPacketToSend> packet;
    int expected_payload_capacity;
    // Choose right packet template:
    if (num_packets == 1) {
      // 单包
      packet = std::move(single_packet);
      expected_payload_capacity =
          limits.max_payload_len - limits.single_packet_reduction_len;
    } else if (i == 0) {
      // 第一个包
      packet = std::move(first_packet);
      expected_payload_capacity =
          limits.max_payload_len - limits.first_packet_reduction_len;
    } else if (i == num_packets - 1) {
      // 最后一个包
      packet = std::move(last_packet);
      expected_payload_capacity =
          limits.max_payload_len - limits.last_packet_reduction_len;
    } else {
      // 中间包
      packet = std::make_unique<RtpPacketToSend>(*middle_packet);
      expected_payload_capacity = limits.max_payload_len;
    }
    // 填充载荷
    if (!packetizer->NextPacket(packet.get()))
      return false;
    RTC_DCHECK_LE(packet->payload_size(), expected_payload_capacity);
    if (!rtp_sender_->AssignSequenceNumber(packet.get()))
      return false;
    // 分配序列号
    if (rtp_sequence_number_map_ && i == 0) {
      first_sequence_number = packet->SequenceNumber();
    }
    // 更新播放延迟信息
    if (i == 0) {
      playout_delay_oracle_->OnSentPacket(packet->SequenceNumber(),
                                          playout_delay);
    }
    // No FEC protection for upper temporal layers, if used.
    // 确定是否保护该包(FEC)
    bool protect_packet = temporal_id == 0 || temporal_id == kNoTemporalIdx;
    // 设置重传允许标记
    packet->set_allow_retransmission(allow_retransmission);
  
    // Put packetization finish timestamp into extension.
    if (packet->HasExtension<VideoTimingExtension>()) {
      packet->set_packetization_finish_time_ms(clock_->TimeInMilliseconds());
    }
    //  23.9 处理 RED(冗余编码)和 FEC
    if (red_enabled()) {
      AppendAsRedMaybeWithUlpfec(std::move(packet), protect_packet,
                                 &rtp_packets);
    } else {
      packet->set_packet_type(RtpPacketToSend::Type::kVideo);
      const RtpPacketToSend& media_packet = *packet;
      rtp_packets.emplace_back(std::move(packet));
      if (flexfec_enabled()) {
        // TODO(brandtr): Remove the FlexFEC code path when FlexfecSender
        // is wired up to PacedSender instead.
        if (protect_packet) {
          flexfec_sender_->AddRtpPacketAndGenerateFec(media_packet);
        }
        GenerateAndAppendFlexfec(&rtp_packets);
      }
    }

    if (first_frame) {
      if (i == 0) {
        RTC_LOG(LS_INFO)
            << "Sent first RTP packet of the first video frame (pre-pacer)";
      }
      if (i == num_packets - 1) {
        RTC_LOG(LS_INFO)
            << "Sent last RTP packet of the first video frame (pre-pacer)";
      }
    }
  }
  // 维护序列号到时间戳的映射,用于重传
  if (rtp_sequence_number_map_) {
    const uint32_t timestamp = rtp_timestamp - rtp_sender_->TimestampOffset();
    rtc::CritScope cs(&crit_);
    rtp_sequence_number_map_->InsertFrame(first_sequence_number, num_packets,
                                          timestamp);
  }
  LogAndSendToNetwork(std::move(rtp_packets), unpacketized_payload_size);

  TRACE_EVENT_ASYNC_END1("webrtc", "Video", capture_time_ms, "timestamp",
                         rtp_timestamp);
  return true;
}
相关推荐
runner365.git3 小时前
语言接入大模型,websocket还是webrtc?
websocket·网络协议·webrtc
好多渔鱼好多3 天前
【流媒体协议】WebRTC 技术详解
webrtc
txp玩Linux3 天前
webrtc降噪模块NS源码解析(1)
webrtc
鲲鹏混子鱼3 天前
WebRTC P2P信令服务架构设计文档
网络协议·webrtc·p2p
平行云10 天前
实时云渲染支持数字孪生智能工厂:迈向“零原型”制造
人工智能·unity·ue5·云计算·webrtc·制造·实时云渲染
笔夏11 天前
【安卓学习之webRTC】学习相关资料
android·学习·webrtc
每日出拳老爷子11 天前
【浏览器方案】只用浏览器访问的内网会议系统设计思路(无客户端)
运维·服务器·webrtc·实时音视频·流媒体
softshow102614 天前
Vue3 :封装 WebRTC 低延迟视频流与 WebSocket 实时状态驱动的大屏可视化
websocket·网络协议·webrtc
雨落秋垣16 天前
大屏可视化系统:WebRTC视频流与WebSocket实时数据集成方案
websocket·网络协议·webrtc