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

相关文章

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

结构体和联合体的区别及说明

《结构体和联合体的区别及说明》文章主要介绍了C语言中的结构体和联合体,结构体是一种自定义的复合数据类型,可以包含多个成员,每个成员可以是不同的数据类型,联合体是一种特殊的数据结构,可以在内存中共享同一... 目录结构体和联合体的区别1. 结构体(Struct)2. 联合体(Union)3. 联合体与结构体的

通过C#和RTSPClient实现简易音视频解码功能

《通过C#和RTSPClient实现简易音视频解码功能》在多媒体应用中,实时传输协议(RTSP)用于流媒体服务,特别是音视频监控系统,通过C#和RTSPClient库,可以轻松实现简易的音视... 目录前言正文关键特性解决方案实现步骤示例代码总结最后前言在多媒体应用中,实时传输协议(RTSP)用于流媒体服

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

PostgreSQL如何查询表结构和索引信息

《PostgreSQL如何查询表结构和索引信息》文章介绍了在PostgreSQL中查询表结构和索引信息的几种方法,包括使用`d`元命令、系统数据字典查询以及使用可视化工具DBeaver... 目录前言使用\d元命令查看表字段信息和索引信息通过系统数据字典查询表结构通过系统数据字典查询索引信息查询所有的表名可

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s