ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Qt MinGW 32位工程集成ffmpeg 3.4:编译产物与避坑指南

Qt MinGW 32位工程集成ffmpeg 3.4:编译产物与避坑指南

简介:这份资源是面向32位Windows平台、基于MinGW32编译环境构建的FFmpeg 3.4版本,主要服务于需要在Qt项目中集成音视频编解码能力的C/C++开发者,以及希望直接调用预编译库完成转码、封装、解封装等任务的终端用户。压缩包共170个文件,以107个C头文件与21个C源文件为主体,配合def、a、lib、dll等导入库与动态库文件,另有pc配置、ffpreset预设及少量示例程序,整体约2.62MB,体积轻量便于嵌入工程。内容预览可见libavcodec、libavformat、libavfilter等模块的导入库,以及transcode_aac、muxing、demuxing_decoding等示例源码,读者可据此快速理解各组件职责并完成二次开发。目前已有229人学习下载,适合需要轻量级解码方案或研究FFmpeg 3.4接口的开发者参考。

1. 32 位 Qt 工程里塞进 ffmpeg-3.4-mingw32:一份能直接落地的编译产物包

手上有个老工控上位机,Qt 5.9 + MinGW 32 位工具链,客户现场还在跑 Win7 32 位系统,视频解码模块要接 ffmpeg。源码自己编?MinGW 下 configure 一跑就是一堆依赖报错,编完还得处理 dll 依赖链,一个下午就没了。这份 ffmpeg-3.4-mingw32 就是给这种场景准备的:32 位 MinGW 工具链编出来的 ffmpeg 3.4 库和可执行文件,直接丢进 Qt 工程就能链。它解决的不是“最新版 ffmpeg 怎么用”,而是“老工具链 + 32 位目标 + 不想自己编”的落地问题。适合还在维护 Qt MinGW 32 位项目、需要稳定解码/转码能力的从业者,新手照着配也能跑通,熟手重点看 ABI 和依赖那几节。

2. 先搞清楚这份包里的东西:32 位 MinGW 构建的 ABI 边界

2.1 为什么是 3.4 而不是更新的版本

ffmpeg 3.4 是 2017 年底的分支,API 相对稳定,av_register_all这类老接口还在,网上大量 Qt + ffmpeg 的示例代码都是按 3.x 的 API 写的。你拿 5.x、6.x 的库去套那些示例,AVCodecContext的字段访问方式、avcodec_decode_video2的废弃状态都会让你改到怀疑人生。选 3.4 不是因为它新,而是因为它和 Qt MinGW 32 位这套老生态的匹配度最高。

更关键的是 32 位这个约束。MinGW 32 位(i686)和 MinGW-w64 的 64 位构建在指针宽度、结构体对齐、调用约定上都不一样。你拿一个 64 位的 ffmpeg dll 往 32 位 Qt 工程里链,链接器会直接告诉你“模块计算机类型 x86 与目标计算机类型 x64 冲突”。这份包的价值就在于它和你的 Qt 工程是同一套 ABI:i686、MinGW 运行时、32 位。

2.2 包里通常有什么,怎么核对

一个标准的 MinGW 32 位 ffmpeg 构建产物,目录结构大致是这样:

路径内容用途
bin/ffmpeg.exe、ffprobe.exe、ffplay.exe及*.dll命令行工具 + 运行时动态库
include/libavcodec/、libavformat/、libavutil/等头文件编译期包含
lib/libavcodec.dll.a等导入库链接期使用
doc/示例与协议说明查阅

拿到包先别急着往工程里塞,做两件事核对。第一,看bin/下 dll 的位数:

# 在 MinGW 的 msys 环境或 Git Bash 里执行 file bin/avcodec-57.dll # 期望输出包含 PE32 executable (DLL) ... Intel 80386,即 32 位

第二,确认导入库和头文件版本对得上。3.4 对应的 soname 是avcodec-57、avformat-57、avutil-55、swscale-4、swresample-2。如果你在lib/里看到的是avcodec-58,那说明这是 4.x 的产物,和 3.4 的头文件不匹配,链上去会在运行期崩在结构体偏移上,这种玄学崩溃最难查。

提示:file命令在纯 Windows cmd 里没有,用 Git Bash 或 MinGW 自带的 msys shell 执行。没有的话,用 Dependency Walker 打开 dll 看 Machine 字段是 x86 还是 x64。

2.3 头文件、导入库、dll 三者的对应关系

很多人翻车在只拷了 dll 没拷导入库,或者头文件版本和 dll 不一致。编译期用的是include/里的头,链接期用的是lib/*.dll.a,运行期加载的是bin/*.dll。三者必须来自同一次构建。判断方法很简单:打开include/libavcodec/version.h,看LIBAVCODEC_VERSION_MAJOR是不是 57、MINOR是不是 89 左右(3.4 分支的典型值)。如果头文件写 57 而 dll 是 58,链接能过,跑起来avcodec_open2返回错误码或者直接访问越界,这就是典型的版本错配。

3. 把库接进 Qt MinGW 工程:.pro 配置与最小解码 Demo

3.1 目录摆放与 .pro 写法

建议在工程根目录建一个thirdparty/ffmpeg32/,把include、lib、bin原样放进去。这样工程自包含,换机器不用重新配路径。.pro里这样写:

# 工程文件:ffmpeg32.pri,在主 .pro 里 include 进来 FFMPEG_DIR = $$PWD/thirdparty/ffmpeg32 INCLUDEPATH += $$FFMPEG_DIR/include # MinGW 下链接导入库,注意顺序:上层库在前,底层库在后 LIBS += -L$$FFMPEG_DIR/lib \ -lavformat -lavcodec -lswscale -lswresample -lavutil # 运行期 dll 拷到输出目录,避免手动复制 FFMPEG_DLLS = $$files($$FFMPEG_DIR/bin/*.dll) for(dll, FFMPEG_DLLS) { QMAKE_POST_LINK += $$QMAKE_COPY \"$$dll\" \"$$OUT_PWD/release/\" $$escape_expand(\\n\\t) }

链接顺序是有讲究的:avformat依赖avcodec,avcodec依赖swresample和avutil,swscale依赖avutil。MinGW 的 ld 是单遍扫描,被依赖的库必须放在后面,顺序写反了就是一堆undefined reference to av_...。这是血泪经验,别问为什么,记住就行。

QMAKE_POST_LINK那段是后悔药:每次构建自动把 dll 拷到 exe 旁边,省得你调试时忘了拷 dll,程序一启动就弹“找不到 avcodec-57.dll”。注意$$OUT_PWD/release/这个路径要按你的构建配置改,debug 构建就换成debug/。

3.2 一个能跑的最小解码流程

下面这段是 Qt 里调 ffmpeg 3.4 打开视频、读一帧、转成 RGB 的最小骨架,API 全部按 3.4 的写法:

extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> } bool decodeFirstFrame(const char *path, QImage &outImage) { av_register_all(); // 3.4 仍需手动注册,4.x 起废弃 AVFormatContext *fmtCtx = nullptr; if (avformat_open_input(&fmtCtx, path, nullptr, nullptr) != 0) return false; // 打开失败,路径或格式问题 if (avformat_find_stream_info(fmtCtx, nullptr) < 0) return false; int vIdx = av_find_best_stream(fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); if (vIdx < 0) return false; AVCodecContext *codecCtx = fmtCtx->streams[vIdx]->codec; AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id); if (!codec || avcodec_open2(codecCtx, codec, nullptr) < 0) return false; AVPacket pkt; av_init_packet(&pkt); AVFrame *frame = av_frame_alloc(); bool got = false; while (av_read_frame(fmtCtx, &pkt) >= 0) { if (pkt.stream_index == vIdx) { int ret = avcodec_send_packet(codecCtx, &pkt); if (ret == 0 && avcodec_receive_frame(codecCtx, frame) == 0) { // 转 RGB24 供 QImage 使用 SwsContext *sws = sws_getContext( frame->width, frame->height, codecCtx->pix_fmt, frame->width, frame->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr); outImage = QImage(frame->width, frame->height, QImage::Format_RGB888); uint8_t *dst[4] = { outImage.bits(), nullptr, nullptr, nullptr }; int dstStride[4] = { outImage.bytesPerLine(), 0, 0, 0 }; sws_scale(sws, frame->data, frame->linesize, 0, frame->height, dst, dstStride); sws_freeContext(sws); got = true; av_packet_unref(&pkt); break; } } av_packet_unref(&pkt); } av_frame_free(&frame); avcodec_close(codecCtx); avformat_close_input(&fmtCtx); return got; }

逻辑说明:av_register_all在 3.4 里必须调,否则avformat_open_input找不到解复用器;av_find_best_stream比手动遍历 streams 稳,能自动跳过封面图之类的伪视频流;avcodec_send_packet/avcodec_receive_frame是 3.1 之后引入的新解码接口,3.4 里已经可用,比老的avcodec_decode_video2更清晰,但注意一包可能出多帧,要循环 receive。

参数说明:SWS_BILINEAR是缩放算法,同尺寸转换时其实用SWS_POINT更快;outImage.bytesPerLine()必须用 QImage 自己的行跨度,不能直接用width * 3,因为 Qt 会对齐到 4 字节,直接传width * 3在宽度不是 4 的倍数时图像会斜。

3.3 用 ffprobe 先验证文件本身

解码失败先别怀疑代码,用包里的ffprobe.exe看文件:

ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,pix_fmt -of default=nw=1 input.mp4

输出里codec_name是h264、pix_fmt是yuv420p就说明文件正常,问题在你的代码或 dll。如果 ffprobe 都报错,那是文件损坏或编码格式这份构建没带对应解码器(比如某些 HEVC 变体),换文件或换构建,别在代码里死磕。

4. 避坑与排查:32 位 MinGW 链 ffmpeg 的五个高频翻车点

4.1 启动即崩,提示找不到 avcodec-57.dll

现象:双击 exe 弹窗“无法启动此程序,因为计算机中丢失 avcodec-57.dll”,或者 Qt Creator 里运行直接退出。

原因:dll 没在 exe 同目录或 PATH 里。MinGW 构建的 ffmpeg 依赖的 dll 是一串的,avformat依赖avcodec,avcodec依赖swresample、avutil,少一个都起不来。

解决:把bin/下所有 dll 一次性拷到 exe 旁边,别只拷你LIBS里写的那几个。用 3.1 里的QMAKE_POST_LINK自动化。排查时用 Dependency Walker 或ldd(msys 下)看缺哪个。

4.2 链接报 undefined reference toav_register_all

现象:编译通过,链接阶段一堆undefined reference,函数名都是av_开头。

原因:三种可能。一是LIBS顺序写反,avutil放在了avcodec前面;二是漏了某个库,比如用了sws_scale却没加-lswscale;三是 C++ 里没加extern "C",导致 C++ 名字修饰和 C 符号对不上。

解决:按 3.1 的顺序排LIBS;用到的每个av_*/sws_*函数都确认对应库在列表里;头文件包含统一包在extern "C" { }里。链接报错时把完整错误贴出来,看第一个 undefined 是哪个函数,反查属于哪个库。

4.3 运行期崩溃在 avcodec_open2 或结构体字段访问

现象:程序能启动,一打开视频就崩,崩的位置在avcodec_open2内部或者访问codecCtx->width时。

原因:头文件版本和 dll 版本不一致。头文件是 3.4(AVCodecContext布局按 57 版本),dll 却是 4.x(58 版本),结构体字段偏移全错,访问就是野指针。

解决:核对include/libavcodec/version.h的LIBAVCODEC_VERSION_MAJOR和 dll 文件名里的数字是否一致(都是 57)。不一致就换整套,别混搭。这也是为什么强调头、导入库、dll 必须同源。

4.4 中文路径或含空格路径打开失败

现象:avformat_open_input返回负值,但同样的文件放英文路径下就正常。

原因:ffmpeg 3.4 在 Windows 下对 UTF-8 路径支持不完整,MinGW 构建尤其明显,avformat_open_input内部走的是窄字符 API。

解决:打开前把路径转成 UTF-8 再传,或者干脆把文件拷到纯英文临时目录再打开。工程里如果有用户选文件的场景,选完先做一次路径规范化。这是老版本 ffmpeg 在 Windows 上的通病,不是这份包独有的问题。

4.5 解码出来的图像颜色发绿或错位

现象:sws_scale之后 QImage 显示颜色不对,或者图像斜切。

原因:dstStride传了width * 3而不是outImage.bytesPerLine(),Qt 的行对齐导致每行错几个字节,累积起来就是斜的;颜色发绿通常是pix_fmt判断错,或者sws_getContext的源格式传了codecCtx->pix_fmt但实际帧的格式在解码后变了(比如带 alpha 的格式)。

解决:dstStride一律用QImage::bytesPerLine();源格式优先用frame->format而不是codecCtx->pix_fmt,因为解码后的实际帧格式以 frame 为准。颜色问题先用ffprobe确认源pix_fmt,再对照sws_getContext的参数。

5. 进阶:把命令行工具当黑匣子用,以及版本锁定的习惯

5.1 用 ffmpeg.exe 做转码兜底

不是所有场景都值得在 Qt 里写解码循环。如果需求只是“把用户选的视频转成统一格式再处理”,直接调包里的ffmpeg.exe更省事,进程隔离还能避免解码库崩溃拖垮主程序:

ffmpeg -y -i input.mov -c:v libx264 -preset veryfast -crf 23 \ -pix_fmt yuv420p -c:a aac -b:a 128k output.mp4

-preset veryfast在速度和压缩率之间取平衡,工控场景够用;-crf 23是质量档,数值越小越清晰文件越大,18~28 是常用区间;-pix_fmt yuv420p强制输出兼容性最好的像素格式,避免某些播放器不认yuv444p。用QProcess调它,读 stderr 解析进度,比自己在代码里算 PTS 省心。注意这份 3.4 构建里的libx264是否编进去,用ffmpeg -encoders | findstr 264确认,没有的话转码会报“Unknown encoder”。

5.2 版本锁定与工程归档

老项目最怕的是“某天换了个库,之前能跑的突然不行了”。我的习惯是:把thirdparty/ffmpeg32/整个目录连同version.h一起提交到版本库,在工程 README 里写死“ffmpeg 3.4 / avcodec-57 / MinGW i686”,任何人换库前先看这行。.pro里也可以加一道保险:

# 构建时校验头文件主版本,防止误换库 FFMPEG_VER = $$system("grep LIBAVCODEC_VERSION_MAJOR $$FFMPEG_DIR/include/libavcodec/version.h") !contains(FFMPEG_VER, 57) { error("ffmpeg 头文件主版本不是 57,与 32 位 3.4 构建不匹配,请检查 thirdparty 目录") }

这段在 qmake 阶段就会拦下版本错配,比运行期崩了再查省太多时间。grep在 Windows 下如果没有,换成$$system("findstr ...")或干脆手动核对。

5.3 验证清单

接手或换机器时,按这个顺序过一遍,基本不会翻车:

检查项方法通过标准
dll 位数file bin/avcodec-57.dllPE32 / Intel 80386
头文件版本看version.h的 MAJOR57
导入库存在ls lib/*.dll.a五个库齐全
链接顺序看.pro的LIBSavformat 在前,avutil 在后
运行期 dllexe 同目录所有依赖 dll 都在
最小 Demo跑 3.2 的代码能出第一帧

从那以后我每次往老 Qt 工程里接第三方库,都强制先跑一遍位数和版本核对,再写一行业务代码——这个习惯帮我省掉了至少三次“明明昨天还能跑”的深夜排查。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进