文章目录
- 一、网络抽象层单元 - NALU ( Network Abstraction Layer Unit )
- 二、H.264 封装模式 - annexb模式 / MP4 模式
- 三、将 MP4 模式 转为 Annex B 模式
-
- 1、转换核心代码示例
- 2、核心要点 - av_bsf_get_by_name 函数 : 根据名称获取 比特流过滤器
- 3、核心要点 - av_bsf_alloc 函数 : 分配比特流过滤器上下文
- 4、核心要点 - avcodec_parameters_copy 函数 : 拷贝 AVCodecParameters 结构体
- 5、核心要点 - av_bsf_init 函数 : 初始化 比特流过滤器上下文 AVBSFContext
- 6、核心要点 - av_bsf_send_packet 函数 : 将数据包发送到比特流过滤器
- 7、核心要点 - av_packet_unref 函数 : 释放 AVPacket 内部资源
- 8、核心要点 - av_bsf_receive_packet 函数 : 从 比特流过滤器 接收修改后的 AVPacket 数据包
- 9、完整代码示例
- 10、代码执行结果
一、网络抽象层单元 - NALU ( Network Abstraction Layer Unit )
1、NALU 简介
NALU ( Network Abstract Layer Unit , 网络抽象层单元 ) 是 H.264 视频编码 中 用于 封装 编码后数据 的 基本传输单元 , 用于将 视频数据 以适合 网络传输的格式 组织起来 ;
NALU 是 对 原始视频帧 进行编码后的数据单元 , 它们 包含了 视频数据 的 编码信息 , 如 图像帧的编码数据、控制信息 等 ;
2、NALU 常见的数据类型
NALU 类型 通过其头部的 nal_unit_type 类型字段 定义 数据类型 , 主要有以下几种常见类型 :
- SPS , Sequence Parameter Set , 序列参数集 , 保存一组 编码视频序列 的 参数 ;
- PPS , Picture Parameter Set , 图像参数集 , 保存 一个序列中 某一个 或 某几个 图像帧 的 参数 ;
- I 帧 , Intra-coded Frame , 帧内编码帧 , 可独立解码成一张完整的画面帧 ;
- P 帧 , Predictive-coded Frame , 前向预测帧 , 参考前面的 I 帧 或 P 帧 解码出完整 画面帧 ;
- B 帧 , Bidirectional Predictive-coded Frame , 双向预测帧 , 参考前后的 I 帧 或 P 帧 解码出完整 画面帧 ;
3、NALU 传输注意事项
传输 H.264 裸流的时候 , 发送 I 帧之前 , 至少要向解码器发送一次 SPS 和 PPS , 如果 解码器 收不到 SPS 和 PPS 数据 , 解码器解码会失败 ;
注意 : 不是每个 I 帧都要发送 SPS 和 PPS 数据 , 在单个 H.264 视频流中 , 至少发一次 ;
SPS 和 PPS 后面的 I 帧 一般都是 IDR 帧 ;
一张 图像帧 可能对应有多个 NALU , 如 上图所示 , 一帧 I 帧 被分成两个 NALU 发送 ;
4、NALU 功能结构
NALU , Network Abstraction Layer Unit , 网络抽象层单元 , 是视频数据的基本组成单元 , 其功能分为两层 , 分别是 视频编码层 ( VCL ) 和 网络提取层 ( NAL ) ;
① 视频编码层 ( VCL : Video Coding Layer )
视频编码层 VCL , 全称 Video Coding Layer , 负责实际的 视频数据 编码 和 压缩 , 是编码器的核心部分 , 处理视频内容的分析、压缩和优化 ;
视频编码层 中 包括 编码块、宏块 Macroblock 、帧 Frame 、片 Slice 等图像数据相关的语法级定义 ;
VCL(Video Coding Layer)相关的类型 : 这些类型与视频的核心编码数据有关 , 主要是帧数据和分片传输 ;
- 类型 1 : 非 IDR 帧 , 包含普通的编码片数据 , 如 : P 帧 或 B 帧 ;
- 类型 2、3、4 : 数据分片 A / B /C , 将大的视频帧分片为更小的单元 , 用于网络传输或码流分割 ;
- 类型 5 : 即时解码刷新帧 ( IDR 帧 ) 和 关键帧 ( I 帧 ) 的编码数据 , 是解码器重置参考帧的起始点 ;
这些类型是视频数据的核心 , 包含实际的帧编码内容 ;
② 网络提取层 ( NAL : Network Abstraction Layer )
网络提取层 NAL , 全称 Network Abstraction Layer , 负责将 VCL 产生的 压缩比特流 适配到不同的 传输协议 和 环境中 , 使 视频数据 能够 在网络中 传输或存储 ,
NAL(Network Abstraction Layer)相关的类型 : 这些类型为网络传输提供支持 , 主要涉及参数集、辅助信息和码流管理 ;
- 类型 6 : SEI , Supplemental Enhancement Information , 包含附加信息 , 如 : 时间戳、字幕、场景切换标志 等 ;
- 类型 7 : SPS , Sequence Parameter Set , 定义全局的序列参数 , 如 : 分辨率、帧率、编码模式 等 ;
- 类型 8 : PPS , Picture Parameter Set , 定义图像级别的参数 , 如 : 分片布局、熵编码模式 ;
- 类型 9 : 访问单元边界符 , 用于标记 访问单元(AU)的边界 , 方便解码器同步解码 ;
- 类型 10 : 序列结束 , 用于指示序列的结束 ;
- 类型 11 : 流结束 , 用于指示整个视频流的结束 ;
- 类型 12 : 填充数据 , 用于填充码率、对齐数据流或占位 ;
这些类型是辅助层 , 提供 元信息 和 流控制 ;
③ NALU 的 VCL 和 NAL 对应的 nal_unit_type 类型
nal_unit_type
描述
归属层级
备注
0
未定义
-
保留,不使用。
1
非 IDR 帧(编码后的片数据)
VCL
普通帧,如 P 帧、B 帧等。
2
数据分片 A
VCL
用于分片传输的 A 类数据片。
3
数据分片 B
VCL
用于分片传输的 B 类数据片。
4
数据分片 C
VCL
用于分片传输的 C 类数据片。
5
IDR 帧(关键帧)
VCL
I 帧,随机访问点,重置解码器参考帧。
6
补充增强信息(SEI)
NAL
包含附加信息(如时间戳、场景切换标记等)。
7
序列参数集(SPS)
NAL
定义视频序列的全局参数(如分辨率、帧率等)。
8
图像参数集(PPS)
NAL
定义单个图像的参数(如熵编码模式、分片信息等)。
9
边界符(Access Unit Delimiter)
NAL
标记访问单元边界,辅助解析器解析视频流。
10
结束序列(End of Sequence)
NAL
指示序列结束。
11
结束流(End of Stream)
NAL
指示整个码流结束。
12
填充数据(Filler Data)
NAL
占位符,用于填充码率或对齐数据流。
13
保留
NAL
当前未使用,保留以备未来扩展。
14-23
未定义
-
预留类型,具体使用取决于扩展协议。
5、NALU 数据结构
H.264 视频流 NALU 单元 组成 :
-
Start Code 起始标志位 , 使解码器能够正确地定位每个 NALU 单元的开始 ;
-
NALU Header 头部 , 提供该 NALU 单元的基本信息 , 包括 NALU 类型、参考级别等 ;
-
NALU Payload 负载 , 包含实际的视频数据 或 参数集 ;
±----------------------------------------------------------+
Start Code (4 bytes) 00 00 00 01 NALU Header (1 byte) ±------------------±----------------±---------------+ ±------------------±----------------±---------------+ NALU Payload (variable size) ----------------------------------------------------------- Encoded Video Data or SPS/PPS/SEI Data ±----------------------------------------------------------+
① Start Code 起始标志位
Start Code 是用来标识 NALU 单元的开始位置 , 确保编码数据流可以被正确地识别和分割 ;
Start Code 是一个 固定的 4 字节序列 : 0x00 0x00 0x00 0x01
, 是 NALU 单元的开始标记 , 帮助解码器识别每个 NALU 单元的起始位置 ;
特殊情况 : 在特定的传输方式中 , 如 : RTP 包 , Start Code 是 3 字节序列 0x00 0x00 0x01
;
② NALU Header 头
NALU Header 用于 说明 当前 NALU 的类型 , 以及 控制信息 , 如 : 是否是参考帧、NALU 的优先级等 ;
I . NALU Header 组成
NALU Header 三个组成部分 :
- F 禁止位 : forbidden_zero_bit , 必须是 0 ;
- R 重要性指示位 : nal_ref_idc , 取值范围 00 ~ 11 , 值越大越重要 , 越需要保护 ;
- 设置为 00 说明可以丢弃 , 不影响视频播放 ;
- 如果画面帧是参考帧 , 该值必须大于 0 ;
- T 负荷数据类型 : nal_unit_type , 取值范围 1 ~ 12 ;
字段
长度
描述
forbidden_zero_bit
1 bit
固定为 0,表示该 NALU 是有效的。
nal_ref_idc
2 bits
表示当前 NALU 的参考级别,表明它是否可以作为参考帧。
nal_unit_type
5 bits
表示当前 NALU 的类型(如 I 帧、P 帧、SPS、PPS 等)。
II . NALU Header 示例
NALU Header 示例 : 假设有一个 I 帧 NALU , 类型值为 5 , 重要性级别为 3 ;
- forbidden_zero_bit = 0
- nal_ref_idc = 3 , 表示该 NALU 可以作为参考帧 ;
- nal_unit_type = 5 , 表示 I 帧 ;
- NALU 头部的二进制表示为:
0 11 00101
;
III . 常见的 NALU 的 nal_unit_type 类型
NALU 类型值
描述
0
未定义
1
非 IDR 帧(I帧,P帧等)
2
视频补充数据(SEI,Supplemental Enhancement Information)
3
片段(Slice)
4
一般的帧内预测数据
5
IDR 帧(关键帧)
6
补充增强信息(SEI)
7
序列参数集(SPS,Sequence Parameter Set)
8
图像参数集(PPS,Picture Parameter Set)
9
补充填充数据(填充用数据)
10
增强编码数据(无特定解释)
11
系统参数集(用于容器或传输中的数据)
12
胶片补充信息(Film Buffers)
13
其他类型数据
③ NALU Payload 载荷
NALU Payload 包含了 NALU 的 实际编码数据 , 数据的具体内容取决于 NALU 的 nal_unit_type 类型 ;
- VCL 数据 : nal_unit_type 类型 取值 1 ~ 5 , 则该 NALU 是视频帧数据 , 如 : I 帧、P 帧、B 帧 , 负载部分会包实际的帧编码内容 ;
- NAL 数据 : nal_unit_type 类型 取值 6 ~ 13 , 这些类型是辅助层 , 提供 元信息 和 流控制 ;
参考上面的 视频编码层 ( VCL : Video Coding Layer ) 和 网络提取层 ( NAL : Network Abstraction Layer ) 两个章节 ;
二、H.264 封装模式 - annexb模式 / MP4 模式
H.264 视频流 有 2 种 不同的 封装模式 , 以适应 不同的 传输和存储场景 ,
- Annex B 模式 : H.264 标准定义的裸流封装方式 , 常用于实时传输 , 就是本博客讲解的 NALU 裸流封装 , 结构为
[Start Code] [NALU Header] [NALU Payload]
; - MP4 模式 : 基于 容器格式 的封装方式 , 主要用于存储 , 如 : MP4 / MKV 等容器格式 ;
1、Annex B 模式
Annex B 封装模式 是 H.264 标准定义的 裸流 封装方式 , 常用于 网络实时传输 , 其特点是通过 0x00000001
起始码 分割每个 NAL 单元 , 用于 RTSP / RTMP / UDP / RTP 实时流媒体传输 ;
Annex B 封装模式 特点 :
- 起始码分隔 : 每个 NAL 单元 以固定的
0x000001
或0x00000001
起始码 开头 , 用于分隔 NALU 单元 ; - 实时解码 : 视频流 中不包含 时间戳 等 元信息 , 适合实时解码 ;
- 适合传输 : 适合实时流媒体传输 , 不适用于存储 , 因为存储时信息不够完整 ;
2、MP4 模式
MP4 封装模式 是基于 文件容器格式 的封装方式 , 主要用于 本地存储 , NALU 单元 被封装到 容器文件中 , 配合 元信息 进行管理 ; 如 : MP4 文件 , MKV 文件 等 ;
MP4 封装模式 特点 :
- 元信息丰富 : 包含 时间戳、帧偏移、关键帧索引等信息,便于随机访问和播放。
- 长度前缀 : 每个 NAL 单元前使用 4 字节 长度前缀 代替起始码,表示后续 NAL 单元的大小。
- 适合存储: 由于元信息完善,适用于文件存储和编辑。
MP4 封装模式 用途:
- 视频文件存储
- 视频编辑和点播场景。
3、Annex B 和 MP4 模式的对比
Annex B 和 MP4 模式的对比 :
- Annex B 封装模式 适合实时流媒体传输 , 封装简单 , 但缺少元信息 , 依赖起始码分割 NAL 单元 ;
- MP4 封装模式 将 H.264 视频 封装在 MP4 容器中 , 支持时间戳等丰富的元信息 , 适合存储和点播场景 , 但复杂度较高 ;
特性
Annex B 模式
MP4 模式
分隔方式
起始码(0x000001
或 0x00000001
)
长度前缀(4 字节)
元信息支持
无元信息,流中仅包含裸 NAL 单元
包含丰富元信息,如时间戳和索引
适用场景
实时流媒体传输(如 RTSP、RTP 等)
文件存储和点播(如 .mp4
、.m4v
)
复杂度
简单直接,解码时可逐个读取 NAL 单元
较高,需解析 MP4 容器结构和时间戳信息
容器支持
无容器,裸流形式
基于 MP4 容器封装
随机访问能力
较弱,依赖特定 IDR 帧标识
强,具备快速定位关键帧和时间戳的能力
封装效率
较低,起始码占用额外空间
较高,节省起始码空间
典型用途
流媒体实时传输、直播
视频存储、编辑、点播
三、将 MP4 模式 转为 Annex B 模式
有的 解码器 只持 H.264 的 Annex B 封装模式 , 不支持 MP4 封装模式 , 这种情况下需要将MP4 封装模式 转为 Annex B 封装模式 ;
1、转换核心代码示例
先给出代码示例 , 然后讲解代码中的核心要点 ;
在下面的代码中 ,
-
首先 , 查找 比特流过滤器 AVBitStreamFilter ;
-
然后 , 定义 比特流过滤器上下文 AVBSFContext , 并进行初始化操作 ;
-
再后 , 将编解码器参数拷贝到 比特流过滤器上下文 AVBSFContext 中 , 用于初始化 比特流过滤器 AVBitStreamFilter ;
-
最后 , 使用 比特流过滤器上下文 AVBSFContext , 将 AVPacket 由 MP4 封装模式 转为 Annex B 封装模式 ;
简要的核心代码如下 : 只有核心代码 , 省略了很多其它代码 , 如 : 解码 MP4 文件 , AVPacket 初始化释放操作等 ;
// 获取指定名称的 比特流过滤器 Bitstream Filter ( BSF )
// 在此代码中,我们使用 "h264_mp4toannexb" 过滤器,
// 该 过滤器 用于将 H.264 的 MP4 封装格式 转换为 Annex B 封装格式。
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
// 定义一个比特流过滤器上下文指针,用于管理过滤器的配置和操作。
AVBSFContext *bsf_ctx = NULL;
// 初始化 比特流过滤器 上下文
// av_bsf_alloc 函数 根据 指定的过滤器 分配一个上下文结构 ( AVBSFContext )
// 参数:
// bsfilter : 要使用的过滤器 , 这里是 h264_mp4toannexb
// &bsf_ctx : 用来存储分配的过滤器上下文的指针
av_bsf_alloc(bsfilter, &bsf_ctx);
// 配置比特流过滤器的输入参数
// 通过将输入流的 编解码参数 ( codec parameters ) 复制到过滤器上下文中,
// 让过滤器知道如何处理输入数据。
// 参数:
// bsf_ctx->par_in:比特流过滤器的输入参数结构。
// ifmt_ctx->streams[videoindex]->codecpar:输入流中视频的编解码参数。
// ifmt_ctx 是输入格式上下文,videoindex 是视频流索引。
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
// 初始化 比特流过滤器
// av_bsf_init 函数会根据前面配置的输入参数和过滤器类型初始化过滤器上下文。
// 初始化后,过滤器就可以用于处理数据了。
av_bsf_init(bsf_ctx);
// AVPacket 数据包 , 省略之后的初始化以及读取数据的过程
AVPacket *pkt = NULL;
// ... 省略之后的 AVPacket 初始化以及读取数据的过程
// 将一个编码数据包发送到比特流过滤器进行处理
av_bsf_send_packet(bsf_ctx, pkt);
/*
* av_bsf_send_packet:
* - 参数 bsf_ctx:比特流过滤器上下文,必须先初始化(通过 av_bsf_alloc 和 av_bsf_init)。
* - 参数 pkt:指向要发送的 AVPacket 数据包。
* - 功能:将数据包发送到比特流过滤器内部进行处理。
* - 注意:此函数会接管 pkt 数据的管理,成功调用后无需再手动释放 pkt 的数据。
*/
// 释放 AVPacket 中的动态内存,并重置数据指针和大小
av_packet_unref(pkt);
/*
* av_packet_unref:
* - 参数 pkt:需要释放资源的 AVPacket。
* - 功能:释放 pkt 内部分配的所有动态内存,例如数据缓冲区(data)和大小信息(size)。
* - 注意:pkt 本身不会被释放,只是其内容被重置为未初始化状态(如 data = NULL, size = 0)。
*/
// 从比特流过滤器中提取一个处理后的编码数据包
av_bsf_receive_packet(bsf_ctx, pkt);
/*
* av_bsf_receive_packet:
* - 参数 bsf_ctx:比特流过滤器上下文,存储过滤器的状态和配置。
* - 参数 pkt:指向一个 AVPacket,用于接收过滤后的输出数据。
* - 功能:从比特流过滤器的内部缓冲区中提取处理后的数据包。
* - 返回值:
* - 0:成功提取到一个数据包。
* - AVERROR(EAGAIN):当前没有可用输出数据包,需要更多输入数据。
* - AVERROR_EOF:数据流结束,没有更多数据输出。
* - 注意:
* - 提取的 pkt 数据包需要在使用后调用 av_packet_unref 释放。
* - 在处理过程中需要检查返回值,区分是否需要提供更多输入数据。
*/
2、核心要点 - av_bsf_get_by_name 函数 : 根据名称获取 比特流过滤器
av_bsf_get_by_name 函数 用于 根据 过滤器名称 查找并获取对应的 过滤器结构体 AVBitStreamFilter , 通常在进行比特流处理 时使用 ; 这里用于 将 H.264 视频流 的 MP4 封装模式 转为 Annex B 封装模式 ;
av_bsf_get_by_name 函数原型 :
const AVBitStreamFilter *av_bsf_get_by_name(const char *name);
const char *name
参数 : 指定要获取的 比特流过滤器 的名称(字符串形式)。常见的过滤器名称如 h264_mp4toannexb 等。- 返回值 :
- 获取成功 : 返回一个指向 AVBitStreamFilter 结构体的指针 , 表示与指定名称对应的比特流过滤器 ;
- 获取失败 : 如果没有找到对应名称的 比特流过滤器 , 返回 NULL ;
常用的 比特流过滤器 AVBitStreamFilter 名称 :
h264_mp4toannexb
: 将 H.264 MP4 封装格式 转换为 Annex B 格式 ;hevc_mp4toannexb
: 将 HEVC MP4 封装格式 转换为 Annex B 格式 ;aac_adtstoasc
: 将 AAC 的 ADTS 格式转换为 Audio Specific Config 格式 ;
3、核心要点 - av_bsf_alloc 函数 : 分配比特流过滤器上下文
av_bsf_alloc 函数 的 作用是 为 指定的 比特流过滤器 AVBitStreamFilter 分配一个新的 比特流过滤器上下文 AVBSFContext , 用于后续的初始化和操作 ;
av_bsf_alloc 函数原型 :
int av_bsf_alloc(const AVBitStreamFilter *filter, AVBSFContext **bsf);
-
参数解析 :
const AVBitStreamFilter *filter
参数 : 指向一个 AVBitStreamFilter 结构的指针 , 表示要分配的比特流过滤器 , 该参数 一般 通过 av_bsf_get_by_name 函数 获取 ;AVBSFContext **bsf
参数 : 指向一个 比特流过滤器上下文 AVBSFContext 的指针,用于接收分配的比特流过滤器上下文(AVBSFContext)
-
返回值解析 : 返回 整型值 , 表示函数的执行结果 ;
- 返回 0 表示成功 ;
- 返回 负数 , 表示失败 , 可根据负数值获取对应的错误描述 ;
4、核心要点 - avcodec_parameters_copy 函数 : 拷贝 AVCodecParameters 结构体
avcodec_parameters_copy 函数 的作用是 将一个 AVCodecParameters 结构中 的 编码参数 复制到另一个 AVCodecParameters 结构体 中 ;
该函数常用于在初始化 AVBSFContext 或 重新配置解码器 时传递编码参数 ;
avcodec_parameters_copy 函数原型 :
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
- 参数说明 :
AVCodecParameters *dst
参数 : 拷贝目标 AVCodecParameters 结构 , 用于存储复制后的编码参数 ;const AVCodecParameters *src
参数 : 拷贝源 AVCodecParameters 结构 , 包含需要复制的编码参数 ;
- 返回值说明 :
- 返回值为 0 表示 参数复制成功 ;
- 返回值为 负值 表示 复制出错 ;
5、核心要点 - av_bsf_init 函数 : 初始化 比特流过滤器上下文 AVBSFContext
av_bsf_init 函数 用于 初始化 比特流过滤器的上下文 AVBSFContext 结构体 , 完成 比特流过滤器 的准备工作 , 一旦调用此函数 , 过滤器即可处理输入数据包 ;
该函数通常用于在配置完 比特流过滤器上下文 AVBSFContext 的 输入参数后 , 正式启用 比特流过滤器 ;
av_bsf_init 函数原型 :
int av_bsf_init(AVBSFContext *ctx);
AVBSFContext *ctx
参数 : 指向 比特流过滤器上下文 AVBSFContext 的指针 , 该 上下文 需要在调用此函数前由 av_bsf_alloc 函数 初始化 内存 , 并且调用 avcodec_parameters_copy 函数 配置好输入参数 ;- 返回值说明 :
- 返回值为 0 表示 初始化 成功 ;
- 返回值为 负值 表示 初始化 出错 ;
6、核心要点 - av_bsf_send_packet 函数 : 将数据包发送到比特流过滤器
av_bsf_send_packet 函数 的 作用是 将一个编码后的 数据包 AVPacket 发送到 比特流过滤器 AVBitStreamFilter 进行处理 , 过滤后的输出的 AVPacket 数据包 可以通过 av_bsf_receive_packet 函数获取 ;
av_bsf_send_packet 函数原型 :
int av_bsf_send_packet(AVBSFContext *ctx, AVPacket *pkt);
- 参数说明 :
AVBSFContext *ctx
参数 : 指向 比特流过滤器上下文 AVBSFContext 的指针 , 比特流过滤器的上下文 , 必须在调用此函数之前通过 av_bsf_init 初始化 ;AVPacket *pkt
参数 : 指向 AVPacket 的指针 , 表示需要发送到比特流过滤器进行处理的数据包 ;
- 返回值说明 :
- 返回值为 0 表示 发送 AVPacket 数据包 成功 ;
- 返回值为 负值 表示 发送 AVPacket 数据包 出错 ;
7、核心要点 - av_packet_unref 函数 : 释放 AVPacket 内部资源
av_packet_unref 函数 释放 AVPacket 结构体中 分配 的 所有动态内存 , 并重置其内容 , 使其处于未初始化状态 , 如 : 数据指针 data 和 大小 size ;
该函数 不会释放 AVPacket 结构体本身的内存 , AVPacket * 指针变量 依然有效 , 仅释放其内部关联的资源 ;
解码或编码过程中 , 当一个 AVPacket 的数据已不再需要时 , 使用 av_packet_unref 释放内存 , 避免内存泄漏 ;
这里 在读取一个 AVPacket 后 , 这是 MP4 封装模式 的 H.264 视频数据 , 将其发送到 比特流过滤器上下文 AVBSFContext 之后 , 该原始 AVPacket 就没用了 , 直接调用该函数 释放 AVPacket 的内部资源 , 该 AVPacket 继续使用 , 传入 av_bsf_receive_packet 函数 , 用于接收 转换后的 Annex B 封装模式的 H.264 视频数据 ;
av_packet_unref 函数原型 :
void av_packet_unref(AVPacket *pkt);
AVPacket *pkt
参数 : 指向需要释放资源的 AVPacket 结构体指针 , 这个 AVPacket 通常用于存储 编码后的视频或音频数据 ;
可参考 【FFmpeg】FFmpeg 内存结构 ④ ( AVPacket 函数简介 | av_packet_unref 函数 | av_packet_move_ref 函数 ) 博客 ;
8、核心要点 - av_bsf_receive_packet 函数 : 从 比特流过滤器 接收修改后的 AVPacket 数据包
av_bsf_receive_packet 函数 的 作用是 从 比特流过滤器 中提取一个处理后的 AVPacket 数据包 , 该函数通常与 av_bsf_send_packet 配合使用 , 用于逐一获取比特流过滤器处理后的输出包 ;
av_bsf_receive_packet 函数原型 :
int av_bsf_receive_packet(AVBSFContext *ctx, AVPacket *pkt);
- 参数说明 :
AVBSFContext *ctx
参数 : 指向 比特流过滤器上下文 AVBSFContext 的指针 , 比特流过滤器的上下文 , 必须在调用此函数之前通过 av_bsf_init 初始化 , 以及调用 av_bsf_send_packet 完成数据准备 ;AVPacket *pkt
参数 : 指向 AVPacket 的指针 , 用于存储从 比特流过滤器 输出的处理后的数据包 ;
- 返回值说明 :
- 返回值为 0 表示 接收 AVPacket 数据包 成功 ;
- 返回值为 负值 表示 接收 AVPacket 数据包 出错 ;
9、完整代码示例
#include <stdio.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
int main(int argc, char **argv)
{
// 打印 FFmpeg 版本号
printf("FFmpeg version is %s
", av_version_info());
// 1. 文件准备
// 要解封装的 视频文件 的 相对路径
char *file_name = "test.mp4";
// 要输出的 文件路径
char *file_name_out = "out.mp4";
// 打开输出文件 , 用于保存 Annex B 封装模式的 H.264 视频数据
FILE *outfp = fopen(file_name_out, "wb");
// 2. 打开视频文件
// 描述 媒体文件 或 媒体流 的构成和基本信息
AVFormatContext *fmt_ctx = NULL;
// 打开文件 , 探测协议类型 , 将获取的参数信息 填充到 AVFormatContext 结构体中
int ret = avformat_open_input(&fmt_ctx, file_name, NULL, NULL);
// avformat_open_input 函数 : 执行成功返回 0 , 执行失败返回负数 ( 错误码 )
if (ret < 0)
{
// 用于接收错误信息的字符串
char buf[1024] = { 0 };
// 获取 负数错误码 对应的 错误日志信息
av_strerror(ret, buf, sizeof(buf) - 1);
// 打印错误信息
printf("avformat_open_input failed : %s
", file_name, buf);
return -1;
}
// 3. 获取 媒体流 详细信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
// 获取 码流 信息失败 , 返回 负数错误码
if (ret < 0)
{
// 用于接收错误信息的字符串
char buf[1024] = { 0 };
// 获取 负数错误码 对应的 错误日志信息
av_strerror(ret, buf, sizeof(buf) - 1);
// 打印错误信息
printf("avformat_find_stream_info failed:%s
", file_name, buf);
return -1;
}
// 4. 打印 视频文件 的 参数信息 ☆☆☆
printf_s("
==== av_dump_format start ===
");
av_dump_format(fmt_ctx, 0, file_name, 0);
printf_s("
==== av_dump_format end ===
");
// 5. 获取视频流索引
int videoindex = -1; // 视频索引
// 查找最佳的视频流索引
videoindex = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
// 查找 视频流 失败
if(videoindex == -1)
{
printf("find video stream failed
");
avformat_close_input(&fmt_ctx);
return -1;
}
// 6. 获取 比特流过滤器 并 初始化 比特流过滤器上下文
// 获取指定名称的 比特流过滤器 Bitstream Filter ( BSF )
// 在此代码中,我们使用 "h264_mp4toannexb" 过滤器,
// 该 过滤器 用于将 H.264 的 MP4 封装格式 转换为 Annex B 封装格式。
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
// 定义一个比特流过滤器上下文指针,用于管理过滤器的配置和操作。
AVBSFContext *bsf_ctx = NULL;
// 初始化 比特流过滤器 上下文
// av_bsf_alloc 函数 根据 指定的过滤器 分配一个上下文结构 ( AVBSFContext )
// 参数:
// bsfilter : 要使用的过滤器 , 这里是 h264_mp4toannexb
// &bsf_ctx : 用来存储分配的过滤器上下文的指针
av_bsf_alloc(bsfilter, &bsf_ctx);
// 配置比特流过滤器的输入参数
// 通过将输入流的 编解码参数 ( codec parameters ) 复制到过滤器上下文中,
// 让过滤器知道如何处理输入数据。
// 参数:
// bsf_ctx->par_in:比特流过滤器的输入参数结构。
// ifmt_ctx->streams[videoindex]->codecpar:输入流中视频的编解码参数。
// ifmt_ctx 是输入格式上下文,videoindex 是视频流索引。
avcodec_parameters_copy(bsf_ctx->par_in, fmt_ctx->streams[videoindex]->codecpar);
// 初始化 比特流过滤器
// av_bsf_init 函数会根据前面配置的输入参数和过滤器类型初始化过滤器上下文。
// 初始化后,过滤器就可以用于处理数据了。
av_bsf_init(bsf_ctx);
// 7. 开始解析 数据包 , 并将 MP4 模式 转为 Annex B 模式 输出到本地文件中
// 为 AVPacket 分配内存空间,用于存储视频数据包
AVPacket *pkt = av_packet_alloc();
// 初始化 AVPacket,清空相关字段
av_init_packet(pkt);
// 文件读取结束标志,初始为 0,表示文件未读取结束
int file_end = 0;
// 循环读取文件,直到文件结束
while (0 == file_end)
{
// 读取一帧数据包
if((ret = av_read_frame(fmt_ctx, pkt)) < 0)
{
// 如果没有更多数据包可读,表示文件已读完
file_end = 1;
printf("read file end: ret:%d
", ret); // 输出读取结束信息
}
// 如果读取到的数据包是视频流
if(ret == 0 && pkt->stream_index == videoindex)
{
#if 1
int input_size = pkt->size; // 获取输入数据包的大小
int out_pkt_count = 0; // 统计输出数据包的数量
// 将数据包送入比特流过滤器(bitstream filter),进行编码格式转换
if (av_bsf_send_packet(bsf_ctx, pkt) != 0)
{
av_packet_unref(pkt); // 如果过滤器处理失败,释放资源
continue; // 继续读取下一个数据包
}
av_packet_unref(pkt); // 释放资源(pkt 内存由过滤器管理)
// 从比特流过滤器接收处理后的数据包
while(av_bsf_receive_packet(bsf_ctx, pkt) == 0)
{
out_pkt_count++; // 每成功接收一个处理后的数据包,计数增加
size_t size = fwrite(pkt->data, 1, pkt->size, outfp); // 将处理后的数据包写入输出文件
if(size != pkt->size)
{
printf("fwrite failed-> write:%u, pkt_size:%u
", size, pkt->size); // 写入失败提示
}
av_packet_unref(pkt); // 处理完一个包后释放其内存
}
// 如果处理后的数据包数量超过 2 个,输出警告信息
if(out_pkt_count >= 2)
{
printf("cur pkt(size:%d) only get 1 out pkt, it get %d pkts
",
input_size, out_pkt_count);
}
#else // TS 格式的文件已经是 Annex B 格式 , 不需要进行转换 , 可以直接写入
size_t size = fwrite(pkt->data, 1, pkt->size, outfp); // 直接写入输出文件
if(size != pkt->size)
{
printf("fwrite failed-> write:%u, pkt_size:%u
", size, pkt->size); // 写入失败提示
}
av_packet_unref(pkt); // 释放资源
#endif
}
else
{
// 如果数据包不是视频流或读取出错 , 直接释放内存 , 继续进行下一次循环
if(ret == 0)
{
av_packet_unref(pkt); // 释放内存
}
}
}
// 如果输出文件存在,关闭文件
if(outfp)
{
fclose(outfp);
}
// 如果比特流过滤器上下文存在,释放资源
if(bsf_ctx)
{
av_bsf_free(&bsf_ctx);
}
// 如果数据包指针存在,释放 AVPacket 的内存
if(pkt)
{
av_packet_free(&pkt);
}
// 如果格式上下文存在,关闭输入流并释放资源
if(fmt_ctx){
avformat_close_input(&fmt_ctx); // 与 avformat_open_input 配对使用,防止内存泄漏
}
// 执行结束
printf("
FFmpeg End
");
return 0;
}
10、代码执行结果
代码执行结果 :
FFmpeg version is 4.2.1
==== av_dump_format start ===
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: mp41
creation_time : 2024-04-21T12:09:43.000000Z
encoder : Bandicam 5.2.0.1855 / GDI
encoder-eng : Bandicam 5.2.0.1855 / GDI
Duration: 00:01:05.77, start: 0.000000, bitrate: 7572 kb/s
Stream #0:0(eng): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 848x480, 7370 kb/s, 29.29 fps, 30 tbr, 30k tbn, 60k tbc (default)
Metadata:
creation_time : 2024-04-21T12:09:43.000000Z
handler_name : VideoHandler
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 194 kb/s (default)
Metadata:
creation_time : 2024-04-21T12:09:43.000000Z
handler_name : SoundHandler
==== av_dump_format end ===
read file end: ret:-541478725
FFmpeg End
进入 项目编译 输出 目录 , 使用
ffplay out.mp4
命令 , 播放 输出 的 annexb 封装模式 out.mp4 文件 , 播放效果如下 :