简介:ffmpeg-3.4-mingw32 是一份面向 32 位 Windows 平台的多媒体开发预编译包,基于 MinGW32 工具链构建,适合使用 Qt/C++ 开发音视频采集、转码、播放或流媒体处理程序的开发者。它对应 FFmpeg 3.4 版本,可作为不依赖 Visual Studio 时的集成选择,便于把编解码、封装与滤镜能力接入桌面项目。压缩包共 170 个文件,约 2.62MB,内容以 C 源码与头文件、动态库和静态库、导入库及 pkg-config 配置为主,并附带导出定义、命令行可执行程序和说明文档,兼顾二次开发与直接调用。已有 229 人学习或下载,说明其在 MinGW32 音视频工程中具有一定参考价值。读者可获得可链接的库文件、开发头文件以及转码、封装、解封装等示例源码,用于搭建 Qt 工程、验证接口调用和排查编译链接问题,也能借助命令行工具完成格式转换与媒体信息查看。
1. 32 位 Qt 工程接 FFmpeg 3.4:先卡住的往往不是代码
上周又被问到一个老问题:Qt 项目里加 FFmpeg,代码是从网上抄的,编译却死活过不去。远程一看,Qt 装的是 64 位 MinGW 套件,手里的 ffmpeg-3.4-mingw32 是 32 位包,链接器一句skipping incompatible就把人挡在门外。这类工程在国内存量很大——老项目、老工控机、老 XP/Win7 现场机,还跑着 32 位 Qt 5.x + MinGW + FFmpeg 3.4 这套组合。装不好不是报错这么简单,而是编过了运行崩、dll 找不到、解出来的帧是花屏。这篇记录的是我从零把这套环境搭通、并且能在同事机器上复现的全过程,适合还在维护 32 位 Qt 工程、又必须用 3.4 这套老 API 的人照着走。
2. 环境对表:Qt 套件、MinGW 位数和 FFmpeg 3.4 包型怎么配
动手之前先把三件事对齐:Qt 套件是多少位、MinGW 是多少位、FFmpeg 包是哪种形态。这三者任意一个错位,后面所有步骤都是白费功夫。我见过最冤的一种情况是:Qt Creator 里明明有Desktop Qt 5.12.x MinGW 32-bit这个套件,但工程一直用的是默认 64 位套件,开发者以为自己在编 32 位程序,直到链接 FFmpeg 才暴露。
2.1 先确认 Qt 套件是不是真的 32 位
不要看安装包名字猜,直接在套件对应的 Qt 命令行环境里问编译器。Qt Creator 菜单里打开对应 MinGW 套件的命令行(或者在 Qt Creator 的「工具 → 选项 → Kits」里找到该套件的编译器路径),执行:
# 用套件自带的 gcc 问目标三元组,这是最硬的证据 gcc -dumpmachine # 补充确认:看指针宽度 gcc -dumpversioni686-w64-mingw32才是 32 位,x86_64-w64-mingw32是 64 位。这两个字符串记住,后面排查链接错误时要反复用到。
还有一种更省事的办法,在.pro里临时把关键变量打出来,编译一次看「概要信息」面板:
# 临时调试用,确认无误后删掉这几行 message("HOST_ARCH = $$QMAKE_HOST.arch") message("QMAKE_CXX = $$QMAKE_CXX") message("QT_ARCH = $$QT_ARCH")QMAKE_HOST.arch是构建机架构,容易误导人——64 位机器上跑 32 位套件时它照样显示 x86_64。真正要看的是QT_ARCH,Qt 5 里 32 位套件输出i386,64 位输出x86_64。这一步做过一次,后面就不会再怀疑「我到底编的是几位」。
提示:Qt 5.15 之后 MinGW 套件的目录名有变化,
QT_ARCH的取值也随 Qt 版本略有差异,以实际编译输出为准,别背结论。
2.2 FFmpeg 3.4 预编译包的三种形态与取舍
Windows 下拿到的 ffmpeg-3.4-mingw32 一般有三种形态,选错一种,就是几小时的返工。
| 形态 | 目录特征 | MinGW 下能否直接用 | 适用场景 |
|---|---|---|---|
| shared + 导入库 | bin 下有avcodec-57.dll等,lib 下有.dll.a | 能,直接-l链接 | 一般 Qt 工程,推荐 |
shared 但只有.lib | bin 有 dll,lib 里是 MSVC 导入库 | 不能,需要自己转 | 需要额外做一步 dlltool |
| static 静态库 | lib 下是.a静态库,无 dll | 能,但要处理依赖顺序和编译选项 | 单文件分发、现场不能放 dll |
对 Qt 桌面工程,我一般选第一种:dll 随 exe 走,升级 FFmpeg 只换 dll,不用重编整个工程。静态库看着省事,实际上会把libwinpthread、libz之类的依赖一起拖进来,链接顺序一错就是一屏 undefined reference,新手不建议碰。
还有两个包内属性要留意:线程模型和许可证构建。带--enable-w32threads的包在多线程解码时行为更贴近 Windows 原生;带 GPL 组件的构建(比如含 x264)在商用分发前要先确认合规。这些信息在包里的ffmpeg -version输出或构建说明里能看到,解压后花两分钟看一眼,比事后返工划算。
2.3 目录归置:include / lib / bin 三处落点
不要把工程直接指向解压出来的原始目录。原始目录名常带版本号和后缀,路径里还可能有空格,一旦换包,.pro里所有路径全要改。我的习惯是在工程根目录下建固定的第三方目录:
MyPlayer/ ├── MyPlayer.pro ├── src/ └── third_party/ └── ffmpeg/ ├── include/ # libavcodec/ libavformat/ libavutil/ libswscale/ libswresample/ ├── lib/ # libavcodec.dll.a 等导入库 └── bin/ # avcodec-57.dll 等运行库把解压包里的include、lib、bin三个目录整体搬过来,之后.pro里只写相对路径$$PWD/third_party/ffmpeg。这样做的好处是工程可提交、可复现,同事拉下来不用问「你的 FFmpeg 装哪了」。搬家之后先确认include下是libavcodec/avcodec.h这种两级结构,而不是把avcodec.h直接摊在include根下——3.4 的包都是两级结构,但有些重新打包的版本会摊平,摊平的包包含路径要另写。
3. 让 qmake 找得到:INCLUDEPATH 与 LIBS 的写法与链接顺序
目录摆好,接下来是.pro。这一步出问题最多,因为 qmake 不报「路径写错了」,它只在链接阶段甩一句找不到库。
3.1 头文件包含:extern "C" 与 3.4 的头文件布局
FFmpeg 是 C 库,Qt 工程是 C++,直接在.cpp里#include <libavcodec/avcodec.h>会编译失败或链接期找不到符号——C++ 会对函数名做名字修饰(mangling),而 dll 里导出的是未经修饰的 C 符号。必须用extern "C"包起来:
// ffmpeghelper.h #ifndef FFMPEGHELPER_H #define FFMPEGHELPER_H extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/avutil.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> #include <libswresample/swresample.h> } #endif // FFMPEGHELPER_H写成一个单独的头文件集中包含,好处是全工程只有这一处extern "C",以后升级或替换 FFmpeg 只改一个文件。注意 3.4 的头文件路径是libavcodec/avcodec.h这种带库名前缀的写法,不是avcodec.h。
老版本在 C++ 里还会要求#define __STDC_CONSTANT_MACROS和__STDC_FORMAT_MACROS,3.4 基本不需要,但如果你的 Qt 工程开了严格 C++ 标准(比如CONFIG += c++11 strict_c++),偶尔还是会撞上INT64_C未定义的报错,那时候在extern "C"之前补这两个宏即可。
3.2 链接顺序:-lavformat 为什么必须写在 -lavcodec 前面
.pro里最关键的几行:
# 第三方库根目录,换包只改这一行 FFMPEG_DIR = $$PWD/third_party/ffmpeg INCLUDEPATH += $$FFMPEG_DIR/include # 导入库搜索路径 LIBS += -L$$FFMPEG_DIR/lib # 依赖在右,被依赖在左:avformat 依赖 avcodec,avcodec 依赖 avutil LIBS += -lavformat -lavcodec -lswscale -lswresample -lavutilGNU ld 是单遍扫描:从左往右读库,遇到未解析符号就去后面的库里找,找到就停下。所以-lavformat必须写在-lavcodec前面,-lavcodec又必须在-lavutil前面。写反了的典型症状是undefined reference to avcodec_alloc_context3这类——明明库在,就是解不出来。这个坑非常玄学,因为报错信息完全不提顺序问题。
如果工程已经很多依赖、顺序实在捋不清,可以让链接器自己循环查找:
# 兜底写法:顺序敏感时用 group 包住,代价是链接稍慢 LIBS += -Wl,--start-group -lavformat -lavcodec -lswscale -lswresample -lavutil -Wl,--end-group3.3 只有 .lib 没有 .dll.a 时,用 gendef + dlltool 补一个
拿到手的包如果lib目录里全是.lib,那是 MSVC 的导入库,MinGW 的 ld 读不了。不用重新找包,用 MinGW 自带的两个工具从 dll 反推一个.dll.a出来:
# 在 MinGW 套件的命令行里,进入 dll 所在目录 cd third_party/ffmpeg/bin # 生成 def 文件 gendef avcodec-57.dll # 由 def 生成 MinGW 可用的导入库,-D 指定 dll 名,运行时按这个名字找 dlltool -d avcodec-57.def -l ../lib/libavcodec.dll.a -D avcodec-57.dll六个库(avformat、avcodec、avutil、swscale、swresample、avfilter)都要来一遍,写个循环更快:
# 批量转换,省得一个个敲 for f in *.dll; do base=$(basename "$f" .dll) gendef "$f" >/dev/null dlltool -d "${base}.def" -l "../lib/lib${base}.dll.a" -D "$f" done-D参数的意义是告诉链接器「运行期去加载这个文件名的 dll」,不写的话生成的导入库里记录的是构建机上的路径,换机器就崩。生成的.def用完可以删,.dll.a留着。
4. 编译通过之后的运行期:dll 落位与 3.4 的 API 分岔
编过只是第一关。我见过太多「Qt Creator 里 F5 跑得好好的,双击 exe 就闪退」的案例,根子都在运行期。
4.1 dll 的三种摆放位置
dll 的搜索路径优先级大致是:exe 所在目录 → 系统目录 → PATH。三种摆法各有适用场景:
# 摆法一:跟 exe 放一起,最稳,发布就这么干 windeployqt.exe --release --no-translations MyPlayer.exe copy third_party\ffmpeg\bin\*.dll build-MyPlayer-Desktop_Qt_5_12_9_MinGW_32_bit-Release\release\摆法二是把bin加进系统 PATH,本地开发省事,但换 FFmpeg 版本时会污染其他工程,不推荐。摆法三是在 Qt Creator 的「项目 → 运行 → 运行环境」里改 PATH,只对这个工程生效——这是我最常用的开发期做法,容器化得干净。
注意:Qt Creator 里运行时的当前工作目录是 build 目录,不是源码目录。代码里写相对路径(比如
"test.mp4")时,文件要放在 build 目录下,否则会得到「文件打不开」的假象,白白怀疑 FFmpeg。
4.2 av_register_all 到底该在哪调、调几次
3.4 这个版本,av_register_all()和avcodec_register_all()仍然需要调用,直到 4.0 才被标记废弃。它们内部有一次性保护,多调无害但没必要。常见做法是放在程序启动的早期,一次就够:
// main.cpp #include "ffmpeghelper.h" #include <QApplication> int main(int argc, char *argv[]) { // 3.4 必须注册,否则 avformat_open_input 找不到任何解复用器 av_register_all(); avcodec_register_all(); QApplication a(argc, argv); // ... return a.exec(); }放在QApplication之前还是之后都可以,但一定要在主线程、任何解码逻辑之前。漏调的典型症状是avformat_open_input返回负值、av_find_input_format返回 nullptr,日志里看不出所以然,纯黑匣子。
4.3 3.4 与 4.x 的 API 分岔:codec 与 codecpar
这是抄代码最容易翻车的地方。网上大量的 FFmpeg 教程是 4.x 之后的,视频流参数从AVStream::codecpar取;而 3.4 里codecpar还没成为主流入口,要用AVStream::codec。照着新教程写,编译期就报「AVStream 没有 codecpar 成员」。
// 兼容写法:用版本宏隔开,将来升级 4.x 时少改一点 #if LIBAVFORMAT_VERSION_MAJOR < 58 // FFmpeg 3.4:libavformat 主版本是 57 AVCodecContext *codecCtx = fmtCtx->streams[videoIndex]->codec; AVCodec *decoder = avcodec_find_decoder(codecCtx->codec_id); if (!decoder || avcodec_open2(codecCtx, decoder, nullptr) < 0) { return false; } #else // 4.x 及以后:从 codecpar 取参数,自己分配上下文 AVCodecParameters *par = fmtCtx->streams[videoIndex]->codecpar; const AVCodec *decoder = avcodec_find_decoder(par->codec_id); AVCodecContext *codecCtx = avcodec_alloc_context3(decoder); avcodec_parameters_to_context(codecCtx, par); avcodec_open2(codecCtx, decoder, nullptr); #endif判断依据是LIBAVFORMAT_VERSION_MAJOR:3.4 对应 57,4.x 对应 58 及以上。同理,avcodec_decode_video2在 3.4 里还能用,新接口avcodec_send_packet/avcodec_receive_frame也已经存在,两者都能跑;写新代码建议直接用后者,升级时不用再改。
另外要留意AVStream::codec在 3.4 里是「借用」关系,avformat_close_input会自动释放它,别再手动avcodec_free_context,否则就是二次释放。这类内存在 3.x 和 4.x 之间的所有权规则有变化,换版本时值得重新读一遍对应版本的示例代码。
5. 排查清单:链接期和运行期最容易翻车的五个点
5.1 链接报 skipping incompatible
现象:链接阶段出现skipping incompatible .../libavcodec.dll.a when searching for -lavcodec,最后汇总成cannot find -lavcodec。
原因:库的位数和编译器目标不一致。最常见的是 32 位 Qt 套件配了 64 位的 FFmpeg 包,或者反过来。
解决:回到 2.1 确认gcc -dumpmachine输出是i686-w64-mingw32;再看包名里的mingw32字样,用file libavcodec.dll.a也能看出目标架构。两者对不上就换包,别试图用链接器参数绕过。
5.2 -L 路径对了,还是 cannot find -lavcodec
现象:-L指到了lib目录,目录里也看得见文件,链接器就是不认。
原因:目录里放的是 MSVC 的.lib,或者导入库文件名不符合 ld 的命名约定(lib前缀 + 库名 +.dll.a或.a)。
解决:按 3.3 用gendef+dlltool现做一份.dll.a,确保文件名形如libavcodec.dll.a。文件名少个lib前缀,-lavcodec就找不到。
5.3 双击 exe 提示丢失 avcodec-57.dll
现象:Qt Creator 里 F5 正常,双击 exe 弹窗提示缺少 dll,或者直接静默退出。
原因:Qt Creator 运行环境里 PATH 带了 FFmpeg 的 bin,系统环境里没有;exe 同目录也没放 dll。
解决:用windeployqt收一遍 Qt 的依赖之后,把third_party/ffmpeg/bin/*.dll一并拷到 exe 同级目录。32 位程序的 dll 必须是 32 位,64 位 dll 混进去会变成 0xc000007b 而不是「找不到」。
5.4 编译报 undefined reference to av_register_all
现象:头文件包含没问题,-lavcodec也写了,就是链不上这几个符号。
原因:要么extern "C"漏了(C++ 名字修饰导致符号名不匹配),要么LIBS顺序反了。
解决:先检查是否走了统一的ffmpeghelper.h;再检查.pro里-lavformat是否在-lavcodec左边。两个都排除后,用nm -g libavcodec.dll.a | grep av_register_all确认导入库里确实有这个符号。
5.5 avformat_open_input 返回失败,路径明明是对的
现象:文件路径确信无误,avformat_open_input就是返回负数,拿不到任何流信息。
原因:一是漏调av_register_all();二是路径里有中文或空格,QString::toUtf8()在某些构建下处理不当。
解决:先补注册调用;再把路径统一走QFile::encodeName(path)转成本地编码传进去,别直接path.toStdString().c_str()。这两条我踩过不止一次,尤其是 Windows 上含中文的目录。
6. 装完怎么验证:三段自检从「能编过」推到「能解出一帧」
装完必须自证,不然只是「看起来能编」。我习惯分三段做,每段一个明确的通过标准。
6.1 第一段:把版本号打出来
// 放在 main 里,av_register_all 之后 qDebug() << "libavcodec :" << AV_VERSION_MAJOR(avcodec_version()) << AV_VERSION_MINOR(avcodec_version()) << AV_VERSION_MICRO(avcodec_version()); qDebug() << "configuration:" << avcodec_configuration();通过标准:主版本打出 57,configuration里能看到--enable-shared和线程模型。这一行输出同时证明了三件事:头文件对、导入库对、dll 能加载。如果程序在这一步就崩,问题一定在 dll 环节,不用往下查。
6.2 第二段:打开一个 mp4 并读出流信息
// 读流信息,不做解码,验证 demuxer 是否正常 AVFormatContext *fmtCtx = nullptr; QByteArray ba = QFile::encodeName(filePath); // 中文路径必须转本地编码 if (avformat_open_input(&fmtCtx, ba.constData(), nullptr, nullptr) < 0) { qWarning() << "open failed"; return; } avformat_find_stream_info(fmtCtx, nullptr); int vIdx = av_find_best_stream(fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); qDebug() << "video stream index:" << vIdx << "duration(us):" << fmtCtx->duration; avformat_close_input(&fmtCtx);通过标准:duration大于 0,vIdx不小于 0。到这里编解码器还没上场,测的是容器解析链路。
6.3 第三段:解出第一帧并存成图片
// 在 6.2 的基础上继续,解第一帧转 RGB24 存盘 AVCodecContext *ctx = fmtCtx->streams[vIdx]->codec; // 3.4 的取法 AVCodec *dec = avcodec_find_decoder(ctx->codec_id); avcodec_open2(ctx, dec, nullptr); AVPacket pkt; av_init_packet(&pkt); AVFrame *frame = av_frame_alloc(); int got = 0; while (av_read_frame(fmtCtx, &pkt) >= 0) { if (pkt.stream_index == vIdx) { avcodec_decode_video2(ctx, frame, &got, &pkt); av_packet_unref(&pkt); if (got) break; } else { av_packet_unref(&pkt); } } SwsContext *sws = sws_getContext(ctx->width, ctx->height, ctx->pix_fmt, ctx->width, ctx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr); QImage img(ctx->width, ctx->height, QImage::Format_RGB888); uint8_t *dst[4] = { img.bits(), nullptr, nullptr, nullptr }; int dstStride[4] = { img.bytesPerLine(), 0, 0, 0 }; sws_scale(sws, frame->data, frame->linesize, 0, ctx->height, dst, dstStride); img.save("first_frame.png"); // 打开看,能出图就说明整条链路通了| 验证阶段 | 通过标准 | 失败通常指向 |
|---|---|---|
| 版本自检 | 主版本 57,configuration 正常输出 | dll 缺失、位数不匹配 |
| 容器解析 | duration 大于 0,流索引有效 | 漏调 av_register_all、路径编码 |
| 首帧解码 | 生成的 png 能正常打开且不花屏 | 链接顺序、pix_fmt 处理 |
从那以后我每次搭这套环境,都会把这三段做成一个单独的 main 先跑一遍,确认全绿再接业务代码。看起来多花二十分钟,实际省下的是「到底是我代码写错还是环境没装好」的半天纠结。希望帮到你。
本文还有配套的精品资源,点击获取