H.264格式分析

一.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=5:表示当前NAL是IDR图像的一个片,在这种情况下,IDR图像中的每个片的nal_unit_type都应该等于5。注意,IDR图像不能使用分区。

nal_unit_type=7或8:每个SPS 或者PPS 仅对应一个NALU。 相应的RBSP数据类型如下表所示:

RBSP类型

缩写

描述

参数集

PS

包括SPS和PPS,序列的全局信息,如图像尺寸,视频格式等

增强信息

SEI

视频序列解码的增强信息

图像界定符

PD

视频图像的边界

编码片

SLICE

编码片的头信息和数据

数据分割

DP片层的数据,用于错误恢复解码

序列结束符

表明一个序列的结束,下一个图像为IDR图像

流结束符

表明该流中已没有图像

填充数据

亚元数据,用于填充字节

序列和图像参数集:减少了重复参数的传送,每个VCL NAL单元包含一个标识,指向有关的图像参数集,每个图像参数集包含一个标识,指向有关的序列参数集的内容因此,只用少数的指针信息,引用大量的参数,大大减少每个VCL NAL单元重复传送的信息。 数据分割:组成片的编码数据存放在 3 个独立的 DP(数据分割,A、B、C)中,各自包含一个编码片的子集。分割A包含片头和片中每个宏块头数据。分割B包含帧内和 SI 片宏块的编码残差数据。分割 C包含帧间宏块的编码残差数据。每个分割可放在独立的 NAL 单元并独立传输。

三.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

☆00 00 00 01 68 00 00 00 01 为NALU的起始标志。 00 00 00 01 后面的 68 为前面说的占1个字节的NALU头。将十六进制的68转换为二进制,得 0110 1000。

字段

所占bit位数

二进制

十进制

类型

forbidden_bit

1

0

0

nal_reference_bit

2

11

3

NALU 的重要性等级系数

nal_unit_type

5

01000

8

图像参数集,pps

☆00 00 03 00 H.264规定,当检测到0x000000时,也可以表征当前NAL的结束。那么NAL中数据出现0x000001或0x000000时怎么办?H.264引入了防止竞争机制,如果编码器检测到NAL数据存在0x000001或0x000000时,编码器会在最后个字节前插入一个新的字节0x03,这样: 0x000000->0x00000300 0x000001->0x00000301 0x000002->0x00000302 0x000003->0x00000303

解码器检测到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图像中的片

2.分析第二段数据:

☆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图像的片

在baseline(基准)类别中nal_unit_type=1表示的就是P帧,因为baseline没有B帧。

关于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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Fred Liang

gg 小组种子杯初赛报告

队员: 柳泓鑫 梁志博 洪志远 AUC: 0.7566 2017年10月1日 Github:https://github.com/ver217/seedc...

502
来自专栏互联网大杂烩

机器学习面试

线性回归的因变量是连续变量,自变量可以是连续变量,也可以是分类变量。如果只有一个自变量,且只有两类,那这个回归就等同于t检验。如果只有一个自变量,且有三类或更多...

694
来自专栏CreateAMind

keras doc 7 Pooling Connceted Recurrent Embedding Activation

‘th’模式下,为形如(samples,channels, rows,cols)的4D张量

843
来自专栏Coding迪斯尼

神经网络实战:快速构建一个基于神经网络的手写数字识别系统

822
来自专栏和蔼的张星的图像处理专栏

opencv小项目练习之数独求解

对于给定的数独照片(尽可能干净整齐),进行一系列处理,提取位置和数字信息,这中间可能要用到一系列图像处理的基本算法,数字识别时初步打算用knn来做,knn对手写...

421
来自专栏奇点大数据

Pytorch神器(4)

上一次,我们用最简短的篇幅讲述了用Pytorch实现线性回归的过程。整个程序仅仅用了约60多行就完成了一个线性回归机器学习程序的全部内容。这次的文章,我们来对上...

673
来自专栏机器之心

教程 | 从零开始PyTorch项目:YOLO v3目标检测实现(下)

选自Medium 作者:Ayoosh Kathuria 机器之心编译 参与:Panda 前几日,机器之心编译介绍了《从零开始 PyTorch 项目:YOLO v...

1.1K6
来自专栏点滴积累

geotrellis使用(十五)使用Bokeh进行栅格数据可视化统计

Geotrellis系列文章链接地址http://www.cnblogs.com/shoufengwei/p/5619419.html 目录 前言 实现方案 ...

3117
来自专栏有趣的Python

TensorFlow应用实战-5- TensorFlow基础知识

从helloworld开始 mkdir 1.helloworld cd 1.helloworld vim helloworld.py 代码: # -*- co...

3588
来自专栏瓜大三哥

多任务验证码识别

使用Alexnet网络进行训练,多任务学习:验证码是根据随机字符生成一幅图片,然后在图片中加入干扰象素,用户必须手动填入,防止有人利用机器人自动批量注册、灌水、...

3937

扫描关注云+社区