版本说明:本文基于 FFmpeg release/8.1 分支源码,涵盖核心库结构、数据流设计、关键 API 调用链、插件注册机制与内存管理模型。所有代码引用均附具体文件路径与行号,可对照源码阅读。
FFmpeg 源码仓库地址:https://github.com/ffmpeg/ffmpeg
目录
- 整体架构:分层与依赖
- 基础设施层:libavutil
- 编解码层:libavcodec
- 容器格式层:libavformat
- 滤镜层:libavfilter
- [转换层:libswscale 与 libswresample](#转换层:libswscale 与 libswresample)
- 设备层:libavdevice
- [插件注册机制:静态数组 + 懒迭代器](#插件注册机制:静态数组 + 懒迭代器)
- 内存管理:零拷贝引用计数体系
- fftools:多线程调度架构
- 完整转码流水线
- 设计模式总结
1. 整体架构:分层与依赖
FFmpeg 的源码组织体现了严格的分层依赖哲学------上层库可以依赖下层库,但反向依赖被严格禁止。
FFmpeg/
├── libavutil/ # 零依赖基础设施层
├── libavcodec/ # 编解码框架
├── libavformat/ # 容器格式读写(demux/mux)
├── libavfilter/ # 滤镜/后处理框架
├── libswscale/ # 像素格式与分辨率转换
├── libswresample/ # 音频采样格式与采样率转换
├── libavdevice/ # 采集/播放设备抽象
└── fftools/ # 命令行工具(ffmpeg, ffprobe, ffplay)
库间依赖关系:
libavutil
零依赖基础层
libavcodec
编解码
libavformat
容器格式
libswscale
图像转换
libswresample
音频重采样
libavfilter
滤镜图
libavdevice
设备驱动
fftools
命令行工具
libavutil 是整个体系的基石,提供内存管理、引用计数、数学工具、像素格式定义等基础能力,自身无任何外部依赖。这种设计使得每个库可以被单独链接,嵌入式场景中只需引入所需的库。
2. 基础设施层:libavutil
2.1 引用计数核心:AVBuffer / AVBufferRef
FFmpeg 实现零拷贝数据传递的核心是 libavutil/buffer.h 中定义的两层引用计数体系。
公开层 --- AVBufferRef (libavutil/buffer.h:82):
c
typedef struct AVBufferRef {
AVBuffer *buffer; // 指向实际内存块(不透明,用户不可直接访问)
uint8_t *data; // 数据指针(可以是 buffer->data 的子区间)
size_t size; // 此引用可见的数据大小
} AVBufferRef;
内部层 --- AVBuffer (libavutil/buffer_internal.h:38):
c
struct AVBuffer {
uint8_t *data;
size_t size;
atomic_uint refcount; // 原子引用计数,无锁线程安全
void (*free)(void *opaque, uint8_t *data);
void *opaque;
int flags;
int flags_internal;
};
引用计数操作的内存序设计 (libavutil/buffer.c):
| 操作 | 内存序 | 原因 |
|---|---|---|
av_buffer_ref() |
memory_order_relaxed |
只需原子性,无需同步 |
av_buffer_unref() |
memory_order_acq_rel |
release 保证写操作对后续 acquire 可见 |
这是无锁引用计数的标准实现。refcount 归零时调用 free 回调释放内存,整个过程无需持有任何锁。
写时复制(CoW):
c
// 判断是否独占持有(refcount == 1)
int av_buffer_is_writable(const AVBufferRef *buf);
// 若引用计数 > 1,分配新缓冲并复制数据,将 buf 替换为独占引用
int av_buffer_make_writable(AVBufferRef **buf);
AVFrame 的 av_frame_make_writable() 和 AVPacket 的 av_packet_make_writable() 均基于此机制。
2.2 缓冲池:AVBufferPool
AVBufferPool(libavutil/buffer_internal.h:88)是视频解码场景中消除 malloc/free 抖动的关键:
操作系统 AVBufferPool 解码器 操作系统 AVBufferPool 解码器 alt [池中有空闲缓冲] [池为空] 解码使用帧缓冲 av_buffer_pool_get() 直接返回缓冲(refcount=1) av_malloc() 新内存 返回新缓冲(refcount=1) av_buffer_unref() refcount→0 pool_release_buffer() 归还到空闲链表,不真正释放
核心技巧 :缓冲的 free 回调被替换为 pool_release_buffer()(buffer.c:345),引用计数归零时将缓冲归还到池的空闲链表,而非调用 free()。在 60fps 4K 视频场景中,每秒约 60 次帧分配均走池的快速路径,实测可减少 90% 以上的系统调用开销。
2.3 AVFrame:帧数据的核心容器
AVFrame(libavutil/frame.h:427)是音视频数据在整个处理管线中流转的核心载体:
c
typedef struct AVFrame {
// === 数据平面 ===
uint8_t *data[AV_NUM_DATA_POINTERS]; // 最多 8 个平面的数据指针
int linesize[AV_NUM_DATA_POINTERS]; // 每行字节数(含对齐填充)
// === 引用计数缓冲 ===
AVBufferRef *buf[AV_NUM_DATA_POINTERS]; // 每个平面对应一个引用
AVBufferRef **extended_buf; // 超过 8 个平面时的扩展引用
// === 时间信息 ===
int64_t pts; // 显示时间戳(Presentation Timestamp)
int64_t duration; // 帧时长(time_base 单位)
AVRational time_base; // 时基(如 1/90000)
// === 视频参数 ===
int width, height;
enum AVPixelFormat format;
enum AVColorSpace colorspace;
enum AVColorRange color_range;
// === 音频参数 ===
int sample_rate;
int nb_samples;
AVChannelLayout ch_layout;
// === Side Data(附加元数据)===
AVFrameSideData **side_data; // HDR 元数据、显示矩阵、CC 等
int nb_side_data;
// === 硬件加速 ===
AVBufferRef *hw_frames_ctx; // GPU 帧上下文(CUDA/VAAPI/VideoToolbox)
// === 用户透传数据 ===
AVBufferRef *opaque_ref; // 携带 FrameData 等用户级元数据
} AVFrame;
AVFrameSideDataType 枚举(frame.h:49)定义了 40+ 种附加数据类型,包括 HDR10+、ICC Profile、显示矩阵、内容亮度等级等,这些数据随帧传播,无需通过参数显式传递。
3. 编解码层:libavcodec
3.1 公开/内部双层结构
libavcodec 采用"公开 API 结构 + 内部实现结构"的经典二元设计:
首字段(安全转换)
使用
AVCodec
+const char* name
+enum AVMediaType type
+enum AVCodecID id
+int capabilities
+const AVRational* supported_framerates
+const enum AVPixelFormat* pix_fmts
FFCodec
+AVCodec p
+unsigned cb_type
+int priv_data_size
+union cb
+int (init)(AVCodecContext)
+int (flush)(AVCodecContext)
+void (close)(AVCodecContext)
+int (*update_thread_context)(dst, src)
AVCodecContext
+const AVCodec* codec
+int width, height
+enum AVPixelFormat pix_fmt
+AVRational time_base
+AVBufferRef* hw_frames_ctx
+int frame_num
+int(*get_buffer2)(avctx, frame, flags)
关键设计 :FFCodec.p(codec_internal.h:131)是首字段,因此 AVCodec* 与 FFCodec* 指向同一内存地址,通过 ffcodec() 可进行零成本的安全类型转换:
c
// codec_internal.h
static inline const FFCodec *ffcodec(const AVCodec *codec) {
return (const FFCodec *)codec;
}
这种设计让库的公开 API 只暴露 AVCodec,内部实现细节(如具体的回调类型、私有数据大小)完全对外隐藏。
3.2 FFCodecType:两代 API 并存
FFCodecType 枚举(codec_internal.h:106)区分了推式(旧)和拉式(新)两代编解码 API:
新式拉式 API(推荐)
FF_CODEC_CB_TYPE_RECEIVE_FRAME
receive_frame(avctx, frame)
FF_CODEC_CB_TYPE_RECEIVE_PACKET
receive_packet(avctx, pkt)
旧式推式 API(仍受支持)
FF_CODEC_CB_TYPE_DECODE
decode(avctx, frame, &got, pkt)
FF_CODEC_CB_TYPE_ENCODE
encode(avctx, pkt, frame, &got)
新式 API 天然支持 B 帧延迟、异步解码和一包多帧输出,是现代硬件解码器(CUDA、VideoToolbox)的标准实现方式。FFmpeg 的公开 API avcodec_send_packet / avcodec_receive_frame 会根据 cb_type 自动路由到对应的内部实现。
3.3 解码流水线:send/receive 模型
现代 FFmpeg 使用基于 send/receive 的异步解码模型,内部实现在 libavcodec/decode.c:
buffer_frame 解码器回调 avcodec API 调用方 buffer_frame 解码器回调 avcodec API 调用方 avcodec_send_packet(avctx, pkt) 将 pkt 存入 buffer_pkt decode_receive_frame_internal() 解码完成(可能0帧或多帧) 存入 buffer_frame avcodec_receive_frame(avctx, frame) 取出 buffer_frame fill_frame_props() 填充色彩空间 guess_correct_pts() 修正PTS apply_cropping() 裁剪 返回解码帧 + avctx->>frame_num++
guess_correct_pts() (decode.c:293):统计历史 PTS/DTS 的误差,动态选择更可靠的时间戳来源,解决 B 帧乱序、容器 PTS 缺失等问题。
decode_bsfs_init() (decode.c:186):在解码前自动插入所需的 Bitstream Filter(如 H.264 的 h264_mp4toannexb),对调用方完全透明。
apply_param_change() (decode.c:114):处理 AV_PKT_DATA_PARAM_CHANGE side data,支持流内动态分辨率/格式变化,无需重新打开解码器。
3.4 avcodec_open2():编解码器初始化流程
解码
编码
avcodec_open2(avctx, codec, options)
libavcodec/avcodec.c:144
验证 codec_type 匹配
检查 codec 白名单
分配内部上下文
DecodeContext / EncodeContext
av_opt_set_dict2()
应用 AVOptions
init_static_data()
静态初始化
编码/解码
decode_preinit()
验证 hw_frames_ctx
检查格式支持
encode_preinit_video()
encode_preinit_audio()
验证格式/采样率/声道
ff_thread_init()
多线程初始化
帧级/片级并行
ffcodec(codec)->init(avctx)
调用具体 codec 初始化
3.5 编码流水线
avcodec_receive_packet codec encode callback avcodec_send_frame 调用方 avcodec_receive_packet codec encode callback avcodec_send_frame 调用方 [首帧] 生成 ICC Profile pad_last_frame() 音频末帧填充 avcodec_send_frame(avctx, frame) 存入 buffer_frame encode_simple_internal() ff_encode_encode_cb() 编码完成,pkt 存入 buffer_pkt avcodec_receive_packet(avctx, pkt) 自动填充 pts/dts/duration 返回压缩包
pad_last_frame() (encode.c:156):音频编码器通常需要固定帧大小(frame_size > 0),对最后一帧用静音填充到完整大小,避免编码器因输入不完整而静默丢弃数据。
4. 容器格式层:libavformat
4.1 格式结构:相同的公开/内部分层
首字段
使用
AVInputFormat
+const char* name
+const char* long_name
+int(read_probe)(const AVProbeData)
+int(read_header)(AVFormatContext)
+int(read_packet)(AVFormatContext, AVPacket*)
+int(read_seek)(AVFormatContext, int, int64_t, int)
FFInputFormat
+AVInputFormat p
+int raw_codec_id
+int priv_data_size
+int flags_internal
+int(read_close)(AVFormatContext)
+int(*read_seek2)(...)
AVFormatContext
+AVInputFormat* iformat
+AVOutputFormat* oformat
+AVIOContext* pb
+AVStream** streams
+unsigned int nb_streams
+int64_t start_time
+int64_t duration
+int64_t bit_rate
+AVDictionary* metadata
+int64_t probesize
4.2 avformat_open_input():打开容器的完整流程
是
否
avformat_open_input(ps, url, fmt, options)
libavformat/demux.c:231
avformat_alloc_context()
分配 AVFormatContext
指定了 fmt?
iformat->read_header(s)
解析容器头部
如 MP4 moov box
s->io_open()
打开 AVIOContext
av_probe_input_buffer2()
读取探测数据,打分匹配格式
update_stream_avctx()
将流参数同步到
AVCodecContext
格式探测机制 :av_probe_input_buffer2() 读取最多 s->probesize(默认 5MB)字节,依次调用每个 demuxer 的 probe() 函数打分(0-100),得分最高者胜出。打分依据:文件头魔数、扩展名、MIME 类型。
4.3 ff_read_packet():读取数据包的核心逻辑
是
否
ff_read_packet(s, pkt)
demux.c:629
raw_packet_buffer
有缓存的包?
从缓冲取包
iformat->read_packet(s, pkt)
格式解析(如 MP4 mdat 读取)
av_packet_make_refcounted(pkt)
确保引用计数
handle_new_packet()
处理损坏包、入队 raw_packet_buffer
update_timestamps()
PTS 环绕修正
实时时钟替换
probe_codec()
对未确认流探测 codec
has_decode_delay_been_guessed() (demux.c:759):专门处理 H.264 B 帧延迟探测------根据已解码帧数和 has_b_frames 值判断延迟是否已稳定,解决 H.264 初始化时 has_b_frames 未知的经典问题。
4.4 avformat_write_header():Mux 初始化
是
否
avformat_write_header(s, options)
init_muxer(s, options)
验证所有输出流参数
codec_tag 匹配
流复制模式?
ff_interleave_packet_passthrough
快速路径,无需重排序
ff_interleave_packet_per_dts
按 DTS 排序,保证时序正确
oformat->write_header(s)
写容器头部(如 MP4 ftyp+moov)
5. 滤镜层:libavfilter
5.1 核心数据模型
libavfilter 的数据模型由四个核心结构构成:
实例化
outputs
dst
包含
1
N
AVFilter
+const char* name
+const AVFilterPad* inputs
+const AVFilterPad* outputs
+int flags
+int (init)(AVFilterContext)
+void (uninit)(AVFilterContext)
+int (activate)(AVFilterContext)
AVFilterContext
+const AVFilter* filter
+char* name
+AVFilterLink** inputs
+unsigned nb_inputs
+AVFilterLink** outputs
+unsigned nb_outputs
+void* priv
+AVFilterGraph* graph
AVFilterLink
+AVFilterContext* src
+AVFilterContext* dst
+enum AVMediaType type
+int w, h
+enum AVPixelFormat format
+AVRational time_base
+AVRational frame_rate
AVFilterGraph
+AVFilterContext** filters
+unsigned nb_filters
+char* scale_sws_opts
+int nb_threads
AVFilter (libavfilter/avfilter.h:216)是静态类型描述符(类比类),AVFilterContext (avfilter.h:274)是运行时实例(类比对象)。每个实例通过 AVFilterLink 与其他实例相连,构成有向无环图。
activate 回调 (新式驱动模型):与旧式 filter_frame/request_frame 回调相比,activate 允许滤镜自主决定何时处理数据,更好地支持异步操作和帧率变换类滤镜(如 fps、setpts)。
5.2 filtergraph 构建与格式协商
AVFilterLink[] AVFilterGraphSegment AVFilterGraph ffmpeg_filter.c AVFilterLink[] AVFilterGraphSegment AVFilterGraph ffmpeg_filter.c 实例化每个 AVFilterContext av_opt_set() 设置参数 连接所有链路,绑定 buffersrc/buffersink 格式约束从 buffersink 向上游传播 avfilter_graph_alloc() avfilter_graph_segment_parse(graph, "scale=1280:720,fps=30") avfilter_graph_segment_create_filters() graph_opts_apply() avfilter_graph_segment_apply(inputs, outputs) avfilter_graph_config() query_formats() 协商格式 自动插入 scale/aresample 转换滤镜
格式约束下压 (ffmpeg_filter.c:383):choose_pix_fmts() 将编码器支持的格式列表(如 yuv420p|yuv444p)序列化为 filtergraph 选项字符串注入到 buffersink,驱动格式协商从输出端向输入端传播,确保 filtergraph 输出格式与编码器输入格式匹配。
5.3 数据注入与提取
av_buffersrc_add_frame_flags()
libavfilter/buffersrc.h
av_buffersink_get_frame()
libavfilter/buffersink.h
解码器
AVFrame
buffersrc
AVFilterContext
scale
1280:720
fps=30
buffersink
AVFilterContext
编码器
AVFrame
音频场景使用 av_buffersink_get_samples(ctx, frame, nb_samples)(buffersink.h:166),支持定长采样提取(音频编码器通常要求固定帧大小)。
5.4 动态重配置机制
filter_thread() 的 send_frame() 逻辑(ffmpeg_filter.c:3065)检测以下变化并触发重配置:
| 变化类型 | 检测条件 |
|---|---|
| 视频分辨率/格式 | w/h/format/sar != link->w/h/format/sar |
| 色彩空间参数 | colorspace/color_range/color_primaries/trc |
| 显示矩阵 | side data AV_FRAME_DATA_DISPLAYMATRIX 变化 |
| 音频格式/声道 | sample_rate/ch_layout/format |
| 硬件加速上下文 | hw_frames_ctx 指针变化 |
重配置流程:flush 现有输出帧 → configure_filtergraph() 重建滤镜图 → 继续处理新帧。这是 FFmpeg 处理动态流参数变化的核心机制,例如直播流中的分辨率切换。
6. 转换层:libswscale 与 libswresample
6.1 libswscale:图像缩放与像素格式转换
SwsContext(libswscale/swscale.h:191)支持 100+ 种像素格式间的相互转换,内部使用 SIMD 指令集(MMX/SSE/AVX/NEON)加速。
算法选择 (SwsFlags 枚举 swscale.h:96):
| 标志 | 算法 | 适用场景 |
|---|---|---|
SWS_FAST_BILINEAR |
快速双线性 | 实时低延迟预览 |
SWS_BILINEAR |
双线性插值 | 一般质量转码 |
SWS_BICUBIC |
双三次插值 | 默认,均衡质量 |
SWS_LANCZOS |
Lanczos 重采样 | 高质量压缩/存档 |
新式 API(推荐):
c
// 一步完成设置和缩放(内部自动检测格式变化并重配置)
int sws_scale_frame(SwsContext *ctx, AVFrame *dst, const AVFrame *src);
遗留 API:
c
// 分步控制(适合需要精细控制的场景)
SwsContext *sws_getContext(srcW, srcH, srcFormat, dstW, dstH, dstFormat, flags, ...);
int sws_scale(c, srcSlice[], srcStride[], srcSliceY, srcSliceH, dst[], dstStride[]);
6.2 libswresample:音频重采样与格式转换
c
// 配置重采样器(新式 API)
int swr_alloc_set_opts2(SwrContext **ps,
const AVChannelLayout *out_ch_layout,
enum AVSampleFormat out_sample_fmt, int out_sample_rate,
const AVChannelLayout *in_ch_layout,
enum AVSampleFormat in_sample_fmt, int in_sample_rate,
int log_offset, void *log_ctx); // swresample.h:259
int swr_init(SwrContext *s);
// 执行转换
int swr_convert(SwrContext *s, uint8_t **out, int out_count,
const uint8_t **in, int in_count); // swresample.h:314
// 精确跟踪采样率转换引起的 PTS 漂移
int64_t swr_next_pts(SwrContext *s, int64_t pts); // swresample.h:334
swr_next_pts() 是正确处理音频 PTS 的关键------采样率转换(如 48000→44100)会导致非整数比例关系,简单线性缩放会积累误差,swr_next_pts() 通过精确跟踪采样计数来消除漂移。
7. 设备层:libavdevice
libavdevice 通过将设备伪装为 AVFMT_NOFILE 类型的 demuxer/muxer,无缝接入 libavformat 框架:
AVFMT_NOFILE
无文件,直接采集
应用层
(ffmpeg -f avfoundation -i ...)
avformat_open_input()
AVInputFormat
(avfoundation/v4l2/dshow)
硬件设备
摄像头/麦克风
设备注册机制 (libavdevice/avdevice.h:77):
c
void avdevice_register_all(void);
// 内部调用 avpriv_register_devices(),通过原子写操作将
// 设备的 demuxer/muxer 列表指针注入到 allformats.c 的迭代器链中
典型设备列表:
| 平台 | 采集设备 | 输出设备 |
|---|---|---|
| macOS | avfoundation |
opengl |
| Linux | v4l2, alsa |
fbdev, opengl |
| Windows | dshow, gdigrab |
sdl2 |
| 跨平台 | lavfi(测试源) |
null(丢弃) |
8. 插件注册机制:静态数组 + 懒迭代器
8.1 编译期静态注册
FFmpeg 不使用运行时动态注册(旧版 avcodec_register() 已废弃),而是在编译期通过构建系统生成静态数组:
c
// libavcodec/allcodecs.c(由构建系统生成)
extern const FFCodec ff_h264_decoder;
extern const FFCodec ff_aac_encoder;
// ... 数百个 codec ...
static const FFCodec * const codec_list[] = {
&ff_h264_decoder,
&ff_aac_encoder,
// ...
NULL, // 哨兵值
};
格式库采用完全相同的模式(libavformat/allformats.c):
c
#include "libavformat/muxer_list.c" // static const FFOutputFormat * const muxer_list[]
#include "libavformat/demuxer_list.c" // static const FFInputFormat * const demuxer_list[]
8.2 迭代器模式:无状态遍历
否
是
avcodec_find_decoder(AV_CODEC_ID_H264)
av_codec_iterate(&opaque)
线性扫描 codec_list[]
匹配?
返回 &FFCodec->p
即 AVCodec*
迭代器 API(allcodecs.c:957):
c
const AVCodec *av_codec_iterate(void **opaque) {
uintptr_t i = (uintptr_t)*opaque;
const FFCodec *c = codec_list[i];
if (c) {
*opaque = (void*)(i + 1);
return &c->p; // 返回公开 AVCodec*
}
return NULL;
}
此设计的优点:线程安全(opaque 是调用方维护的状态,无全局锁)、无需初始化(首次调用即可使用)、内存占用固定(无动态链表)。
8.3 ff_sd_global_map:side data 类型映射
ff_sd_global_map[](libavcodec/avcodec.c:57)定义了容器级 side data(AVPacketSideDataType)到帧级 side data(AVFrameSideDataType)的映射:
c
const struct AVSideDataDescriptor ff_sd_global_map[] = {
{ AV_PKT_DATA_REPLAYGAIN, AV_FRAME_DATA_REPLAYGAIN },
{ AV_PKT_DATA_DISPLAYMATRIX, AV_FRAME_DATA_DISPLAYMATRIX },
{ AV_PKT_DATA_ICC_PROFILE, AV_FRAME_DATA_ICC_PROFILE },
{ AV_PKT_DATA_HDR_DYNAMIC_METADATA, AV_FRAME_DATA_DYNAMIC_HDR_PLUS },
// ...
};
通过此映射,HDR 元数据、ICC Profile 等全局信息从容器层自动传播到解码帧,滤镜和编码器无需关心数据来源。
9. 内存管理:零拷贝引用计数体系
9.1 三层引用计数架构
层次 1:实际内存
层次 2:引用句柄
层次 3:媒体容器
AVFrame
buf[0..7]: AVBufferRef*
AVPacket
buf: AVBufferRef*
AVBufferRef
data偏移, size
AVBufferRef
data偏移, size
AVBuffer
atomic_uint refcount
free() callback
uint8_t* data
(实际内存块)
零拷贝传递示例:
c
AVFrame *src = decode_one_frame(dec_ctx);
AVFrame *dst = av_frame_alloc();
// av_frame_ref() 只增加 AVBuffer.refcount,不复制像素数据
av_frame_ref(dst, src);
// 两帧共享同一 AVBuffer,refcount == 2
// 只有当两者都 unref 后,内存才被释放
av_frame_unref(src); // refcount → 1
av_frame_unref(dst); // refcount → 0 → free()
9.2 硬件帧:GPU 内存管理
硬件解码(CUDA/VAAPI/VideoToolbox)场景中,AVFrame 持有 hw_frames_ctx(AVBufferRef* 指向 AVHWFramesContext),管理 GPU 侧帧池:
物理 GPU 内存
AVFrame
hw_frames_ctx: AVBufferRef*
AVHWFramesContext
GPU 帧池
device_ctx: AVBufferRef*
AVHWDeviceContext
CUDA ctx / VA display
GPU 显存
(实际帧数据)
CPU 侧的 AVFrame 结构本身极小(约 1KB),GPU 显存通过 hw_frames_ctx 的引用计数管理。av_hwframe_transfer_data() 在需要时将 GPU 帧下载到 CPU,支持 GPU→CPU 和 CPU→GPU 双向传输。
9.3 FrameData:帧级元数据的引用计数透传
FrameData(fftools/ffmpeg.h:720)通过 frame->opaque_ref 附着在帧上随管线传播:
c
typedef struct FrameData {
// 解码器填充的时间信息
struct {
int64_t pts;
AVRational tb;
int64_t frame_num;
} dec;
int64_t dts_est; // Demux 侧估算的 DTS
AVRational frame_rate_filter; // Filtergraph 输出帧率
// 延迟分析探针点(微秒时间戳)
int64_t wallclock[LATENCY_PROBE_NB];
// LATENCY_PROBE_DEMUX → LATENCY_PROBE_DEC_PRE/POST
// → LATENCY_PROBE_FILTER_PRE/POST → LATENCY_PROBE_ENC_PRE/POST
} FrameData;
wallclock[] 在每个处理阶段记录 av_gettime_relative() 时刻,可精确分析每个处理单元的延迟贡献,是 FFmpeg 内建的性能分析机制。
10. fftools:多线程调度架构
10.1 从单线程到 Scheduler
FFmpeg 4.x 及以前使用单线程轮询模型(一个 while 循环串行完成所有工作)。现代 FFmpeg 引入了基于 Scheduler 的多线程流水线架构(fftools/ffmpeg_sched.c),每个处理单元独立运行在自己的线程中:
Muxer 线程
Encoder 线程组
FilterGraph 线程
Decoder 线程组
Demuxer 线程
ThreadQueue
AVPacket
ThreadQueue
AVPacket
ThreadQueue
AVFrame
ThreadQueue
AVFrame
ThreadQueue
AVFrame
ThreadQueue
AVFrame
ThreadQueue
AVPacket
ThreadQueue
AVPacket
input_thread()
av_read_frame()
ts_fixup()
decoder_thread()
视频 H.264
decoder_thread()
音频 AAC
filter_thread()
scale + fps
encoder_thread()
libx264
encoder_thread()
libfdk-aac
muxer_thread()
av_interleaved_write_frame()
10.2 Scheduler 内部结构
Scheduler
+SchDemux* demux
+SchMux* mux
+SchDec* dec
+SchEnc* enc
+SchFilterGraph* fg
+SchSyncQueue* sq_enc
+pthread_mutex_t finish_lock
+unsigned nb_mux_done
SchDec
+SchedulerNode src
+SchDecOutput* outputs
+SchTask task
+ThreadQueue* queue
+AVFrame* send_frame
SchEnc
+SchedulerNode src
+SchedulerNode* dst
+int sq_idx[2]
+int opened
+ThreadQueue* queue
+AVPacket* send_pkt
+int(*open_cb)(opaque, frame)
SchFilterGraph
+SchFilterIn* inputs
+SchFilterOut* outputs
+SchTask task
+ThreadQueue* queue
+SchWaiter waiter
+unsigned best_input
SchWaiter
+pthread_mutex_t lock
+pthread_cond_t cond
+atomic_int choked
SchWaiter (ffmpeg_sched.c:52)实现背压(back-pressure)控制:当下游消费速度跟不上时,将上游线程标记为 choked,使其阻塞等待,防止内存无限增长。
SCHEDULE_TOLERANCE (ffmpeg_sched.c:45):100ms,允许各流时间戳在此阈值内差异,超出则触发背压。
10.3 调度器 DAG 构建 API
c
// fftools/ffmpeg_sched.h:333
// 连接 demuxer 流 → decoder
sch_connect(sch, SCH_DEMUX_OUT(demux_idx, stream_idx),
SCH_DEC_IN(dec_idx));
// 连接 decoder → filtergraph 输入
sch_connect(sch, SCH_DEC_OUT(dec_idx, output_idx),
SCH_FILTER_IN(fg_idx, input_idx));
// 连接 filtergraph 输出 → encoder
sch_connect(sch, SCH_FILTER_OUT(fg_idx, output_idx),
SCH_ENC_IN(enc_idx));
// 连接 encoder → muxer 流
sch_connect(sch, SCH_ENC_OUT(enc_idx),
SCH_MUX_IN(mux_idx, stream_idx));
这些连接构成了处理管线的有向无环图(DAG),调度器根据此图初始化所有 ThreadQueue,启动线程,并处理 EOF 传播和错误恢复。
10.4 main() 与 transcode()
main(argc, argv)
fftools/ffmpeg.c:981
av_log_set_flags()
avdevice_register_all()
avformat_network_init()
sch = sch_alloc()
分配调度器
ffmpeg_parse_options(argc, argv, sch)
解析所有命令行选项
构建 InputFile/OutputFile/Decoder/Encoder
通过 sch_connect() 建立 DAG
transcode(sch)
ffmpeg.c:887
sch_start(sch)
启动所有处理线程
while !sch_wait():
print_report() 打印进度
sch_stop(sch)
等待所有线程 join
of_write_trailer()
写容器尾部
如 MP4 moov 重写
10.5 Demuxer 线程:时间戳处理流水线
DemuxStream(fftools/ffmpeg_demux.c:43)扩展 InputStream,在 ts_fixup() 中实现完整的时间戳修正链:
ts_fixup(d, pkt, fd)
ffmpeg_demux.c:372
wrap_correction
PTS 环绕修正
减去 1<
ts_offset 应用
pkt->dts += ts_offset
(首帧校正全局偏移)
ts_scale 应用
pkt->pts *= ds->ts_scale
(-itsscale 选项)
loop duration 累加
(-stream_loop 选项)
max_pts/min_pts 跟踪
ist_dts_update()
更新 first_dts/next_dts
音频: next_dts += samples/rate
视频: next_dts 基于 framerate
fd->wallclock[LATENCY_PROBE_DEMUX]
= av_gettime_relative()
记录离开 demuxer 的时刻
10.6 Encoder 懒初始化:open_cb 机制
编码器在收到第一帧时才真正调用 avcodec_open2(),这是因为编码参数(分辨率、格式、采样率)只有 filtergraph 处理第一帧后才能确定:
avcodec_open2 open_cb (enc_open) encoder_thread() 调度器 avcodec_open2 open_cb (enc_open) encoder_thread() 调度器 后续帧直接调用 avcodec_send_frame() sch_enc_receive(sch, idx, frame) [首帧] open_cb(enc, frame) 从 frame 填充 enc_ctx 参数 分辨率/格式/声道等 hw_device_setup_for_encode() avcodec_open2(enc_ctx, codec, NULL) 初始化完成 of_stream_init() → sch_mux_stream_ready() 返回首帧
10.7 Muxer 时间戳精修:mux_fixup_ts()
mux_fixup_ts()(fftools/ffmpeg_mux.c:138)是保证输出文件时间戳正确性的最后一道关卡:
| 流类型 | 处理方式 | 原因 |
|---|---|---|
| 音频 | av_rescale_delta() |
非线性时基转换,考虑采样对齐,消除累积误差 |
| 视频/字幕 | av_packet_rescale_ts() |
线性缩放,视频帧时长整除不会有误差 |
| 所有流 | 强制 DTS 单调递增 | 若 dts <= last_mux_dts,则 dts = last_mux_dts + 1 |
强制 DTS 单调是 MP4/MKV 等容器的硬性要求,违反则播放器无法正确 seek。
10.8 PreMuxQueue:多流延迟启动缓冲
PreMuxQueue(ffmpeg_sched.c:172)解决了多路流开启时序不一致的问题:
真正的 Muxer 线程 PreMuxQueue 音频编码器 视频编码器 真正的 Muxer 线程 PreMuxQueue 音频编码器 视频编码器 所有流 stream_ready 后 缓冲视频包(Muxer 未就绪) 音频编码器尚未 open(等待首帧) open_cb → sch_mux_stream_ready() sch_mux_stream_ready() 启动 Muxer 线程 avformat_write_header() 冲刷缓冲包 后续视频包直接写入 后续音频包直接写入
11. 完整转码流水线
以 ffmpeg -i input.mp4 -vf scale=1280:720 -c:v libx264 -c:a aac output.mp4 为例:
阶段 5: 封装 (Muxer Thread)
阶段 4: 编码 (Encoder Threads)
阶段 3: 滤镜图 (FilterGraph Thread)
阶段 2: 解码 (Decoder Threads)
阶段 1: 解封装 (Demuxer Thread)
AVPacket H.264
ThreadQueue
AVFrame YUV420P
ThreadQueue
AVFrame 1280x720
ThreadQueue
AVPacket H.264
ThreadQueue
input.mp4
AVIOContext 读取
MP4 demuxer
read_packet()
解析 mdat box
ts_fixup()
PTS环绕/偏移/缩放修正
sch_demux_send(pkt)
推入 ThreadQueue
sch_dec_receive(pkt)
avcodec_send_packet(dec_ctx, pkt)
H.264 / AAC
avcodec_receive_frame(dec_ctx, frame)
guess_correct_pts()
fill_frame_props()
sch_dec_send(frame)
sch_filter_receive(frame)
av_buffersrc_add_frame_flags()
推入 buffersrc
scale=1280:720
调用 libswscale
avfilter_graph_request_oldest()
驱动图执行
av_buffersink_get_frame()
从 buffersink 取帧
sch_filter_send(frame)
sch_enc_receive(frame)
首帧触发 enc_open()
avcodec_send_frame(enc_ctx, frame)
libx264 / aac
avcodec_receive_packet(enc_ctx, pkt)
mux_fixup_ts()
sch_enc_send(pkt)
sch_mux_receive(pkt)
输出侧 BSF 处理
sync_queue_process()
多流 DTS 排序
av_interleaved_write_frame()
写入 output.mp4
关键数据结构在各阶段的状态:
| 阶段 | 数据结构 | 格式 | 时间戳来源 |
|---|---|---|---|
| Demux 输出 | AVPacket |
H.264 Annex B / AAC ADTS | 容器 PTS/DTS(已修正) |
| Decode 输出 | AVFrame |
YUV420P / FLTP |
guess_correct_pts() |
| Filter 输出 | AVFrame |
YUV420P 1280x720 |
av_rescale_q() 转换时基 |
| Encode 输出 | AVPacket |
H.264 / AAC | avcodec_receive_packet() 自动填充 |
| Mux 输入 | AVPacket |
H.264 / AAC | mux_fixup_ts() 精修后 |
EOF 传播机制:
空包 data=NULL
drain 完成
filtergraph EOF
encoder draining
muxer 结束
Demuxer 读完
av_read_frame() 返回 AVERROR_EOF
avcodec_send_packet(NULL)
触发 draining 模式
avcodec_receive_frame() 返回 AVERROR_EOF
av_buffersrc_close()
av_buffersink_get_frame() 返回 AVERROR_EOF
avcodec_send_frame(NULL)
avcodec_receive_packet() 返回 AVERROR_EOF
sch_enc_send(NULL)
of_write_trailer()
av_write_trailer()
EOF 沿管线逐级传播,每个阶段先 drain 完缓冲中的数据再向下游发送 EOF 信号,确保不丢帧。
12. 设计模式总结
通过深度阅读 FFmpeg 源码,可以归纳出以下贯穿全项目的设计模式:
12.1 公开/内部双层结构(Pimpl 变体)
c
// 公开层(API 稳定,向后兼容)
typedef struct AVCodec { ... } AVCodec;
// 内部层(首字段必须是公开结构,允许安全转换)
typedef struct FFCodec {
AVCodec p; // 首字段!
// 内部实现细节...
} FFCodec;
AVCodec/FFCodec、AVInputFormat/FFInputFormat 均采用此模式。公开 API 永不破坏,内部实现可自由演进。
12.2 静态数组 + 迭代器(无锁插件系统)
编译期生成插件数组,运行时通过无状态迭代器遍历。相比动态链表,无需初始化、无全局锁、内存占用固定、缓存友好。
12.3 Send/Receive 解耦模型(生产者/消费者)
avcodec_send_packet() → 内部缓冲 → avcodec_receive_frame()
将数据推送和结果消费解耦,自然支持 1:0(丢帧)、1:1(正常)、1:N(多帧)的输入输出比例,以及异步解码(GPU)场景。
12.4 引用计数 + 零拷贝传递
AVBuffer → AVBufferRef → AVFrame/AVPacket 的三层引用计数体系,配合原子操作实现无锁线程安全,整个处理管线中媒体数据从不复制。
12.5 懒初始化(Lazy Initialization)
编码器在第一帧到达时才 avcodec_open2(),filtergraph 在所有输入格式已知时才 avfilter_graph_config()。这解决了鸡生蛋问题------参数依赖于数据,数据才能到来后才能初始化。
12.6 背压感知调度(Back-Pressure Aware Scheduling)
SchWaiter + ThreadQueue 实现了背压传播:下游处理慢时,上游生产者阻塞等待,系统自动平衡各阶段吞吐,防止内存无限增长。
附录:关键文件索引
| 文件 | 核心职责 |
|---|---|
libavutil/buffer.c |
AVBuffer 引用计数实现 |
libavutil/buffer_internal.h |
AVBuffer/AVBufferPool 内部结构 |
libavutil/frame.h |
AVFrame 定义 |
libavcodec/codec_internal.h |
FFCodec 内部结构、FFCodecType 枚举 |
libavcodec/avcodec.c |
avcodec_open2、ff_sd_global_map |
libavcodec/decode.c |
解码核心:send_packet/receive_frame |
libavcodec/encode.c |
编码核心:send_frame/receive_packet |
libavformat/demux.c |
avformat_open_input、ff_read_packet |
libavformat/mux.c |
avformat_write_header |
libavformat/allformats.c |
格式注册与迭代器 |
libavfilter/avfilter.h |
AVFilter/AVFilterContext/AVFilterLink |
libswscale/swscale.h |
SwsContext、SwsFlags |
libswresample/swresample.h |
SwrContext API |
fftools/ffmpeg.c |
main()、transcode() |
fftools/ffmpeg_sched.c |
Scheduler 多线程调度器 |
fftools/ffmpeg_demux.c |
Demuxer 线程、时间戳处理 |
fftools/ffmpeg_dec.c |
Decoder 线程 |
fftools/ffmpeg_filter.c |
FilterGraph 线程、动态重配置 |
fftools/ffmpeg_enc.c |
Encoder 线程、懒初始化 |
fftools/ffmpeg_mux.c |
Muxer 线程、时间戳精修 |
FFmpeg 是一个活跃演进的项目,部分实现细节可能随版本变化。如发现错误或过时内容,欢迎通过 FFmpeg 邮件列表或 Issue 反馈。