简介:面向音视频开发者的 FFmpeg n5.1.2 编译库资源包,基于 MinGW64 构建,适用于 Windows 平台下快速接入转码、封装、滤镜等多媒体能力。压缩包共 377 个文件,大小仅 9.12MB,包含 274 个头文件、23 个 C 源文件、14 个动态库、8 个静态库,以及 ffmpeg.exe 等命令行工具和 ffpreset 预设文件;目录涵盖 bin、include、lib、share,另有 MinGW64 编译说明文本,便于还原构建环境。库结构清晰,可链接 libavcodec、libavformat 等核心模块,并借助 AVPacket、AVFrame 等结构完成帧处理与流管理。开发中可从 include 目录获取接口声明,在 lib 目录选择静态或动态链接方式,配合命令行工具快速验证效果。已有 2459 人学习下载,是一份适合入门 FFmpeg 开发、快速搭建音视频处理工程的实用基础资源。
1. 把 ffmpeg n5.1.2 当开发库来用:命令行只是最外面那层壳
第一次看到“音视频开发-FFmpeg-n5.1.2开发库”这个标题,很多人第一反应是命令行工具又出教程了。但做音视频开发的人清楚,FFmpeg 真正值钱的是那套 C 接口的开发库:libavcodec、libavformat、libavfilter、libavutil 这批模块。它们能让你在自己的 C/C++ 程序里直接做解封装、解码、滤镜、编码、推流,而不是拿子进程去调用命令行 ffmpeg。n5.1.2 是 FFmpeg 5.1 系列的补丁版本,接口冻结,老代码迁移成本低,网上能搜到的样例也足够多,适合作为第一套认真啃下来的开发库。这篇就按“拿源码、编库、跑通解码、再上编码推流”的路线,把每个步骤的参数和坑位都摆出来。
2. 拿到 n5.1.2 源码:先分清哪些目录才是开发库
2.1 用 git checkout 锁定 n5.1.2,不要追 master
做音视频开发的应该都有过这种经历:上周还能编过的代码,这周拉一下 FFmpeg 主分支,链接就断,错误信息还完全陌生。FFmpeg 的 master 分支每天都在变,今天还在用的接口,下个月可能就标记了 deprecated,再下个月直接删掉。n5.1.2 是 5.1 系列的补丁版本,所有对外接口在这个 tag 上是冻结的,这也是团队做商用库最看重的一点。
我一般这样拿源码:
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg-src cd ffmpeg-src git checkout n5.1.2 git log -1 --onelineclone 拉的是完整仓库,如果你只是为了裁剪编译,也可以直接下载 n5.1.2 的 tar.xz 源码包,解压后效果一样。checkout 到 n5.1.2 之后,git log -1 会输出这个 tag 对应的 commit,先抄下来。后面不管是编译报警告还是链接出问题,回来对一下 commit,能省不少排查时间。
这地方第一个坑:很多人嫌编译麻烦,去云盘或博客找现成的“ffmpeg master latest win64 essentials.zip”之类压缩包。那个包跑命令行够用,但里面缺开发头文件、缺 pkg-config 文件,拿它当开发库是给自己挖坑。Windows 用户别问“FFmpeg 有 windows 版吗”,官方源码在 Windows 下也能编,只是想省事也得认准带 dev 头文件的包,而不是裸命令行版。
另一个类似问题出现在 Linux:用 yum 或 apt 安装 ffmpeg,装上的是运行库和命令行,系统自带的 -dev 开发包往往没装全,头文件找不齐。有的国产 ARM Linux 发行版(比如银河麒麟 v10)默认源的 ffmpeg 版本还比较老,编出来的库接口和 n5.1.2 对不上。凡是说要做“音视频开发”的,我建议都自己拿源码编一遍,至少要亲手编一次。
2.2 七个库模块,按你的业务选型
进入源码目录后,真正会进链接命令的是下面这几个子库,ffmpeg 可执行文件反而是最后拼装出来的那个外壳。先把每个模块的职责和必选场景列清楚:
| 库模块 | 干什么用 | 什么场景必须带 |
|---|---|---|
| libavformat | 解封装/封装:mp4、flv、m3u8、ts | 几乎所有音视频开发 |
| libavcodec | 编解码:H.264、HEVC、AAC、MP3 | 解码或编码必带 |
| libavutil | 公共工具:内存、时间基、数学 | 永远要带,别裁它 |
| libavfilter | 滤镜:缩放、裁剪、水印、音量 | 做滤镜或转码预处理 |
| libswscale | 像素格式转换、分辨率缩放 | 要显示或截图必带 |
| libswresample | 音频重采样、声道布局转换 | 音频要输出到声卡必带 |
| libavdevice | 摄像头、麦克风、屏幕采集 | 做采集类设备才带 |
选型逻辑一句话:想清楚数据从哪来、到哪去。你的业务是播放器,输入是压缩文件,输出是显示器和声卡,那 libavformat、libavcodec、libavutil、libswscale、libswresample 基本都跑不掉。你只做转封装,不改画面和声音,那 libavfilter 和 libswscale 可以裁掉,库体积和依赖面都小很多。
2.3 头文件和 pkg-config:开发库的正确打开方式
编译安装后,头文件按模块分目录放在 include 下,比如 include/libavcodec/avcodec.h、include/libavformat/avformat.h,库文件在 lib 下面。但真正喂给编译器的,推荐用 pkg-config 生成的 .pc 文件,里面记录了头文件路径、库路径和依赖顺序。
我习惯给 FFmpeg 指定独立前缀,方便以后换版本:
./configure --prefix=/opt/ffmpeg-n5.1.2 ... make -j8 && make install装完先这样验证:
export PKG_CONFIG_PATH=/opt/ffmpeg-n5.1.2/lib/pkgconfig pkg-config --modversion libavcodec pkg-config --cflags --libs libavformat libavcodec libavutil第一行输出以 59 开头的版本号,说明库装好了。cflags 给出 -I 路径,libs 给出 -L 和 -l 参数,后面写 CMake 或 Makefile 直接引用。新手经常卡在这一步:没设置 PKG_CONFIG_PATH,编译器满世界找 avformat.h 却找不到,其实库就在那里,只是没告诉 pkg-config 去哪个目录找。
提示:自己源码安装的 FFmpeg 开发库,一定要带独立 --prefix,不要和系统的 FFmpeg 混装。混装会导致两个版本的头文件互相干扰,链接时甚至同时加载两个版本的 .so,运行阶段的行为怪得没法查。
3. 从源码到可链接的库:n5.1.2 的 configure 参数这样给
3.1 先把库编出来:跑通最小配置
我第一次编译 FFmpeg 开发库时,configure 什么都不加,直接默认参数编了一版,结果 make 出来的 ffmpeg 命令确实好用,但回到自己的工程里链接,发现 libavdevice 也编进来了,还拉进来一堆用不到的依赖。开发库的编译思路和命令行版不一样,第一原则是裁剪:不需要的命令行工具、文档、示例全部关掉,编出来的库才干净。
最少配置:
./configure \ --disable-programs \ --disable-doc \ --disable-avdevice \ --disable-avfilter \ --enable-shared \ --prefix=/opt/ffmpeg-n5.1.2 make -j8 make install参数说明:
--disable-programs不生成 ffmpeg、ffprobe、ffplay 三个可执行文件,编译时间省一大截。做开发库根本不需要它们。--disable-doc不生成文档,避免 make install 卡在 man page 安装上。--disable-avdevice和--disable-avfilter是裁剪,不做采集和滤镜,就能把这两个模块连带的 xcb、SDL2 依赖一起甩掉。--enable-shared生成动态库,方便开发期调试。后面如果要给移动端或嵌入式板子用,再改成静态库。
跑完 make install 后,/opt/ffmpeg-n5.1.2/lib 下应该有 libavcodec.so、libavformat.so、libavutil.so、libswscale.so、libswresample.so。注意这组配置还没编进任何第三方编解码器,H.264 靠 FFmpeg 内置解码器能解,编码则只有 mpeg4 这类老编码可选。
3.2 生产配置:把 x264、x265、fdk-aac 加进来
做实际业务,H.264 和 AAC 基本跑不掉。FFmpeg 内置的 H.264 编码器质量一般,行业里通用做法是用 libx264、libx265、libfdk-aac 这三个外部库。先把它们单独编译安装好,再回来给 FFmpeg 开 configure:
./configure \ --disable-programs \ --disable-doc \ --disable-avdevice \ --disable-avfilter \ --enable-gpl \ --enable-libx264 \ --enable-libx265 \ --enable-libfdk-aac \ --enable-nonfree \ --enable-shared \ --prefix=/opt/ffmpeg-n5.1.2这里有两个许可证相关的点必须讲清楚。--enable-libx264和--enable-libx265要求同时开--enable-gpl,因为这两个库用的是 GPL 协议;--enable-libfdk-aac要求开--enable-nonfree,因为它的许可证不允许按 FFmpeg 的开源条款再分发。如果公司产品要对外发布,这个组合在法务上要提前确认。这是很多团队编到一半停下来返工的血泪经验。
在 Ubuntu 上做这套配置之前,通常还要先装依赖,常见的做法是:
sudo apt-get install libx264-dev libx265-dev libfdk-aac-dev另外有个实用参数是--enable-small,会牺牲少量速度换库体积,做嵌入式板子(比如 rk3588 这类 ARM 平台)或移动端 SDK 时值得开。桌面端和服务器端没必要。
3.3 静态库还是动态库:先想清楚交付方式
--enable-shared生成 .so,--enable-static生成 .a,这个选择直接决定后面集成方式。
动态库好处是主程序体积小、库能单独升级,坏处是部署时要带上所有 .so,还要配好 rpath 或 LD_LIBRARY_PATH。静态库好处是链接时全部打进去,到目标机器上不用配环境,坏处是可执行文件大不少,而且 FFmpeg 一升级就得重新链接整个工程。
我的习惯:PC 端开发期用动态库,方便替换版本做 A/B 对比;交付 Android 或嵌入式 ARM 板子时用静态库。Android 上做 FFmpeg for Android 的常见做法是编出一组 .a 静态库,再在 JNI 层包成单个 .so,这样 APK 里只有一个 so,体积可控,也不容易出现库路径找不到的问题。
注意:静态库和动态库混用是链接问题高发区。比如 libavformat 动态加载、libavcodec 静态加载,两个库内部互相调用时符号归属会乱,运行起来崩了都不知道往哪查。要么全动态,要么全静态,别混着来。
3.4 交叉编译到 Android 与 ARM 的最小配置
做嵌入式或移动端时,configure 要加交叉编译参数。通用的一组配置长这样:
./configure \ --enable-cross-compile \ --target-os=android \ --arch=aarch64 \ --sysroot=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ --cc=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \ --disable-programs \ --enable-sharedtarget-os 和 arch 必须匹配,rk3588 这类 ARM64 板子用 aarch64,32 位 ARM 用 armv7a。sysroot 指向 NDK 的 sysroot,cc 指定带 API 级别的 clang wrapper。编完之后用file libavcodec.so检查架构,不要想当然把 x86 的库装到板子上。交叉编译只是把 configure 参数换一套,后面 make 的逻辑一模一样。
4. 用 n5.1.2 的 C API 跑通第一个解码流程:从初始化到取帧
4.1 打开输入文件,定位视频流
这里用一个最小但完整的 C 程序,目标是把任意视频文件的第一帧视频解出来。先打开文件并拿到流信息:
#include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> int main(int argc, char **argv) { AVFormatContext *fmt_ctx = NULL; if (argc < 2) { fprintf(stderr, "usage: %s input.mp4\n", argv[0]); return -1; } if (avformat_open_input(&fmt_ctx, argv[1], NULL, NULL) < 0) { fprintf(stderr, "cannot open input file\n"); return -1; } if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { fprintf(stderr, "cannot find stream info\n"); return -1; } int video_stream_idx = -1; for (int i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_idx = i; break; } } if (video_stream_idx == -1) { fprintf(stderr, "no video stream found\n"); return -1; }逻辑说明:avformat_open_input只负责打开容器,mp4 的 moov、flv 的 header 都在这步解析;avformat_find_stream_info会尝试读一部分帧数据,把流的基本信息填充到每个 stream 的 codecpar 里。很多新手忘了第二步,后面codecpar->width全是 0,解码出来的帧尺寸就不对。
4.2 创建解码器上下文,打开解码器
拿到 stream 之后,要把 codecpar 里的参数搬到 AVCodecContext 里,再打开解码器。n5.1.2 里推荐直接用avcodec_find_decoder加avcodec_parameters_to_context的组合:
AVCodecParameters *codecpar = fmt_ctx->streams[video_stream_idx]->codecpar; const AVCodec *decoder = avcodec_find_decoder(codecpar->codec_id); if (!decoder) { fprintf(stderr, "unsupported codec\n"); return -1; } AVCodecContext *dec_ctx = avcodec_alloc_context3(decoder); if (avcodec_parameters_to_context(dec_ctx, codecpar) < 0) { fprintf(stderr, "cannot copy codec parameters\n"); return -1; } if (avcodec_open2(dec_ctx, decoder, NULL) < 0) { fprintf(stderr, "cannot open decoder\n"); return -1; }参数说明:codec_id来自 codecpar,比如 H.264 对应AV_CODEC_ID_H264。avcodec_parameters_to_context会把分辨率、帧率、extradata(就是 H.264 的 SPS/PPS)一并拷过去,这一步失败最常见的原因就是上一步没调用avformat_find_stream_info,extradata 还没读到。avcodec_open2的第三个参数是字典类型的 options,比如要开启低延迟模式就传{ "tune", "zerolatency" },日常解码传 NULL 就行。
4.3 解码主循环:读包、送包、收帧
到这里就进入音视频开发最日常的循环了。AVPacket 装压缩数据,AVFrame 装解码后的原始图像:
AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); AVFrame *rgb_frame = av_frame_alloc(); struct SwsContext *sws = sws_getContext( dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, dec_ctx->width, dec_ctx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); while (av_read_frame(fmt_ctx, pkt) >= 0) { if (pkt->stream_index == video_stream_idx) { int ret = avcodec_send_packet(dec_ctx, pkt); if (ret < 0) { fprintf(stderr, "send packet failed\n"); break; } while (ret >= 0) { ret = avcodec_receive_frame(dec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } if (ret < 0) { fprintf(stderr, "receive frame failed\n"); return -1; } sws_scale(sws, (const uint8_t *const *)frame->data, frame->linesize, 0, frame->height, rgb_frame->data, rgb_frame->linesize); fprintf(stderr, "decoded one frame: %dx%d\n", frame->width, frame->height); break; } } av_packet_unref(pkt); }这个循环是整个播放器、转码器、推流器的骨架。av_read_frame按帧读压缩包,先判断是不是目标视频流;avcodec_send_packet把压缩包交给解码器,avcodec_receive_frame取出解码结果。这里有个关键认知:一次 send 之后,可能需要多次 receive,因为 H.264 一个包可能包含多个帧,而且解码器内部有缓存帧。收到 EAGAIN 说明解码器暂时没有输出,不是错误,继续下一包即可。
sws_scale这步是把 YUV 转成 RGB24,很多场景你用不上,因为直接渲染到屏幕要用纹理格式,截图才转 RGB。但建议在调试期保留它,能直观验证解码是否成功。rgb_frame 在做 sws_scale 前必须先分配缓冲区,完整工程里会加一行 av_image_alloc,这里为了短例子省略了,实际开发别漏。
4.4 收尾:不要省掉 unref
解码循环跑完,资源释放顺序也有讲究:
av_frame_free(&frame); av_frame_free(&rgb_frame); av_packet_free(&pkt); avcodec_free_context(&dec_ctx); avformat_close_input(&fmt_ctx); return 0; }av_frame_free和av_packet_free会先把引用计数减一,再释放对象本身。如果你的代码在循环里没有对 pkt 和 frame 做 unref,内存在解码几千帧后必然爆掉。很多人写 demo 不释放,跑一个文件没事,放线上跑一个月就出问题。音视频开发的内存管理就一句话:AVPacket 和 AVFrame 是引用计数的,谁读谁负责 unref。
5. 编译运行 n5.1.2 开发库的 5 个排查点:现象、原因、解决
5.1 avcodec_open2 返回 invalid argument
现象:程序编译过了,运行时avcodec_open2返回负值,日志里只有一句 “Invalid argument”。
原因:最常见的是avcodec_parameters_to_context没调用或调用失败,导致 AVCodecContext 里的宽高、像素格式、extradata 都是初始值。解码器一校验参数就对不上,直接拒绝打开。还有一种可能:解码器是 H.264 但 codec_id 匹配错,比如文件里实际是 HEVC,你却按 AVC 去找解码器。
解决:在报错点前面加一段打印,把dec_ctx->codec_id、dec_ctx->width、dec_ctx->height、dec_ctx->extradata_size都打出来。width 为 0 或 codec_id 与文件不符,问题就定位了。按上一章的顺序,先avformat_find_stream_info,再avcodec_parameters_to_context,就不会走到这个坑。
5.2 链接时一堆 undefined reference
现象:链接阶段报几十个undefined reference to av_xxx,看着像库没找到,但 -l 参数明明加了。
原因:两个常见原因。第一,静态库链接有顺序要求,-lavformat -lavcodec -lavutil的顺序不能乱,libavformat 依赖 libavcodec,libavcodec 依赖 libavutil,被依赖的库要放在后面。第二,FFmpeg 还依赖 zlib、lzma、bzlib 这些系统库,光加 -l 不够,还要补-lz -llzma -lbz2 -lm -lpthread。
解决:别再手写链接参数了,直接用 pkg-config 的输出:
gcc decode.c -o decode \ $(pkg-config --cflags --libs libavformat libavcodec libavutil libswscale)pkg-config 输出的顺序是经过设计的,能直接喂给 gcc。如果你用 CMake,用pkg_check_modules或find_package(PkgConfig)自动带出依赖,别自己拼字符串。
5.3 解码几千帧后内存占用一路涨,最后卡死
现象:程序刚跑时正常,解码到几千帧后 RSS 内存持续上升,最终卡死或 OOM。
原因:AVPacket 和 AVFrame 没有 unref。AVPacket 是引用计数对象,每次av_read_frame会拿到新数据,如果不清,旧缓冲区一直被引用着不释放。AVFrame 类似,特别是转出 RGB 后如果还持有,内存涨得极快。
解决:在循环末尾确保每个分支都有av_packet_unref(pkt),取出的 frame 处理完立刻av_frame_unref(frame)。我的习惯是收到帧后先把数据拷贝走或直接渲染,然后马上 unref,绝不让 frame 活到下一轮循环。实在不确定哪里漏了,用 valgrind 跑一遍,definitely lost的条目会直接指到问题行。
5.4 音画不同步,播放越久偏差越大
现象:用开发库读一段视频,视频流和音频流各走各的,刚开始差几百毫秒,最后差好几秒。
原因:没按时间基换算。FFmpeg 里每个流有自己的 time_base,视频流常见是 1/90000,音频流是 1/44100 或 1/48000。直接把pkt.pts当毫秒用,两种流的单位就乱了,越到后面积累偏差越大。
解决:
double pts_sec = pkt->pts * av_q2d(fmt_ctx->streams[pkt->stream_index]->time_base);把每个流的 pts 先转成秒,再统一调度。同步策略可以按播放器标准来:音频时钟为主,视频帧早到就等,晚到就丢。这是播放器核心原理里最基础的一段,很多“越播越卡”的问题根源就在这,不是解码太慢,是时间基根本没对齐。
5.5 命令行能转 m3u8,换成开发库就失败
现象:命令行ffmpeg -i index.m3u8 -c copy out.mp4一次就过,换成开发库代码却总是报错,输出文件时长不对甚至直接失败。
原因:命令行工具自动处理了 m3u8 里每个 segment 的时间戳跳变、全局头部信息、音视频流的选择。开发库没这些隐藏逻辑,AVPacket 里 pts/dts 不连续时,muxer 写入 mp4 就会出现负 dts 或断流,看起来就像“格式不支持”。
解决:在写封装之前先检查并归一化时间戳。用avformat_find_stream_info拿到真实时间基后,把每帧的 pts/dts 统一转到输出容器的 time_base,再交给 muxer。如果只是转封装,可以用AVFMT_FLAG_GENPTS,但根治方法是自己在读包后补一句时间基换算。命令行能做而你做不了的地方,差的就是这些隐藏包装。
6. 从解码走向编码推流:n5.1.2 开发库的进阶玩法
6.1 用 libavcodec 编码 H.264 的最小套路
解码跑通之后,下一个绕不开的需求是编码。用 n5.1.2 里的 libx264 编码一帧视频,核心参数这样设:
const AVCodec *enc = avcodec_find_encoder_by_name("libx264"); AVCodecContext *enc_ctx = avcodec_alloc_context3(enc); enc_ctx->width = 1280; enc_ctx->height = 720; enc_ctx->time_base = (AVRational){1, 30}; enc_ctx->framerate = (AVRational){30, 1}; enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P; if (avcodec_open2(enc_ctx, enc, NULL) < 0) { fprintf(stderr, "cannot open encoder\n"); }time_base在这里表示单帧时长 1/30 秒,编码器会按这个基准给帧打 pts。常见翻车点是 pix_fmt 不匹配,x264 只接受 YUV420P,你喂 RGB24 数据进去,avcodec_send_frame会直接报格式错误,所以源头采集或转码时要先用 libswscale 把像素格式转好。
6.2 推流到 SRS,延迟大多毁在时间戳上
很多人在 rk3588 上编好 FFmpeg 库去推流,结果发现推到 SRS 的流肉眼可见地延迟,几秒之后延迟还在累积。编码参数没问题的前提下,问题基本都在 PTS/DTS 不线性。推流时每个包写入前,要用av_rescale_q把本地时间基转到 muxer 的 time_base,而且要保证 DTS 单调递增:
pkt->pts = av_rescale_q(last_pts, (AVRational){1, 30}, fmt_ctx->streams[stream_idx]->time_base); pkt->dts = pkt->pts;我的习惯是维护一个单调递增的帧计数,按 framerate 推算出 pts,而不是直接从源文件原样拷贝,尤其是做转码推流时。原时间戳里藏着 VFR 的抖动,直接抄过来就会让 SRS 端判断流异常,缓冲越积越深。
6.3 用 ffprobe 跟开发库“对拍”,少走弯路
最后分享一个我常用的验证方法。开发库不是黑匣子,它的输入输出行为和命令行 ffprobe 是一致的。写任何新的封装、提取逻辑之前,先跑一句:
ffprobe -show_streams input.mp4把输出里的 codec_name、width、height、time_base 记下来,再跑你自己的开发库代码,打印同样的字段。两者对不上,说明你的上下文初始化哪个字段漏了,而不是怀疑人生去调解码参数。这套“对拍”帮我排查过的版本不兼容问题,比看文档快得多。
我自己这些年做音视频开发,最大的教训就是:拿到库先别看功能,先看版本、看字段。任何新环境里第一段代码永远是打印av_version_info()和相关模块版本,确认和编译期一致,再谈业务逻辑。FFmpeg 开发库的坑大多不是它设计得差,而是使用者跳过了版本对齐这一步。如果你正从命令行向开发库迁移,希望这篇能帮你少走几步弯路。
本文还有配套的精品资源,点击获取