[FFmpeg] AVFormatContext、AVInputFormat、AVOutputFormat | libavformat
第二章:容器处理
欢迎回来!
在第一章:I/O访问中,我们学习了FFmpeg如何从源(如文件或网络流)获取原始字节并将字节写入目标。这如同为多媒体数据流搭建管道。
但原始字节本身并不实用。多媒体数据(视频、音频、字幕等)通常以结构化方式封装。这就像将不同原料(视频、音频)放入特定类型的盒子(MP4、MKV、AVI),并附加标签(标题、时长等元数据)和内容组织与同步的说明。
这种"封装"与"解封装"工作由FFmpeg的容器处理层完成,主要由FFmpeg内部的libavformat
库实现。
该层的核心功能包括:
- 读取媒体文件结构(解封装或"拆箱"):识别内部数据流类型(是否存在视频?音频?字幕?),解析组织结构,并读取标题或时长等关联信息
- 写入数据流至特定文件结构(封装或"装箱"):创建输出文件头,正确编排不同数据流,并在末尾写入最终结构细节(如索引)
若缺乏容器处理能力,即便FFmpeg能读取MP4文件的原始字节,也无法分辨视频数据何处结束、音频数据何处开始,更不知如何正确同步播放。
应用案例:更换容器(重新封装)
理解容器处理的最佳方式是更换现有媒体文件的容器格式而不改变内部音视频数据,此过程称为重新封装。
假设我们拥有视频文件input.mp4
,希望将其内容封装至AVI容器生成output.avi
。此时并不改变音画质量,仅更换包装形式。
使用ffmpeg
命令行工具的操作示例如下:
ffmpeg -i input.mp4 -c copy output.avi
从容器处理视角解析此命令:
-i input.mp4
:FFmpeg打开MP4文件,容器处理层立即识别其格式,读取MP4头信息以确认可用流(如H.264视频、AAC音频)及其基本属性和元数据-c copy
:指示FFmpeg不重新编码流数据,而是直接将压缩数据从输入容器复制至输出容器(重新封装的关键)output.avi
:根据扩展名.avi
,容器处理层选择AVI封装器,创建AVI头并将输入流数据编排为AVI结构
容器处理层将MP4输入的结构化
数据重新编排为AVI输出
所需的格式。
容器处理机制(拆箱与装箱部门)
深入解析重新封装过程的容器处理细节:
-
打开输入容器(解封装初始化):FFmpeg通过I/O层(第一章)打开
input.mp4
,容器处理层识别MP4格式(使用AVInputFormat
类结构)并读取文件头(avformat_open_input
)。头信息包含:- 存在哪些流(视频/音频/字幕等)
- 各流基础编解码信息(如视频H.264,音频AAC)
- 流特定属性(视频分辨率、帧率,音频采样率、声道布局)
- 元数据(标题、作者、时长等)
- 文件结构导航信息(如索引或目录)
FFmpeg随后获取流的详细信息(avformat_find_stream_info
)
-
打开输出容器(封装初始化):根据
output.avi
识别目标格式为AVI,查找对应的AVI封装器规范(AVOutputFormat
)并设置写入上下文(avformat_alloc_output_context2
) -
准备输出流:对于从输入映射至输出的每个流(本示例隐式映射所有流,或通过
-map 0
显式指定),容器处理层在输出容器中添加对应流(avformat_new_stream
),并复制编解码参数(分辨率、采样率等)(avcodec_parameters_copy
) -
写入输出头:容器处理层写入输出文件头(
avformat_write_header
)。该头信息告知播放器文件类型(AVI)及内部流结构,基于先前收集的信息。此过程依赖I/O层向output.avi
写入字节 -
逐包传输数据:FFmpeg进入数据传输循环:
- 请求输入容器层读取下一个压缩数据包(
av_read_frame
)。数据包是单个流的小数据块(如若干视频帧或音频片段)。容器层读取文件结构并通过I/O层获取该包原始字节 - 对每个读取的数据包,必要时调整时间戳以适应输出容器要求(
av_packet_rescale_ts
) - 将数据包传递至输出容器层写入(
av_interleaved_write_frame
)。输出容器层决定数据包在输出文件中的位置,可能与其他流的数据包交叉排列,并通过I/O层写入字节 - 循环持续至输入文件结束
- 请求输入容器层读取下一个压缩数据包(
-
写入输出尾部:处理完所有数据包后,容器处理层写入输出文件结构的最终部分(
av_write_trailer
),可能包含数据包索引以支持快速跳转。此过程同样使用I/O层 -
关闭容器:最终关闭输入输出容器并释放资源(
avformat_close_input
,avio_closep
)
简化版流程序列图如下:
此图展示容器处理层如何协调结构化数据(数据包)与元数据流动,依赖I/O层实现文件底层字节传输。
常见容器格式
FFmpeg支持多种容器格式,典型示例如下:
格式名称 | 常见扩展名 | 典型应用场景 | 特性说明 |
---|---|---|---|
MP4 | .mp4, .m4v, .m4a | 网络流媒体、移动设备、存储 | 高度通用、灵活性强 |
Matroska | .mkv, .mka, .mks | 高质量存储、PC播放 | 支持多流及高级特性 |
AVI | .avi | 旧式视频文件存储 | 传统格式,灵活性低于MP4/MKV |
MPEG-TS | .ts | 广播电视(DVB/ATSC)、流媒体传输 | 专为不稳定链路设计 |
WebM | .webm | 网页视频(HTML5) | 基于Matroska,采用开放编解码器 |
FLV | .flv | Flash视频、RTMP流媒体 | 用于旧式网页视频及直播流传输 |
容器格式还存储元数据——描述文件自身的信息,包括:
- 标题、作者、专辑
- 时长
- 创建日期
- 流语言
- 章节信息
- 封面艺术
FFmpeg容器层可读写元数据,但支持程度因格式而异。
C语言API:🎢AVFormatContext、AVInputFormat、AVOutputFormat
开发者使用FFmpeg库时,容器处理核心结构包括:
AVFormatContext
:媒体文件/流操作的核心结构,包含容器格式信息、流列表(AVStream
结构体)及元数据(AVDictionary
)。每个输入输出对应一个AVFormatContext
AVInputFormat
:表示已注册的解封装器(如MP4解封装器),定义读取特定容器格式的方法AVOutputFormat
:表示已注册的封装器(如AVI封装器),定义写入特定容器格式的方法
libavformat
常用函数:
avformat_open_input()
:读取输入头信息并填充AVFormatContext
avformat_find_stream_info()
:探测输入数据以获取流详细信息av_read_frame()
:从输入读取下一个压缩数据包avformat_alloc_output_context2()
:基于文件名创建输出AVFormatContext
avformat_new_stream()
:向输出AVFormatContext
添加新流avcodec_parameters_copy()
:复制流参数(编解码ID、分辨率等)至输出流avformat_write_header()
:写入输出文件头av_interleaved_write_frame()
:写入压缩数据包并处理多流交叉排列av_write_trailer()
:写入输出文件尾部(如索引)avformat_close_input()
:关闭输入AVFormatContext
及相关I/O上下文avio_closep()
:关闭输出I/O上下文avformat_free_context()
:释放AVFormatContext
doc/APIchanges
文件记录了这些API的演进历史。例如:
avformat_open_input
与avformat_close_input
的引入与修改(2011-06-16)AVFormatContext
字段如url
的增删(2018-01-28)- 数据包读写函数的更新(
av_read_frame
于2020-03-29,avcodec_free_context
于2014-05-18)
FFmpeg源码中的demux_decode.c
和remux.c
示例文件演示了如何通过相关函数实现解封装、读取数据包及重新封装。
结语
本章深入解析了媒体文件的"包装盒"机制。
我们了解到FFmpeg通过容器处理层(libavformat
)实现文件结构解析(解封装)与新建结构化文件(封装)。
该层负责识别数据流、读写元数据,并管理压缩数据的封装逻辑。通过简单重新封装操作,我们实现了容器格式转换而无需修改内容本身。
现在我们已经掌握FFmpeg如何从容器中提取结构化数据(数据包),下一步将探索这些数据包在解码为原始视频帧或音频样本前的本质。
下一章将深入解析压缩媒体数据包。
压缩媒体数据包
补充:
类似包装盒
的网络部分
前文传输:Linux网络实验
也是各种协议包的层层封装