RTP之H264封包和解包
目录
- H264打包RTP的方法
- 打包方式之Single NAL Unit
- 打包方式之FU-A
- FU indication
- FU header
1. H264打包RTP的方法
- RTP的特点不仅仅⽀持承载在UDP上,这样利于低延迟⾳视频数据的传输,另外⼀个特点是它允许通过其它协议接收端和发送端协商⾳视频数据的封装和编解码格式,这样固定头的playload type字段就⽐较灵活。截⽌⽬前为⽌,RTP是我⻅过传输⾳视频数据类型最多的,具体参考:https://en.wikipedia.org/wiki/RTP_payload_formats
- 以H264裸码流NALU为例,进⾏H264的打包,其中H264打包的详细⽅法要参考RFC6184⽂档。
- H.264标准协议定义了两种不同的类型:⼀种是VCL即Video Coding Layer , ⼀ 种 是 NAL 即Network Abstraction Layer。其中前者就是编码器吐出来的原始编码数据,没有考虑传输和存储问题。后⾯这种就是为了展现H.264的⽹络亲和性,对VCL输出的slice⽚数据进⾏了封装为NALUs(NAL Units),然后再封装为RTP包进⾏传输,这些都是H.264的基础,⻅后续⽂章。
- NALU的基本格式是:NALU Header + NALU Data,其中NALU的头由⼀个字节组成如下所示:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
- 其中
- F (1bit):如果是坏帧,则置1,其余H.264固定为0。
- NRI(2 bit):⽤来指示该NALU 的重要性等级。值越⼤,表示当前NALU越重要。具体⼤于0 时取何值,没有具体规定。
例如:如果是00,则表示此帧即使丢失了,也不影响解码;其他值则表示此帧如果丢失了,会影响解码,这个字段指明了该NALU的重要性,但是实际我们不太关⼼这个字段。 - Nalu_Type(5 bit):NAL Unit的类型,这个值指明了NALU的类型,其中NALU的类型⻅下表
Nalu_Type | NALU内容 | 备注 |
---|---|---|
0 | 未指定 | |
1 | ⾮IDR图像编码的slice | ⽐如普通I、P、B帧 |
2 | 编码slice数据划分A | 2类型时,只传递⽚中最重要的信息,如⽚头,⽚中宏块的预测模式等;⼀般不会⽤到; |
3 | 编码slice数据划分B | 3类型是只传输残差;⼀般不会⽤到; |
4 | 编码slice数据划分C | 4时则只可以传输残差中的AC系数;⼀般不会⽤到; |
5 | IDR图像中的编码slice | IDR帧,IDR⼀定是I帧但是I帧不⼀定是IDR帧。 |
6 | SEI补充增强信息单元 | 可以存⼀些私有数据等; |
7 | SPS 序列参数集 | 编码的参数配置 |
8 | PPS 图像参数集 | 编码的参数配置 |
9 | 接⼊单元定界符 | |
10 | 序列结束 | |
11 | 码流结束 | |
12 | 填充数据 | |
24 | STAP-A Single-time aggregation packet | 单⼀时间聚合包模式,意味着⼀个RTP包可以传输多个NALU,但是这些NALU的编码时间要⼀样才能聚合到⼀个RTP。 |
25 | STAP-B Single-time aggregation packet | 单⼀时间聚合包模式,⽐STAP-B多⼀个DON |
26 | MTAP 16 Muti-time aggregation packet | 多个时间聚合包模式:意味着⼀个RTP包可以传输多个NALU,但是这些NALU的编码时间有可能不⼀样。 |
27 | MTAP 24 Muti-time aggregation packet | 多个时间的聚合包模式 |
28 | FU-A Fragmentation unit | 分包模式:当⼀个RTP容纳不下⼀个NALU时,就需要FUs这种格式。 |
29 FU-B | Fragmention unit | 分包模式 |
30-31 | 未指定,保留 |
- 参考⽂档:https://tools.ietf.org/html/rfc6184
- 我们看到1-11就是NALU的单个包类型,但是⼀个NALU的⼤⼩是不⼀样的,如果是⾮视频数据的SPS PPS才⼗⼏个字节,对于IDR帧,则有可能⼏⼗KB。
- 这样把NALU打包到RTP⽅式就很多,分为:
- ⼀个RTP包承载⼀个NALU;
- 多个NALU合并到⼀个RTP;
- ⼀个⼤的NALU切分成多个RTP
- 同时由于时间戳的问题,就有了24-29⼏种类型。
- 但是对于发送端组RTP包的⼀⽅来说,尽可能找简单的打包⽅式。对于接受端则需要适配各种发送端的打包⽅式,因为⽆法决定输⼊源的打包⽅式。这⾥先分享下我们的打包⽅式,⽐较简单(打包的时候不要搞太复杂的模式):
- 我们对于NALU的⻓度<=1400(rtp payload size)的则采⽤的是单⼀NALU打包到单⼀的RTP包中;
- 我们对于NALU的⻓度>1400的则采⽤了FU-A的⽅式进⾏了打包,这种就是把⼀个⼤的NALU进⾏了切分,最后接收⽅则进⾏了合并,把多个RTP包合并成⼀个完整的NALU即可;
- 为什么NALU的⻓度⼤于1400字节就要进⾏FU-A切⽚,是因为底层MTU⼤⼩值固定为1500,从传输效率讲,这⾥⽤1400作为切分条件。
- 同时我们发现现在视频监控领域摄像头通过RTP 传输码流的打包⽅式都是基本这种,这种打包⽅案简单容易实现,⼜满⾜需要。
- 28、29、30三个RTP分别传输的SPS、PPS、SEI这三种NALU,其中⼀个NALU分到⼀个RTP包,这是单⼀打包⽅式;
- 31、32三个RTP包分别传输了IDR帧的NALU单元,由于⽐较⼤,发送⽅采⽤了FU-A的打包⽅式;
- 上图还显示了SPS、PPS、SEI的RTP包固定头,Seq初始值不为0,为随机值,并且⼀个RTP包就顺序+1,这跟上⾯分析的⼀致;
- 上⾯SPS、PPS、SEI本身不涉及时间戳,但是这⾥头填充为和⾃⼰后⾯的第⼀个RTP保持⼀致,同样IDR的NALU切分的不同RTP包时间戳也是⼀样的;
- RTP一般一个包的大小是140bbyte左右,指的是RTP payload,不包含RTP header
- udp每个包有大小限制,MTU一般是1500byte,sendto一次不能发送超过1500byte的数据
- RTP包有大小之分
- SPS:25byte左右
- PPS:5byte左右
- SEI:600byte左右
- I帧:1080p为200K字节左右,一个RTP包发送不完一个I帧
- 同一个NALU划分成多个RTP包进行发送,比如I帧拆分成多个RTP包
- 一个RTP包也可以发送多个NALU,比如:sps和pps、sei帧一起发送,很多时候,为了简化程序,sps,pps,sei帧还是发送独立的包。
- 那么到底将单⼀的NALU打包到RTP或者把⽐较⼤的NLAU打包到多个RTP即FU-A⽅式是怎么操作的呢?
1. 打包方式之Single NAL Unit
- 就是⼀个RTP包打包⼀个单独的NALU⽅式,其实最好理解,就是在RTP固定头后⾯直接填充NLAU单元数据即可,即:
- RTP Header + NALU Header + NALU Data; (不包括startcode)
- 进⾏抓包和写⽂件,我们发现写⽂件的SPS和抓包RTP包固定头后⾯的负载完全是⼀致的,写⽂件中的SPS:
- 抓包中的RTP固定头后⾯的SEI:
2. 打包方式之FU-A
- 这种打包⽅式也不复杂,为了解释清楚,需要了解下⾯两个数据包头即FU indicator和Fu header
1. FU indication
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
- 这⾥⾯的的F和NRI已经在NALU的Header解释清楚了,就是NALU头的前⾯三个bit位,后⾯的TYPE就是NALU的FU-A类型28,这样在RTP固定头后⾯第⼀字节的后⾯5bit提取出来就确认了该RTP包承载的不是⼀个完整的NALU,是其⼀部分。
- 那么问题来了,⼀个NALU切分成多个RTP包传输,那么到底从哪⼉开始哪⼉结束呢?可能有⼈说RTP包固定头不是有mark标记么,注意区分那个是以帧图像的结束标记,这⾥要确定是NALU结束的标记,其次NALU的类型呢?那么就需要RTP固定12字节后⾯的Fu Header来进⾏区分。
2. 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。也就是说⼀个NALU切⽚时,第⼀个切⽚的SE是10,然后中间的切⽚是00,最后⼀个切⽚时01。
- R: 1 bit,保留位必须设置为0,接收者必须忽略该位。
- Type: 5 bits:此处的Type就是NALU头中的Type,取1-23的那个值,表示 NAL单元荷载类型定义。
-
综上所述:
-
对于⽐较⼤的NLAU进⾏FU-A切⽚时,其中NALU的Header字段在RTP打包时划分为两个字节
- NALU header的前3bit为RTP固定头后⾯第⼀个字节FU-indication的前3bit,后⾯5bit后⾯跟了FU-A打包这种类型28;
- NALU header的后⾯5bit变成了RTP固定头第⼆字节的后⾯5bit,其中前3bit标识了分⽚的开始和结束
-
为了验证这种打包⽅式,我们同样进⾏了写⽂件和抓包,对第⼀个IDR帧的NLAU采取的这种分⽚进⾏了研究。
-
第⼀个IDR帧的NALU第⼀个切⽚:
FU indication
⼗六机制:0x7C
⼆进制:0111 1100
FU header
⼗六进制:0x85
⼆进制:1000 0101
这⾥的SE是10,则说明该RTP包承载的NALU的第⼀个切⽚
- 这样我们提取FU indication字节的前3bit位和Fu header字节后5bit位则为0110 0101即0x65这刚好符合IDR帧的NALU Header定义,后⾯的88 84 00 2f等⼆进制就是NALU的DATA字段。
- 第⼀个IDR帧的NALU第⼆个切⽚:
FU indication
⼗六机制:0x7C
⼆进制:0111 1100
FU header
⼗六进制:0x05
⼆进制:0000 0101
这⾥的SE是00,则说明该RTP包承载的NALU的中间切⽚。
- 按照同样⽅法提取,则NALU Header的⼆进制为0110 0101即0x65,同样说明是IDR帧类型,类似这种的中间切⽚有很多,从31-56RTP包都是中间切⽚,直到最后⼀个NALU的切⽚。
- 第⼀个IDR帧的NALU最后⼀个切⽚:
FU indication
⼗六机制:0x7C
⼆进制:0111 1100
FU header
⼗六进制:0x45
⼆进制:0100 0101
这⾥的SE是01,则说明该RTP包承载的NALU的最后⼀个切⽚
- 当然通过抓包你还可以到IDR帧时⽐较⼤的,⽽后⾯的P帧就相对⽐较⼩,因为⼀个NALU切⽚4次就完了,同样能看到时间戳的变化值等信息。
- 我们可以看到发送端⼀般采⽤Single NAL Unit和FU-A打包⽅式就基本可以将H264数据发送到接收端了,对于AAC⾳频来说,直接将ADTS头部去掉以1024字节组成⼀帧直接塞到RTP即可,打包并不难。