Android音视频【五】H265/HEVC&码流结构
人间观察
我好像还什么都没有准备好,就到了而立之年的年纪,不是吃一个糖就能开心的年纪了。
前面我们了解了H264/AVC
的一些知识。今天我们看H265
, 只有了解了这些基础的,什么协议(flv等)啦,什么封装格式(mp4等)啦,网络传输啦等都是很有帮助的。
背景知识
H265 又被叫做HEVC
(全称叫做 Hight Efficiency Video Coding,高效率视频编码),它同H264一样也是ITU-T和ISO两个组织共同制定的视频压缩标准,是H264/AVC标准的继承者。
H265/HEVC
是面向更高清晰度、更高帧率、更高压缩率视频的协议标准,是一套标准组织制定的视频压缩标准、规范。
为什么会有h265
说白了就是人们对视频的要求清晰度越来越高,而在有限的带宽下保证视频的质量是很难的。对于2k,4k的高清视频来说在存储空间和网络带宽有限的情况下,现有的视频压缩技术已经不能满足现实的应用需求。为了解决高清及超高清视频急剧增长的数据率给网络传输和数据存储带来的冲击,所以有了具有更高压缩效率的新一代视频压缩标准H265/HEVC。
标清:480x800
普通高清:720x1280 720P
高清:1920x1080 1080P
2K:2048*1024
超高清 4K:3840x2160
嗯,说白了,就是需求导致,然后产生的标准。ok,我们看一下技术上:
(1) 视频画面要求更高(如上),视频流畅度要求更高,帧率从30fps到60fps甚至更高,会导致H264的如下情况:
- 宏块(MB)个数爆发式增加
- 宏块内容的复杂度升高
- 运动矢量数值的大幅度增加
(2) 由于H264本身的缺点
由于宏块级压缩视频的处理过程,很久没有改变了,还是2003年发布的实现方式,它的压缩算法没有调整。
H265延续了H264的很多定义,两个都是基于宏块的视频编码技术,h265是在H264的基础上进行了一些强优化。比如:
- 以宏块来划分图像,并最终以块来细分。
- 使用帧内压缩技术减少空间冗余
- 使用帧间压缩技术减少时间冗余(运动估计和运动补偿)
- 使用转换和量化来进行残留数据压缩
- 使用熵编码来减少残留,运动矢量传输和信号发送中的最后冗余。
上面的描述太过于官方了,但是我这里还是参考一些书籍写下了(怕有遗漏造成理解偏差/知识的丢失就不好了),上面的这些技术都是编码器的内部实现,都是算法。我也知道这些概念而已,至于如何实现的我还清楚,我觉得我还没有到达到这一步,前几天看到了阿里淘系的音视频团队有自己的编码器实现,叫奇点编码器,还是觉得很厉害的。
H265的码流结构
H265 的码流结构和H264的结构类似
网络分层结构
和H264/AVC
结构类似,H265/HEVC
也采用了视频编码层(video code layer ,简称VCL)和网络适配层(network abstract layer,简称NAL).VCL层包含了视频压缩的数据, NAL主要负责对数据的压缩数据进行划分和封装,保证数据在磁盘上保存和网络上进行传输。
和h264的码流结构一样,也是通过启始码(0x000001
或者0x00000001
)进行分割压缩数据,每一个称为NAL单元(NAL Unit,简称NALU
)。NALU有不同的类型,主要是对数据内容进行区分。
对于一个码流文件来说,和h264一样,有一系列的NALU的类型定义,可以分为VPS,SPS,PPS,SEI,I帧,P帧 6种类型。码流结构如下所示:
启始码+VPS+启始码+SPS+启始码+PPS+启始码+SEI+启始码+I帧+启始码+P帧+启始码+P帧+.....
如上就是一个图像系列的组成,为什么这么说呢? 一般我们在网络上发送数据,比如采集端一般在发送压缩数据的I帧前先发送VPS,SPS,PPS。解码端不可能先启动后等着发送端数据到来吧,只有解码器拿到VPS,SPS,PPS后才可以解码H265的数据。VPS,SPS,PPS,SEI,一个I帧,一个P帧都可以常委一个NALU。
从上面可以看到h265比h264多了一个VPS,VPS是视频参数集。
我们这里看一下经过h265编码器编码后的码流文件,截取文件开头的数据,
因为h265码流最开始永远是VPS,SPS,PPS,可能含有SEI,后面接着是I帧P帧数据。
16进制打开文件如下:
0000 0001 4001 0c01 ffff 0160 0000 0300 // 4001
b000 0003 0000 0300 5aac 0900 0000 0142 // 4201
0101 0160 0000 0300 b000 0003 0000 0300
5aa0 0442 00f0 77e5 aee4 c92e a520 a0c0
c05d a142 5000 0000 0144 01c0 e30f 0330 // 4401
840a 0000 0001 2601 af0b e075 8d53 b010 // 2601
af65 bfb4 0b53 823d e91c ad66 f973 ce21
5d92 9227 9159 3dc6 2cae 5adf 4cda f9b5
6105 3165 97cd 64cd f04d 09d5 5e10 d231
// ...省略其它数据
2f04 c9cc 1e01 700a 0000 0001 0201 d08f // 0201
// ...省略其它数据
单元NALU的结构
可以看到上面的数据和h264一样,H265的NALU的结构也是:启始码+ NALU头+NALU数据。
如果NALU对应的Slice为一帧的开始(即视频流的首个NALU)就用0x00000001
,否则就用0x000001
。
- 启始码:是一个固定值4个字节
00 00 00 01
(十六进制)或者3个字节00 00 01
(十六进制) - NALU的头大小为2个字节,第1位是0,第2-7位是NALU的类型,表示该NALU的数据内容是什么类型的,是VPS,SPS,PPS,SEI,I帧还是P帧。第8-15位是1
- NALU的数据就是编码器编出来的图像信息或者图像压缩数据了
NALU的nal_unit_type
官方文档所示:
可以上面的文件数据片段中可以计算出6种NALU的头类型nal_unit_type
,取2个字节的2-7位即可。计算方法:
// 0x7E的二进制的后8位是 0111 1110
int naluType = (byteOffset & 0x7E) >> 1
byteOffset就是00 00 00 01
或者00 00 01
后面的2个字节。
- VPS(视频参数集)NALU的头值为0x4001(十六进制),取出2-7位
(40 & 0x7E)>>1 =32
(十进制) - SPS(序列参数集)NALU的头值为0x4201(十六进制),取出2-7位
(42 & 0x7E)>>1 =33
(十进制) - PPS(图像参数集)NALU的头值为0x4401(十六进制),取出2-7位
(44 & 0x7E)>>1 =34
(十进制) - SEI(补充增强信息)NALU的头值为0x4e01(十六进制),取出2-7位
(4e & 0x7E)>>1 =39
(十进制) - I帧 NALU的头值为0x2601(十六进制),取出2-7位
(26 & 0x7E)>>1 =19
(十进制) - P帧 NALU的头值为0x0201(十六进制),取出2-7位
(02& 0x7E)>>1 =1
(十进制)
NALU的类型官方文档所示:
H265-nalu-type.pngRBSP的结构
H265的 RBSP(raw byte sequence payload)和H264的一样。
NAL 根据送压缩数据的规则,可以封装称不同的NALU, NALU包含VPS,SPS,PPSl类型信息,还包含视频片(Slice)的压缩数据,包含压缩的NALU被称为VCLU(VCL NALU),包含其它信息的压缩数据的NALU,则被称为non-VCLU(non-VCL NALU)。
H265下的NALU包含两部分数据结构:NALU头(header)和负载(payload),NALU头长度为固定的2字节,反应NALU的内容特征,而NALU的负载长度为整数字节,包含视频压缩后的原始字节序列负载RBSP(raw byte sequence payload)。RBSP是对视频 编码后的原始比特流片段SODB(string of data bits)进行添加尾部(添加比特1,以凑足整字节)的包装。
同样在H265中,为了避免字节流片段和NALU的启起码及结束码发生冲突,需要对RBSP的字节流进行冲突处理0x3
,经过处理后的rbsp才可以直接作为NALU的负载信息,才可以进程磁盘保存和网络传输。
关于冲突和RBSP的结构的结构可以参考之前的h264码流分析文章:
Android音视频【二】 H264码流结构
H265远比此篇介绍的复杂的多,如果哪里不正确,欢迎指正。
H265官方文档,可以下载里面的pdf文件:
https://www.itu.int/rec/T-REC-H.265-201911-I/en