从零开始学习音视频编程技术(40) H.264数据解析

前面我们已经开发了视频播放器和录屏软件,但目前为止,我们对原理性的东西其实还不是很了解。

现在我们需要稍微了解一下理论知识,然后才能继续,先从h.264数据讲起。



H.264数据结构解析:


NALU可以简单认为就是一帧视频数据(我们暂且先这样认定),那么h264的结构就是一帧帧数据排列下去,帧与帧直接用StartCode隔开,

StartCode说明:如果该NALU对应的slice为一帧的开始则用4位字节表示,既为0x00000001, 否则用3位字节表示,既为0x000001.



现在深入讲解NALU结构:


在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:

1.VCL (VideoCoding Layer,视频编码层)。核心算法引擎,块,宏块及片的语法级别的定义,负责高效的视频内容表示,通俗的讲就是编码器直接编码之后的数据,这部分数据还不能直接用于保存和网络传输,否则在解析上存在困难。

2.NAL(NetworkAbstraction Layer,网络抽象层)。负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输,通俗的讲NAL就是讲上面的VCL加了一些头部信息封装了一下。

现实中的传输系统是多样化的,其可靠性,服务质量,封装方式等特征各不相同,NAL这一概念的提出提供了一个视频编码器和传输系统的友好接口,使得编码后的视频数据能够有效地在各种不同的网络环境中传输。


所谓NALU就是一个NAL单元,其组成内容如下:

       NAL单元是NAL的基本语法结构,它包含一个字节的头信息和一系列来自VCL的称为原始字节序列载荷(RBSP)的字节流。
      送到解码器端的NAL单元必须遵守严格的顺序,如果应用程序接收到的NAL单元处于乱序,则必须提供一种恢复其正确顺序的方法。

      每个NAL单元是一个一定语法元素的可变长字节字符串,包括包含一个字节的头信息(用来表示数据类型),以及若干整数字节的负荷数据。一个NAL单元可以携带一个编码片、A/B/C型数据分割或一个序列或图像参数集。


NALU头头信息中包含着一个可否丢弃的指示标记,标识着该NAL单元的丢弃能否引起错误扩散,一般,如果NAL单元中的信息不用于构建参考图像,则认为可以将其丢弃;最后包含的是NAL单元的类型信息,暗示着其内含有效载荷的内容。

NALU头信息结构如下:

(1)nal_unit_type:NALU类型位 

       可以表示NALU的32种不同类型特征,类型1~12是H.264定义的,类型24~31是用于H.264以外的,RTP负荷规范使用这其中的一些值来定义包聚合和分裂,其他值为H.264保留。 


(2)nal_reference_bit:重要性指示位 

       用于在重构过程中标记一个NAL单元的重要性,值越大,越重要。值为0表示这个NAL单元没有用于预测,因此可被解码器抛弃而不会有错误扩散;值高于0表示此NAL单元要用于无漂移重构,且值越高,对此NAL单元丢失的影响越大。 


(3)forbidden_bit:禁止位 

       编码中默认值为0,当网络识别此单元中存在比特错误时,可将其设为1,以便接收方丢掉该单元,主要用于适应不同种类的网络环境(比如有线无线相结合的环境)。例如对于从无线到有线的网关,一边是无线的非IP环境,一边是有线网络的无比特错误的环境。假设一个NAL单元到达无线那边时,校验和检测失败,网关可以选择从NAL流中去掉这个NAL单元,也可以把已知被破坏的NAL单元前传给接收端。在这种情况下,智能的解码器将尝试重构这个NAL单元(已知它可能包含比特错误)。而非智能的解码器将简单地抛弃这个NAL单元。NAL单元结构规定了用于面向分组或用于流的传输子系统的通用格式。在H.320和MPEG-2系统中,NAL单元的流应该在NAL单元边界内,每个NAL单元前加一个3字节的起始前缀码。在分组传输系统中,NAL单元由系统的传输规程确定帧界,因此不需要上述的起始前缀码。一组NAL单元被称为一个接入单元,定界后加上定时信息(SEI),形成基本编码图像。该基本编码图像(PCP)由一组已编码的NAL单元组成,其后是冗余编码图像(RCP),它是PCP同一视频图像的冗余表示,用于解码中PCP丢失情况下恢复信息。如果该编码视频图像是编码视频序列的最后一幅图像,应出现序列NAL单元的end,表示该序列结束。一个图像序列只有一个序列参数组,并被独立解码。如果该编码图像是整个NAL单元流的最后一幅图像,则应出现流的end。



EBSP说明:

在了解EBSP之前需要理解SODB、RBSP和EBSP这三个名词

1.SODB: String OData Bits 原始数据比特流。既编码后的原始数据。

2.RBSP:原始字节序列载荷。由于SODB长度不一定是8的倍数,而我们最终保存的h264数据是一帧帧的数据排列下去的,这样就会造成字节不对齐,当需要读取的时候,就非常困难,因此,为了寻址方便,在在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)和若干比特“0”这样就得到了RBSP。

3.EBSP:扩展字节序列载荷。前面提到了我们保存的h264数据,NALU之间是通过startcode(0x000001或0x00000001)来隔开的,如果编码后的原始数据含有0x000001,就无法知道这个到底是不是startcode,因此为了使NALU主体中不包括与开始码相冲突的数据,在RBSP数据中每遇到两个字节连续为0,就插入一个字节的0x03,这样就得到了EBSP。解码时再将0x03去掉。 也称为脱壳操作。



最后所有的数据逻辑关系总结如下:

SODB  + RBSP trailing bits = RBSP


RBSP 将0x0000替换为0x000003 = EBSP


NAL header(1 byte) + EBSP = NALU


Start Code Prefix(3 bytes) + NALU + Start Code Prefix(3 bytes) + NALU + … + = H.264BitsStream


到此,我们就掌握了h.264的数据结构了。


音视频技术交流讨论欢迎加 QQ群 121376426  

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭