一、源码结构概览
FFmpeg 采用模块化设计,核心功能被拆分为多个独立库(libav*),每个库有明确的职责:
| 库名 | 功能 |
|---|---|
| libavutil | 通用工具库:提供内存管理、数学运算、哈希、像素格式、线程等基础设施。是所有其他库的依赖。 |
| libavcodec | 编解码器核心:包含所有音视频编码器(encoder)、解码器(decoder)的实现,以及相关的帧类型判断、码流解析等。 |
| libavformat | 封装格式处理:负责解复用(demuxer)与复用(muxer),支持 MP4、FLV、MKV、RTMP 等几乎所有容器格式。 |
| libavfilter | 滤镜系统:提供音视频滤镜(如缩放、裁剪、水印、混音等),支持构建复杂的滤镜图。 |
| libavdevice | 设备输入输出:从摄像头、麦克风等硬件读取或写入数据。 |
| libswscale | 图像缩放与颜色空间转换:高效实现像素格式转换、尺寸缩放。 |
| libswresample | 音频重采样与格式转换:实现采样率转换、声道布局转换、样本格式转换。 |
源码根目录下,还有 ffmpeg.c(命令行工具主程序)、ffplay.c(简易播放器)、ffprobe.c(媒体信息分析工具)等入口文件。这些工具通过调用上述库实现功能,对理解库 API 有很好的参考价值。
二、核心数据结构与生命周期
FFmpeg 的数据流转围绕几个关键结构体展开,理解它们的关系是读懂源码的基础。
1. AVFormatContext —— 格式上下文
代表一个媒体文件或流的上下文,封装了封装格式相关的信息:AVInputFormat(解复用器)或 AVOutputFormat(复用器)、AVStream 数组、AVIOContext(I/O 抽象)等。
生命周期:通过 avformat_open_input() 创建并填充输入上下文;通过 avformat_write_header() 初始化输出上下文;最后用 avformat_close_input() 释放。
2. AVStream —— 流信息
表示一个媒体流(视频、音频、字幕等)。包含编解码器参数(AVCodecParameters)、时间基(time_base)、帧率、时长等。解复用后,每个流对应一个 AVStream。
3. AVCodecContext —— 编解码器上下文
保存编解码器实例的状态和参数(如码率、分辨率、帧率、编码预设等)。通过 avcodec_open2() 与具体的 AVCodec(编解码器实现)绑定,是编解码操作的入口。
4. AVPacket —— 压缩数据包
存储压缩后的音视频数据(即编码后的帧,如 H.264 NAL 单元)。包含 pts(显示时间戳)、dts(解码时间戳)、stream_index(所属流索引)、数据指针等。解复用器输出 AVPacket,送入解码器。
5. AVFrame —— 未压缩的帧数据
存放解码后的原始数据(如 YUV 图像、PCM 音频)。包含多平面数据指针、宽高、格式、pts 等。编码器接收 AVFrame,输出 AVPacket。
数据流示意图:
输入文件 → AVFormatContext (demuxer) → AVPacket → AVCodecContext (decoder) → AVFrame
↓
输出文件 ← AVFormatContext (muxer) ← AVPacket ← AVCodecContext (encoder) ← AVFrame
三、解复用与复用(libavformat)
1. I/O 层:AVIOContext
FFmpeg 使用 AVIOContext 抽象所有 I/O 操作,支持本地文件、网络流、自定义回调(如内存读写)。通过 avio_open() 可以指定 URL 协议(file://、http://、rtmp:// 等),内部根据协议选择对应的 URLProtocol(位于 libavformat/ 下的 protocols.c 中定义)。
2. 解复用流程
当调用 avformat_find_stream_info() 时,解复用器会读取文件头部并探测流信息。每个解复用器(如 libavformat/mov.c 对应 MP4)实现 read_header()、read_packet()、read_seek() 等函数。av_read_frame() 循环调用 read_packet() 返回 AVPacket,内部会处理帧边界拆分(如对 H.264 Annex B 格式进行 startcode 检测)。
3. 复用流程
复用器实现 write_header()、write_packet()、write_trailer()。av_interleaved_write_frame() 负责将 AVPacket 按 dts 排序交错写入,确保输出文件(如 MP4)的媒体数据符合容器规范。
四、编解码器(libavcodec)
编解码器是 FFmpeg 最复杂的部分,支持数百种格式。核心设计如下:
1. 编解码器注册
所有编解码器通过 REGISTER_ENCODER/DECODER 宏在编译时注册,存储在全局链表中。每个 AVCodec 结构体包含名称、类型、id、初始化函数、编码/解码函数、支持的像素格式等。
2. 解码流程
avcodec_find_decoder(AV_CODEC_ID_H264);
avcodec_alloc_context3(codec);
avcodec_parameters_to_context(ctx, stream->codecpar);
avcodec_open2(ctx, codec, NULL);
// 循环
avcodec_send_packet(ctx, packet); // 发送压缩数据
while (avcodec_receive_frame(ctx, frame) == 0) {
// 处理解码后的 frame
}
send_packet将AVPacket送入解码器内部缓冲区。receive_frame从缓冲区取出解码好的AVFrame。这种“生产者-消费者”模式支持帧延迟(如 B 帧需要缓存),也便于硬件解码的异步处理。
3. 编码流程
avcodec_find_encoder(AV_CODEC_ID_H264);
// ... 设置参数
avcodec_open2(ctx, codec, NULL);
// 循环
avcodec_send_frame(ctx, frame);
while (avcodec_receive_packet(ctx, packet) == 0) {
// 处理编码好的 packet
}
4. 硬件加速
FFmpeg 通过 hwaccel 机制支持硬件解码(如 DXVA2、VDPAU、Vulkan、CUDA)。在 AVCodecContext 中设置 hw_device_ctx 和 hw_frames_ctx,解码时通过 avcodec_receive_frame() 得到的 AVFrame 中的 buf[0] 指向硬件缓冲区,可通过 av_hwframe_transfer_data() 将数据下载到内存。编码方面,也支持硬件编码器(如 h264_nvenc),通过 AVCodec 的 pix_fmts 指定硬件支持的像素格式。
五、滤镜系统(libavfilter)
滤镜系统允许将多个处理单元(filter)连接成有向无环图(filter graph),实现复杂的音视频处理。
1. 核心概念
- AVFilter:滤镜的定义,如
scale、overlay、amix。 - AVFilterContext:滤镜实例,包含滤镜的参数、输入输出 pad(端口)。
- AVFilterGraph:滤镜图容器,管理所有 filter 及其连接关系。
- AVFilterLink:两个滤镜之间的连接通道,负责传递
AVFrame以及参数协商(如分辨率、格式)。
2. 构建与执行
通过 avfilter_graph_parse2() 从字符串描述(如 [in]scale=1920x1080,format=yuv420p[out])构建图,或者手动创建 AVFilterContext 并用 avfilter_link() 连接。
执行时,调用 av_buffersrc_add_frame() 向入口 buffer 源滤镜送入 AVFrame,然后循环调用 av_buffersink_get_frame() 从出口 buffer 接收处理后的帧。内部由 ff_filter_graph_run() 驱动,根据滤镜的 filter_frame() 回调进行帧处理。
3. 线程模型
FFmpeg 支持两种滤镜线程模式:
- slice 并行:将一帧图像分割成多个 slice,每个线程处理一部分,适用于像素级操作(如
scale、yadif)。 - frame 并行:多帧并行处理,适用于多个独立帧的处理(如
fps滤镜)。
通过 avfilter_graph_parse2() 中的 thread_type 参数可指定。
六、数据流转与线程模型
1. 传统的单线程模式
ffmpeg.c 命令行工具采用单线程轮询:主循环从输入读取 AVPacket,解码得到 AVFrame,经过滤镜处理后送入编码器,最后复用输出。这种方式简单,但效率较低。
2. 多线程解码
解码器通过 AVCodecContext 的 thread_count 和 thread_type 启用多线程。常见实现:
- 帧级并行:将视频帧分发给多个解码线程,每帧独立解码(适用于 intra-only 编码或能分片的编码)。
- 切片并行:将一帧的多个 slice 分给不同线程,解码后合并(适用于 H.264/HEVC 等)。
内部通过 avcodec_send_packet() 和 avcodec_receive_frame() 的异步接口,配合任务队列实现。
3. 复杂流水线
现代 FFmpeg 通过 ffmpeg 命令行工具的 -filter_complex 支持多输入、多输出的复杂图。其内部实现了一个调度器,管理多个输入线程、滤镜线程、输出线程,通过队列同步。源码位于 fftools/ffmpeg_filter.c 和 fftools/ffmpeg_scheduler.c,可以深入阅读。
七、硬件加速与跨平台
FFmpeg 支持多种硬件加速 API,其设计遵循“框架抽象 + 平台适配”模式。
- 解码:通过
hwaccel框架,解码器将解码任务交给硬件,输出AVFrame中包含AVBufferRef指向硬件帧。用户通过av_hwframe_ctx_create()分配硬件帧池,并通过av_hwframe_transfer_data()将数据从 GPU 拷贝到 CPU。 - 编码:硬件编码器作为普通
AVCodec注册,但通常需要设置pix_fmt为硬件支持的格式(如AV_PIX_FMT_CUDA)。
跨平台方面,FFmpeg 使用大量预处理器宏(#if HAVE_...)和条件编译,确保在不同操作系统上选用正确的系统调用和硬件 API。
八、性能优化与关键算法
-
汇编优化
在libavcodec/x86/、libavcodec/arm/等目录下,使用 x86 SIMD(MMX/SSE/AVX)、ARM NEON 等指令集手写汇编,对核心函数(如 DCT、运动补偿、像素转换)进行极致优化。 -
内存管理
使用引用计数(AVBufferRef)管理数据缓冲区,避免不必要的拷贝。AVFrame内部通过AVBufferRef管理实际数据,允许多个帧共享同一块内存(如零拷贝滤镜)。 -
关键算法
- H.264 熵编码:CABAC 和 CAVLC 的实现(
libavcodec/h264_cabac.c)大量使用查表和位操作。 - 运动估计:在
libavcodec/motion_est.c中实现菱形搜索、六边形搜索等。 - 音频重采样:
libswresample内部使用高质量的 sinc 插值、多相滤波器,并针对不同架构优化。
- H.264 熵编码:CABAC 和 CAVLC 的实现(
九、如何学习 FFmpeg 源码
- 先读文档:官方
doc/目录下的APIchanges、example/示例代码是入门捷径。 - 调试跟踪:用 GDB/LLDB 跟踪
ffmpeg_g(调试版本),观察av_read_frame()、avcodec_send_packet()等关键函数的调用链。 - 聚焦一个模块:例如先深入理解 MP4 解复用(
mov.c),再扩展到 H.264 解码器(h264dec.c)。 - 阅读书籍:雷霄骅的《FFmpeg 从入门到精通》或《深入理解 FFmpeg》对源码分析较系统。