3. FFmpeg核心架构
FFmpeg 并不是一个单体程序,而是一组职责清晰、可独立编译复用的库。理解这些库的边界,是后续做 RTP 推流、性能调优和故障定位的基础。很多“FFmpeg 卡顿”“花屏”“音画不同步”的问题,本质上不是某个 API 用错,而是没有分清数据在哪个库里被处理、在哪个库里被转换、在哪个库里被丢弃。
核心记忆法:avformat 管“容器与协议”,avcodec 管“压缩与解压”,avutil 管“公共底座”,swscale 管“图像像素”,swresample 管“音频采样”,avfilter 管“滤镜与图处理”。
一、六大核心库的职责边界
从数据流角度看,FFmpeg 的处理链路可以概括为:输入协议/容器 → 解复用 → 解码 → 滤镜/转换 → 编码 → 复用 → 输出协议。六大库分别落在链路的不同位置,彼此通过 AVPacket、AVFrame 等公共结构体传递数据。
- libavformat:负责封装与解封装,即容器格式(MP4、FLV、TS、MKV)和网络协议(RTMP、RTP、RTSP、HLS、UDP)。它输出/输入的是 AVPacket,不关心音视频如何压缩。
- libavcodec:负责编解码,把 AVPacket 解成 AVFrame,或把 AVFrame 编成 AVPacket。H.264、H.265、AAC、Opus 等编解码器都在这里。
- libavutil:公共工具库,提供 AVFrame、AVPacket、AVDictionary、AVRational、内存管理、错误码、日志、数学运算等。几乎所有其他库都依赖它。
- libswscale:图像缩放与像素格式转换,例如 YUV420P 转 RGB24、1080p 转 720p。它只处理视频原始帧。
- libswresample:音频重采样与格式转换,例如 48kHz 转 44.1kHz、立体声转单声道、S16 转 FLTP。
- libavfilter:滤镜框架,支持 scale、crop、overlay、drawtext、fps、atempo 等,可对音视频帧做复杂处理,也是 ffmpeg 命令行 filtergraph 的底层实现。
二、数据流与库的对应关系
下面这张图展示了从输入到输出的典型数据流,以及每个阶段由哪个库负责。注意 AVPacket 与 AVFrame 是贯穿全流程的两类核心数据对象。
三、方法流程:一次典型转码的库调用顺序
以“读取 MP4,转成 RTP 推流”为例,代码层面的调用顺序大致如下。可以看到每个库各司其职,avutil 贯穿始终。
// 1. avformat:打开输入,读取流信息
avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);
avformat_find_stream_info(fmt_ctx, NULL);
// 2. avcodec:查找并打开解码器
avcodec_find_decoder(codecpar->codec_id);
avcodec_open2(dec_ctx, decoder, NULL);
// 3. avformat:读取压缩包
av_read_frame(fmt_ctx, pkt);
// 4. avcodec:解码为原始帧
avcodec_send_packet(dec_ctx, pkt);
avcodec_receive_frame(dec_ctx, frame);
// 5. swscale / swresample:格式与采样转换
sws_scale(sws_ctx, frame->data, frame->linesize, 0, height, dst_data, dst_linesize);
swr_convert(swr_ctx, out, out_count, in, in_count);
// 6. avfilter:按需做 scale、fps、overlay 等处理
av_buffersrc_add_frame_flags(bufsrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF);
av_buffersink_get_frame(bufsink_ctx, filtered_frame);
// 7. avcodec:重新编码
avcodec_send_frame(enc_ctx, filtered_frame);
avcodec_receive_packet(enc_ctx, out_pkt);
// 8. avformat:封装并写出 RTP
av_interleaved_write_frame(out_fmt_ctx, out_pkt);
四、案例/场景:RTP 推流中的库分工
在 RTP 性能调优中,最常被问到的是“延迟高、CPU 高、花屏分别该查哪个库”。可以按下面的清单快速定位:
| 现象 | 优先排查库 | 常见原因 |
|---|---|---|
| RTP 收不到包 / 时间戳错乱 | libavformat | 协议参数、MTU、时间基、SDP 协商 |
| 解码延迟高、卡顿 | libavcodec | 线程数、低延迟标志、B 帧、码率控制 |
| 画面颜色异常、花屏 | libswscale | 像素格式不匹配、linesize 对齐 |
| 音频变调、杂音、不同步 | libswresample | 采样率、声道布局、重采样质量 |
| 滤镜链 CPU 占用高 | libavfilter | filtergraph 复杂、未硬件加速 |
| 内存泄漏、崩溃 | libavutil | AVFrame/AVPacket 未释放、引用计数错误 |
五、误区与复盘
误区一:把 avformat 当解码器用。 avformat 只负责“拆包”,拿到的 AVPacket 仍是压缩数据,必须交给 avcodec 解码才能得到 AVFrame。
误区二:用 swscale 处理音频。 swscale 只处理视频像素,音频重采样必须用 swresample,两者不可混用。
误区三:忽略 avutil 的引用计数。 AVFrame 和 AVPacket 都有引用计数,av_frame_unref / av_packet_unref 必须成对出现,否则在长时间 RTP 推流中会持续泄漏内存。
误区四:认为 avfilter 一定比手写转换快。 简单 scale 用 swscale 往往更直接;avfilter 适合复杂图处理,但 filtergraph 配置不当会引入额外拷贝和延迟。
误区二:用 swscale 处理音频。 swscale 只处理视频像素,音频重采样必须用 swresample,两者不可混用。
误区三:忽略 avutil 的引用计数。 AVFrame 和 AVPacket 都有引用计数,av_frame_unref / av_packet_unref 必须成对出现,否则在长时间 RTP 推流中会持续泄漏内存。
误区四:认为 avfilter 一定比手写转换快。 简单 scale 用 swscale 往往更直接;avfilter 适合复杂图处理,但 filtergraph 配置不当会引入额外拷贝和延迟。
复盘时建议按“数据流方向”逐库检查:输入协议是否正常(avformat)→ 解码是否报错(avcodec)→ 像素/采样是否匹配(swscale/swresample)→ 滤镜是否必要(avfilter)→ 内存与日志是否干净(avutil)。把这条链路走通,FFmpeg 的架构就不再是黑盒,而是可定位、可调优的工程系统。