H.264的两种打包/封装方法:字节流AnnexB格式 AVCC格式
放用于网络发送时,要封装成RTP格式
1、AnnexB格式----用于实时播放
处于H264文档附录B(Annex-B Byte stream format)中
开始前缀(00000001或000001)+NALU数据 绝大部分编码器的默认输出格式
一共有两种起始码start_code
①3字节0x000001 单帧多slice(即单帧多个NALU)之间间隔
②4字节0x00000001 帧之间,或者SPS等之前
4字节类型的开始码在在连续的数据传输中非常有用,因为用字节来对齐、分割流数据,比如:用连续的31个bit0后接一个bit1来分割流数据,是很容易的。
AnnexB格式每个NALU都包含起始码,且通常会周期性的在关键帧之前重复SPS和PPS
👉👉👉所以解码器可以从视频流随机点开始进行解码,实时的流格式
2、AVCC—用于存储
解码器配置参数在一开始就配置好了,系统可以很容易的识别NALU的边界,不需要额外的起始码,减少了资源的浪费,同时可以在播放时调到视频的中间位置。这种格式通常被用于可以被随机访问的多媒体数据,如存储在硬盘的文件。MP4、MKV通常用AVCC格式来存储。
AVCC格式不使用起始码作为NALU的分界,这种格式在每个NALU前都加上一个大端格式的前缀(1、2、4字节,代表NALU长度)
所以在解析AVCC格式的时候需要将指定的前缀字节数的值保存在一个头部对象中,这个都通常称为extradata或者sequence header。同时,SPS和PPS数据也需要保存在extradata或者叫’sequence header’中。
H.264 extradata / sequence header’语法如下:
bits
8 version ( always 0x01 )
8 avc profile ( sps[0][1] )
8 avc compatibility ( sps[0][2] )
8 avc level ( sps[0][3] )
6 reserved ( all bits on )
2 NALULengthSizeMinusOne 变量告诉我们用几个字节来存储NALU的长度(前缀:1、2或4)
// 【第5字节的后2位】这个值是(前缀长度-1),如果值=3,那前缀就是4,因为4-1=3
值=0 对应前缀1字节 对应每个NALU包最大长度255字节
值=1 对应前缀2字节 对应每个NALU包最大长度64K
值=3 对应前缀4字节 使用最多
3 reserved ( all bits on )
5 number of SPS NALUs (usually 1) repeated once per SPS:
16 SPS size
N variable SPS NALU data
8 number of PPS NALUs (usually 1) repeated once per PPS
16 PPS size
N variable PPS NALU data
使用上面的例子,那么AVCC extradata看起来像是这样的:
0x0000 | 01 64 00 0A FF E1 00 19 67 64 00 0A AC 72 84 44
0x0010 | 26 84 00 00 03 00 04 00 00 03 00 CA 3C 48 96 11
0x0020 | 80 01 07 68 E8 43 8F 13 21 30
你会发现SPS和PPS被存储在了非NALU包中(out of band带外),即独立于基本流数据。
这些数据的存储和传输是文件容器的任务,超出了本文的范畴。
虽然AVCC格式不使用起始码,但防竞争字节还是有的。
防字节竞争处理(Annxb和AVCC均有):RBSP👉EBSP
>用起始码定位NALU边界存在一个问题,即NALU中可能存在与起始码相同的数据。
>为了防止这个问题,在构建NALU时,需要在数据中的0x000000,0x000001,0x000002,0x000003中插入防竞争字节(Emulation Prevention Bytes)0x03,使其变为:
0x000000 = 0x0000 03 00
0x000001 = 0x0000 03 01
0x000002 = 0x0000 03 02
0x000003 = 0x0000 03 03
解码器在检测到0x000003时,将0x03抛弃,恢复原始数据。
3、RTP封装=12字节固定RTP包头 + 载荷(NALU)
V: RTP协议的版本号,当前协议版本号为2。
P: 填充标志,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
X: 扩展标志,如果X=1,则在RTP报头后跟有一个扩展报头
CC: CSRC计数器,指示CSRC 标识符的个数。
M: 标记位(不同载荷含义不同,视频标记一帧的最后一个分片slice则=1,其他=0)
PT: 载荷类型RTP_PAYLOAD_RTSP 如GSM音频、JPEM图像等。例如H264=96
序列号: 用于标识发送者所发送的 RTP 报文的序列号,每发送一个报文,序号增加 1
时间戳: 时间戳反映了该 RTP 报文的第一个八位组的采样时刻。 接受者使用时间戳来计算延迟和抖动, 并进行同步控制。
SSRC:同步信源标识符 区分是在和谁通信。值随机选择,参加同一视频会议的两个同步信源的SSRC要相同。
//特约信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。
针对IP网络的RTP打包方式。为原始的NAL打包格式,就是开始的若干字节(1,2,4字节)是NAL的长度,而不是start_code,此时必须借助某个全局的数据来获得编码器的profile,level,PPS,SPS等信息才可以解码。
RTP 协议实际上是由实时传输协议RTP(Real-time Transport Protocol)和实时传输控制协议RTCP(Real-time Transport Control Protocol)两部分组成。
RTP 协议基于多播或单播网络为用户提供连续媒体数据的实时传输服务;
RTCP 协议是 RTP 协议的控制部分,用于实时监控数据传输质量,为系统提供拥塞控制和流控制。
3.0 RTP单次发送有上限👉2种RTP打包:拆包or不拆包
在IP网络中,当要传输的IP报文大小超过【最大传输单元MTU】时就会产生IP分片情况。(若交给底层协议拆包容易出问题→主动拆分NALU再打包成RTP包后发送)
3.1 不分包进行RTP打包:nalu_head(不分包时的包头)
即NALU自身原本的nalu_header
1-12是NALU数据类型
24-31是RTP打包头类型
一个NALU_header解读:
3.2 分包进行RTP打包:FU_indicator和FU_head(RTP分包时的包头)
H264的RTP中有三种不同的封包模式(Single NAL,Non-interleaved,Interleaved)
通过SDP参数中指定,如:
m=video 49170 RTP/AVP 98
a=rtpmap:98 H264/90000
a=fmtp:98 profile-level-id=42A01E; packetization-mode=1; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==
1、packetization-mode决定封包模式:
1、单一NAL单元模式( Single NAL unit mode):packetization-mode = 0 或者无此字段时缺省
2、非交错模式(Non-interleaved mode): packetization-mode = 1
3、交错模式(Interleaved mode): packetization-mode = 2
2、sprop-parameter-sets: SPS,PPS
这个参数可以用于传输 H.264 的序列参数集和图像参数 NAL 单元. 这个参数的值采用 Base64 进行编码. 不同的参数集间用","号隔开。
//若不用Base64则可能会有数据丢失
3、profile-level-id:
这个参数用于指示 H.264 流的 profile 类型和级别. 由 Base16(十六进制) 表示的 3 个字节. 第一个字节表示 H.264 的 Profile 类型, 第三个字节表示 H.264 的 Profile 级别
3种打包模式–7种打包方式对应关系
另一种看待角度:
①single NAL unit packet 单包(1个RTP包:1个NALU)
②aggregation packets 聚合(组合)包(1个RTP包:多个NALU,提高传输效率),需要解包时在重组。
①STAP (Single-time aggregation packet)
STAP-A
STAP-B
② MTAP (Multi-time aggregation packet)
MTAP16
MTAP24
③Fragmentation Unit 拆包处理【一个NALU→多包 NALU>最大传输单元MTU】
FU-A //非交错模式
FU-B //交错模式
1、单一NALU的RTP包:
2、组合NALU的RTP包:
3、分片NALU的RTP包:
2、FU-A的分片格式
数据比较大的H264视频包,被RTP分片发送。12字节的RTP头后面跟随的就是FU-A分片:
FU_indicator:
F 禁止位
NRI 重要标识位👈即拆包的nalu自身的NRI
type RTP打包头类型,FU-A时type=28
FU_header:
S 开始位 1表示分片NAL单元的开始,反之=0
E 结束位 1表示分片NAL单元的结束,反之=0。
R 保留位 必须为0,接收者必须忽略该位。
type NALU数据类型 👈NALU_header
拆包和解包:
发送端—拆包:NAL_header与分片后的FU的单元头有如下关系:
NAL_header前三位为FU_indicator的前三位
NAL_header后五位为FU_header的后五位
接收端—解包:将所有的分片包组合还原成原始的NAl包
nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f)