#前言
RTSP 协议流程 已经介绍在SETUP 确定了传输模式,而在PLAY的时候就开始传输RTP 包
并且,确定了第一个RTP url 序列号 和时间戳
RTP-Info: url=rtsp://10.0.2.15/ss1.mkv/track1;seq=57885;rtptime=4285367567
,url=rtsp://10.0.2.15/ss1.mkv/track2;seq=41406;rtptime=1364100230
开始学习RTP包组成
RTP 包组成
- 协议基本知识
看协议文档还是算了,初学者不建议直接看,全英文就算了,没有实践过,理解起来很难,还是等熟悉后,再反过来当参考资料比较好
RTP协议地址 https://tools.ietf.org/html/rfc3550
RTP负载格式H264视频 地址 https://tools.ietf.org/html/rfc6184
很多大佬的资料如下
- 流媒体协议 https://blog.csdn.net/fishmai/article/details/53676194
- 分包说明 https://blog.csdn.net/jwybobo2007/article/details/7235942
- RTP 封装 H264 https://blog.csdn.net/jwybobo2007/article/details/7054140
上面的看完,估计就立即的差不多了,我自己总结一份
我记得计算机网络分为七层模型,后来变成常用的5层 每一层其实就是给数据 加上header ,RTP也是一样的。加一个RTP header 如下
首先了解RTP格式
RTP :Real Time Protocol 实时协议
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 |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| |
| |
| payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
一个RTP 包 由 RTP header + payload组成
RTP header 解释
- V:版本 v=2 占用2bit
- P:填充标志 占用1bit 如果 P=1 表示RTP包末尾有填充
- X:扩展标志 占用1bit 如果 X=1 表示RTP报头末尾有扩展报头
- CC:CSRC计数器,占4位,指示CSRC 标识符的个数
- M:标记 在包中标记 占用1bit
- PT:Payload type 说明负载类型 占用 7bit 一般来说 H264协议 视频是96 97 是音频 AMR-WB
- Sequence number:SN 序列号 随机生成的,表示一个RTP包的序列,后面依次+1
上面就表示一行 4个字节 - timestamp:时间戳 占用 32bit 必须采用 90KHz 采样率 当前RTP 时间
- synchronization source (SSRC) identifier:同步信源标识 随机产生 唯一
- contributing source (CSRC) identifiers: 特约信源标识符: 每个CSRC标识符占32bits,最多有15个。用于多房间会议
NALU
payload 是RTP 包的载荷,而在视频中 ,RTP载荷的是NALU或者分包后的FU-A 或者PPS 和SPS
H264 是视频编解码标准
H264 视频码流 理解:视频经过H264 编码后,就是H264码流
但是 H264 码流太大,所以需要拆分多个码流,放在多个RTP包中,但是这些拆分后的码流需要NALU header(报头) 标识,才能放进RTP 负载中,这个就是NALU
NALU :网络提取层单元
所以完整的 payload = NALU = NAL header+ H264原始码流(RBSP)
NAL header 结构 占用 8 bit
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
NAL header 解释
F:forbidden_zero_bit 禁止位 占用1 bit ,初始为0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
NRI :占用2bit 00~11 值越大 重要性越大,00 标识重要性很低,可以直接丢了,不影响恢复视频
Type:nal_unit_type 占用 5 bit, 指定 NALU的有效载荷类型 ,和上面的Payload type 不一样 ,这里的type 用来说明不同的载荷结构
nal_unit_type = 7 8 或者5 的时候 NRI必须是11
nal_unit_type 是什么?
nal_unit_type | NAL类型 |
---|---|
1 | 非IDR图像中不采用数据划分的片段 |
2 | 非IDR图像中A类数据划分片段 |
3 | 非IDR图像中B类数据划分片段 |
4 | 非IDR图像中C类数据划分片段 |
5 | IDR图像的片 |
6 | 补充增强信息单元(SEI) |
7 | 序列参数集 |
8 | 图像参数集 |
9 | 分界符 |
10 | 序列结束 |
11 | 码流结束 |
12 | 填充 |
13…23 | 保留 |
24…31 | 不保留(RTP打包时会用到) |
比较重要的是
nal_unit_type = 7 RTP 负载的是序列参数集
nal_unit_type = 8 RTP 负载的是图像参数集
nal_unit_type = 5 IDR图像的片 (立即刷新图像,I帧给P帧和B帧作为参考)
但是超过23的时候 RTP打包开始用到,决定NALU 如何打包进 RTP。因为NALU 大小有可能远远小于RTP payload,也有可能正好等于RTP payload,或者远大于RTP payload ,那么NALU 就需要再次拆分包,即一帧拆开发送
Table 3. Summary of allowed NAL unit types for each packetization
mode (yes = allowed, no = disallowed, ig = ignore)
Payload Packet Single NAL Non-Interleaved Interleaved
Type Type Unit Mode Mode Mode
-------------------------------------------------------------
0 reserved ig ig ig
1-23 NAL unit yes yes no
24 STAP-A no yes no
25 STAP-B no no yes
26 MTAP16 no no yes
27 MTAP24 no no yes
28 FU-A no yes yes
29 FU-B no no yes
30-31 reserved ig ig ig
上面的payload type指的是 nal_unit_type ,容易混淆
NALU 几种情况
- nal_unit_type = 1-23 就是:单一NAL单元模式
一般NALU的SIZE 小于 MTU {MTU 最大传输单元 一般是1500byte}
此时 单个NALU 放入 RTP payload中
H.264 NALU格式 [Start Code] [NALU Header] [NALU Payload]
H.264 NALU 单元 一般 包含 [Start Code]“0x00 00 00 01” 或 “0x00 00 01” 标识NALU header开始,再打包进RTP PAYLOAD的时候会去除 ,作用:检测出NAL的分界
例子:
如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F … ]
这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.
封装成 RTP 包将如下:
[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ]
即只要去掉 4 个字节的开始码就可以了.
小知识:
H.264引入了防止竞争机制,如果编码器检测到NAL数据存在0x000001或0x000000时,编码器会在最后个字节前插入一个新的字节0x03,这样:
0x000000->0x00000300
0x000001->0x00000301
0x000002->0x00000302
0x000003->0x00000303
解码器检测到0x000003时,把03抛弃,恢复原始数据(脱壳操作)。解码器在解码时,首先逐个字节读取NAL的数据,统计NAL的长度,然后再开始解码。
PS: 当NALU 放在网络中传输,即加入NALU header 的时候,会取出0x00000001 机制,因为有header 来区分,但是存放磁盘的时候,媒体文件会加start code
所以单一NAL单元模式 结构:
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 |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a Single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
聚合包 nal_unit_type = 24~27
本类型用于聚合多个NAL单元到单个RTP荷载中。本包有四种版本,单时间聚合包类型A (STAP-A),单时间聚合包类型B (STAP-B),多时间聚合包类型(MTAP)16位位移(MTAP16), 多时间聚合包类型(MTAP)24位位移(MTAP24)。赋予STAP-A, STAP-B, MTAP16, MTAP24的NAL单元类型号分别是 24,25, 26, 27 -
分片单元 nal_unit_type = 28 或者 29
将NALU 单元拆分到多个RTP包中发送 典型的就是FU-A,而FU-B,没遇到过
当 单个NALU size 大于 MTU 时候就要拆分 使用FU-A
NALU 因为过大,按照 FU-A分片的流程 (参考)
1)第一个FU-A包的FU indicator:F应该为当前NALU头的F,而NRI应该为当前NALU头的NRI,Type则等于28,表明它是FU-A包。FU header生成方法:S = 1,E = 0,R = 0,Type则等于NALU头中的Type。
2)后续的N个FU-A包的FU indicator和第一个是完全一样的,如果不是最后一个包,则FU header应该为:S = 0,E = 0,R = 0,Type等于NALU头中的Type。
3)最后一个FU-A包FU header应该为:S = 0,E = 1,R = 0,Type等于NALU头中的Type。
同一个NALU分包厚的FU indicator头是完全一致的,FU header只有S以及E位有区别,分别标记开始和结束,它们的RTP分包的序列号应该是依次递增的,并且它们的时间戳必须一致,而负载数据为NALU包去掉1个字节的NALU头后对剩余数据的拆分,这点很关键,你可以认为NALU头被拆分成了FU indicator和FU header,所以不再需要1字节的NALU头了
FU-A结构
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 |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
这里的FU indicator 就是
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
而 FU header 就是
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S E: 10 表示 分片开始(start) 即FU-A的开始 而 01 表示分片结束 即FU-A 结束 (end)
R:占用1bit R=0
type 判断 是什么类型的帧
上面的 [start code]和小知识同样适用所有的NALU
WireShark 抓的RTP包
因为有audio 和 video 我在wireshark中 video采用的是H264协议解码,而音频aac-hdr 使用的AMR-WB 解码的
video:
第一帧是SPS 然后是PPS 、SEI帧,开始第一个I帧 很关键,后面就是非I帧,参考I帧
上面讲的FU-A 分片下图就有
FU-A NALU header S 和 E 标记开始和结束 可以区分出来, RTP标记M 也显示出来了
audio
后面只会分析H264 具体协议
保存这份log,以防以后再拿出来,还是自己手动操作一遍,心里踏实很多。
log地址 https://download.csdn.net/download/engineer_james/10609273