Android音视频【五】H265/HEVC码流结构

2024-03-10 19:48

本文主要是介绍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官方文档所示:

h265-nal_unit_type

可以上面的文件数据片段中可以计算出6种NALU的头类型nal_unit_type,取2个字节的2-7位即可。计算方法:

// 0x7E的二进制的后8位是 0111  1110int 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

RBSP的结构

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官方文档:
https://www.itu.int/rec/T-REC-H.265-201911-I/en

这篇关于Android音视频【五】H265/HEVC码流结构的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/795331

相关文章

Oracle查询表结构建表语句索引等方式

《Oracle查询表结构建表语句索引等方式》使用USER_TAB_COLUMNS查询表结构可避免系统隐藏字段(如LISTUSER的CLOB与VARCHAR2同名字段),这些字段可能为dbms_lob.... 目录oracle查询表结构建表语句索引1.用“USER_TAB_COLUMNS”查询表结构2.用“a

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

如何使用Maven创建web目录结构

《如何使用Maven创建web目录结构》:本文主要介绍如何使用Maven创建web目录结构的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录创建web工程第一步第二步第三步第四步第五步第六步第七步总结创建web工程第一步js通过Maven骨架创pytho

Python循环结构全面解析

《Python循环结构全面解析》循环中的代码会执行特定的次数,或者是执行到特定条件成立时结束循环,或者是针对某一集合中的所有项目都执行一次,这篇文章给大家介绍Python循环结构解析,感兴趣的朋友跟随... 目录for-in循环while循环循环控制语句break语句continue语句else子句嵌套的循

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio