资讯动态

Qt 32位 mingw32 工程集成 FFmpeg 3.4 解码与抽帧

发布时间:2026/10/27 10:37:19 来源:尧图企业网站定制
简介ffmpeg-3.4-mingw32 是面向 32 位 Windows 平台、基于 MinGW32 编译环境构建的 FFmpeg 3.4 多媒体处理套件适合使用 Qt 开发音视频应用的 C/C 开发者也便于需要轻量解码能力或二次开发的用户快速接入。压缩包共 170 个文件约 2.62MB包含 107 个 h 头文件、21 个 c 示例源码、6 个 dll 与配套 lib/a 导入库、6 个 def 模块定义文件及 6 个 pc 配置文件另有 readme、makefile 和少量 ffpreset 预设覆盖从接口声明、示例工程到动态链接库的完整开发素材。资源已有 229 人学习下载。借助包内的编解码、封装、滤镜、缩放与重采样模块库以及转码、封装、解复用解码等示例读者可对照头文件与源码理解音视频处理流程并将 DLL 直接用于 Qt/MinGW 工程减少自行编译与配置依赖的时间。1. 32 位 Qt 工程里塞 FFmpeg为什么偏偏要留一份 3.4 的 mingw32 库上个季度接手一个还在跑的 32 位上位机Qt 5.9 的 mingw32 Kit界面里要嵌一路本地视频回放还得按时间点抽帧存证。第一反应是拿最新版 FFmpeg头文件一包含就出问题——AVStream::codec被挪走、av_register_all也被标记弃用编译直接出现大量报错。换 64 位包链接器又输出架构不匹配的提示。最后落到 ffmpeg-3.4-mingw32 上FFmpeg 3.4 源码用 i686 mingw 工具链编出来的 32 位动态库、导入库加头文件。它解决的不是“最新最好”而是“老工程还得再活几年”解码、抽帧转 QImage、时间戳换算、音频重采样3.4 这套 API 都齐更关键的是3.4 处在旧接口avcodec_decode_video2尚未移除、新接口avcodec_send_packet/avcodec_receive_frame已经稳定的重叠区间老代码抄过来能编过新写法也能用。适合还在维护 Qt 5 32 位 mingw 桌面程序、需要本地解码或取帧、又不想为了编个库去搭一整套 msys 环境的人。2. 先看清包里有什么从 include/lib/bin 到 Qt 的 .pro拿到这种预编译包最怕的不是不会写代码是目录结构没搞明白就急着往.pro里塞路径然后花两小时在链接错误里绕。这个包的结构和所有 FFmpeg 官方 Windows 构建是一套逻辑先认清楚再动手。2.1 32 位 mingw 构建的目录分工路径内容用途include/libavcodec、libavformat、libavutil、libswscale、libswresample等头文件全是 C 头C 里必须extern C包住lib/*.dll.amingw 格式导入库-lavcodec实际找的是它lib/*.libMSVC 格式导入库mingw 用不上留着不碍事bin/*.dll运行时动态库部署时必须跟 exe 放一起lib/pkgconfig/.pc文件用 qmake 时基本用不到写 CMake 才有意义判断这份库是不是真的 32 位别看目录名叫不叫 mingw32直接看 DLL 的文件头。用 mingw 自带的objdump一行就能确认看到pei-i386才算对pei-x86-64就是拿错了包# 确认导入库背后那份 DLL 的架构 objdump -f bin/avcodec-57.dll | grep architecture # 期望输出file format pei-i386还有一个容易被忽略的点FFmpeg 3.4 这几个库的主版本号是固定的libavcodec是 57、libavformat是 57、libavutil是 55、libswscale是 4、libswresample是 2、libavfilter是 6。这个号后面选 DLL、写部署脚本都要用记牢比记版本字符串有用——因为不同小版本之间这些主版本号是不变的DLL 名字也就不变但 ABI 细节可能变所以“同名不同版本混用”是后面要重点防的事。2.2 .pro 里怎么写INCLUDEPATH、LIBS 与链接顺序qmake 的写法本身不复杂复杂的是条件判断和链接顺序。我一般把三方库统一放工程目录下的third_party/用$$PWD起相对路径这样整个工程拷给同事也能直接编。同时在.pro里显式判断架构别指望开发者每次都记得切 Kit# 只有 32 位 Kit 才走这条分支避免同一个 .pro 在 64 位机器上误链 contains(QT_ARCH, i386) { FFMPEG_ROOT $$PWD/third_party/ffmpeg-3.4-mingw32 INCLUDEPATH $$FFMPEG_ROOT/include LIBS -L$$FFMPEG_ROOT/lib # 顺序有讲究调用方在前被调用方在后 LIBS -lavformat -lavcodec -lswscale -lswresample -lavutil # 3.4 的头文件里用了 UINT64_C 这类 stdint 宏C 下不加这个会编不过 DEFINES __STDC_CONSTANT_MACROS } else { error(本工程只提供 32 位 mingw 版 FFmpeg请切换到 x86 Kit) }逐行说清楚这几行每一行都是踩过坑才补上的。contains(QT_ARCH, i386)是 qmake 提供的架构变量判断32 位 mingw Kit 下它等于i386。加这个分支的意义在于一个团队里总有人用 64 位 Kit 打开工程如果不拦他会得到一堆“架构不匹配”的链接错误然后跑来问你库是不是坏的。直接error()报一句人话省掉一轮沟通。LIBS里的顺序在动态链接场景下影响其实不大因为导入库只记符号不打包代码但一旦以后换成静态库.a顺序错了就是满屏undefined reference。养成“高层在前、底层在后”的习惯avformat依赖avcodecavcodec依赖avutilswscale/swresample挂在外围。按这个顺序写静态库也能过。__STDC_CONSTANT_MACROS这一行是老生常谈但每次换环境都会忘。FFmpeg 的common.h里会间接包含stdint.h在 C 编译单元里如果这个宏没定义UINT64_C(0)这类宏展开会失败报错长得像UINT64_C was not declared in this scope。C 文件里没这个问题偏偏 Qt 工程全是.cpp。-L后面如果路径带空格一定要整体加引号Windows 下 qmake 对空格的容忍度不高LIBS -L$$FFMPEG_ROOT/lib。这个坑在“把工程放在D:/My Projects/下”的时候必现。2.3 用一段最小代码验证链接是否真的通过不要一上来就写解码器。先用二十行代码把“能不能链接、DLL 能不能加载、版本号对不对”这三件事一次性验完出问题时排查范围小得多。// main.cpp —— 只验证链接和运行时加载不碰任何解码逻辑 #include QCoreApplication #include QDebug extern C { #include libavcodec/avcodec.h #include libavformat/avformat.h #include libavutil/avutil.h } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // 3.4 里 av_register_all 还没被移除本地文件解码其实可以不调 // 但老代码里常和对网络协议注册写在一起保留以对齐习惯 av_register_all(); qDebug() av_version_info: av_version_info(); // 形如 3.4.x qDebug() libavcodec: avcodec_version(); // 57000 以上 qDebug() libavformat major: LIBAVFORMAT_VERSION_MAJOR; // 57 qDebug() libavcodec major: LIBAVCODEC_VERSION_MAJOR; // 57 return 0; }extern C这个包裹必须加。FFmpeg 是纯 C 库头文件里没有__cplusplus保护直接用 C 编译器包含会做名字修饰链接时符号名对不上报的错是undefined reference to avcodec_version()——注意末尾那个空括号就是这个错误的典型标志。av_version_info()返回的是字符串能直接看出小版本avcodec_version()返回整数3.4 下大概是 57 万多。两个都打出来可以快速判断“头文件版本”和“运行时 DLL 版本”是不是同一份——这两者不一致是最阴的一类 bug编译期完全不报跑起来在avcodec_open2里崩栈还看不出所以然。运行时如果在这一步就崩在av_register_all或av_version_info上那基本可以断定是 DLL 加载到了别的版本。把bin/下的 DLL 全拷到 exe 同级目录再跑一次。Windows 的 DLL 搜索顺序里exe 所在目录优先级很高但 PATH 里的目录也会被搜开发机上装过别的东西就很容易撞车。3. 解码链路3.4 的 API 边界与 QImage 落地验证链接通过之后才是真正写解码。3.4 这个版本的微妙之处在于新旧两套接口并存网上抄来的代码可能两种都有混着用不出错但职责不清会埋雷。我一般统一走 send/receive 这套因为它对 flush、EAGAIN 的处理语义更清楚。3.1 打开输入到找到解码器这一段是所有播放器都一样的骨架打开容器、探测流信息、挑视频流、建解码器上下文。3.4 里AVStream::codec还能用但已经是弃用状态正确做法是从codecpar拷参数到自己的AVCodecContext。bool Decoder::open(const QString path) { AVDictionary *opts nullptr; // 本地文件用不上但对 rtsp/http 源是保命的单位微秒 // 注意 rtsp 用 stimeouthttp 用 timeout名字不一样 av_dict_set(opts, stimeout, 5000000, 0); // Windows 下 FFmpeg 内部会把 UTF-8 转成宽字符再 fopen // 所以这里必须传 UTF-8传 GBK 反而打不开中文路径 QByteArray utf8Path path.toUtf8(); int ret avformat_open_input(fmtCtx_, utf8Path.constData(), nullptr, opts); av_dict_free(opts); if (ret 0) return false; // 探测流信息这一步会读若干帧网络源上耗时明显本地文件几乎无感 if (avformat_find_stream_info(fmtCtx_, nullptr) 0) return false; int idx av_find_best_stream(fmtCtx_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); if (idx 0) return false; videoIndex_ idx; AVStream *st fmtCtx_-streams[idx]; AVCodec *dec avcodec_find_decoder(st-codecpar-codec_id); if (!dec) return false; codecCtx_ avcodec_alloc_context3(dec); if (avcodec_parameters_to_context(codecCtx_, st-codecpar) 0) return false; // 32 位进程地址空间紧张线程给 2 个就够开多了帧缓存会把内存吃满 codecCtx_-thread_count 2; if (avcodec_open2(codecCtx_, dec, nullptr) 0) return false; // 时间戳换算用的是流的时基不是 codecCtx 的时基这两个别搞混 timeBase_ st-time_base; return true; }av_find_best_stream比手写“遍历所有流找第一个视频流”靠谱它会参考容器里的默认流标记也能顺手把解码器返回给你最后一个参数传nullptr就不返回。有些 MP4 里带封面图封面也是一条视频流手写循环很容易抽到封面而抽不到正片。codecCtx_-thread_count 2这行在 64 位下我一般写 0让 FFmpeg 自己决定32 位下反而要手动压住。原因很实际帧级多线程会同时持有多帧缓冲1080p 的 YUV420P 一帧大概 3MB解码器内部排队的帧加上应用层的帧很容易把 2GB 的用户态地址空间切碎。后面第 4 章会有一条专门讲这个。timeBase_一定要存下来。AVStream::time_base和AVCodecContext::time_base在 3.4 里都可能不是 1/1000很多视频是 1/90000 或 1/15360直接拿 pts 当毫秒用进度条就会走成各种奇怪的速度。3.2 收帧send_packet / receive_frame 的正确循环3.4 里avcodec_send_packet和avcodec_receive_frame已经完全可用avcodec_decode_video2虽然还能编过但已被标记弃用。新接口的核心是“一次喂包不一定出一帧一次收帧不一定需要新包”循环结构写对了后面很多玄学问题自然消失。// 取一帧成功返回 true文件结束或出错返回 false bool Decoder::readFrame(AVFrame *frame) { for (;;) { int ret avcodec_receive_frame(codecCtx_, frame); if (ret 0) return true; // 拿到一帧直接返回 if (ret AVERROR_EOF) return false; // 解码器已吐完 if (ret ! AVERROR(EAGAIN)) return false; // 真错误 // EAGAIN解码器饿了需要新包 av_packet_unref(pkt_); if (av_read_frame(fmtCtx_, pkt_) 0) { // 文件读完 ! 帧读完。解码器里可能还压着几帧 // 塞一个空包进去把它冲出来这是新接口的固定套路 avcodec_send_packet(codecCtx_, nullptr); continue; } if (pkt_-stream_index ! videoIndex_) continue; // 音频/字幕包直接丢 ret avcodec_send_packet(codecCtx_, pkt_); if (ret 0 ret ! AVERROR(EAGAIN)) return false; } }av_packet_unref(pkt_)在每次av_read_frame之前调这是引用计数包的标准用法。少了它包内部的AVBufferRef不会释放内存会随着播放时长线性上涨——但涨得很慢播十分钟看不出来跑一晚上才爆属于最难查的那类。avcodec_send_packet(codecCtx_, nullptr)这行是收尾的关键。很多移植代码直接return false就结束了结果视频最后几帧永远出不来表现为“播到结尾卡住”或者“抽帧少了几帧”。空包是约定俗成的 flush 信号用完不需要额外清理。还有一个细节av_read_frame返回负值之后代码会一直continue下一轮receive_frame如果还是 EAGAIN会再次send_packet(nullptr)。第二次之后 send 会返回AVERROR_EOF此时 receive 也必然返回 EOF循环就退出了。所以不会死循环但保险起见可以在上面加个计数器兜底。3.3 sws_scale 转 RGB 与 linesize 的对齐坑FFmpeg 解出来的帧是 YUV420P 居多Qt 显示要 RGB。中间的转换全靠libswscale而这里有一个几乎人人都会踩一次的坑目标缓冲的行宽不能假设等于width * 4。bool Decoder::toImage(AVFrame *frame, QImage out) { // 分辨率变了必须重建上下文否则会读到越界内存直接崩 if (sws_ (srcW_ ! frame-width || srcH_ ! frame-height)) { sws_freeContext(sws_); sws_ nullptr; } if (!sws_) { sws_ sws_getContext(frame-width, frame-height, (AVPixelFormat)frame-format, frame-width, frame-height, AV_PIX_FMT_BGRA, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!sws_) return false; srcW_ frame-width; srcH_ frame-height; } // 目标缓冲按 32 字节对齐分配av_image_alloc 会把实际行宽写回 rgbLinesize_ if (!rgbBuf_) { if (av_image_alloc(rgbBuf_, rgbLinesize_, frame-width, frame-height, AV_PIX_FMT_BGRA, 32) 0) return false; } uint8_t *dstData[4] { rgbBuf_, nullptr, nullptr, nullptr }; int dstLinesize[4] { rgbLinesize_, 0, 0, 0 }; sws_scale(sws_, frame-data, frame-linesize, 0, frame-height, dstData, dstLinesize); // 关键构造 QImage 时把 rgbLinesize_ 传进去不能传 width * 4 out QImage(rgbBuf_, frame-width, frame-height, rgbLinesize_, QImage::Format_ARGB32); return true; }av_image_alloc的最后一个参数是 alignment传 32 之后rgbLinesize_通常会大于width * 4比如 1920×47680本来就对齐但 1366 宽就不一定了。如果构造 QImage 时用width * 4当 bytesPerLineQt 和 FFmpeg 对“每行多少字节”的理解就会错位画面表现为经典的斜切——每一行都比上一行偏移一点像被推歪的百叶窗。这个现象非常容易被误判成“解码坏了”其实是 stride 没对上。像素格式选AV_PIX_FMT_BGRA而不是AV_PIX_FMT_RGB32是因为前者在小端机器上的内存布局正好是 B、G、R、A 四字节对应QImage::Format_ARGB32的 0xAARRGGBB。选AV_PIX_FMT_RGB24配Format_RGB888也能用但每像素少一个字节意味着sws_scale要做非对齐写32 位下性能反而略低而且 QImage 内部还是会按 4 字节对齐补空。除非内存特别紧我一般直接用 BGRA。最后一个隐患QImage这个构造函数不拷贝数据它直接引用rgbBuf_。所以返回出去的 QImage 生命周期不能超过下一次sws_scale。需要在别处长期持有的话老老实实调.copy()一帧 1080p 大概 8MB拷一次不算贵但别在 60fps 的循环里每帧拷两遍。3.4 释放顺序反着建、顺着毁FFmpeg 的资源释放顺序错了会崩而且崩的位置往往在无关的地方。原则是“后创建的先销毁”因为后面的对象可能持有前面的指针。Decoder::~Decoder() { if (sws_) { sws_freeContext(sws_); sws_ nullptr; } if (rgbBuf_) { av_freep(rgbBuf_); } // av_image_alloc 必须配 av_freep if (pkt_) { av_packet_free(pkt_); } if (frame_) { av_frame_free(frame_); } if (codecCtx_) { avcodec_free_context(codecCtx_); } if (fmtCtx_) { avformat_close_input(fmtCtx_); } }注意av_freep传的是指针的地址它会顺手把指针置空av_packet_free和av_frame_free也是同理。这几个_free系列的参数都是二级指针写成av_packet_free(pkt_)会编不过写成free(pkt_)则是另一种错误——AVPacket内部还有AVBufferRef直接free会漏掉它引用的那块内存。fmtCtx_最后关。它持有所有AVStream而codecCtx_在avcodec_parameters_to_context的时候已经把参数拷走了所以先关哪个理论上都行但按“后建先毁”来永远不会错。4. 避坑与排查五个在 32 位 mingw 上翻过的车下面这五条基本覆盖了我在这套组合上遇到的全部高频问题。按编译期、链接期、运行期分组每条都按“现象 → 原因 → 解决”写方便对号入座。4.1 编译期那个永远会忘的宏现象包含libavcodec/avcodec.h之后报UINT64_C was not declared in this scope或者INT64_C、UINT32_MAX一串类似错误位置在common.h里跟自己的代码毫无关系。原因stdint.h在 C 模式下默认不暴露UINT64_C这类宏需要__STDC_CONSTANT_MACROS显式打开。FFmpeg 的头文件依赖这些宏来拼常量C 文件里因为是 C 模式没这个问题所以官方构建完全正常只有 C 调用方会撞上。解决.pro里加DEFINES __STDC_CONSTANT_MACROS。如果用的是 CMake对应add_definitions(-D__STDC_CONSTANT_MACROS)。这条不解决后面所有问题都无从谈起。4.2 链接期架构、格式与路径的三重误判现象链接报undefined reference to avformat_open_input或者更奇怪的file format not recognized、cannot find -lavcodec。原因三种可能按出现频率排一是-L路径写错或者路径里带空格没有引号二是拿 64 位包配 32 位 Kit或者反过来导入库里的符号机器码不匹配三是 mingw 去链了lib/下的 MSVC 格式.lib——有些打包者会把两种导入库放同一个目录文件名前缀一样qmake 找的时候先命中哪个看运气。解决先跑objdump -f确认 DLL 架构再确认-L指向的目录里.dll.a确实存在最后检查.pro里有没有别的地方重复加了LIBS把顺序搞乱。我一般会在.pro里加一行message($$LIBS)编译时输出实际链接参数比猜快得多。4.3 运行期DLL 版本错配导致 avcodec_open2 崩溃现象编译链接都通过启动也能跑但一调avcodec_open2就崩或者avformat_open_input返回一个莫名其妙的负值错误信息是“Invalid data found when processing input”可文件明明是好的。原因进程加载到的avcodec-57.dll不是这份包里的。开发机上只要装过任意一个带 FFmpeg 的软件PATH 里就可能有一份同名但版本不同的 DLL。主版本号相同意味着 ABI 大体兼容但内部结构体字段顺序、枚举值可能已经变了指针一解引用就飞。解决把bin/下所有 DLL 拷到 exe 同级目录让 exe 目录的搜索优先级生效发布前用objdump -p把 exe 的依赖列表打出来逐项确认来源。开发阶段更狠一点直接改 DLL 名字前缀比如加个my_谁被加载一目了然——这招只在排查时用别带进发布。4.4 运行期花屏、斜切与颜色通道错位现象画面能出来但整体斜着推、每行错开几个像素或者颜色发蓝/发红人脸变阿凡达。原因斜切是QImage的 bytesPerLine 和sws_scale实际写入的行宽不一致百分之百是width * 4硬编码导致的。颜色错位则是像素格式对错了AV_PIX_FMT_RGB32在小端上是 BGRA 布局配Format_RGB32才对配成Format_RGB888每像素按 3 字节读整张图会越读越偏。解决永远用av_image_alloc返回的 linesize构造 QImage 时显式传进去像素格式固定用AV_PIX_FMT_BGRAQImage::Format_ARGB32这一对别在几个格式之间来回试试出来的正确只是巧合换个分辨率又坏。4.5 运行期内存缓慢上涨与 2GB 天花板现象循环抽帧跑几十分钟后进程被系统杀掉或者申请内存直接失败任务管理器里看到内存一路涨到 1.8GB 左右然后归零。原因32 位进程的用户态地址空间只有 2GB还得留出碎片。三个常见泄漏点av_read_frame之后没av_packet_unrefAVFrame每次av_frame_alloc新开一个而不复用sws_getContext在分辨率变化时重建但没释放旧的。另外解码线程开太多帧缓冲本身就会吃掉几百 MB。解决packet 和 frame 都只分配一次循环里复用sws_重建前先sws_freeContextcodecCtx_-thread_count压到 2如果业务允许把解码和显示解耦显示端只保留最新一帧中间帧直接丢。真要处理 4K32 位就该考虑换了这不是代码能绕过去的物理限制。5. 分发windeployqt 不认识的 DLL 和 32 位依赖检查开发机上跑通只完成了一半真正让人难受的是打包发到客户那台机器上。Qt 自带的windeployqt只认 Qt 自己的库和编译器运行时第三方 DLL 一个都不会带这是分发环节最基础也最容易漏的一环。5.1 依赖清单与放置策略先明确这份包里到底哪些 DLL 是你的程序真正需要的不要一股脑全拷——多拷几 MB 在客户端更新时就是几 MB 的流量而且带上一堆用不到的库只会增加版本冲突的概率。DLL对应库是否必须avutil-55.dlllibavutil必须所有库的基础avcodec-57.dlllibavcodec必须解码主体avformat-57.dlllibavformat必须容器解析swscale-4.dlllibswscale必须YUV 转 RGBswresample-2.dlllibswresample只做视频可不带avfilter-6.dlllibavfilter只做纯解码可不带avdevice-57.dlllibavdevice只有采集摄像头/麦克风才需要放置策略上我一般把 exe 和所有 DLL 放在同一层不建bin/子目录。原因是 Windows 的 DLL 搜索顺序里 exe 所在目录排在最前放一起最稳建子目录的话要么改 PATH要么在代码里SetDllDirectory多一层配置就多一个失败点。确实不想让目录太乱的话Qt 提供了QCoreApplication::addLibraryPath但那只管 Qt 插件第三方 DLL 还是得靠系统搜索路径。5.2 依赖检查确认没有混入 64 位或别的版本发布前必做的一步是把 exe 的依赖列表打出来逐项对。用 mingw 自带的objdump就够不需要额外装工具# 列出 exe 依赖的所有 DLL objdump -p release/Player.exe | grep DLL Name # 抽查其中几个的架构必须全是 pei-i386 objdump -f release/avcodec-57.dll | grep architecture objdump -f release/Qt5Core.dll | grep architectureobjdump -p的输出里除了 DLL 名字还会列出导入的符号和序号。如果某个 FFmpeg 的 DLL 在列表里但版本不对符号表那一栏会明显缺项——比如 3.4 的avcodec-57.dll里应该能找到avcodec_send_packet找不到就说明拿到的是更早的构建。另一个容易忽略的点是编译器运行时。mingw 编译出来的 exe 依赖libgcc_s_dw2-1.dll、libstdc-6.dll、libwinpthread-1.dll这几个Qt 的 Kit 目录下都有但windeployqt只会拷它认为需要的部分。最保险的做法是跑完windeployqt之后把 Qt Kit 的bin/目录下这几个运行时也手动拷一份过来然后在一台干净机器上双击验证。5.3 干净机器验证与路径相关的老问题“我这能跑”是分发环节最大的谎言。开发机上有完整的 Qt 环境、有 PATH 里的一堆目录、有装过的各种运行时任何一个都能把你漏拷的 DLL 补上。验证必须在一次都没装过开发环境的机器上做虚拟机快照最省事拷贝整个发布目录进去双击能起来、能解码、能拖进度条才算过。路径问题在 32 位老项目上格外突出因为客户现场经常是映射网络驱动器或者放到很深的目录里。三个具体场景要提前测带中文的路径前面说过本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑