嵌入式音视频开发——H.264视频编码
一、H.264概述与基本架构
H.264是由国际标准化组织(ISO/IEC)和国际电信联盟(ITU-T)联合认证的视频编码标准,于2003年正式发布。作为MPEG-2之后的重要升级,H.264在相同画质下较H.263、MPEG-2等标准减少50%以上的带宽需求,成为视频编码领域的主流方案。
H.264的编解码流程主要包括五个关键部分:
- 帧间和帧内预测(Estimation)
- 变换(Transform)和反变换
- 量化(Quantization)和反量化
- 环路滤波(Loop Filter)
- 熵编码(Entropy Coding)
H.264码流由一系列NALU(网络抽象层单元)组成,分为两层结构:
- VCL(视频编码层):包含核心压缩引擎和块、宏块和片的语法级别定义
- NAL(网络提取层):负责将VCL产生的比特字符串适配到各种网络和多元环境中
H.264相关概念解析
1. 序列(Coded Video Sequence)
在H.264中,序列指一段连续编码的视频数据流,以IDR帧开始,到下一个IDR帧结束。序列参数集(SPS)包含该序列所有图像的共享编码参数,如分辨率、帧率等。序列的划分基于内容变化程度:运动平缓时序列较长(多个P/B帧参考一个I帧),运动剧烈时序列较短(可能仅含I帧和少量P帧)。
2. 帧类型
H.264定义了三种帧类型:
- I帧(帧内编码帧):通过帧内预测独立编码,不依赖其他帧,压缩率约7:1(类似JPEG)。每个GOP的第一个I帧必须是IDR帧。
- P帧(前向预测帧):参考前一个I/P帧进行运动补偿,仅存储差异信息,压缩率约20:1。解码需依赖参考帧。
- B帧(双向预测帧):同时参考前后帧的差异,压缩率可达50:1。解码复杂度最高,需缓存前后参考帧。
3. GOP(Group of Pictures)
GOP是两个IDR帧之间的图像组,包含一个IDR帧和若干P/B帧。其结构参数包括:
- N值:两个IDR帧间的总帧数(如N=24表示1秒GOP,假设帧率24fps)。
- M值:I帧与P帧间距(如M=3表示IBBPBBP结构)。 GOP越长压缩率越高,但随机访问和错误恢复能力越差。开放GOP允许跨GOP参考(无IDR帧),闭合GOP则禁止(以IDR帧起始)。
4. IDR帧(关键帧)
在编码解码中为了方便,将 GOP 中首个 I 帧要和其他 I 帧区别开,把第一 个 I 帧叫 IDR,这样方便控制编码和解码流程,所以 IDR 帧一定是 I 帧,但 I 帧不一定是 IDR 帧;IDR 帧的作用是立刻刷新,使错误不致传播,从 IDR 帧开始算新的序列开始编码。I 帧有被跨帧参考的可能,IDR 帧不会。IDR 帧不允许这样,例如:
IDR1 P4 B2 B3 P7 B5 B6 I10 B8 B9 P13 B11 B12 P16 B14 B15
- 这里的 B8 可以跨过 I10 去参考 P7
IDR1 P4 B2 B3 P7 B5 B6 IDR8 P11 B9 B10 P14 B11 B12
- 这里的 B9 就只能参照 IDR8 和 P11,不可以参考 IDR8 前面的帧
IDR帧是特殊I帧(NAL类型为5),其作用:
H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR 图像之后的图像永远不会使用 IDR 之前的图像的数据来解码。
H264 压缩方式
H264 采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成 I 帧的算法,帧 间压缩是生成 B 帧和 P 帧的算法。
帧内(Intraframe)压缩也称为空间压缩(Spatialcompression)。当压缩一帧 图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,这实际上与静态图 像压缩类似。帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像, 所以可以独立的解码、显示。帧内压缩一般达不到很高的压缩,跟编码 jpeg 差 不多。
帧间(Interframe)压缩的原理是:相邻几帧的数据有很大的相关性,或者说前后两帧信息变化很小的特点。也即连续的视频其相邻帧之间具有冗余信息,根据这一特性,压缩相邻帧之间的冗余量就可以进一步提高压缩量,减小压缩比。 帧间压缩也称为时间压缩(Temporalcompression),它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的。帧差值(Framedifferencing) 算法是一种典型的时间压缩法,它通过比较本帧与相邻帧之间的差异,仅记录本 帧与其相邻帧的差值,这样可以大大减少数据量。
压缩方式说明
- 分组,也就是将一系列变换不大的图像归为一个组,也就是一个序列, 也可以叫 GOP(画面组);
- 定义帧,将每组的图像帧归分为 I 帧、P 帧和 B 帧三种类型;
- 预测帧, 以 I 帧做为基础帧,以 I 帧预测 P 帧,再由 I 帧和 P 帧预测 B 帧;
- 数据传输, 最后将 I 帧数据与预测的差值信息进行存储和传输。
H264 分层结构详解
H.264(又称AVC)的分层结构是其高效压缩和网络适应性的核心设计,主要分为视频编码层(VCL)和网络抽象层(NAL)两层。这种分层设计既保证了编码效率,又增强了数据在不同网络环境下的传输特性。


以下是详细解析:
1. 视频编码层(VCL, Video Coding Layer)
VCL负责视频数据的核心压缩,通过复杂的算法将原始像素数据转换为高效的压缩比特流。其特点包括:
- 功能:处理宏块(Macroblock)、片(Slice)的编码,包括帧内预测、帧间预测、变换量化、熵编码等。
- 输出数据:生成SODB(String of Data Bits),即原始的比特流,长度可能不是8的整数倍。
- 关键单元:
- 宏块(Macroblock):16×16像素的基本编码单元,包含亮度和色度信息(如YUV格式)。
- 片(Slice):由多个宏块组成,分为I片、P片、B片等类型,确保编码独立性以限制误码扩散。
2. 网络抽象层(NAL, Network Abstraction Layer)
NAL将VCL输出的数据适配到不同网络环境,提供封装和传输支持:
- 功能:将SODB转换为适合传输的NALU(NAL Unit),添加头部信息以标识数据类型和重要性。
- 关键处理:
- RBSP(Raw Byte Sequence Payload):对SODB进行字节对齐,末尾补1和若干0以凑整。
- EBSP(Encapsulated Byte Sequence Payload):在RBSP中插入防竞争字节(0x03),避免与起始码冲突。
- NALU结构:
- NALU Header(1字节):包含 forbidden_zero_bit 、 nal_ref_idc (重要性标识)和 nal_unit_type (数据类型,如SPS、PPS、IDR帧等)。
- NALU Body:即EBSP或RBSP,承载实际编码数据。
3. 码流组织方式
H.264码流有两种主流格式,适应不同应用场景:
- AnnexB格式:
- 使用起始码(0x000001或0x00000001)分隔NALU,常见于实时流(如TS流、RTP传输)。
- 通过防竞争字节(0x03)避免数据与起始码混淆。
- AVCC(MP4格式):
- 使用4字节长度前缀标识NALU大小,便于随机访问(如MP4文件)。
4. H264的层次化数据划分
从视频内容到传输单元的逻辑分层:
- 序列(GOP):一组连续帧,以IDR帧开头,包含I/P/B帧。
- 帧(Picture):一帧图像可划分为多个片(Slice),片间独立编码。
- 片(Slice):由宏块组成,类型包括I/P/B片,决定预测方式。
- 宏块(Macroblock):16×16像素单元,承载具体图像数据。
二、H264码流结构解析


H264 码流封装格式
H.264(AVC)码流的封装格式主要有两种:Annex B 和 AVCC。它们在结构、应用场景及解析方式上有显著差异,以下是详细对比与分析:
Annex B 封装(基于起始码)
1. 核心特点
- 起始码分隔:每个 NALU(Network Abstraction Layer Unit)以固定的起始码( 0x000001 或 0x00000001 )开头,无显式长度字段。
- 3字节起始码( 0x000001 )用于普通NALU;
- 4字节起始码( 0x00000001 )用于SPS/PPS或码流开头。
- 自包含性:NALU可独立解析,无需依赖其他数据。
- 防止竞争:通过防竞争字节(0x03)避免数据与起始码混淆。
- NALU的起始码为0x000001或0x00000001,同时H264规定,当检测到0x000000时,也可以表示当前NALU的结束。那这样就会产生一个问题,就是如果在NALU的内部,出现了0x000001或0x000000时该怎么办?因此,H264就提出了“防止竞争”这样一种机制,当编码器编码完一个NAL时,应该检测NALU内部,是否出现如下左侧的四个序列。当检测到它们存在时,编码器就在最后一个字节前,插入一个新的字节:0x03。

- NALU的起始码为0x000001或0x00000001,同时H264规定,当检测到0x000000时,也可以表示当前NALU的结束。那这样就会产生一个问题,就是如果在NALU的内部,出现了0x000001或0x000000时该怎么办?因此,H264就提出了“防止竞争”这样一种机制,当编码器编码完一个NAL时,应该检测NALU内部,是否出现如下左侧的四个序列。当检测到它们存在时,编码器就在最后一个字节前,插入一个新的字节:0x03。
- 这样一来,当我们拿到EBSP时,就需要检测EBSP内是否有序列:0x000003, 如果有,则去掉其中的0x03。这样一来,我们就能得到原始字节序列载荷:RBSP。
2. 结构示例
[0x00000001][SPS NALU][0x00000001][PPS NALU][0x00000001][IDR帧 NALU][0x000001][P帧 NALU]...
SPS/PPS作为独立NALU存在于码流中,通常位于视频开头或关键帧前。
3. 应用场景
- 实时流媒体(如RTSP、RTP、TS流);
- 文件存储(.264裸流、.mkv等);
- 硬件设备(安防摄像头、机顶盒直接输出)。
4. 优缺点
- 优点:兼容性好,支持流式传输,无需预先解析完整配置。
- 缺点:起始码占用额外空间(每个NALU至少3字节开销),随机访问效率低(需扫描起始码定位)。
AVCC 封装(基于长度字段)
1. 核心特点
- 长度前缀:每个NALU前用4字节(大端序)表示其长度,替代起始码。
- 集中配置:SPS/PPS存储在文件头部的 extradata (如MP4的AVCC盒子),而非分散在码流中。
2. 结构示例
[extradata头部][NALU长度(4字节)][NALU数据][NALU长度(4字节)][NALU数据]...
extradata 包含编码参数(如profile、level、SPS/PPS)。
3. 应用场景
- 文件存储(MP4、MOV、FLV等容器);
- 点播平台(如YouTube、Netflix);
- API接口(Android MediaMuxer、iOS AVAssetWriter)。
4. 优缺点
- 优点:节省空间(无起始码),支持快速随机访问(通过长度字段直接跳转)。
- 缺点:需先解析 extradata 才能解码,不适合无完整头部的实时流。
关键差异对比
|
维度 |
Annex B |
AVCC |
|---|---|---|
|
NALU分隔方式 |
起始码( 0x000001 或 0x00000001 ) |
4字节长度前缀(大端序) |
|
参数集存储 |
分散在码流中(SPS/PPS NALU) |
集中在文件头( extradata ) |
|
随机访问性能 |
较差(需扫描起始码) |
更优(直接跳转长度字段) |
|
典型应用 |
实时流、TS流、.h264裸流 |
MP4、FLV、MKV等容器 |
格式转换与工具
两种封装常需相互转换:
- Annex B → AVCC:提取SPS/PPS到 extradata ,替换起始码为长度前缀。
- AVCC → Annex B:从 extradata 提取SPS/PPS并添加起始码,长度字段替换为起始码。 FFmpeg实现
h264_mp4toannexb滤镜确实能自动完成该转换:
ffmpeg -i input.mp4 -c:v copy -bsf:v h264_mp4toannexb output.h264
H264的NAL单元
H.264的NALU(Network Abstraction Layer Unit)是视频码流的基本传输单元,负责将编码后的视频数据封装为适合网络传输或存储的格式。
以下是其核心要点:
1. H.264 NALU (网络抽象层单元) 结构详解
一个 NALU 是 H.264 码流的基本组成单元。其结构主要包含三个部分:

NALU Header由三部分组成:
- 起始码(Start Code):标识NALU的开始,通常为3字节 0x000001 (流媒体)或4字节 0x00000001 (文件存储如MP4)。解码器通过起始码分割NALU单元。
- 头部(NALU Header):1字节,包含以下字段:
- forbidden_zero_bit(1 bit):固定为0,若为1表示传输错误需丢弃。
- nal_ref_idc(2 bits):重要性标识(0~3),值越大优先级越高(如SPS、PPS、IDR帧通常为3)。
- nal_unit_type(5 bits):NALU类型(见下表)。
- 有效载荷(Payload):实际视频数据或参数集(如SPS、PPS、帧数据等)。
常见NALU类型:
|
Type |
说明 |
|---|---|
|
1 |
非IDR帧(P/B帧) |
|
5 |
IDR帧(关键帧) |
|
6 |
SEI(增强信息) |
|
7 |
SPS(序列参数集) |
|
8 |
PPS(图像参数集) |
2. NALU在码流中的组织
典型H.264码流顺序为:
SPS → PPS → IDR帧 → P帧 → B帧 → ....

- SPS/PPS:全局参数(分辨率、帧率等),需在IDR帧前传输以确保解码正确性。
- IDR帧:关键帧,触发解码器刷新参考帧队列。
- P/B帧:依赖参考帧进行解码,压缩率更高。
H.264(AVC)视频编码标准中的SPS(序列参数集)、PPS(图像参数集)和SEI(补充增强信息)是码流中的关键元数据,分别承担不同的功能。以下是它们的简要说明:
1. SPS(Sequence Parameter Set,序列参数集)
- 作用:存储整个视频序列的全局参数,是解码器初始化的基础。
- 核心内容:
- 视频属性:分辨率(通过 pic_width_in_mbs_minus1 和 pic_height_in_map_units_minus1 计算)、帧率、色度格式(如4:2:0)。
- 编码配置:编码档次(Profile,如Baseline/Main/High)、级别(Level,限制码率和复杂度)、参考帧数量( max_num_ref_frames )。
- 其他参数:帧编号方式( log2_max_frame_num_minus4 )、场编码标志( frame_mbs_only_flag )等。
- 位置:通常位于码流开头或关键帧(IDR帧)前,NALU类型为7。
2. PPS(Picture Parameter Set,图像参数集)
- 作用:定义单帧或一组帧的解码参数,需引用SPS。
- 核心内容:
- 熵编码模式: entropy_coding_mode_flag (0=CAVLC,1=CABAC)。
- 量化参数:初始QP值( pic_init_qp_minus26 ),影响画质与码率平衡。
- 分片控制:分片组数量( num_slice_groups_minus1 )及去块滤波设置。
- 位置:紧邻SPS或周期性插入,NALU类型为8。
3. SEI(Supplemental Enhancement Information,补充增强信息)
- 作用:提供非必需但有助于解码或应用的附加信息。
- 核心内容:
- 常见类型:用户自定义数据(如 user_data_unregistered )、时间戳、版权信息、编码器参数等。
- 特性:不参与核心解码流程,但可用于同步(如直播答题场景)或增强体验。
- 位置:NALU类型为6,可出现在码流任意位置。
4. 关系与协同
- 层级依赖:PPS通过 seq_parameter_set_id 关联SPS,帧数据通过 pic_parameter_set_id 关联PPS。
- 传输要求:在实时流(如RTSP)中,SPS/PPS需周期性重复发送以确保解码容错性。
示例码流结构
(起始码 00 00 00 01 标识NALU边界)
00 00 00 01 67 ... // SPS(NALU类型7)
00 00 00 01 68 ... // PPS(NALU类型8)
00 00 00 01 06 ... // SEI(NALU类型6)
00 00 00 01 65 ... // IDR帧(NALU类型5)
3. NALU 主体部分
H.264的NALU(Network Abstraction Layer Unit)主体部分是编码数据的核心承载区域,其结构层次和数据处理机制如下:
1. NALU主体的组成结构
NALU的主体由三层数据单元构成,从原始编码数据到传输适配的封装形式依次为:
- SODB(String of Data Bits):最原始的编码数据比特流,由视频编码层(VCL)直接生成,长度可能不是8的整数倍。
- RBSP(Raw Byte Sequence Payload):对SODB进行字节对齐处理,在末尾添加一个比特“1”和若干比特“0”以补齐字节。例如,若SODB最后一个字节不足8位,会在最高位补1后填充0。
- EBSP(Encapsulation Byte Sequence Payload):在RBSP基础上插入防竞争字节(0x03),防止NALU内部出现与起始码(0x000001或0x00000001)冲突的序列。例如,遇到“00 00 00”会插入0x03变为“00 00 03 00”。
2. 防竞争机制(Emulation Prevention)
H264 中规定,当检测到 0x000000 时,也可以表示当前 NALU 的结束。那这样就会产生一个问题,就是如果在 NALU 的内部,出现了 0x000001 或 0x000000 时该怎么办?
在 RBSP 基础上填加了仿校验字节(0x03)它的原因是:
在 NALU 加到 Annexb 上时,需要填加每组 NALU 之前的起始码 StartCodePrefix,如果该 NALU 对应的 Slice 为一帧的开始则用 4 位字节表示 0x00000001;否则用 3 位字节表示 0x000001。为了使 NALU 主体中的数据不与起始码相冲突,在编码时,每遇到两个字节连续为 0,就插入一个字节的 0x03。解码时将 0x03 去掉。也称为脱壳操作。
因此,为防止NALU主体内的数据被误解析为起始码或结束码,H.264规定在以下序列中插入0x03:
- “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”
解码时需移除这些插入的0x03以恢复原始RBSP数据。

4. NALU主体的数据类型
NALU主体(RBSP)承载的数据类型由NALU头部的 nal_unit_type 决定,主要包括:
- VCL数据:如片(Slice)数据(类型1-5),包含I帧、P帧、B帧的编码内容。
- 非VCL数据:如序列参数集(SPS,类型7)、图像参数集(PPS,类型8)、补充增强信息(SEI,类型6)等,用于传递解码配置或辅助信息。
5. 层次化处理目的
- VCL与NAL分离:VCL负责视频压缩(生成SODB),NAL负责网络适配(封装为RBSP/EBSP),便于在不同网络环境中传输。
- 兼容性:通过起始码和防竞争机制,确保NALU在流式传输(如Annex B格式)或分片传输(如RTP打包)中的正确解析。
NALU主体通过SODB→RBSP→EBSP的转换,结合防竞争机制,实现了编码数据的安全封装与传输。其内容既包含视频压缩数据(Slice),也包含关键参数集(SPS/PPS),是H.264码流中连接编码与解码的核心环节。
更多推荐
所有评论(0)