实时视频系统中的媒体传输,绝大多数都会采用RTP(实时传输协议)标准。H.264视频作为当前应用最广泛的视频编码标准,其传输协议也会首选RTP标准。在设计实现H.264的实时传输时,H.264协议基于RTP的打包和解包定义于IETF标准-RFC6184,RTC系统需要遵循这个标准来设计打包和解包处理模块。在通信理论中,这个过程可以被认为是基于传输的信道编码。本篇技术文章带你了解H.264在RTP中的基本格式和技术实践。
#01
基本格式
使用RTP对H.264打包和解包需要遵循IETF标准RFC6184, 我们先来了解一下H.264在RTP中的封包协议。
01
H.264的RTP报头
图1 RTP报头
对于H.264的RTP负载格式而言,RTP报头的格式和RFC 3550里面的定义是一致的,不过有一些字段需要特别说明一下。
标记位 (M):1位
对RTP时间戳所对应访问单元的最后一个数据包来设置标记位,符合视频中M位的正常使用格式,以允许有效的播放缓冲处理。解码器可以使用这个位作为访问单元最后一个数据包的早期指示,但是不能完全依赖这个属性。
负载类型 (PT):7位
没有特别指定的负载类型,需要通过协商来确定。
序列号(SN):16位
根据RFC 3550设置和使用。对于单NAL单元和非交错打包模式,序列号用于确定NAL单元的解码顺序。
时间戳:32位
RTP时间戳设置为视频内容的采样时间戳。必须使用90 kHz时钟频率。
02
H.264的RTP负载类型
H.264的RTP负载可分为三大类,类型如下:
单个NAL单元数据包:
此类RTP负载中仅包含单个NAL单元。负载报头类型编号等于原始NAL单元类型,即从 1 到 23 的范围值,详见H.264规范。
聚合数据包:
此类型用于聚合多个NAL单元成为单个 RTP 负载。这类数据包有四个细分版本:单时间聚合包A (STAP-A)、单时间聚合包B (STAP-B)、16位偏移多时间聚合包 (MTAP16) 和24位偏移多时间聚合包 (MTAP24)。负载类型编号分配给 STAP-A、STAP-B、MTAP16 和 MTAP24 的值分别为 24、25、26 和27。
分片单元:
用于将单个NAL单元分片到多个RTP 数据包。存在两个版本:FU-A 和 FU-B,负载类型编号分别为 28 和 29。
负载类型 | 数据包类型 | 数据包类型名 |
---|---|---|
1-23 | NAL单元 | 单个NAL单元包 |
24 | STAP-A | 单个时间聚合包 |
25 | STAP-B | 单个时间聚合包 |
26 | MTAP16 | 多个时间聚合包 |
27 | MTAP24 | 多个时间聚合包 |
28 | FU-A | 分片单元 |
29 | FU-B | 分片单元 |
表1 H.264负载类型
03
H.264的RTP打包模式
H.264的RTP打包模式有三种:
单NAL单元模式
所有的接收端都必须支持这种模式,主要应用于兼容低时延应用中的硬件设备。只有单NAL单元数据包可以在这种模式下使用。
非交错模式
建议接收端去支持这种模式,主要应用于低时延应用。只有单NAL单元、STAP-A和FU-A数据包可以在这种模式下使用。
交错模式
有需求的接收端可以去支持这种模式,主要应用于非低延时应用。STAP-B、两种MTAP、FU-A和FU-B数据包可以在这种模式下使用。
负载类型 | 数据包类型 | 单NAL单元模式 | 非交错模式 | 交错模式 |
---|---|---|---|---|
1-23 | NAL单元 | 允许 | 允许 | 不允许 |
24 | STAP-A | 不允许 | 允许 | 不允许 |
25 | STAP-B | 不允许 | 不允许 | 允许 |
26 | MTAP16 | 不允许 | 不允许 | 允许 |
27 | MTAP24 | 不允许 | 不允许 | 允许 |
28 | FU-A | 不允许 | 允许 | 允许 |
29 | FU-B | 不允许 | 不允许 | 允许 |
表2 H.264打包模式允许的负载类型
单NAL单元和非交错模式中,NAL单元必须以NAL单元解码顺序传输,这两种模式更适合低延时需求的交互系统。
交错模式中NAL单元的传输顺序和解码顺序可以是不一致的,导致接收端的解包过程中需要按照解码顺序重新排序,引入更多的时延,因此并不适合需要低时延的交互系统。
04
H.264的RTP负载报头
图2 H.264的RTP负载报头
H.264的RTP负载报头位于负载的第1个字节,分成三个字段:
F:1位
forbidden_zero_bit。值为 0 表示NAL单元类型字节和负载不应包含位错误或其他语法违规。值为 1 表示NAL单元类型字节和负载可能包含位错误或其他语法违规。
NRI:2位
nal_ref_idc。00值和非零值的语义与H.264规范保持不变。值00表示NAL单元的内容不是用于重建图片间预测的参考图片,这样的NAL单元可以被丢弃并不会导致参考图片的不完整。值大于00表示需要对NAL单元进行解码以保持参考图片的完整性。
类型:5位
负载类型,包括表1里面列举的所有类型。
05
H.264的RTP负载格式
因为只有单NAL单元模式和非交错模式打包模式更适合应用于低时延交互系统中,而这两种打包模式所涉及的只有单NAL数据包、单时间聚合包A(STAP-A)和分片单元A(FU-A)三种RTP负载,所以在这里只对这三种负载格式做个简单的介绍。
单NAL数据包
图3 单NAL数据包负载格式
单NAL数据包就是将原始的NAL单元直接放置到RTP的负载中,NAL单元头就是作为单NAL数据包的负载类型。
单时间聚合包A(STAP-A)
图4 聚合数据包负载格式
聚合数据包的负载中包含一个或者多个聚合单元。一个聚合包可以携带尽可能多的聚合单元;不过聚合数据包中的总数据量应该选择合适大小,以便生成的IP数据包小于MTU大小。聚合数据包负载报头中的NRI字段的值必须是所有聚合NAL单元中最大值。
图5 单时间聚合单元格式
STAP-A数据包中,每个聚合单元的NAL都应该是共享相同的NALU时间。负载的首字节是STAP-A负载报头,每个聚合单元是由两字节的NAL单元尺寸字段和原始NAL单元组成。如果STAP-A数据包中包含两个聚合单元,负载格式如下图:
图6 包含两个聚合单元的STAP-A数据包示例
分片单元A(FU-A)
图7 FU-A数据包负载格式
FU-A数据包的负载包含1字节的分片单元标识(负载报头)、1字节的分片单元报头和分片单元负载。分片单元负载报头中的NRI字段的值等同于被分片NAL单元的值。
分片单元报头的格式如下:
图8 分片单元报头
S: 1 位
起始位。当设置为 1 时,指示一个分片NAL 单元的开始。当 FU 负载不是分片NAL单元的开始片段,设置起始位为 0。
E: 1 位
结束位。当设置为 1 时,指示一个分片NAL单元的结束。当 FU负载不是分片NAL单元的最后一个片段,设置结束位为 0 。
R: 1 位
保留位。必须等于 0,并且必须被接收者忽略。
类型:5位
被分片的原始NAL单元类型(1 - 23)。
#02
实践分享
RTC系统中的视频处理的结构大致如下图,RTP打包解包是视频编解码和传输之间的桥梁。
图9 视频流工作流程
01
H.264打包
H.264的打包的基本流程大致如下:
- 输入H.264 NAL,判决当前的H.264 NAL的打包格式,可以选择单NAL单元包格式、STAP-A包格式,或者是FU-A格式。MTAP格式一般不在实时系统中使用,考量的重点在于兼顾打包效率和传输效率。
- Single-NAL-Unit 打包比较简单,一个NAL封装为一个RTP包。
- STAP-A在NAL包比较小的时候采用,多个相同时间戳的NAL包被打到一个RTP包。
- FU在NAL包比较大的时候采用,限制RTP包的大小小于MTU。一个NAL包被拆成多个碎片(Fragment), 碎片被打成RTP包。
02
H.264解包
在此只对三种打包模式下的解包过程做一个大致的介绍。
单NAL单元和非交错模式
接收端包括一个接收缓冲器来补偿传输延迟和抖动。接收端将传入的数据包按照接收顺序存储到接收缓冲器中。数据包按RTP序列号的顺序被解包。如果解包的数据包是单个NAL单元包,包中包含的NAL单元直接传递给解码器。如果解包的数据包是 STAP-A,则包含在数据包中的NAL单元按照它们封装在数据包中的顺序被传递给解码器。对于所有 FU-A包含单个NAL单元片段的数据包,解包的片段按其发送顺序恢复出NAL单元,然后传递给解码器。
交错模式
交错模式的解包规则一般是从传输顺序到解码顺序来重新排序NAL单元。在实时系统中应用比较少见,具体过程在此就不展开了。
参考文献
1、RFC 3550 – RTP: A Transport Protocol for Real-time Application
2、RFC 6184 – RTP Payload Format for H.264 Video