前面我们已经开发了视频播放器和录屏软件,但目前为止,我们对原理性的东西其实还不是很了解。
现在我们需要稍微了解一下理论知识,然后才能继续,先从h.264数据讲起。
H.264数据结构解析:
NALU可以简单认为就是一帧视频数据(我们暂且先这样认定),那么h264的结构就是一帧帧数据排列下去,帧与帧直接用StartCode隔开,
StartCode说明:如果该NALU对应的slice为一帧的开始则用4位字节表示,既为0x00000001, 否则用3位字节表示,既为0x000001.
=======错误修正========
2019-01-16更新:
之前所说的一帧数据就是一个NAL单元(当然已经排除了sps或者pps了)是个错误的说法,我在实践过程中,发现了一些包含很多个NAL单元的帧,比如ffmpeg编码出来的I帧数据就是包含了很多个NAL单元。
另外关于起始码的说法也有问题,修正后如下:
关于起始码startcode的两种形式:3字节的0x000001和4字节的0x00000001
3字节的0x000001只有一种场合下使用,就是一个完整的帧被编为多个slice的时候,
包含这些slice的nalu使用3字节起始码。其余场合都是4字节的。
因此查找一帧的话,只需要查找四字节的起始码即可。
鉴于此,后面播放h264文件的例子和通过rtp发送h264文件的例子都有bug,已经修复了,请自行前往相应文章下载。
=======错误修正========
现在深入讲解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 Of Data 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