【H.264】一文搞懂H.264协议


<> 博客简介:Linux、rtos系统,arm、stm32等芯片,嵌入式高级工程师、面试官、架构师,日常技术干货、个人总结、职场经验分享

<> 公众号:嵌入式技术部落

<> 系列专栏:C/C++、Linux、rtos、嵌入式开发、流媒体、数据结构、网络协议、开源库、CMake、Makefile、架构设计模式等

一、H.264 协议概述

H.264 协议介绍

H.264,是一种视频压缩编码标准 ,同时也是MPEG-4第10部分规范(ISO/IEC 14496-10),MPEG-4 Part 10,⼜叫Advanced Video Codec(高级视频编码),因此H.264常常称为MPEG-4 AVC或直接叫AVC。所以在H.264的参数中可以看到 AVC == H264,而 HECV == H265。H.264已经成为高精度视频录制、压缩和发布的最常用格式之一。常见的写法H264,标准写法应该是H.264。

H.264 协议目的

H.264/AVC 协议的目的是为了创建一个更佳的视频压缩标准,在更低的比特率的情况下依然能够提供良好视频质量的标准(如,一半或者更少于MPEG-2,H.263,或者MPEG-4 Part2 )。同时,还要不会太大的增加设计的复杂性。H.264的另外一个目标是提供足够的灵活性,以允许该标准能够应用于各种各样的网络和系统的各应用上,包括低和高比特率,低和高分辨率视频,广播,DVD存储,RTP / IP分组网络和ITU-T多媒体电话系统。

二、H.264 协议解析

H.264码流

H.264码流是指按照H.264标准编码的视频数据流。H.264码流只存放了原始的视频数据,而不包含其他信息。例如,我们通常说的视频要包含音频,而H.264码流里就没有。除了音频之外,我们通常说的视频里还存放着其他辅助信息,比如视频的播放时长,每一帧的播放时间、帧率、画面是否旋转等等很多信息,而H.264码流里也没有。所以严格来说,H.264码流和我们通常意义上的“视频”还是有一定差距的。

H.264编码标准从码流功能的角度主要分为两个层次:视频编码层(Video Coding Layer, VCL)和 网络抽象层(Network Abstraction Layer, NAL) 。下图为H.264的分层结构图
在这里插入图片描述

视频编码层(Video Coding Layer, VCL)

视频编码层负责将原始视频数据进行编码,它定义了如何将原始视频数据压缩成更小的格式,VCL使用多种编码工具和技术来实现高效的压缩。

网络抽象层(Network Abstraction Layer, NAL)

网络抽象层负责将视频编码层产生的比特流组织成适合传输和存储的格式。它的主要任务是将编码数据打包成网络抽象层单元(Network Abstraction Layer Unit,NALU),这些单元可以在不同的网络和存储介质上进行传输和存储。

简单的说,视频编码层将原始视频数据压缩编码,网络抽象层将压缩编码后的数据打包成NALU(网络抽象层单元)来进行传输或存储。

码流组织方式

多个NALU在网络中传输或者写到一个文件里的时候,多个NALU首位相连成一串,因为NALU本身长度不一,也没有具体的标识符用来表明自己是一个独立的NALU,那么我们在从网络上接收到数据或者读取这个文件的时候,其实没有办法将在一起的NALU有效的进行区分。为了解决这个问题,我们必须在H264码流中将NALU进行分割,以便将在一起的NALU有效的进行区分,这种分割的方式就是H.264码流的组织方式。目前H264主流的码流组织方式有两种:AnnexB和AVCC两种格式。

AnnexB

AnnexB的格式比较简单,每个NALU之间通过分隔符0x00 00 00 01或者0x00 00 01区分不同的NALU。当我们读取一个 H264 流的时候,一旦遇到0x00 00 00 01或者 0x00 00 01,我们就认为一个新的 NALU 开始了。这些用来做分隔符的字节,一般被称为 start code, 起始码。
在这里插入图片描述

起始码(start code)

起始码有两种模式,四字节模式(0x00 00 00 01)和三字节模式(0x00 00 01)。一路H.264码流中NALU的起始码可能都是0x00 00 00 01,也可能都是0x00 00 01,同样也有可能既有0x00 00 00 01也有0x00 00 01。

但是只在 NALU 前面加上起始码还是会产生问题的,因为原始码流中,是有可能出现 0x00 00 00 01 或者 0x00 00 01 的,这样就会导致读取程序将一个 NALU 误分割成多个 NALU。为了防止这种情况发生,AnnexB 引入了防竞争字节(Emulation Prevention Bytes)的概念。
在这里插入图片描述

防竞争字节 (Emulation Prevention Bytes)

防竞争字节,就是在给 NALU 添加起始码之前,先对码流进行一次遍历,查找码流里面的存在的 0x00 00 00,0x00 00 01,0x00 00 02,0x00 00 03 的字节,然后对其进行如下修改

00 00 00 => 00 00 03 00
00 00 01 => 00 00 03 01
00 00 02 => 00 00 03 02
00 00 03 => 00 00 03 03

即在 00 00 之后,插入一个字节,内容是 03。经过这样处理的码流,就不会再和起始码(0x00 00 01, 0x00 00 00 01)重复而发生冲突。
在这里插入图片描述

当然,在解码过程中,通过起始码成功分割 NALU 数据之后,还要将防竞争字节去掉。

00 00 03 00 => 00 00 00
00 00 03 01 => 00 00 01
00 00 03 02 => 00 00 02
00 00 03 03 => 00 00 03
AVCC

AnnexB 的原理是在每个 NALU 前面写上一个特殊的起始码,通过这个起始码来当做 NALU 的分隔符,从而分割每个 NALU。
而 avcC 则采用了另外一种方式,那就是每一个NALU包都加上了一个指定NALU长度的前缀,前缀这几个字节(大端字节序)转成一个整数表示整个 NALU 的长度,我们不妨把这个前缀叫做 NALU Length。指定前缀字节数的值保存在一个头部对象中(流开始的部分),这个头通常称为 extradata 或者 sequence header 。

在读取的时候,先把这个extradata读出来,解析得到NALU Length的字节数,NALU Length这几个字节转成一个整数即为NALU Length的值,然后按照长度值读取整个 NALU。

它的基本格式如下:
在这里插入图片描述

extradata

在一路采用 avcC 打包的 H.264 流中,我们首先看到的将是 extradata 的数据,我们来看一下 extradata 数据格式

长度(bits)名称备注
8versionalways 0x01
8avc profilesps[0][1] 所存放第一个 SPS 的第一个字节
8avc compatibilitysps[0][2] 所存放第一个 SPS 的第二个字节
8avc levelsps[0][3] 所存放第一个 SPS 的第三个字节
6reserved保留字段
2NALULengthSizeMinusOneNALU Length 数据的长度减去 1,单位字节数
3reserved保留字段
5number of SPS NALUs有几个 SPS,一般情况下这里是 1
for(int i=0; i<number of SPS NALUs; i++){~
16  SPS sizeSPS 的长度
变长  SPS NALU dataSPS NALU 的数据
}~
8number of PPS NALUs有几个 PPS,一般情况下这里是 1
for(int i=0; i<number of PPS NALUs; i++){~
16  PPS sizePPS 的长度
变长  PPS NALU dataPPS NALU 的数据
}~

注: avcC 允许存多个 PPS 和 SPS。在 AnnexB 中,SPS 和 PPS 被当做了普通的 NALU 进行处理;而在 avcC 中,SPS 和 PPS 被当做特殊的信息进行了处理。

NALU Length

extradata中有一个变量NALULengthSizeMinusOne,这个变量告诉我们用几个字节来存储NALU的长度。我们注意一下上表中的这个值 NALULengthSizeMinusOne 这个字段长度是 2 个 bit,取值范围为0~3。NALULengthSizeMinusOne翻译一下为"NALU长度减1",单位是字节数,即 NALU Length 的字节数 - 1 = NALULengthSizeMinusOne ,通过将 NALULengthSizeMinusOne 加 1 ,我们就得出了后续每个 NALU 的 前缀 NALU Length 的字节数。

例如,这个 NALULengthSizeMinusOne 是 3,那么每个 NALU 的 NALU Length 长度就是 4 个字节。我们在读取后续数据时,可以先读 4 个字节,然后把这四个字节转成整数,就是这个 NALU 的长度了,注意,这个长度并不包含前缀 NALU Length的4个字节,是单纯 NALU 的长度。
在这里插入图片描述

AVCC和AnnexB对比

AnnexB格式:

1、AnnexB格式使用起始码(start code)作为NALU的分隔符。

2、AnnexB格式通常用于实时的流格式,比如说传输流、实时播放、通过无线传输的广播、DVD等。在这些格式中通常会周期性的重复SPS和PPS包,经常是在每一个关键帧之前。因此据此建立解码器可以一个随机访问的点,这样就可以加入一个正在进行的流,及播放一个已经在传输的流。

3、适合网络流。

AVCC格式:

1、AVCC格式在每个NALU之前包含了长度信息,可以直接根据长度信息来定位和提取NALU。

2、AVCC格式也叫AVC1格式,MPEG-4格式,常用于mp4/flv等封装中。AVCC格式的一个优点是在开始配置解码器的时候可以跳到流的中间播放,这种格式通常用于可以被随机访问的多媒体数据,如存储在硬盘的文件。解码器配置参数在一开始就配置好了,系统可以很容易的识别NALU的边界,不需要额外的起始码,减少了资源的浪费,同时可以在播放时调到视频的中间位置。这种格式通常被用于可以被随机访问的多媒体数据,如存储在硬盘的文件。MP4、MKV通常用AVCC格式来存储。

3、适合存储文件。

NALU的作用

NALU(Network Abstraction Layer Unit,网络抽象层单元)在H.264编码标准中起着至关重要的作用,是构建H.264码流的基本单元。NALU的主要作用和功能包括以下几个方面:

1、数据封装与传输
NALU将H.264编码后的视频数据进行封装,使其适用于各种网络传输环境。NALU为编码后的数据提供了一种标准化的格式,使得这些数据可以在不同的网络和设备之间传输。

2、 数据结构化
NALU帮助将视频数据结构化。H.264编码产生的原始视频数据通常会较大且复杂,NALU将这些数据分割成更小、更易管理的单元。

3、网络适应性
NAL头(NAL Header)中包含的参数使得视频数据能够适应不同的网络传输条件。例如,在一个丢包率较高的网络环境中,可以根据NAL头中的信息进行适当的错误恢复和数据纠错。

4、数据同步
NALU有助于在解码过程中进行数据同步。例如,特殊类型的NALU,如同步代码(SPS, Sequence Parameter Set)和图像参数集(PPS, Picture Parameter Set),提供了视频编码参数的信息,使得解码器能够正确地解码视频数据。

5、错误恢复
在网络传输中视频数据可能会丢失或损坏,NALU中的结构化信息和头部信息可以帮助接收端进行错误检测和恢复,提高传输的鲁棒性。

6、适应不同封装格式
H.264视频数据可以封装在不同的容器格式中,如MP4、MKV、FLV等。NALU提供了封装这些视频数据的基本单元,使得编码后的数据能够轻松地适应不同的封装格式。

通过使用NALU,H.264编码标准能够实现高效的视频数据压缩和灵活的网络传输,满足各种应用场景的需求。

NALU的组成

一个NALU = NALU Header + NALU 主体
在这里插入图片描述

NALU 主体

NALU的主体涉及到三个重要的名词,分别为EBSP、RBSP和SODB。其中EBSP完全等价于NALU主体,而且它们三个的结构关系为:EBSP包含RBSP,RBSP包含SODB。

EBSP(Encapsulated Byte Sequence Payload,扩展字节序列载荷)

在 H.264 的文档中,并没有 EBSP 这一名词出现,但是在 H.264 的官方参考软件 JM 里,却使用了 EBSP。NALU 的组成部分为:NALU = NALU Header + RBSP,其实严格来说,这个等式是不成立的,因为RBSP并不等于NALU刨去NALU Header。严格来说,NALU的组成部分应为:NALU = NALU Header + EBSP。
在这里插入图片描述

RBSP(Raw Byte Sequence Payload,原始字节序列载荷)

RBSP 是 H.264 标准中的概念,要说 RBSP,我们就要提到一个我们之前提到的概念,防竞争字节 (Emulation Prevention Bytes)。EBSP 相较于 RBSP,多了防竞争字节:0x03。

我们知道, H.264 在编码的时候,会加上 0x03 的字节来防止和 start code 产生冲突。而在读取到 NALU 之后,我们就要把编码时候加上的防竞争字节去掉。NALU 去掉 NALU Header 就是 EBSP,接着再去掉防竞争字节之后的数据,就叫做 RBSP。
在这里插入图片描述

SODB(String of Data Bits,数据比特串)

SODB是一种以比特为单位的数据表示形式。RBSP 相较于 SOBP,多了tariling bits。

一个SODB可以包含任意数量的比特,通常用于表示二进制数据流。视频在编码的时候,会将一比特一比特的数据写入到码流里面,而在写完之后,就有可能发生一种情况,就是写入的数据数量不满一个字节。
在这里插入图片描述
可以看到上图中数据共 19 bit,最后多出来 3 bit 的数据,不满一个字节,这在后续操作中会很不方便,所以在构建 RBSP 的时候,会将最后不满一个字节的情况进行补齐。

补齐的规则是先写入 1 bit 数据,数据内容是 1,然后开始补齐 0,直到补齐到一整个字节。
在这里插入图片描述
而在解码的时候,也要把补齐的部分去掉,RBSP 去掉补齐的数据后,就是 SODB。

这里其实还有另外一种情况,那就是你的码流在写入的时候,刚刚好写满了一整个字节,那么这种情况下拖尾内容要怎么处理呢?这种的拖尾系数,是新增加一个字节。新增加的字节第一位是 1,后面都是 0。
在这里插入图片描述
NALU、EBSP、RBSP和SODB的关系:
NALU - NALU Header = EBSP
EBSP - 防竞争字节 = RBSP
RBSP - 补齐的数据 = SODB
在这里插入图片描述

NALU Header

NALU Header占一个字节大小,NALU Header的结构如下:
在这里插入图片描述
我们可以看到,forbidden_zero_bit 占用 1 位,nal_ref_idc 占用 2 位,nal_unit_type 占用 5 位。三个元素一共占用 8 位,也就是一个字节。

forbidden_zero_bit

在网络传输中发生错误时,会被置为1,告诉接收方丢掉该单元,否则为0。

nal_ref_idc

用于表示当前NALU的重要性,值越大,越重要。解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU。可能的值有 4 个,详情如下表。

nal_ref_idc重要性
3HIGHEST
2HIGH
1LOW
0DISPOSABLE
nal_unit_type

表示NALU数据的类型,有以下几种
在这里插入图片描述
1-4:I/P/B帧,它们是依据VLC的slice区分的。
5:IDR帧。立即解码刷新单元,可以独立解码和显示,即一种特殊的I帧I,告诉解码器,之前依赖的解码参数集合(接下来要出现的SPS\PPS等)可以被刷新了。
6:SEI,英文全称Supplemental Enhancement Information,翻译为“补充增强信息”,提供了向视频码流中加入额外信息的方法。
7:SPS,全称Sequence Paramater Set,翻译为“序列参数集”。SPS中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数,比如profile level 分辨率和帧率等。
8: PPS,全称Picture Paramater Set,翻译为“图像参数集”。该类型保存了整体图像相关的参数,包含有关熵编码模式、分片组、运动预测和去块滤波器等信息。
9:AU分隔符,AU全称Access Unit,它是一个或者多个NALU的集合,代表了一个完整的帧。

片(Slice)

片是 H.264 提出的新概念,实际的原始视频图像数据保存在NALU的 VCL 层级中,这部分数据在码流中被称作是片(slice)。
片(slice)的概念不同与帧(frame),帧(frame)是用作描述一张图片的,一帧(frame)对应一张图片,而片(slice)是通过编码图片后切分图片通过高效的方式整合出来的概念。
一个 slice 包含一帧图像的部分或全部数据,换言之,一帧视频图像可以编码为一个或若干个 slice。在不同的编码实现中,同一帧图像中所构成的 slice 数目不一定相同。
一个 slice 编码之后被打包进一个 NALU,所以一个NALU包含一个slice。

一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice)。这属于编码阶段,由VCL实现。
在这里插入图片描述
而装载着这些片(slice)的载体,就是 NALU 了,我们可以来看看 NALU 跟片的关系(slice)。这部分由NAL实现。
在这里插入图片描述
上图中可以看出,片(slice)都是由 NALU 装载并进行网络传输的,但是这并不代表 NALU 内就一定是切片,这是充分不必要条件,因为 NALU 还有可能装载着其他用作描述视频的信息,比如前面提到的SPS、PPS。

片的主要作用是用作宏块(Macroblock)的载体。一个 slice 最少包含一个宏块,最多包含整帧图像的宏块。我们可以理解为一帧图像可以编码为一个或多个片(Slice),而每一个片(Slice)最少包含一个宏块,最多包含整帧图像的宏块。

片的主要目的是为限制误码的扩散和传输。如何限制误码的扩散和传输呢?每个片(slice)都应该是互相独立被传输的,某片的预测(片内预测和片间预测)不能以其它片中的宏块(Macroblock)为参考图像。

每个片也包含头和数据两部分:
1、片头中包含着片类型、片中的宏块类型、片帧的数量、片属于哪个图像以及对应的帧的设置和参数等信息。
2、片数据中则是宏块,这里就是我们要找的存储像素数据的地方。
在这里插入图片描述

宏块(Macroblock)

宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。

组成部分:一个宏块由一个16×16亮度像素和附加的一个8×8 Cb和一个 8×8 Cr 彩色像素块组成。每个图象中,若干宏块被排列成片的形式。
在这里插入图片描述

从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern 编码的块模式、Quantization Parameter 量化参数、像素的亮度和色度数据集等等信息。

序列、图像、片、宏块、子宏块整体结构图:
在这里插入图片描述
上图中的一张图像即对应一帧。

写在后面
受限于时间,同时为了方便讲解,部分内容可能不是很严谨,如有错漏,敬请指出。如有问题,也欢迎随时交流

  • 30
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CAN(Controller Area Network,控制器局域网)总线协议是一种广泛应用于工业自动化、汽车电子等领域的串行通讯协议。其帧格式如下: <img src="https://img-blog.csdnimg.cn/20200925125252655.png" width="400"> CAN总线协议的帧分为标准帧和扩展帧两种,其中标准帧包含11位标识符,扩展帧包含29位标识符。在CAN总线上,所有节点都可以同时发送和接收数据,因此需要在帧中包含发送方和接收方的信息。 帧格式的具体解释如下: 1. 帧起始符(SOF):一个固定的位模式,表示帧的起始。 2. 报文控制(CTRL):包含几个控制位,如IDE、RTR等。其中IDE表示标识符的类型,0表示标准帧,1表示扩展帧;RTR表示远程请求帧,0表示数据帧,1表示远程请求帧。 3. 标识符(ID):11位或29位的标识符,用于区分不同的CAN消息。 4. 控制域(CTL):包含几个控制位,如DLC、EDL等。其中DLC表示数据长度,即数据域的字节数;EDL表示数据长度是否扩展,0表示标准数据帧,1表示扩展数据帧。 5. 数据域(DATA):0~8字节的数据。 6. CRC:用于校验数据是否正确。 7. 确认位(ACK):由接收方发送的确认信息,表示数据是否正确接收。 8. 结束符(EOF):一个固定的位模式,表示帧的结束。 以上就是CAN总线协议的帧格式。在实际应用中,节点之间通过CAN总线进行数据交换,通过解析帧中的各个字段,可以判断消息的发送方、接收方、数据内容等信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值