本篇为步骤总结(建议大家结合理解):
具体代码请前往基于 FFmpeg 实现 H.264 转 MP4 视频转码-优快云博客
1. 初始化
- 注册组件:调用
av_register_all
函数,注册 FFmpeg 库中所有可用的编解码器、格式解析器等组件,为后续音视频处理奠定基础。 - 分配上下文:使用
avformat_alloc_context
函数分别为输入和输出文件分配AVFormatContext
结构体,用于存储文件的格式、流信息等。 - 分配 AVPacket 内存:通过
malloc
函数为AVPacket
分配内存,AVPacket
用于存储从输入文件读取的编码数据或将要写入输出文件的编码数据。 - 初始化视频流索引:将视频流索引
videoIndex
初始化为 -1,后续查找视频流时,若找到会更新该值。
2. 打开 H264 文件并获取信息
- 打开文件:使用
avformat_open_input
函数尝试打开指定路径的 H264 文件,并将文件的格式信息填充到输入格式上下文inFormatContext
中。若打开失败,输出错误信息并终止操作。 - 获取流信息:调用
avformat_find_stream_info
函数读取文件的流信息,填充inFormatContext
中的流相关字段,如每个流的编解码器、时间基等。若获取失败,输出错误信息并终止操作。 - 查找视频流:遍历输入文件中的所有流,通过判断流的编码类型是否为
AVMEDIA_TYPE_VIDEO
来找到视频流。若找到,记录其索引到videoIndex
中;若未找到,输出提示信息。
3. 准备输出文件
- 猜测输出格式:利用
av_guess_format
函数根据输出文件的路径猜测输出文件的格式。若猜测失败,输出错误信息并终止操作。 - 设置输出格式:将猜测得到的输出格式赋值给输出格式上下文
outFormatContext
的oformat
字段,确定输出文件的格式。 - 打开 I/O 流:使用
avio_open
函数以写入模式打开输出文件的 I/O 流,用于后续向文件中写入数据。若打开失败,输出错误信息并终止操作。 - 创建新视频流:调用
avformat_new_stream
函数在输出格式上下文中创建一个新的视频流,用于存储转码后的视频数据。若创建失败,输出错误信息并终止操作。 - 复制编解码器参数:使用
avcodec_parameters_copy
函数将输入文件中视频流的编解码器参数复制到新创建的输出视频流中,确保输出视频的编码设置与输入视频一致。 - 写入文件头:调用
avformat_write_header
函数写入输出文件的头信息,包含文件的格式、流信息等,为后续写入数据做准备。若写入失败,输出错误信息并终止操作。 - 分配 AVPacket 内存:根据新视频流的宽度和高度计算所需的内存大小,使用
av_new_packet
函数为AVPacket
分配内存。若分配失败,输出错误信息并终止操作。
4. 循环转码并写入数据
- 读取帧数据:使用
av_read_frame
函数从输入文件中循环读取一帧数据,存储到AVPacket
中。当读取到文件末尾或出现错误时,循环结束。 - 处理视频帧:检查读取的数据包是否属于视频流(通过
stream_index
判断),若是则进行处理:- 处理时间戳:若数据包的显示时间戳(
PTS
)为无效值(AV_NOPTS_VALUE
),根据帧数、时间基和帧率重新计算PTS
、解码时间戳(DTS
)和数据包的时长。若PTS
小于DTS
,认为是异常帧,跳过该帧。 - 时间基转换:使用
av_rescale_q_rnd
和av_rescale_q
函数将PTS
、DTS
和时长从输入视频流的时间基转换到输出视频流的时间基。 - 设置数据包属性:将
pos
设置为 -1 表示未知位置,设置flags
标记为关键帧,将stream_index
设置为 0。
- 处理时间戳:若数据包的显示时间戳(
- 写入数据:调用
av_interleaved_write_frame
函数将处理后的AVPacket
写入输出文件。若写入失败,输出错误信息。 - 重置 AVPacket:使用
av_packet_unref
函数释放AVPacket
中的数据和引用,以便下次使用。
5. 清理资源
- 写入文件尾:调用
av_write_trailer
函数写入输出文件的尾信息,包含文件的结束标志等。 - 关闭 I/O 流:使用
avio_close
函数关闭输出文件的 I/O 流,释放相关资源。 - 释放上下文:使用
av_free
函数分别释放输出和输入格式上下文占用的内存。 - 输出成功信息:最后输出转码成功的提示信息。