五、H264编码
H264在视频采集到输出中属于编解码层次的数据,如下图所示,是在采集数据后做编码压缩时通过编码标准编码后所呈现的数据。
常用Nalu_type:
0x67 (0 11 00111) SPS 非常重要 type = 7
0x68 (0 11 01000) PPS 非常重要 type = 8
0x65 (0 11 00101) IDR帧 关键帧 非常重要 type = 5
0x61 (0 11 00001) 非IDR的I帧 或 P帧 非常重要 type = 1 非IDR的I帧 不大常见 【这个大概率是P帧,通过工具查看某个H264的P slice,开头为00 00 00 01 61】
0x41 (0 10 00001) 非IDR的I帧 或 P帧 重要 type = 1 【非IDR的I帧和P帧都是有可能的,具体通过工具分析】
0x01 (0 00 00001) B帧 不重要 type = 1
0x06 (0 00 00110) SEI 不重要 type = 6
所以判断是否为I帧的算法为: (NALU类型 & 0001 1111) = 5
最简单的办法是找0x65或0x25(I frame启始位),
或者去找0x67或0x27(SPS)
和0x68或0x28(PPS)后面的完整包。
SPS和PPS后面必然跟着I frame。(68之后,出现 000001 开始就是关键帧数据 )
好,对于H.264格式了解这么多就够了,我们的目的是想从一个H.264的文件中将一个一个的NALU提取出来,然后封装成RTP包,下面介绍如何将NALU封装成RTP包。一般程序中的定义。
enum {
NAL_IDR = 5,
NAL_SEI = 6,
NAL_SPS = 7,
NAL_PPS = 8,
NAL_AUD = 9,
NAL_B_P = 1,
};
3) H264 NAL RTP打包
NALU是H264用于网络传输的单元类型,一个完整的NALU单元一般是以0x000001或者0x00000001开始,其后跟的则是NALU头和NALU的数据;我们在网络传输的时候,会去掉开始的0x000001或者0x00000001的标志;一般需要将这些标志替换为RTP payload的头部(1个字节);
RTP载荷第一个字节
格式跟NALU头一样:【后面的格式与H264的RTP打包格式相关】
F:【1 bit】 forbidden_zero_bit, 占1位,在 H.264 规范中规定了这一位必须为 0
NRI:【2 bits】 nal_ref_idc, 占2位,取值从0到3,指示这个 NALU 的重要性,取值越大约重要。
Type:【5 bits】nalu是指包含在 NAL 单元中的 RBSP 数据结构的类型,其中0未定义,1-19在264协议中有定义,20-23为264协议指定的保留位。
单一NAL单元模式
即一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下 RTP NAL 头类型字段和原始的 H.264的 NALU 头类型字段是一样的。【RTP载荷第一个字节 type=1-23】
如有一个 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 个字节的开始码就可以了。
组合封包模式
即可能是由多个 NAL 单元组成一个 RTP 包. 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24。 【RTP载荷第一个字节 type=24-27】
【STAP-A 单一时间的组合包 24
STAP-B 单一时间的组合包 25
MTAP16 多个时间的组合包 26
MTAP24 多个时间的组合包 27】
如下图为一个包含sps、pps的包
第一个字节为0X18(即24),表示组合封包。0x00 0x15表示一个封包的长度21,后面的21个字节为一个封包,67(01100111,00111即7为sps帧)为NAL头;0x00 0x04表示一个封包的长度4,后面的4个字节为一个封包,68(01101000,01000即8为pps帧)为NAL头
分片封包模式
对于较大的NALU,一个NALU可以分为多个RTP包发送。存在两种类型 FU-A 和 FU-B.。【RTP载荷第一个字节 type=28-29】
【FU-A 分片的单元 28
FU-B 分片的单元 29
没有定义 30-31 】
注意,FU payload中并没有传送NALU的头部,
NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);
- RTP载荷第一个字节位FU Indicator,其格式如下:
高三位:与NALU第一个字节的高三位相同
Type:28<----->0x1c,表示该RTP包一个分片,为什么是28?因为H.264的规范中定义的,此外还有许多其他Type,这里不详讲。
- RTP载荷第二个字节位FU Header,其格式如下:
S:标记该分片打包的第一个RTP包
E:比较该分片打包的最后一个RTP包
R: 占1位,保留位,为0
Type:NALU的Type,后续才是NALU data)
注意,FU payload中并没有传送NALU的头部,NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);
- 第一个包
- RTP header中的M为0.
- FU Indicator为3C,即00111100, 11100即为28,表示是分片分包。
- FU Header为81,即10000001,即s为1,第一个封包;type为1,即b帧。
- 中间的包
- RTP header中的M为0.
- FU Indicator为3C,即00111100, 11100即为28,表示是分片分包。
- FU Header为01,即00000001,即s、e都为0,中前的封包;type为1,即b帧。
- 最后的包
- RTP header中的M为1.
- FU Indicator为3C,即00111100, 11100即为28,表示是分片分包。
- FU Header为41,即0 1000001,即s为0、e为1,最后的封包;type为1,即b帧。
FU-A分包方式注意点
①关于时间戳,需要注意的是h264的采样率为90000HZ(被标准固定死的,为了方便转换成npt时间,见维基百科:https://en.wikipedia.org/wiki/RTP_audio_video_profile),因此时间戳的单位为1(秒)/90000,因此如果当前视频帧率为25fps,那时间戳间隔或者说增量应该为3600,如果帧率为30fps,则增量为3000,以此类推。
②关于h264拆包,按照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。
③FU payload中并没有传送NALU的头部,NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);
④FU-A 还原的时候,也是0x00 00 00 01 开始,不需要自己额外添加0x00 00 00 01
⑤FU-A 的的解析,start end等数据要解析好
⑥single nal unit 也是以0x00 00 00 01开始,也不需要自己添加分隔符