一.H.264基本流结构
H.264 的基本流(elementary stream,ES)的结构分为两层,包括视频编码层(VCL)和网络适配层(NAL)。视频编码层负责高效的视频内容表示,而网络适配层负责以网络所要求的恰当的方式对数据进行打包和传送。引入NAL并使之与VCL分离带来的好处包括两方面:1、使信号处理和网络传输分离,VCL 和NAL 可以在不同的处理平台上实现;2、VCL 和NAL 分离设计,使得在不同的网络环境内,网关不需要因为网络环境不同而对VCL比特流进行重构和重编码。
☆VCL(Video Coding Layer):VCL是对核心算法引擎,块,宏块及片的语法级别的定义,他最终输出编码完的数据 SODB
☆NAL(Net Abstraction Layer):NAL将SODB打包成RBSP然后加上NAL头,组成一个NALU(NAL单元)
一个典型的NALU如下图所示:
☆SODB(String Of Data Bits):原始数据比特流, 长度不一定是8的倍数,故需要补齐☆RBSP(Raw Byte Sequence Payload):原始数据字节流,SODB+RBSP trailing bits=RBSP,添加加trailing bits是为了使一个RBSP为整字节数
H.264 的基本流由一系列NALU (Network Abstraction Layer Unit )组成,不同的NALU数据量各不相同。H.264 草案指出,当数据流是储存在介质上时,在每个NALU 前添加起始码:0x000001或0x00000001,用来指示一个NALU 的起始和终止位置。在这样的机制下,在码流中检测起始码,作为一个NALU得起始标识,当检测到下一个起始码时,当前NALU结束。
H.264 码流中每个帧的开头的3~4个字节是H.264 的start_code(起始码),0x00000001或0x000001。3字节的0x000001只有一种场合下使用,就是一个完整的帧被编为多个slice(片)的时候,从第二个slice开始,包含这些slice的NALU 使用3字节起始码。也就是说,如果NALU对应的slice为一帧的开始就用0x00000001,否则就用0x000001。
关于这一点从《ITU-T H.264建议书》和x264源码中可以看出,下面是部分x264源码。
//一个一个NALU处理
for( int i = start; i < h->out.i_nal; i++ )
{
int old_payload_len = h->out.nal[i].i_payload;
h->out.nal[i].b_long_startcode = !i || h->out.nal[i].i_type == NAL_SPS || h->out.nal[i].i_type == NAL_PPS || h->param.i_avcintra_class;
//添加起始码
x264_nal_encode( h, nal_buffer, &h->out.nal[i] );
nal_buffer += h->out.nal[i].i_payload;
if( h->param.i_avcintra_class )
{
h->out.nal[i].i_padding -= h->out.nal[i].i_payload - (old_payload_len + NALU_OVERHEAD);
if( h->out.nal[i].i_padding > 0 )
{
memset( nal_buffer, 0, h->out.nal[i].i_padding );
nal_buffer += h->out.nal[i].i_padding;
h->out.nal[i].i_payload += h->out.nal[i].i_padding;
}
h->out.nal[i].i_padding = X264_MAX( h->out.nal[i].i_padding, 0 );
}
}
代码中的b_long_startcode,就是在编码前判断是否用长起始码,即四字节起始码0x00000001。
然后调用x264_nal_encode函数添加起始码。
//添加起始码
void x264_nal_encode( x264_t *h, uint8_t *dst, x264_nal_t *nal )
{
uint8_t *src = nal->p_payload;
uint8_t *end = nal->p_payload + nal->i_payload;
uint8_t *orig_dst = dst;
//起始码
//annexb格式,起始码为0x000001或0x00000001
if( h->param.b_annexb )
{
if( nal->b_long_startcode )
*dst++ = 0x00;
*dst++ = 0x00;
*dst++ = 0x00;
*dst++ = 0x01;
}
else /* save room for size later */
dst += 4;//mp4格式
......
......
......
}
二.NAL头结构分析
NAL头结构如下图所示:
长度:1Byte,orbidden_bit(1bit) + nal_reference_bit(2bit) + nal_unit_type(5bit)
☆F(forbidden_zero_bit):1 位,初始为0。当网络识别此单元存在比特错误时,可将其设为 1,以便接收方丢掉该单元
☆NRI(nal_ref_idc):2 位,用来指示该NALU 的重要性等级。值越大,表示当前NALU越重要。具体大于0 时取何值,没有明确规定
☆Type(nal_unit_type):5 位,指出NALU 的类型
nal_unit_type |
NALU类型 |
nal_reference_bit |
0 |
未指定 |
0 |
1 |
非IDR的片 |
此片属于参考帧,则不等于0, 不属于参考帧,则等与0 |
2 |
片数据A分区 |
同上 |
3 |
片数据B分区 |
同上 |
4 |
片数据C分区 |
同上 |
5 |
IDR图像的片 |
5 |
6 |
补充增强信息单元(SEI) |
0 |
7 |
序列参数集(SPS) |
非0 |
8 |
图像参数集(PPS) |
非0 |
9 |
分界符 |
0 |
10 |
序列结束 |
0 |
11 |
码流结束 |
0 |
12 |
填充 |
0 |
13..23 |
保留 |
0 |
24..31 |
未指定 |
0 |
nal_unit_type=7或8:每个SPS 或者PPS 仅对应一个NALU。
相应的RBSP数据类型如下表所示:
RBSP类型 | 缩写 | 描述 |
参数集 | PS | 包括SPS和PPS,序列的全局信息,如图像尺寸,视频格式等 |
增强信息 | SEI | 视频序列解码的增强信息 |
图像界定符 | PD | 视频图像的边界 |
编码片 | SLICE | 编码片的头信息和数据 |
数据分割 | DP片层的数据,用于错误恢复解码 | |
序列结束符 | 表明一个序列的结束,下一个图像为IDR图像 | |
流结束符 | 表明该流中已没有图像 | |
填充数据 |
亚元数据,用于填充字节 |
三.frame、field、slice和macro block
☆帧(frame):当采样视频信号时,如果是通过逐行扫描,那么得到的信号就是一帧图像,通常帧频为25帧每秒(PAL制)、30帧每秒(NTSC制)
从宏观上来说,SPS、PPS、IDR 帧(包含一个或多个I-Slice)、P 帧(包含一个或多个P-Slice )、B 帧(包含一个或多个B-Slice )共同构成典型的H.264 码流结构。
☆场(field):当采样视频信号时,如果是通过隔行扫描(奇、偶数行),那么一帧图像就被分成了两场(每次扫描—奇扫描或偶扫描,各称为一场),通常场频为50Hz(PAL制)、 60Hz(NTSC制)
☆片(slice):一帧图像可编码成一个或者多个片,每片包含整数个宏块(macro block),即每片至少一个宏块,最多时包含整个图像的宏块。分片的目的是为了限制误码的扩散和传输,使编码片相互间保持独立。片共有5种类型:I片(只包含I宏块)、P片(P和I宏块)、B片(B和I宏块)、SP片(用 于不同编码流之间的切换)和SI片(特殊类型的编码宏块)。
片的语法结构如下图所示:
☆宏块(Macro Block):一个编码图像首先要划分成多个块(4x4 像素)才能进行处理,显然宏块应该是整数个块组成,通常宏块大小为16x16个像素。宏块分为I、P、B宏块,I宏块只能利用当前片中已解码的像素作为参考进行帧内预测;P宏块可以利用前面已解码的图像作为参考图像进行帧内预测;B宏块则是利用前后向的参考图形进行帧内预测
图像以序列为单位进行组织,而图像通常称为帧,帧、片和宏块的关系如下图所示:
当一帧图像包含多个片时,如下图所示:
帧、片与参数集的关系如下图所示:
如果不采用DP(数据分割)机制,则一个片就是一个NALU,一个 NALU 也就是一个片。否则,一个片由三个 NALU 组成,即DPA、DPB和DPC,对应的nal_unit_type 值为 2、3和4。
由于一帧可能编码成多个片,解码时需要保证帧的完整性。例如IDR帧就可能分成多个IDR片,可以从码流中搜索并提取连续存放的若干个nalu_type 等于05 的nalu,即可获得一个完整的IDR 帧。这里实际上涉及到了帧边界识别问题,H.264 将构成一帧图像所有NALU的集合称为一个AU(Access Unit),帧边界识别实际上就是识别AU。因为H.264 取消帧级语法,所以无法简单地从码流中获取AU 。解码器只有在解码的过程中,通过某些语法元素的组合才能判断一帧图像是否结束。
四.NALU解码流程
五.UltraEdit分析H.264文件
将test.264文件用UItraEdit打开,效果如下图:
test.264用MPlayer播放效果如下图:
由于数据量较大,我挑选了其中2段数据来分析。
1.分析第一段数据:
☆00 00 00 01 67
00 00 00 01 为NALU的起始标志。
00 00 00 01 后面的 67 为前面说的占1个字节的NALU头。将十六进制的67转换为二进制,得 0110 0111。
字段 | 所占bit位数 | 二进制 | 十进制 | 类型 |
---|---|---|---|---|
forbidden_bit | 1 | 0 | 0 | |
nal_reference_bit | 2 | 11 | 3 | NALU 的重要性等级系数 |
nal_unit_type | 5 | 00111 | 7 |
序列参数集,sps
|
字段 | 所占bit位数 | 二进制 | 十进制 | 类型 |
---|---|---|---|---|
forbidden_bit | 1 | 0 | 0 | |
nal_reference_bit | 2 | 11 | 3 | NALU 的重要性等级系数 |
nal_unit_type | 5 | 01000 | 8 | 图像参数集,pps |
解码器检测到0x000003时,把03抛弃,恢复原始数据(脱壳操作)。解码器在解码时,首先逐个字节读取NAL的数据,统计NAL的长度,然后再开始解码。
☆00 00 00 01 65
00 00 00 01为NALU的起始标志。
00 00 00 01 后面的 65 为前面说的占1个字节的NALU头。将十六进制的65转换为二进制,得 0110 0101。
字段 | 所占bit位数 | 二进制 | 十进制 | 类型 |
---|---|---|---|---|
forbidden_bit | 1 | 0 | 0 | |
nal_reference_bit | 2 | 11 | 3 | NALU 的重要性等级系数 |
nal_unit_type | 5 | 00101 | 5 | IDR图像中的片 |
☆00 00 00 01 41
00 00 00 01 为NALU的起始标志。
00 00 00 01 后面的 41 为前面说的占1个字节的NALU头。将十六进制的41转换为二进制,得 0100 0001。
字段 | 所占bit位数 | 二进制 | 十进制 | 类型 |
---|---|---|---|---|
forbidden_bit | 1 | 0 | 0 | |
nal_reference_bit | 2 | 10 | 2 | NALU 的重要性等级系数 |
nal_unit_type | 5 | 00001 | 1 | 不分区,非IDR图像的片 |
关于H.264的类别和等级详见:H.264视频压缩标准
参考书籍:《新一代视频压缩编码标准H.264-AVC》
参考链接:http://depthlove.github.io/2015/09/23/use-tool-to-analyze-h264-file/
参考链接:http://www.cnblogs.com/TaigaCon/p/5215448.html
参考链接:http://blog.csdn.net/chinadragon76/article/details/22408727