FFmpeg 源码原理 笔记

5 阅读8分钟

一、源码结构概览

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) → AVPacketAVCodecContext (decoder) → AVFrame
                                                                                  ↓
输出文件 ← AVFormatContext (muxer) ← AVPacketAVCodecContext (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() 负责将 AVPacketdts 排序交错写入,确保输出文件(如 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_packetAVPacket 送入解码器内部缓冲区。
  • 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_ctxhw_frames_ctx,解码时通过 avcodec_receive_frame() 得到的 AVFrame 中的 buf[0] 指向硬件缓冲区,可通过 av_hwframe_transfer_data() 将数据下载到内存。编码方面,也支持硬件编码器(如 h264_nvenc),通过 AVCodecpix_fmts 指定硬件支持的像素格式。


五、滤镜系统(libavfilter)

滤镜系统允许将多个处理单元(filter)连接成有向无环图(filter graph),实现复杂的音视频处理。

1. 核心概念

  • AVFilter:滤镜的定义,如 scaleoverlayamix
  • 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,每个线程处理一部分,适用于像素级操作(如 scaleyadif)。
  • frame 并行:多帧并行处理,适用于多个独立帧的处理(如 fps 滤镜)。

通过 avfilter_graph_parse2() 中的 thread_type 参数可指定。


六、数据流转与线程模型

1. 传统的单线程模式

ffmpeg.c 命令行工具采用单线程轮询:主循环从输入读取 AVPacket,解码得到 AVFrame,经过滤镜处理后送入编码器,最后复用输出。这种方式简单,但效率较低。

2. 多线程解码

解码器通过 AVCodecContextthread_countthread_type 启用多线程。常见实现:

  • 帧级并行:将视频帧分发给多个解码线程,每帧独立解码(适用于 intra-only 编码或能分片的编码)。
  • 切片并行:将一帧的多个 slice 分给不同线程,解码后合并(适用于 H.264/HEVC 等)。

内部通过 avcodec_send_packet()avcodec_receive_frame() 的异步接口,配合任务队列实现。

3. 复杂流水线

现代 FFmpeg 通过 ffmpeg 命令行工具的 -filter_complex 支持多输入、多输出的复杂图。其内部实现了一个调度器,管理多个输入线程、滤镜线程、输出线程,通过队列同步。源码位于 fftools/ffmpeg_filter.cfftools/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。


八、性能优化与关键算法

  1. 汇编优化
    libavcodec/x86/libavcodec/arm/ 等目录下,使用 x86 SIMD(MMX/SSE/AVX)、ARM NEON 等指令集手写汇编,对核心函数(如 DCT、运动补偿、像素转换)进行极致优化。

  2. 内存管理
    使用引用计数(AVBufferRef)管理数据缓冲区,避免不必要的拷贝。AVFrame 内部通过 AVBufferRef 管理实际数据,允许多个帧共享同一块内存(如零拷贝滤镜)。

  3. 关键算法

    • H.264 熵编码:CABAC 和 CAVLC 的实现(libavcodec/h264_cabac.c)大量使用查表和位操作。
    • 运动估计:在 libavcodec/motion_est.c 中实现菱形搜索、六边形搜索等。
    • 音频重采样libswresample 内部使用高质量的 sinc 插值、多相滤波器,并针对不同架构优化。

九、如何学习 FFmpeg 源码

  1. 先读文档:官方 doc/ 目录下的 APIchangesexample/ 示例代码是入门捷径。
  2. 调试跟踪:用 GDB/LLDB 跟踪 ffmpeg_g(调试版本),观察 av_read_frame()avcodec_send_packet() 等关键函数的调用链。
  3. 聚焦一个模块:例如先深入理解 MP4 解复用(mov.c),再扩展到 H.264 解码器(h264dec.c)。
  4. 阅读书籍:雷霄骅的《FFmpeg 从入门到精通》或《深入理解 FFmpeg》对源码分析较系统。