(一)H.264的主要目标
1.高的视频压缩比;2.良好的网络亲和性;
为了完成这些目标H264的解决方案是:
1.VCL video coding layer 视频编码层; 视频编码层,H264编码/压缩的核心,主要负责将视频数据编码/压缩,再切分。 2.NAL network abstraction layer 网络提取层; 网络抽象层,负责将VCL的数据组织打包。
其中,VCL层是对核心算法引擎,宏块及片的语法级别的定义,他最终输出编码完的数据 SODB;
1.压缩:预测(帧内预测和帧间预测)-> DCT变化和量化 -> 比特流编码;
2.切分数据,主要为了第三步。这里一点,网上看到的“切片(slice)”、“宏块(macroblock)”是在VCL中的概念,一方面提高编码效率和降低误码率、另一方面提高网络传输的灵活性。(这块本文将不展开)
3.输出编码完的数据 SODB
NAL层定义片级以上的语法级别(如序列参数集和图像参数集,针对网络传输),
同时支持以下功能:独立片解码,起始码唯一保证,SEI以及流格式编码数据传送,NAL层将SODB打包成RBSP然后加上NAL头,组成一个NALU(NAL单元);
NAL旨在提供“网络友好性”,以便为各种系统简单有效地定制VCL的使用。 NAL有助于将VCL数据映射到传输层,例如: · RTP / IP适用于任何类型的实时有线和无线互联网服务。 · 文件格式,例如用于存储和MMS的ISO MP4。 · H.32X用于有线和无线会话服务。 · 用于广播服务等的MPEG-2系统。
(二)名词解释(和下面没关系,可以忽略)
H.264 中,句法元素共被组织成 序列、图像、片、宏块、子宏块五个层次。
H.264 的码流结构并没有大家想的那么复杂,编码后视频的每一组图像(GOP,图像组)都给予了传输中的序列(PPS)和本身这个帧的图像参数(SPS),所以,我们的整体结构,应该如此:
GOP (图像组)主要用作形容一个 i 帧 到下一个 i 帧之间的间隔了多少个帧,增大图片组能有效的减少编码后的视频体积,但是也会降低视频质量,至于怎么取舍,得看需求了。
注意: GOP是不包括SPS、PPS等NALU的
GOP 就是一段连续图像帧且画面之间变化不大。//当某个帧与前面的帧图像变化很大,无法参考前面帧生成时,就要开始一个新序列。
补充:
宏块:宏块大小通常为16×16像素,分为I、B、P宏块;编码处理的基本单元,由多个块组成。
块:一个编码图像要划分成多个块才能进行处理,一个块是4X4像素
------1.一帧图片跟 NALU 的关联 :
一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice)。<编码阶段,由VCL实现>
而装载着这些片(slice)的载体,就是 NALU 了,我们可以来看看 NALU 跟片的关系(slice)。<由NAL实现>
注意:实际中NAL还要对切片进行处理,然后形成NALU。
片(slice)的概念不同与帧(frame),帧(frame)是用作描述一张图片的,一帧(frame)对应一张图片,而片(slice),是 H.264 中提出的新概念,是通过编码图片后切分通过高效的方式整合出来的概念,一张图片至少有一个或多个片(slice)。
上图中可以看出,片(slice)都是又 NALU 装载并进行网络传输的,但是这并不代表 NALU 内就一定是切片,这是充分不必要条件,因为 NALU 还有可能装载着其他用作描述视频的信息,比如后面提到的SPS、PPS。
------2.什么是切片(slice)?
片的主要作用是用作宏块(Macroblock)的载体(ps:下面会介绍到宏块的概念)。片之所以被创造出来,主要目的是为限制误码的扩散和传输。
如何限制误码的扩散和传输?
每个片(slice)都应该是互相独立被传输的,某片的预测(片(slice)内预测和片(slice)间预测)不能以其它片中的宏块(Macroblock)为参考图像。
1、分片头中包含着分片类型、分片中的宏块类型、分片帧的数量、分片属于那个图像以及对应的帧的设置和参数等信息。 2、分片数据中则是宏块,这里就是我们要找的存储像素数据的地方。
------3.什么是宏块?
宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。 组成部分:
一个宏块由一个16×16亮度像素和附加的一个8×8 Cb和一个 8×8 Cr 彩色像素块组成。
每个图象中,若干宏块被排列成片的形式。
从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern 编码的块模式、Quantization Parameter 量化参数、像素的亮度和色度数据集等等信息。
------4. 切片(slice)类型跟宏块类型的关系
I片:只包 I宏块,I 宏块利用从当前片中已解码的像素作为参考进行帧内预测(不能取其它片中的已解码像素作为参考进行帧内预测)。
P片:可包 P和I宏块,P 宏块利用前面已编码图象作为参考图象进行帧内预测,一个帧内编码的宏块可进一步作宏块的分割:即 16×16、16×8、8×16 或 8×8 亮度像素块(以及附带的彩色像素);如果选了 8×8 的子宏块,则可再分成各种子宏块的分割,其尺寸为 8×8、8×4、4×8 或 4×4 亮度像素块(以及附带的彩色像素)。
B片:可包 B和I宏块,B 宏块则利用双向的参考图象(当前和 来的已编码图象帧)进行帧内预测。
SP片(切换P):用于不同编码流之间的切换,包含 P 和/或 I 宏块
SI片:扩展档次中必须具有的切换,它包 了一种特殊类型的编码宏块,叫做 SI 宏块,SI 也是扩展档次中的必备功能。
二:H264的格式
详见更多:https://blog.csdn.net/qq_42024067/article/details/102292535
H.264的两种打包/封装方法:字节流AnnexB格式 和 AVCC格式 (只有这两种)
其中在https://www.yuque.com/keith-an9fr/aab7xp/vng2pb中介绍的应该是有部分问题!!!
在H264用于网络发送时,要封装成RTP格式!!!
(一)AnnexB格式---用于实时播放
------1.AnnexB流结构:使用start code分隔NAL(start code为三字节或四字节,0x000001或0x00000001,一般是四字节);SPS和PPS按流的方式写在头部。
开始前缀(00000001或000001)+ NALU数据 绝大部分编码器的默认输出格式
NALU 就是 h264的实际数据部分。NALU = NALUHeader+EBSP 组成; EBSP = 防止竞争码+RBSP; RBSP = SODB + RBSP尾部 。
EBSP为扩展字节序列载荷(Encapsulated Byte Sequence Payload) EBSP = RBSP插入防竞争字节(0x03)
RBSP为原始字节序列载荷(Raw Byte Sequence Payload)-------- RBSP = SODB + RBSP Trailing Bits(RBSP尾部补齐字节);引入RBSP Trailing Bits做8位字节补齐。
SODB为原始数据比特流 (String Of Data Bits) ------- 就是最原始的编码/压缩得到的数据。
H264码流结构:(下面两种表示都不错)
一共有两种起始码start_code:
①3字节0x000001 单帧多slice(即单帧多个NALU)之间间隔
②4字节0x00000001 帧之间,或者SPS、PPS等之前
4字节类型的起始码在连续的数据传输中非常有用,因为用字节来对齐、分割流数据,比如:用连续的31个bit 0 后接一个bit 1 来分割流数据,是很容易的。
如果接下来的bit是0(因为每个NALU都以bit0开始),那么这就是一个NALU包数据的起始位置了。4字节类型的开始码通常只用于标识流中的随机访问点,如SPS PPS AUD和IDR,然后其他地方都用3字节类型的开始码以减少数据量。
防止竞争字节(0x03):前面讲到用StartCode的字节串来分割NALU,于是问题来了,如果RBSP中也包括了StartCode(0x000001或0x00000001)怎么办呢?所以,就有了防止竞争字节(0x03):
编码时,扫描RBSP,如果遇到连续两个0x00字节,就在后面添加防止竞争字节(0x03);
解码时,同样扫描EBSP,进行逆向操作即可。
编码如下:
解码操作:
在解码的时候如果在内部遇到0x000003序列时,就可以将其抛弃即可以恢复原始数据。EBSP 去除防止竞争码后就可以得到 RBSP。
------2.NALU Header
上面是截取的 NAL 层的语法头部部分。如果先不考虑语法,可以先如下理解,将第一个字节(1+2+5 正好是一个字节)按 bits 展开。
- 第一位为 forbidden_zero_bit forbidden_zero_bit 禁止位,初始为0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
- 后两位为 nal_ref_idc nal_ref_idc 代表 NALU 的重要性。值越大说明约重要。取值范围0~3,解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU。当前的 NAL 是参考帧,序列集参数集或图像集重要数据时必须大于0。
- 最后五位为 nal_unit_type 指的是当前 NAL 的类型。
1-4:I/P/B帧,合起来介绍的原因是,他们是依据VLC的slice区分的,这块因为本文不涉及,一方面是这个太过于细节,真要展开篇幅太长;另一个原因是就算不了解slice、macroblock也不影响对H264格式的理解。 5:IDR帧。I帧的一种,告诉解码器,之前依赖的解码参数集合(接下来要出现的SPS\PPS等)可以被刷新了。 6:SEI,英文全称Supplemental Enhancement Information,翻译为“补充增强信息”,提供了向视频码流中加入额外信息的方法。 7:SPS,全称Sequence Paramater Set,翻译为“序列参数集”。SPS中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数。 8: PPS,全称Picture Paramater Set,翻译为“图像参数集”。该类型保存了整体图像相关的参数。 9:AU分隔符,AU全称Access Unit,它是一个或者多个NALU的集合,代表了一个完整的帧。
根据以上字段:判断类型
例如上面00000001后有67,68以及65:
其中0x67的二进制码为:0110 0111
4-8为00111,转为十进制7,参考第二幅图:7对应序列参数集SPS
其中0x68的二进制码为:0110 1000
4-8为01000,转为十进制8,参考第二幅图:8对应图像参数集PPS
其中0x65的二进制码为:0110 0101
4-8为00101,转为十进制5,参考第二幅图:5对应IDR图像中的片(I帧)
其中0x41的二进制码为:0100 0001
4-8为00001,转为十进制1,参考第二幅图:1对应非IDR图像中的片(这里指的是P帧)
其中0x61的二进制码为:0110 0001
4-8为00001,转为十进制1,参考第二幅图:1对应非IDR图像中的片(同上,为P帧,仅仅是重要性不同)
其中0x06的二进制码为:0000 0100
4-8为00100,转为十进制6,参考第二幅图:6对应SEI
特殊的NALU类型:SPS和PPS
SPS和PPS存储了编解码需要一些图像参数。
AnnexB格式每个NALU都包含起始码,且通常会周期性的在关键帧之前重复SPS和PPS (在I帧之前)
????????????所以解码器可以从视频流随机点开始进行解码,实时的流格式
I/P/B帧:
I帧(帧内编码帧)是一种自带全部信息的独立帧,无需参考其它图像便可独立进行解码。可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)
视频序列中的第一个帧始终都是I帧。
如果所传输的比特流遭到破坏,则需要将I帧用作新查看器的起始点或重新同步点。
I帧可以用来实现快进、快退以及其它随机访问功能。
1.它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输;
2.解码时仅用I帧的数据就可重构完整图像;
3.I帧描述了图像背景和运动主体的详情;
4.I帧不需要参考其他画面而生成;
5.I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
6.I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧;
7.I帧不需要考虑运动矢量;
8.I帧所占数据的信息量比较大。
P帧(帧间预测编码帧)需要参考前面的I帧和/或P帧的不同部分才能进行编码。
与I帧相比,P帧通常占用更少的数据位,但其缺点是,由于P帧对前面的P和I参考帧有着复杂的依赖性,因此对传输错误非常敏感。
P帧属于前向预测的帧间编码,它只参考 前面最靠近它 的I帧或者P帧。
P帧是以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。
在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。
1.P帧是I帧后面相隔1~2帧的编码帧;
2.P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
3.解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
4.P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧;
5.P帧可以是其后面P帧的参考帧,也可以是其 前后 的B帧的参考帧;
6.由于P帧是参考帧,它可能造成解码错误的扩散;
7.由于是差值传送,P帧的压缩比较高。
B帧:双向预测内插编码帧。
B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别(具体比较复杂,有4种情况,但我这样说简单些),
换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。
1.B帧是由 前面的I或P帧 和 后面的P帧 来进行预测的;
2.B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量;
3.B帧是双向预测编码帧;
4.B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确;
5.B帧不是参考帧,不会造成解码错误的扩散。
I、B、P各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。一般来说,I帧的压缩率是7(跟JPG差不多),P帧是20,B帧可以达到50。可见使用B帧能节省大量空间,节省出来的空间可以用来保存多一些I帧,这样在相同码率下,可以提供更好的画质。
AU分隔:
一个单独的NALU包、或者甚至一个VCL NALU包都不意味着是一个独立的帧,一帧数据可以被分割成几个NALU,一个或多个NALU组成了一个Access Units(AU),AU包含了一个完整的帧。把帧分割成几个独立的NALU需要耗费许多CPU资源,所以分割帧数据并不经常使用。实际上AU分隔不常用:http://www.360doc.com/content/13/0913/15/13084517_314201133.shtml
案例:分析一帧数据(查看了其他视频,好像都是以大端方式存放:高地址放低位,比如 0000 0001,按2字节存放时,就是高地址<越往后越高>放低位01,低地址存放高位00)
0000 0001 6764 000a ac72 8444 2684 0000 0300 0400 0003 00ca 3c48 9611 8000 0000 0168 e843 8f13 2130 0000 0165 8881 0005 4e7f 87df 61a5 8b95 eea4 e938 b76a 306a 71b9 5560 0b76 2eb5 0ee4 8059 27b8 67a9 6337 5e82 2055 fbe4 6ae9 3735 72e2 2291 9e4d ff60 86ce 7e42 b795 ce2a e126 be87 7384 26ba 1636 f4e6 9f17 dad8 6475 54b1 f345 0c0b 3c74 b39d bceb 5373 87c3 0e62 4748 62ca 59eb 863f 3afa 86b5 bfa8 6d06 1650 82c4 ce62 9e4e e64c c730 3ede a10b d883 0bb6 b828 bca9 eb77 43fc 7a17 9485 21ca 376b 3095 b546 7730 60b7 12d6 8cc5 5485 29d8 69a9 6f12 4e71 dfe3 e2b1 6b6b bf9f fb2e 5730 a969 76c4 46a2 dffa 91d9 5074 551d 4904 5a1c d686 687c b661 486c 96e6 124c 27ad bac7 5199 8ed0 f0ed 8ef6 6579 79a6 12a1 95db c8ae e3b6 35e6 8dbc 48a3 7faf 4a28 8a53 e27e 6808 9f67 7798 52db 5084 d65e 25e1 4a99 5834 c711 d643 ffc4 fd9a 4416 d1b2 fb02 dba1 8969 34c2 3255 98f9 9bb2 313f 4959 0c06 8cdb a5b2 9d7e 122f d087 9444 e40a 76ef 992d 9118 3950 3b29 3bf5 2c97 7348 9183 b0a6 f34b 702f 1c8f 3b78 23c6 aa86 4643 1dd7 2a23 5e2c d948 0af5 f52c d1fb 3ff0 4b78 37e9 45dd 72cf 8035 c395 07f3 d906 e54a 5876 036c 8120 6245 6544 73bc fec1 9f31 e5db 895c 6b79 d868 90d7 26a8 a188 8681 dc9a 4f40 a523 c7de be6f 76ab 7916 5121 6783 2ef3 d627 1a42 c294 d15d 6cdb 4a7a e2cb 0bb0 680b be19 5900 50fc c0bd 9df5 f5f8 a817 19d6 b3e9 74ba 50e5 2c45 7bf9 93ea 5af9 a930 b16f 5b36 241e 8d55 57f4 cc67 b265 6aa9 3626 d006 b8e2 e373 8bd1 c01c 5215 cab5 ac60 3e36 42f1 2cbd 9977 aba8 a9a4 8e9c 8b84 de73 f091 2997 aedb afd6 f85e 9b86 b3b3 03b3 ac75 6fa6 1169 2f3d 3ace fa53 8660 956c bbc5 4ef3