流媒体协议

RTSP 在上层发号施令(管控制),RTP 在底层埋头搬砖(运数据),RTCP 在旁边盯着质量(做监督)
三者关系:
- RTSP (应用层): 流媒体的“遥控器”。 它不传数据,只负责播放、暂停、快进和挂断等控制信令,并在正式传输前商量好底层数据通道的端口。
- RTP (传输层/应用层): 流媒体的“快递员”。 它负责将庞大的音视频切片、打包、加上时间戳和序列号,然后通过 UDP 疯狂地发往目的地。
- RTCP (应用层): 流媒体的“质检员”。 它不传数据,只在 RTP 旁边拉一条线,定期汇报丢包率和网络延迟。上层流媒体服务器(如 ZLMediaKit)根据它的反馈来决定是否降低码率或进行丢包重传(NACK)。
在实际传输中,RTP 和 RTCP 就像连体婴儿,它们的网络端口是成对绑定的:
- RTP 占用偶数端口 $N$(如
9000)传输音视频裸数据; - RTCP 占用相邻的奇数端口 $N+1$(如
9001)传输控制反馈报告。
RTP(实时传输协议)
- RTP/RTCP 是实际传输数据的协议
头部
0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number | // 4Byte 固定
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp | // 4Byte 固定
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier | // 4Byte 固定
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| contributing source (CSRC) identifiers | <- 每个4 字节, 共 CC 个
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| extension header (opt) | ext_length | <- 16 bit + 16 bit
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| extension data … | <- ext_length × 4 字节
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| payload … |
固定头部字段
头部固定12Byte:
- V (2 bits):版本号(通常为2)
- P (1 bit):填充位(末尾有填充字节时置1)
- X (1 bit):扩展头标志
- CC (4 bits):CSRC计数器(混合流数量)
- M (1 bit):标记位(视频帧结束/音频静音等)
- PT (7 bits):负载类型(映射SDP中的a=rtpmap)G.711音频的负载类型为0,H.264视频的负载类型为96。负载类型的明确标识使得接收端能够正确解码数据
- Sequence Number:包序号(检测丢包和乱序)
- Timestamp:时间戳(基于采样频率的时钟值)
- SSRC:数据源标识(唯一ID)
- CSRC:贡献源列表(混流时使用)
扩展头部
扩展头部是RTP协议的一个重要扩展机制,它是RTP头部的扩展标志(X)位决定的,它允许在RTP数据包中添加额外的信息,以满足特定应用的需求。扩展头部的格式如下:
- 扩展头部标识符:占16位,用于标识扩展头部的类型和格式。不同的标识符对应不同的扩展头部定义,使得接收端能够正确解析扩展头部的内容。
- 扩展头部长度:占16位,指示扩展头部的长度(以32位字为单位)。扩展头部的长度可以根据实际需求进行调整,从而灵活地携带各种额外信息。
- 扩展头部内容:扩展头部的内容根据具体的扩展头部标识符而定,可以包含各种类型的信息,如时间戳扩展、序列号扩展、加密信息等。扩展头部的灵活性使得RTP协议能够适应不断发展的应用需求。扩展头部的使用提高了RTP协议的适应性和扩展性,使得RTP协议能够在多种复杂的网络环境中稳定运行,并满足不同应用的特定需求。
RTP会话过程
会话建立
RTP会话的建立是实时数据传输的起点,涉及多个关键步骤,确保数据能够正确传输到接收端。
- 确定传输地址:当应用程序启动一个RTP会话时,首先需要确定一对目的传输地址。这包括一个网络地址和一对端口,一个用于RTP数据包,另一个用于RTCP控制包**。RTP数据通常发送到偶数UDP端口,而RTCP数据发送到相邻的奇数UDP端口**。这种端口对的设置使得RTP和RTCP数据能够正确地发送和接收。
- 初始化RTP和RTCP:在会话建立过程中,RTP和RTCP协议需要进行初始化。RTP协议从上层接收流媒体信息码流(如H.263),将其封装成RTP数据包;RTCP协议则从上层接收控制信息,封装成RTCP控制包。根据国际数据公司(IDC)的统计,RTP协议在初始化过程中,平均每个会话需要处理约1000个数据包,确保数据的完整性和实时性。
- 同步源标识符(SSRC)的生成:每个RTP会话都有一个唯一的同步源标识符(SSRC),用于标识数据的来源。SSRC是随机生成的,确保在同一RTP会话中不会出现相同的SSRC值。这使得接收端能够区分不同的数据源,从而正确处理多源数据。根据国际电信联盟(ITU)的统计,SSRC的生成过程在99%的情况下能够在1毫秒内完成,确保会话建立的高效性。
载荷
H.264 封装
RTP 是传输协议,H.264 是编码格式。RTP 对 H.264 码流进行封装传输。
┌─────────────┬──────────────────────────────────────────┐
│ RTP Header │ RTP Payload │
│ (12字节) │ (H.264 NALU数据,不含起始码) │
└─────────────┴──────────────────────────────────────────┘
H.264 NALU 头格式
在RTP头之后就是数据负载部分,在H264的负载封装中,第一个字节包含了数据传输的类型
+---------------+
|F| NRI | Type |
+---------------+
1 2 5 bits
h264的NALU头长度为:1字节
| 字段 | 长度 | 说明 |
|---|---|---|
| F (forbidden_zero_bit) | 1 bit | 必须为 0 |
| NRI (nal_ref_idc) | 2 bit | NALU 重要性,值越大越重要(0-3) |
| Type | 5 bit | NALU 类型 |
Type 取值:
- 1-23:单个 NAL 单元包
- 24-27:组合包(STAP-A、STAP-B、MTAP16、MTAP24)
- 28-29:分片包(FU-A、FU-B)
例如:
0x67(二进制:0110 0111)- 拆解:F=
0,NRI=11(最高重要性),Type=7(SPS)。
- 拆解:F=
0x68(二进制:0110 1000)- 拆解:F=
0,NRI=11(最高重要性),Type=8(PPS)。
- 拆解:F=
0x65(二进制:0110 0101)- 拆解:F=
0,NRI=11(最高重要性),Type=5(I 帧)。
- 拆解:F=
0x41(二进制:0100 0001)- 拆解:F=
0,NRI=10(中重要性),Type=1(P 帧)。
- 拆解:F=
单 NALU 模式

单一NAL单元模式是最简单的RTP封装方式,每个RTP包只包含一个H.264的NAL单元。这种模式适用于NAL单元大小较小,且网络条件较好,不需要对NAL单元进行分片或聚合的场景。在这种模式下,RTP负载的第一个字节是NAL单元的头信息(1字节),紧接着是NAL单元的RBSP数据。封装和解封装过程简单,易于实现。由于每个RTP包只包含一个NAL单元,因此在解码端可以快速识别和处理每个NAL单元,减少了处理复杂度。适用于NAL单元较小(如SPS、PPS等参数集或较小的I帧切片)的场景,或者在网络带宽充足且对延迟要求不高的情况下。
- NAL 类型(NAL Unit Type): 1–23
- 结构: RTP 载荷直接就是一个完整的 NAL 单元(不含起始码 0x00000001),大小不得超过 MTU。
- 处理: 收到包后,直接将
payload[0..]作为一个完整的 NALU,加入当前 RTP 时间戳对应的帧缓存,并在标记位(M=1)出现时输出整帧。
分片封包(FU-A)
RTP Header + FU indicator (1字节) + FU header(1字节) + Fragment data
FU indicator :
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
Type = 28 表示这是FU-A
FU header :
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
- S(开始位): 1 bit, 当设置成1,指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
- E(结束位): 1 bit, 当设置成1,指示分片NAL单元的结束,即,荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的 FU荷载不是分片NAL单元的最后分片,结束位设置为0。
- R(保留位): 1 bit, 保留位必须设置为0,接收者必须忽略该位。
- Type(类型):5 bit,原始NAL类型
H.265 封装
H.265 NALU头格式
+-------------------------------+
| F | Type | LayerId |TID|
+-------------------------------+
Bits: F(1), Type(6), LayerId(6), TID(3)
h265的NALU头长度为:2字节
| 字段 | 长度 | 说明 |
|---|---|---|
| F (forbidden_zero_bit) | 1 bit | 禁止位。最高位(第 15 位),必须为 0。若为 1 则标识该帧为无效帧、损坏帧。 |
| Type (nal_unit_type) | 6 bit | 帧类型(第 9 ~ 14 位)。0 - 31:VCL NAL 单元(携带编码视频图像数据的流,如 19/20 为 IDR 关键帧,1 为普通 P/B 帧)。32 - 63:非 VCL NAL 单元(控制数据流,如 32 为 VPS,33 为 SPS,34 为 PPS)。 |
| LayerId (nuh_layer_id) | 6 bit | 层标识(第 3 ~ 8 位)目前普通单视角的 IPC 编码中一般固定为 0 |
| TID (nuh_temporal_id_plus1) | 3 bit | 时间层 ID 加上 1(第 0 ~ 2 位)。用于时域可分级(Temporal Scalability)。值等于 $tid + 1$(实际时间层 $tid = TID - 1$)。由于 $tid$ 值不能为负,且为了确保报头中至少有一个比特等于 1(防止与防惊群启动代码冲突),它的值一般默认写为 1(即对应 $tid = 0$ 基础时间层)。 |