news 2026/10/14 13:09:20

FFmpeg 3.4与Qt MinGW 32位集成指南:环境配置、解码显示与避坑实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FFmpeg 3.4与Qt MinGW 32位集成指南:环境配置、解码显示与避坑实践

简介:ffmpeg-3.4-mingw32是FFmpeg 3.4版本经由MinGW32工具链编译的32位Windows发行包,面向在Qt/C++环境下进行音视频处理、转码或播放器开发的工程师与学生,同时适合需要轻量级解码库的嵌入式或资源受限场景。压缩包体积仅2.62MB,共170个文件,主要包括头文件、C源码、动态库与导入库、pkg-config配置以及ffpreset预设文件,便于开发者直接链接调用或按需裁剪功能。已有227人学习下载。除核心二进制外,包内还提供transcode_aac、muxing、demuxing_decoding等示例工程源码,可帮助理解音频转码、复用与解复用流程;从“desktop-MINGW32-lite”命名可见其为精简版,剔除非核心模块,保留常用解码与命令行能力,适合快速集成到Windows桌面应用。整体轻巧实用,兼顾学习与二次开发。

1. 为什么我还在用 ffmpeg 3.4 + mingw 32 位

你可能觉得 32 位加 ffmpeg 3.4 是上个时代的组合,但当我接手一台只能跑 32 位系统的工控机,要在 Qt 里做视频预览时,这套 ffmpeg-3.4-mingw32 资源反而成了最省事的答案。它解决的是:在 32 位 Windows 环境下,用 Qt 的 MinGW 工具链集成 FFmpeg 的解码、转码能力,不碰 MSVC、不折腾交叉编译。适合维护老设备、做离线视频处理工具、以及被 Qt 官方包里 ffmpeg 版本不匹配搞晕的从业者。这套资源把编译好的库和头文件打包好了,拿到就能接进 .pro 工程,省掉自己用 MinGW 从源码编 ffmpeg 的痛苦。

2. 选型:ffmpeg 3.4 与 mingw 32 位、Qt 版本怎么配

2.1 为什么盯上 32 位版

现在很多开发机早就不用 32 位了,但实际项目里 32 位 Windows 的存量设备远比想象的多。工业触摸屏、老式一体机、银行柜台的终端、医疗设备的工作站,很多跑的都是 Win7 32 位或者 Win10 32 位,内存 4G 封顶。这些设备上部署的是 32 位版本的 Qt 应用,那链接的 ffmpeg 就必须是 32 位。如果拿 64 位库硬接,编译能过,一运行就崩溃,因为进程位数必须和库位数一致。

另外,32 位的 ffmpeg 3.4 静态库体积明显小于 64 位版本。我做的一个转码工具,Release 编译后整个 exe 加 dll 也就十几 MB,这对需要拷贝到多台老机器上的场景很友好。还有一个隐藏原因:mingw 32 位的工具链在 Windows 上部署最省心,不需要装庞大的 Visual Studio 运行时环境,拷过去就能跑。

2.2 ffmpeg 3.4 这个版本的特殊位置

ffmpeg 3.4 在 2017 年发布,但它在 API 演进上很有点承上启下的意思。从 3.x 开始,ffmpeg 已经全面切到了avcodec_send_packet/avcodec_receive_frame这套新解码接口,老的avcodec_decode_video2虽然在兼容层还在,但新项目没人会用。3.4 里AVStream->codec字段官方标记为废弃,要求通过avcodec_parameters_to_context把流的参数复制给解码器上下文。

这个变化意味着,网上那些教avcodec_open2后直接把stream->codec拿来用的老教程,在 3.4 上已经跑不通了。3.4 还让av_register_all()变成了空函数,很多从 2.x 转型来的人会卡在这一步。所以这套资源带着 3.4 的头文件和库,等于帮你锁定了一套稳定且不算太旧的 API 集合:既能享受新接口的清晰逻辑,又不至于像 5.x 那样频繁调整内部结构。

2.3 mingw 工具链和 msvc 的差别

Qt 在 Windows 上有两套主流编译器,MSVC 和 MinGW。MSVC 版的 Qt 库和依赖用微软工具链编译,链接时不能直接混用 mingw 导出的 lib;MinGW 版则可以配合 ffmpeg-mingw32 的库文件直接静态链接。这套资源挂在 mingw32 下,就是给 Qt MinGW 用户准备的。

从实战角度说,MinGW 的好处是:第一,不强制装庞大的 Visual Studio,一个 Qt 安装器自带的工具链就够用;第二,静态链接配置简单,.a文件就是给 MinGW 用的,不像 MSVC 那样要处理.lib的格式差异;第三,用 MinGW 编译的程序不依赖 VC++ 运行库,部署时少一层崩溃风险。代价也有,比如调试器 gdb 在 Windows 下体验不如 Visual Studio 的调试器顺手,看 Qt 容器内部变量比较费劲。但我个人习惯是先用 qDebug 打点定位,够用就行。

3. 环境搭建:从拿到资源到跑通第一个转码程序

3.1 拿到资源后先确认这三样

把 ffmpeg-3.4-mingw32 解压后,你会看到 include、bin、lib 三个目录。include 里是头文件,lib 里是.a静态库文件,bin 里是几个运行时 dll。第一步不要急着写代码,先把目录结构认全,确认三件事:

  • include 下有 libavcodec、libavformat、libavutil、libswscale、libswresample 这五个核心头文件目录
  • lib 下有对应的libavcodec.dll.a、libavformat.dll.a等文件
  • bin 下有 avcodec-57.dll、avformat-57.dll、avutil-55.dll、swscale-4.dll、swresample-2.dll

这里注意到一个细节:lib 下的.dll.a是导入库,不是完整静态库。也就是说这套资源默认是动态链接 ffmpeg 的 dll,这对 32 位项目来说其实更合理——多个应用共享一套 dll,部署时只要把 bin 目录一起拷走就行。如果要做完全静态,需要另外编译 a 版本的完整静态库,一般没必要。

3.2 .pro 文件配置

在 Qt 工程里接入这套 ffmpeg,核心是.pro文件的配置。我一般会在工程根目录下建一个 third_party 目录,把解压后的 ffmpeg 文件夹放进去,然后在 .pro 里写这样一段:

# 指定 ffmpeg 头文件路径 INCLUDEPATH += $$PWD/third_party/ffmpeg/include # 指定导入库路径 LIBS += -L$$PWD/third_party/ffmpeg/lib \ -lavformat \ -lavcodec \ -lavutil \ -lswscale \ -lswresample # 顺手把 dll 拷贝到构建目录,避免手动复制 CONFIG(debug, debug|release) { DESTDIR = $$OUT_PWD/debug } else { DESTDIR = $$OUT_PWD/release } QMAKE_POST_LINK += $$quote(cmd /c xcopy /E /I /Y \ $$PWD/third_party/ffmpeg/bin\\*.dll $$DESTDIR\\)

这段配置里有几个容易踩的地方。INCLUDEPATH用的是$$PWD,确保路径不会因为构建目录不同而失效。LIBS里的顺序有讲究:-lavformat放最前,因为它依赖avcodec和avutil;如果顺序反了,MinGW 的链接器会报 undefined reference。QMAKE_POST_LINK里的xcopy是 Windows 下的拷贝命令,注意结尾的反斜杠在 .pro 里要写成\\,否则路径拼接会出问题。

3.3 第一个转码 demo:把任意视频抽出一帧存成图片

环境配好后,先用一个最小程序验证整条链路。我习惯先写一个抽帧程序,输入视频路径,输出一张 jpg 图片,能跑通就说明解码器、解封装、格式转换全部正常。代码如下:

#include <QCoreApplication> #include <QDebug> #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" #include "libswscale/swscale.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); if (app.arguments().size() < 3) { qDebug() << "用法: ffmpeg_probe <输入视频> <输出图片>"; return -1; } // 1. 打开输入文件 AVFormatContext *fmtCtx = nullptr; if (avformat_open_input(&fmtCtx, app.arguments()[1].toLocal8Bit().data(), nullptr, nullptr) != 0) { qDebug() << "打开视频失败"; return -1; } // 2. 查找视频流 int videoStreamIndex = av_find_best_stream(fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); if (videoStreamIndex < 0) { qDebug() << "未找到视频流"; avformat_close_input(&fmtCtx); return -1; } AVStream *stream = fmtCtx->streams[videoStreamIndex]; // 3. 打开解码器 AVCodecParameters *codecParams = stream->codecpar; const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id); AVCodecContext *codecCtx = avcodec_alloc_context3(codec); if (avcodec_parameters_to_context(codecCtx, codecParams) < 0) { qDebug() << "参数复制失败"; return -1; } if (avcodec_open2(codecCtx, codec, nullptr) < 0) { qDebug() << "解码器打开失败"; return -1; } // 4. 读取帧数据并解码到 AVFrame AVPacket packet; av_init_packet(&packet); AVFrame *frame = av_frame_alloc(); bool gotFrame = false; while (av_read_frame(fmtCtx, &packet) >= 0) { if (packet.stream_index == videoStreamIndex) { int ret = avcodec_send_packet(codecCtx, &packet); if (ret == 0) { ret = avcodec_receive_frame(codecCtx, frame); if (ret == 0) { gotFrame = true; break; } } } av_packet_unref(&packet); } // 5. 用 swscale 转成 RGB 并保存 if (gotFrame) { SwsContext *swsCtx = sws_getContext( codecCtx->width, codecCtx->height, codecCtx->pix_fmt, codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr); AVFrame *rgbFrame = av_frame_alloc(); int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecCtx->width, codecCtx->height, 1); QByteArray buffer(numBytes, 0); av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, reinterpret_cast<uint8_t *>(buffer.data()), AV_PIX_FMT_RGB24, codecCtx->width, codecCtx->height, 1); sws_scale(swsCtx, frame->data, frame->linesize, 0, codecCtx->height, rgbFrame->data, rgbFrame->linesize); QImage image(rgbFrame->data[0], codecCtx->width, codecCtx->height, rgbFrame->linesize[0], QImage::Format_RGB888); image.save(app.arguments()[2]); sws_freeContext(swsCtx); av_frame_free(&rgbFrame); } av_frame_free(&frame); avcodec_free_context(&codecCtx); avformat_close_input(&fmtCtx); return gotFrame ? 0 : -1; }

这段代码的流程是标准的:avformat_open_input打开媒体文件,av_find_best_stream拿到视频流索引,avcodec_parameters_to_context把codecpar里的编解码参数复制到新建的解码器上下文里,注意 3.4 里不能直接拿stream->codec,这步是新旧版本的关键差异。av_read_frame在循环里读 packet,交给avcodec_send_packet和avcodec_receive_frame配对取帧。

有个参数值得说:av_image_get_buffer_size的最后一个参数是字节对齐,传 1 就是最紧凑布局,但有些旧显卡的 DMA 需要 32 字节对齐,如果后面要传给硬件编码器,建议改成 32。sws_getContext的缩放算法我用了SWS_BILINEAR,在性能和画质之间比较平衡;如果只是抽一帧做预览,也可以直接用SWS_FAST_BILINEAR,速度更快但边缘会有一点点锯齿。

4. 在 Qt 里用 ffmpeg 3.4:解码、显示与播放

4.1 打开媒体文件与探测信息

很多从 2.x 时代过来的人第一反应是调av_register_all(),但在 3.4 里这个函数已经是个空实现,调用不会报错但也没有意义。真正要做的是avformat_network_init(),即使你暂时只处理本地文件,顺手调用也没坏处,万一后续要看 rtsp 流就能少改一行。

打开文件前,我习惯先封装一个探测信息的小工具,把时长、分辨率、编码格式、比特率打印出来。这样在界面上能直接显示媒体信息,排查问题也方便。

void probeMedia(const QString &filePath) { AVFormatContext *fmtCtx = nullptr; if (avformat_open_input(&fmtCtx, filePath.toLocal8Bit().data(), nullptr, nullptr) != 0) { qDebug() << "无法打开文件" << filePath; return; } // 读取流信息,3.4 里这一步会填充 streams 数组 if (avformat_find_stream_info(fmtCtx, nullptr) < 0) { qDebug() << "读取流信息失败"; avformat_close_input(&fmtCtx); return; } qDebug() << "文件时长(s):" << (fmtCtx->duration / AV_TIME_BASE); qDebug() << "流数量:" << fmtCtx->nb_streams; for (unsigned int i = 0; i < fmtCtx->nb_streams; ++i) { AVStream *stream = fmtCtx->streams[i]; AVCodecParameters *par = stream->codecpar; if (par->codec_type == AVMEDIA_TYPE_VIDEO) { qDebug() << "视频流索引" << i << "编码" << avcodec_get_name(par->codec_id) << "分辨率" << par->width << "x" << par->height << "帧率" << av_q2d(stream->avg_frame_rate); } else if (par->codec_type == AVMEDIA_TYPE_AUDIO) { qDebug() << "音频流索引" << i << "编码" << avcodec_get_name(par->codec_id) << "采样率" << par->sample_rate; } } avformat_close_input(&fmtCtx); }

avformat_find_stream_info这步很多人跳过,直接拿stream->codecpar用,但其实有时候文件头里没有完整信息,调一下这个函数会让 ffmpeg 主动去读几帧,把avg_frame_rate和duration补全。av_q2d是把有理数转成 double 的常用工具,后面做进度条百分比也要用到它。

4.2 解码循环:从 AVPacket 到 AVFrame

解码循环的关键在于 packet 和 frame 的生命周期管理。3.4 里avcodec_send_packet成功入队后,需要循环调avcodec_receive_frame直到返回AVERROR(EAGAIN),表示这一批 packet 暂时榨不出更多帧了。这个逻辑在 3.x 之前不是这样——老接口是单线程直接调avcodec_decode_video2,新接口更像生产者和消费者的关系。

实际开发里,我习惯在解码时额外处理一个边界情况:视频流里包了多帧数据,receive_frame一次可能拿到多帧,所以要循环接受。

void decodeLoop(AVFormatContext *fmtCtx, int videoIndex, AVCodecContext *codecCtx) { AVPacket packet; av_init_packet(&packet); while (av_read_frame(fmtCtx, &packet) >= 0) { if (packet.stream_index != videoIndex) { av_packet_unref(&packet); continue; } int ret = avcodec_send_packet(codecCtx, &packet); av_packet_unref(&packet); // packet 用完立刻释放 if (ret < 0 && ret != AVERROR(EAGAIN)) { qDebug() << "解码错误:" << ret; break; } while (true) { int recvRet = avcodec_receive_frame(codecCtx, frame); if (recvRet == AVERROR(EAGAIN)) { break; // 当前没有更多帧,继续读下一个 packet } else if (recvRet < 0) { qDebug() << "取帧错误:" << recvRet; break; } // 这里拿到一帧完整数据,交给显示或者转换 processFrame(frame, codecCtx); av_frame_unref(frame); // 释放帧内部引用计数 } } }

这段代码里有个细节值得注意:av_read_frame成功后会占用 packet 内部的缓存,在用完avcodec_send_packet后立刻av_packet_unref,避免内存持续堆积。send_packet调用后,ffmpeg 内部会引用 packet 的数据,但 unref 只是减少引用计数并清空字段,真正数据由 ffmpeg 内部持有,所以不会出现数据丢失。receive_frame返回EAGAIN时不要当成错误,这是正常的"等下一个 packet"的信号。

4.3 在 QLabel 上显示视频帧

拿到 AVFrame 后要显示到 Qt 控件上,绕不开格式转换这一步。AVFrame 默认是 YUV420P,而 QLabel 不能直接显示 YUV,必须转成 RGB。

转换这块有两个选择:用sws_getContext转换,或者直接手动写像素转换。手写 YUV420P 转 RGB888 的公式在边缘颜色上容易偏色,而且慢,不推荐。用 swscale 是最稳妥的。

QImage convertFrameToQImage(AVFrame *frame, int width, int height, AVPixelFormat pixFmt) { static SwsContext *swsCtx = nullptr; static int swsWidth = 0; static int swsHeight = 0; int w = width; int h = height; if (swsCtx == nullptr || swsWidth != w || swsHeight != h || pixFmt != AV_PIX_FMT_YUV420P) { if (swsCtx) { sws_freeContext(swsCtx); } swsCtx = sws_getContext(w, h, pixFmt, w, h, AV_PIX_FMT_RGB888, SWS_BILINEAR, nullptr, nullptr, nullptr); swsWidth = w; swsHeight = h; } QImage img(w, h, QImage::Format_RGB888); uint8_t *dstData[1] = { img.bits() }; int dstLinesize[1] = { static_cast<int>(img.bytesPerLine()) }; sws_scale(swsCtx, frame->data, frame->linesize, 0, h, dstData, dstLinesize); return img.copy(); // 拷贝一次,避免 QImage 持有 AVFrame 内部数据 }

这里我做了个小的缓存优化:swsCtx用静态变量缓存,只有分辨率或像素格式变化时才重新创建。因为sws_getContext内部要分配查找表,频繁创建性能损耗很大。img.copy()这一步虽然多一次内存拷贝,但非常重要——如果不拷贝,QImage 的bits()指向的是刚才sws_scale写入的临时缓冲区,一旦 AVFrame 被 unref,这个地址就悬空了,界面会花屏或崩溃。

把它接到 QLabel 上:

// 在解码回调里调用 QImage frameImage = convertFrameToQImage(frame, codecCtx->width, codecCtx->height, codecCtx->pix_fmt); QPixmap pixmap = QPixmap::fromImage(frameImage); ui->videoLabel->setPixmap(pixmap.scaled(ui->videoLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));

scaled这一步在新手代码里经常被忽略,直接setPixmap(pixmap)会导致控件放不下大分辨率视频,而KeepAspectRatio可以保持画面比例不拉伸变形。

5. 避坑:ffmpeg 3.4 mingw 32 位最常见的 5 个坑

5.1 undefined reference 到__imp_系列符号

现象:编译链接时报一堆undefined reference to '__imp_avformat_open_input'之类的错误。

原因:链接器拿到的导入库和你写的函数名对不上。有两种典型情况:一是你链接的是 64 位版本的库,二是 MinGW 在 32 位下要求链接.dll.a文件时,需要把库路径写对。__imp_前缀是 MinGW 为 dll 导入表自动生成的跳转符号,报这个错说明库文件本身没被正确加载。

解决:先确认 lib 目录下的文件名,确保是libavformat.dll.a而不是libavformat.a。然后检查.pro里LIBS的-L路径有没有用$$PWD拼接,Qt Creator 在影子构建时工作目录和源码目录不同,路径写绝对路径就容易漏。最后确认一下库顺序,依赖库必须放在引用它的库后面。

5.2av_register_all要不要调

现象:很多从旧教程抄来的代码里第一行就是av_register_all(),编译不报错,但运行时有博主说这是废弃的,到底要不要加?

原因:ffmpeg 3.4 里av_register_all已经成了空函数,调用它没有任何效果,也不会初始化任何东西。3.x 之后,所有编解码器都是自动注册的,不需要手动注册。

解决:删掉这行,或者用条件编译保留兼容性:

#if FF_API_REGISTER_ALL av_register_all(); #endif

但更推荐直接删掉,因为 4.x 里这个函数已经彻底移除了,留着它会在后续升级版本时报错。网上很多老教程是 2.x 时代的,照着敲之前先确认版本。

5.3 32 位内存对齐:SWS_FAST_BILINEAR的玄学闪退

现象:用SWS_FAST_BILINEAR做格式转换时,程序在sws_scale调用时随机崩溃,Release 模式必现,Debug 模式偶尔能跑过去。

原因:32 位环境下,某些 CPU 指令集对未对齐的内存访问会触发异常。SWS_FAST_BILINEAR会尝试用 SIMD 加速像素拷贝,而av_frame_alloc默认分配的缓冲区只有 16 字节对齐,不是 SIMD 需要的 32 字节对齐。

解决:把SWS_FAST_BILINEAR换成SWS_BILINEAR,后者代码路径走的是纯 C 标量实现,对齐要求低。如果想要快速转换又不牺牲稳定性,可以在创建 AVFrame 时手动指定对齐:

AVFrame *frame = av_frame_alloc(); frame->format = AV_PIX_FMT_YUV420P; frame->width = codecCtx->width; frame->height = codecCtx->height; av_frame_get_buffer(frame, 32); // 32 字节对齐

这样 frame 内部的数据缓冲区就会按 32 字节对齐分配,SWS_FAST_BILINEAR的崩溃问题就能规避。但注意,av_frame_get_buffer要在拿到解码帧之前手动创建并分配,如果你是直接从avcodec_receive_frame拿到的帧,它的对齐方式由解码器内部决定,你也做不了主。

5.4 库链接顺序导致 undefined reference

现象:.pro里明明写了-lavformat -lavcodec -lavutil -lswscale -lswresample,但还是报av_register_all或者sws_getContext找不到。

原因:MinGW 的链接器对静态库和导入库的解析顺序非常敏感,它只扫描一次库文件。如果一个库 A 依赖库 B,B 必须放在 A 后面,否则 B 里的符号已经被跳过,链接器不会回头再看。

解决:按依赖关系从上层到底层排列:

LIBS += -lavformat \ -lavcodec \ -lavutil \ -lswscale \ -lswresample

avformat依赖avcodec和avutil,avcodec依赖avutil,swscale也依赖avutil。如果遇到循环依赖,可以在中间加一个-lavutil的重复项。这个坑也和动态链接库有关,因为导入库本质上只是 dll 的跳转表,顺序错了一样报。

5.5 运行时缺 DLL:libgcc_s_dw2-1.dll和libwinpthread-1.dll

现象:程序在开发机上正常运行,拷到别的 32 位机器上提示缺少libgcc_s_dw2-1.dll或libwinpthread-1.dll,有的是启动直接弹窗,有的是功能模块调用时报错。

原因:MinGW 编译的程序默认动态链接 GCC 运行时库和线程库。这两个 dll 在 Qt 自带的mingw32/bin目录里能找到,但目标机器上没有装 Qt/MinGW 环境,自然加载不到。

解决:部署时把这两个 dll 一起拷过去。最省事的办法是在.pro里加QMAKE_POST_LINK配合windeployqt一并处理。手动拷贝的话,从 Qt 安装目录的Tools/mingw.../bin下找,一般是libgcc_s_dw2-1.dll、libwinpthread-1.dll、libstdc++-6.dll这三个。

如果希望彻底避免这个麻烦,可以在 MinGW 编译时用静态链接:

QMAKE_LFLAGS += -static-libgcc -static-libstdc++

但线程库libwinpthread静态链接需要在编译工具链层面配-static,整体改动比较大。我的习惯是保持动态链接,部署时统一用脚本拷贝。

6. 进阶:把播放器装进安装包

6.1 用 windeployqt 部署之外还要补哪些库

Qt 自带windeployqt工具能自动识别工程依赖的 Qt 模块并拷贝 dll,但它不会管 ffmpeg 的 dll。所以部署脚本要额外处理avformat-57.dll、avcodec-57.dll、avutil-55.dll、swscale-4.dll、swresample-2.dll这几个文件。

我习惯把部署脚本放在工程根目录,用批处理串联:

@echo off set PROJECT_DIR=%~dp0 set BUILD_DIR=%PROJECT_DIR%build\release set DEPLOY_DIR=%PROJECT_DIR%deploy windeployqt %BUILD_DIR%\MyPlayer.exe --release --dir %DEPLOY_DIR% copy %BUILD_DIR%\MyPlayer.exe %DEPLOY_DIR%\ copy %PROJECT_DIR%\third_party\ffmpeg\bin\*.dll %DEPLOY_DIR%\

这里有两处容易翻车。第一是windeployqt的路径必须在 PATH 环境变量里,否则需要写完整路径;第二是 ffmpeg 的 dll 版本号要跟你链接的库版本完全一致,3.4 对应的是 avcodec-57,如果混了 4.x 的 dll,启动时会直接报无法定位程序输入点。

6.2 验证帧率与内存:别只看能跑

程序跑起来不代表没问题。32 位环境下内存地址空间只有 4G,而实际可用通常只有 2G,视频解码时 AVFrame 动辄几 MB,一不留神就 OOM。我一般会在解码循环里加一个计数器,每 100 帧打一次当前帧率和已解码帧数:

static int frameCount = 0; static QElapsedTimer timer; if (frameCount == 0) timer.start(); frameCount++; if (frameCount % 100 == 0) { double fps = 100.0 / (timer.elapsed() / 1000.0); qDebug() << "平均帧率:" << fps << "已解码:" << frameCount; timer.restart(); }

同时开着任务管理器观察内存曲线,如果持续增长不回落,说明有帧泄漏——多半是某条分支里av_frame_unref没调,或者 RGB 转换后的 QImage 没释放。

验证完帧率后叫一个测试的同事来用两天,让他专门试各种格式的文件。我遇到过最离谱的一次是:某款 mp4 文件在 3.4 下avformat_find_stream_info拿到错误的 duration,导致进度条计算偏到天边。后来才发现是文件本身 B 帧标记有点问题,解复用器解析顺序受到干扰。从那以后我每次集成新的 ffmpeg 版本,都会把样本库里的各种扩展名、各种编码组合都跑一遍。后来碰上这种玄学问题,第一反应就是切样本,而不是改代码。这个习惯帮我省了不少排查时间,希望你也能用上。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 13:09:14

江门新会汽车贴膜哪家好?LLumar 龙膜贴膜江门授权店,本地高端汽车贴膜标杆门店推荐

江门私家车、新能源汽车保有量持续攀升&#xff0c;沿海高温暴晒、高速飞石、紫外线侵蚀&#xff0c;让隐形车衣、隔热窗膜成为众多车主刚需。不少车主网上反复搜索江门汽车贴膜哪家好、江门隐形车衣哪家靠谱、新会汽车贴膜哪家口碑强、江门新能源车贴膜哪家专业。坐落于江门市…

作者头像 李华
网站建设 2026/10/14 13:09:05

魔方V10与轻舟模块集成实战:ERP系统模块化升级方案

1. 项目背景与核心价值去年在给某大型零售企业做系统升级时&#xff0c;我第一次接触到魔方V10这个ERP系统。当时客户提出要对接轻舟模块实现供应链可视化&#xff0c;这个需求让我意识到企业级系统集成正在从"大而全"向"模块化灵活组合"转型。魔方V10作为…

作者头像 李华
网站建设 2026/10/14 13:08:56

单臂路由原理与实操:用一个物理接口实现VLAN间通信

1. 什么是单臂路由&#xff1f;它到底在解决什么实际问题&#xff1f;单臂路由综合实验&#xff0c;这个名字听起来有点技术味儿&#xff0c;但拆开来看&#xff0c;它其实解决的是一个非常具体、非常常见的网络部署痛点&#xff1a;如何用一台路由器&#xff0c;让多个不同网段…

作者头像 李华
网站建设 2026/10/14 13:08:51

NC57凭证Excel导入工具:解决科目编码校验与辅助核算格式难题

简介&#xff1a;本资源是一款专为用友NC57财务系统设计的总账凭证Excel格式导入工具&#xff0c;面向企业财务人员、ERP实施顾问及二次开发工程师&#xff0c;解决传统XML模板导入方式操作繁琐、学习成本高、数据整理效率低等痛点&#xff0c;尤其适用于需批量导入凭证的月结、…

作者头像 李华
网站建设 2026/10/14 13:08:29

蓝桥杯题库导入QDUOJ:兼容性改造与避坑指南

简介&#xff1a;面向蓝桥杯竞赛选手的题库合集&#xff0c;已针对青岛大学QDUOJ在线评测系统完成优化适配&#xff0c;适合需要在特定OJ环境中集中练习、验证算法代码的备赛者&#xff0c;尤其适用于参加蓝桥杯全国编程竞赛的大学生和算法爱好者&#xff0c;覆盖从基础巩固到拔…

作者头像 李华
网站建设 2026/10/14 13:08:27

知识工作插件集:从网页剪藏到双链检索的本地工作流

如果你每天要在浏览器里翻十几篇文档&#xff0c;在网页、Markdown 编辑器、PDF 阅读器之间来回切&#xff0c;你大概率体会过那种“信息进来容易、出去难”的憋屈。我花了大半年时间&#xff0c;攒了一套自用的插件集&#xff0c;仓库名就叫 knowledge-work-plugins 。它不是…

作者头像 李华