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博客
一 概述
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 协议
如果想详细了解,可以看下面博客:
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;
}