H264学习_基本数据结构
原文地址:http://blog.csdn.net/yangzhongxuan/article/details/8003494
名词解释
场和帧 : 视频的一场或一帧可用来产生一个编码图像。在电视中,为减少大面积闪烁现象,把一帧分成两个隔行的场。
片: 每个图象中,若干宏块被排列成片的形式。片分为I片、B片、P片和其他一些片。
I片只包含I宏块,P片可包含P和I宏块,而B片可包含B和I宏块。
I宏块利用从当前片中已解码的像素作为参考进行帧内预测。
P宏块利用前面已编码图象作为参考图象进行帧内预测。
B宏块则利用双向的参考图象(前一帧和后一帧)进行帧内预测。
片的目的是为了限制误码的扩散和传输,使编码片相互间是独立的。
某片的预测不能以其它片中的宏块为参考图像,这样某一片中的预测误差才不会传播到其它片中去。
宏块 : 一个编码图像通常划分成若干宏块组成,一个宏块由一个16×16亮度像素和附加的一个8×8 Cb和一个8×8 Cr彩色像素块组成。
数据之间的关系:
H264结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由16x16的yuv数据组成。宏块作为H264编码的基本单位。
H264编码过程中的三种不同的数据形式:
SODB 数据比特串 ---->最原始的编码数据,即VCL数据;
RBSP 原始字节序列载荷 ---->在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若干比特“0”,以便字节对齐;
EBSP 扩展字节序列载荷 ---- > 在RBSP基础上填加了仿校验字节(0X03)它的原因是: 在NALU加到Annexb上时,需要添加每组NALU之前的开始码StartCodePrefix,如果该NALU对应的slice为一帧的开始则用4位字节表示,ox00000001,否则用3位字节表示ox000001(是一帧的一部分)。另外,为了使NALU主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为0,就插入一个字节的0x03。解码时将0x03去掉。也称为脱壳操作。
H264/AVC 的分层结构
H.264的主要目标是:
1.高的视频压缩比;
2.良好的网络亲和性;
为了完成这些目标H264的解决方案是:
1.VCL video coding layer 视频编码层;
2.NAL network abstraction layer 网络提取层;
其中,VCL层是对核心算法引擎,块,宏块及片的语法级别的定义,他最终输出编码完的数据 SODB;
NAL层定义片级以上的语法级别(如序列参数集和图像参数集,针对网络传输),
同时支持以下功能:独立片解码,起始码唯一保证,SEI以及流格式编码数据传送,NAL层将SODB打包成RBSP然后加上NAL头,组成一个NALU(NAL单元);
H264网络传输的结构
H264在网络传输的是NALU,NALU的结构是:NAL头+RBSP,实际传输中的数据流如图所示:
NALU头用来标识后面的RBSP是什么类型的数据,他是否会被其他帧参考以及网络传输是否有错误。
NALU头结构
长度:1byte
forbidden_bit(1bit) + nal_reference_bit(2bit) + nal_unit_type(5bit)
1.forbidden_bit: 禁止位,初始为0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
2.nal_reference_bit: nal重要性指示,标志该NAL单元的重要性,值越大,越重要,解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU。
不同类型的NALU的重要性指示如下表所示。
nal_unit_type |
NAL类型 |
nal_reference_bit |
0 |
未使用 |
0 |
1 |
非IDR的片 |
此片属于参考帧,则不等于0, 不属于参考帧,则等与0 |
2 |
片数据A分区 |
同上 |
3 |
片数据B分区 |
同上 |
4 |
片数据C分区 |
同上 |
5 |
IDR图像的片 |
5 |
6 |
补充增强信息单元(SEI) |
0 |
7 |
序列参数集 |
非0 |
8 |
图像参数集 |
非0 |
9 |
分界符 |
0 |
10 |
序列结束 |
0 |
11 |
码流结束 |
0 |
12 |
填充 |
0 |
13..23 |
保留 |
0 |
24..31 |
不保留 |
0 |
所谓参考帧,就是在其他帧解码时需要参照的帧。比如一个I帧可能被一个或多个B帧参考,一个B帧可能被某个P帧参考。
从这个表我们也可以看出来,DIR的I帧是非常重要的,他一丢,那么这个序列的所有帧都没办法解码了;
序列参数集和图像参数集也很重要,没有序列参数集,这个序列的帧就没法解;
没有图像参数集,那用到这个图像参数集的帧都没法解。
3.nal_unit_type:NALU类型取值如下表所示。
nal_unit_type |
NAL类型 |
C |
0 |
未使用 |
|
1 |
非IDR图像中不采用数据划分的片段 |
2,3,4 |
2 |
非IDR图像中A类数据划分片段 |
2 |
3 |
非IDR图像中B类数据划分片段 |
3 |
4 |
非IDR图像中C类数据划分片段 |
4 |
5 |
IDR图像的片 |
2,3 |
6 |
补充增强信息单元(SEI) |
5 |
7 |
序列参数集 |
0 |
8 |
图像参数集 |
1 |
9 |
分界符 |
6 |
10 |
序列结束 |
7 |
11 |
码流结束 |
8 |
12 |
填充 |
9 |
13..23 |
保留 |
|
24..31 |
不保留(RTP打包时会用到) |
RTP 打包时的扩展类型
24 | STAP-A | Single-time aggregation packet |
25 | STAP-B | Single-time aggregation packet |
26 | MTAP16 | Multi-time aggregation packet |
27 | MTAP24 | Multi-time aggregation packet |
28 |
FU-A |
Fragmentation unit |
29 | FU-B | Fragmentation unit |
30-31 | undefined |
RBSP
RBSP数据是下表中的一种
RBSP类型 | 所写 | 描述 |
参数集 | PS | 序列的全局信息,如图像尺寸,视频格式等 |
增强信息 | SEI | 视频序列解码的增强信息 |
图像界定符 | PD | 视频图像的边界 |
编码片 | SLICE | 编码片的头信息和数据 |
数据分割 | DP片层的数据,用于错误恢复解码 | |
序列结束符 | 表明一个序列的结束,下一个图像为IDR图像 | |
流结束符 | 表明该流中已没有图像 | |
填充数据 | 亚元数据,用于填充字节 |
从前面的分析我们知道,VCL层出来的是编码完的视频帧数据,
这些帧可能是I、B、P帧,而且这些帧可能属于不同的序列,再者同一个序列还有相对应的一套序列参数集和图片参数集等等,
所以要完成视频的解码,不仅需要传输VCL层编码出来的视频帧数据,还需要传输序列参数集、图像参数集等数据。
参数集:包括序列参数集 SPS 和图像参数集 PPS
SPS 包含的是针对一连续编码视频序列的参数,如标识符 seq_parameter_set_id、帧数及 POC 的约束、参考帧数目、解码图像尺寸和帧场编码模式选择标识等等。
PPS对应的是一个序列中某一幅图像或者某几幅图像,
其参数如标识符 pic_parameter_set_id、可选的 seq_parameter_set_id、熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等等。
数据分割:组成片的编码数据存放在 3 个独立的 DP(数据分割,A、B、C)中,各自包含一个编码片的子集。
分割A包含片头和片中每个宏块头数据。
分割B包含帧内和 SI 片宏块的编码残差数据。
分割 C包含帧间宏块的编码残差数据。
每个分割可放在独立的 NAL 单元并独立传输。
NAL的开始和结束
编码器将每个NAL各自独立、完整地放入一个分组,因为分组都有头部,解码器可以方便地检测出NAL的分界,并依次取出NAL进行解码。
每个NAL前有一个起始码 0x00 00 01(或者0x00 00 00 01),解码器检测每个起始码,作为一个NAL的起始标识,当检测到下一个起始码时,当前NAL结束。
同时H.264规定,当检测到0x000000时,也可以表征当前NAL的结束。那么NAL中数据出现0x000001或0x000000时怎么办?H.264引入了防止竞争机制,如果编码器检测到NAL数据存在0x000001或0x000000时,编码器会在最后个字节前插入一个新的字节0x03,这样:
0x000000->0x00000300
0x000001->0x00000301
0x000002->0x00000302
0x000003->0x00000303
解码器检测到0x000003时,把03抛弃,恢复原始数据(脱壳操作)。解码器在解码时,首先逐个字节读取NAL的数据,统计NAL的长度,然后再开始解码。
NALU的顺序要求
H.264/AVC标准对送到解码器的NAL单元顺序是有严格要求的,如果NAL单元的顺序是混乱的,必须将其重新依照规范组织后送入解码器,否则解码器不能够正确解码。
1.序列参数集NAL单元
必须在传送所有以此参数集为参考的其他NAL单元之前传送,不过允许这些NAL单元中间出现重复的序列参数集NAL单元。
所谓重复的详细解释为:序列参数集NAL单元都有其专门的标识,如果两个序列参数集NAL单元的标识相同,就可以认为后一个只不过是前一个的拷贝,而非新的序列参数集。
2.图像参数集NAL单元
必须在所有以此参数集为参考的其他NAL单元之前传送,不过允许这些NAL单元中间出现重复的图像参数集NAL单元,这一点与上述的序列参数集NAL单元是相同的。
3.不同基本编码图像中的片段(slice)单元和数据划分片段(data partition)单元在顺序上不可以相互交叉,即不允许属于某一基本编码图像的一系列片段(slice)单元和数据划分片段(data partition)单元中忽然出现另一个基本编码图像的片段(slice)单元片段和数据划分片段(data partition)单元。
4.参考图像的影响:如果一幅图像以另一幅图像为参考,则属于前者的所有片段(slice)单元和数据划分片段(data partition)单元必须在属于后者的片段和数据划分片段之后,无论是基本编码图像还是冗余编码图像都必须遵守这个规则。
5.基本编码图像的所有片段(slice)单元和数据划分片段(data partition)单元必须在属于相应冗余编码图像的片段(slice)单元和数据划分片段(data partition)单元之前。
6.如果数据流中出现了连续的无参考基本编码图像,则图像序号小的在前面。
7.如果arbitrary_slice_order_allowed_flag置为1,一个基本编码图像中的片段(slice)单元和数据划分片段(data partition)单元的顺序是任意的,如果arbitrary_slice_order_allowed_flag置为零,则要按照片段中第一个宏块的位置来确定片段的顺序,若使用数据划分,则A类数据划分片段在B类数据划分片段之前,B类数据划分片段在C类数据划分片段之前,而且对应不同片段的数据划分片段不能相互交叉,也不能与没有数据划分的片段相互交叉。
8.如果存在SEI(补充增强信息)单元的话,它必须在它所对应的基本编码图像的片段(slice)单元和数据划分片段(data partition)单元之前,并同时必须紧接在上一个基本编码图像的所有片段(slice)单元和数据划分片段(data partition)单元后边。假如SEI属于多个基本编码图像,其顺序仅以第一个基本编码图像为参照。
9.如果存在图像分割符的话,它必须在所有SEI 单元、基本编码图像的所有片段slice)单元和数据划分片段(data partition)单元之前,并且紧接着上一个基本编码图像那些NAL单元。
10.如果存在序列结束符,且序列结束符后还有图像,则该图像必须是IDR(即时解码器刷新)图像。序列结束符的位置应当在属于这个IDR图像的分割符、SEI 单元等数据之前,且紧接着前面那些图像的NAL单元。如果序列结束符后没有图像了,那么它的就在比特流中所有图像数据之后。
11.流结束符在比特流中的最后。
h264有两种封装,
一种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中
一种是mp4模式,一般mp4 mkv会有,没有startcode,SPS和PPS以及其它信息被封装在container中,每一个frame前面是这个frame的长度
很多解码器只支持annexb这种模式,因此需要将mp4做转换:
在ffmpeg中用h264_mp4toannexb_filter可以做转换
实现:
注册filter
avcbsfc = av_bitstream_filter_init("h264_mp4toannexb");
转换bitstream
av_bitstream_filter_filter(AVBitStreamFilterContext *bsfc,
AVCodecContext *avctx, const char *args,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size, int keyframe)
H264学习_基本数据结构相关推荐
- python的基本数据结构_Python学习笔记——基本数据结构
列表list List是python的一个内置动态数组对象,它的基本使用方式如下: shoplist = ['apple', 'mango', 'carrot', 'banana'] print 'I ...
- DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台、刷题集合、问题为导向的十大类刷题算法(数组和字符串、栈和队列、二叉树、堆实现、图、哈希表、排序和搜索、动态规划/回溯法/递归/贪心/分治)总
DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台.刷题集合.问题为导向的十大类刷题算法(数组和字符串.栈和队列.二叉树.堆实现.图.哈希表.排序和搜索.动态规划/回溯法/递归/贪心/分治)总 ...
- python ui bs_Guibs的Python学习_列表
Guibs 的 Python学习_列表# 列表# 列表由一系列按特定顺序排列的元素组成, 其中元素和元素之间可以没有任何关系 # 在 Python 中, 用方括号 [] 来表示列表, 并用逗号 , 分 ...
- PyTorch框架学习二——基本数据结构(张量)
PyTorch框架学习二--基本数据结构(张量) 一.什么是张量? 二.Tensor与Variable(PyTorch中) 1.Variable 2.Tensor 三.Tensor的创建 1.直接创建 ...
- AVI音视频封装格式学习(三)——AVI 数据结构解析
这里介绍AVI会使用到的数据结构,为了避免翻译引入歧义,决定该部分还是使用英文原文,如后续有时间再进行翻译. AVIMAINHEADER structure The AVIMAINHEADER str ...
- 【Python基础学习】基本数据结构:列表、元组、栈、字典、集合与队列
[Python基础学习]基本数据结构:列表.元组.栈.字典.集合与队列 Python的基本数据结构中,包含了列表.元组等一系列数组式数据结构,但各个结构各有不同.因此单独列出来,分析相同与不同 列表( ...
- C1认证学习十三(数据结构常识)
C1认证学习十三(数据结构常识) 任务背景 数据结构是计算机中存储.组织数据的方式,他研究如何构造复杂的软件系统,它的核心是如何分解以及抽象,并且得到软件开发过程中的所需要的逻辑结构. 任务目标 1. ...
- python学习_循环语句
python学习_循环语句 第1关:斐波那契数列 斐波那契数列(Fibonacci sequence),又称黄金分割数列. 因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子 ...
- linux系统编程学习_(2)进程控制-- fork函数、exec函数族、回收子进程--孤儿进程僵尸进程、wait函数
linux系统编程学习_(2)进程控制-- fork函数.exec函数族.回收子进程–孤儿进程僵尸进程.wait函数 进程控制 fork()函数 创建一个子进程. pid_t fork(void); ...
最新文章
- 博士生宿舍条件太好,本科生因疫情暂住惊到结巴!网友:不然咱读个博?
- ios5 中文键盘高度变高覆盖现有ui问题的解决方案(获取键盘高度的方法)
- Java程序员进阶的 3 个层次,你处于哪个?
- 网站如何启用SSL安全证书?IIS7启用新建Https:/
- Pytorch基础(一) —— tensorboard的应用
- ASP.NET Core中使用MediatR实现命令和中介者模式
- 宁波python学习_python学习第五天
- 关于HTML的FORM上传文件问题
- JavaScript(三)数据类型转换
- JS有哪几种传参方式?
- PHP non-thread-safe和thread-safe这两个版本的区别
- java全世界各国城市地址解析
- 【Windows 问题系列第 5 篇】常见电脑蓝屏的解决办法
- polyval matlab 怎么用,matlab 中polyval的用法 最好能举个例子
- 超硬核!十万字c++题,让你秒杀老师和面试官(上)
- Window 错误代码大全
- linu重置root密码(CentOS7)
- 关于若依管理系统配置多数据源的原理分析
- 什么是APP封装?APP封装有什么途径?
- java中view是什么_深入理解Android中View
热门文章
- 再现神人!仅仅只花4天半就解开了史上最难密码,这下整个圈子都炸开了.........
- 大牛逝世 = 新人上位 = 科学进步?新研究表明确实如此
- 程序猿专属成语 get√
- 女生转行IT与男生有什么不一样?
- 程序员,为什么给你50万年薪,你还要搞死我公司?
- 国内各大平台的推荐算法,看到360的时候笑喷了……
- 加密货币的时代,真的来临了吗?
- 逻辑回归算法背后的数学
- java注解 sql_mybatis中注解映射SQL示例代码
- $.ajax datatype默认是什么类型,理解jquery ajax中的datatype属性选项值