简介这份资源面向在Linux平台从事多媒体应用开发的Qt5开发者与运维人员提供一套可直接编译运行的FFmpeg调用测试程序帮助解决Qt工程中集成音视频编解码库的配置与调用问题。压缩包共213个文件约23.76MB以126个h头文件、23个c源文件为核心配合7个so动态库、ffpreset预设文件及ffmpeg、ffplay、ffprobe等可执行程序另含少量cpp、makefile与readme说明覆盖头文件、库文件与运行工具三类内容。资源围绕Qt5事件驱动模型与进程通信展开演示通过QProcess启动FFmpeg读取视频元数据并给出.pro文件中INCLUDEPATH与LIBS的链接配置同时涉及FFmpegWrapper封装、错误处理与异步调用等进阶思路。已有578人学习下载适合希望掌握Qt与FFmpeg联合开发、构建视频播放与转码等多媒体功能的读者参考实践。1. 在 Linux 下用 Qt5 调 FFmpeg一个测试程序能帮你省掉多少联调时间如果你在 Linux 桌面端做音视频工具大概率绕不开这个组合Qt5 做界面和事件循环FFmpeg 做解封装、解码、缩放、编码。真正让人头疼的不是“能不能编过”而是“跑起来之后黑屏、花屏、音画不同步、内存一路涨”。我一般会先写一个最小测试程序把 FFmpeg 的调用链在 Qt5 里跑通再往上叠业务逻辑。这个测试程序要解决三件事确认开发环境里 FFmpeg 的头文件和库能被 Qt5 的 qmake 正确找到确认解码后的 AVFrame 能转成 QImage 并显示在 QLabel 或 QWidget 上确认音视频时间戳、线程和资源释放不会在 Qt 的事件循环里翻车。适合已经会写 C、用过 Qt 信号槽、但还没把 FFmpeg 接进 Qt 工程的人。下面按“环境准备 → 最小解码显示 → 参数与排错 → 进阶技巧”推一遍代码可以直接抄。2. 环境准备与工程骨架让 qmake 找到 FFmpeg2.1 确认 FFmpeg 开发包和 Qt5 版本在动手写代码之前先把系统里的开发包和 Qt5 的版本对齐。常见做法是用发行版包管理器装libavcodec-dev、libavformat-dev、libswscale-dev、libavutil-dev再确认qmake --version输出的是 Qt 5.x。不要混用 Qt4 的 qmake 和 Qt5 的库否则链接阶段会出现一堆undefined reference而且报错位置往往在 Qt 的 moc 文件里看起来像 Qt 的问题实际是版本错配。# 查看 Qt5 版本和 qmake 路径 qmake --version which qmake # 查看 FFmpeg 开发库是否就位 pkg-config --modversion libavcodec pkg-config --modversion libavformat pkg-config --modversion libswscale # 如果 pkg-config 找不到检查头文件和库路径 ls /usr/include/x86_64-linux-gnu/libavcodec/avcodec.h ls /usr/lib/x86_64-linux-gnu/libavcodec.so上面命令的逻辑说明qmake --version确认 Qt 版本pkg-config用来拿编译和链接参数后面会写进.pro文件ls是兜底检查因为有些发行版把 FFmpeg 头文件放在多架构目录下pkg-config不一定能自动识别。参数上libavcodec负责编解码libavformat负责解封装libswscale负责像素格式转换这三个是测试程序的最小依赖。如果只做解码显示libavfilter和libswresample可以先不链等要做音频重采样再加。2.2 写一个能编译的 .pro 文件Qt5 工程用 qmake 管理时.pro文件决定了头文件搜索路径和链接库。很多人直接写LIBS -lavcodec -lavformat -lswscale在部分系统上能过但换一台机器就找不到库。更稳的做法是用pkg-config把编译参数和链接参数都拉进来。QT core gui widgets TARGET ffmpeg_qt_test TEMPLATE app CONFIG c11 # 用 pkg-config 拉取 FFmpeg 的编译和链接参数 CONFIG link_pkgconfig PKGCONFIG libavcodec libavformat libswscale libavutil SOURCES main.cpp \ decoder.cpp HEADERS decoder.h逻辑说明QT core gui widgets是 Qt5 Widgets 应用的最小模块集CONFIG link_pkgconfig让 qmake 调用pkg-configPKGCONFIG ...列出四个 FFmpeg 模块。参数上c11是 FFmpeg 新版本头文件常见要求不加可能报nullptr或std::atomic相关错误。如果目标机器没有pkg-config可以退化成手动写INCLUDEPATH和LIBS但那样跨机器复现性差不建议作为首选。2.3 最小解码器的类结构测试程序不需要一上来就做播放器。我一般先写一个Decoder类只做“打开文件 → 找视频流 → 解码一帧 → 转 QImage → 发信号”。这样能把 FFmpeg 的调用链和 Qt 的信号槽解耦后面加线程或加音频都容易。// decoder.h #ifndef DECODER_H #define DECODER_H #include QObject #include QImage #include QString extern C { #include libavformat/avformat.h #include libavcodec/avcodec.h #include libswscale/swscale.h #include libavutil/imgutils.h } class Decoder : public QObject { Q_OBJECT public: explicit Decoder(QObject *parent nullptr); ~Decoder(); bool open(const QString path); bool decodeOneFrame(QImage outImage); signals: void errorOccurred(const QString msg); private: AVFormatContext *fmtCtx nullptr; AVCodecContext *codecCtx nullptr; SwsContext *swsCtx nullptr; int videoStreamIndex -1; AVFrame *frame nullptr; AVFrame *rgbFrame nullptr; uint8_t *rgbBuffer nullptr; }; #endif // DECODER_H逻辑说明extern C是必须的因为 FFmpeg 是 C 库Qt 是 C不包extern C会出现链接符号不匹配。成员变量里fmtCtx管解封装codecCtx管解码swsCtx管像素格式转换rgbFrame和rgbBuffer用来承接转换后的 RGB 数据。参数上videoStreamIndex初始化为 -1表示还没找到视频流frame是解码后的原始帧rgbFrame是转换后的帧两者不能混用。3. 解码一帧并显示从 AVFrame 到 QImage 的完整链路3.1 打开文件与查找视频流打开文件这一步FFmpeg 4.x 和 5.x 的 API 有差异。常见做法是用avformat_open_input打开用avformat_find_stream_info探测流信息然后遍历streams找codecpar-codec_type AVMEDIA_TYPE_VIDEO。不要用av_find_best_stream之后就以为万事大吉仍然要检查返回的流索引是否有效。bool Decoder::open(const QString path) { QByteArray utf8 path.toUtf8(); if (avformat_open_input(fmtCtx, utf8.constData(), nullptr, nullptr) ! 0) { emit errorOccurred(avformat_open_input failed); return false; } if (avformat_find_stream_info(fmtCtx, nullptr) 0) { emit errorOccurred(avformat_find_stream_info failed); return false; } videoStreamIndex -1; for (unsigned i 0; i fmtCtx-nb_streams; i) { if (fmtCtx-streams[i]-codecpar-codec_type AVMEDIA_TYPE_VIDEO) { videoStreamIndex static_castint(i); break; } } if (videoStreamIndex 0) { emit errorOccurred(no video stream found); return false; } AVCodecParameters *par fmtCtx-streams[videoStreamIndex]-codecpar; const AVCodec *codec avcodec_find_decoder(par-codec_id); if (!codec) { emit errorOccurred(avcodec_find_decoder failed); return false; } codecCtx avcodec_alloc_context3(codec); if (!codecCtx) { emit errorOccurred(avcodec_alloc_context3 failed); return false; } if (avcodec_parameters_to_context(codecCtx, par) 0) { emit errorOccurred(avcodec_parameters_to_context failed); return false; } if (avcodec_open2(codecCtx, codec, nullptr) 0) { emit errorOccurred(avcodec_open2 failed); return false; } frame av_frame_alloc(); rgbFrame av_frame_alloc(); if (!frame || !rgbFrame) { emit errorOccurred(av_frame_alloc failed); return false; } return true; }逻辑说明avformat_open_input的第二个参数是文件路径Qt 的QString要先转QByteArray再取constData()避免中文路径乱码。avformat_find_stream_info会读一小段数据来探测编码参数网络流或损坏文件可能在这里卡住测试程序里可以接受。avcodec_parameters_to_context把流里的编码参数复制到解码上下文这一步不能省否则codecCtx里没有宽高和像素格式。参数上avcodec_open2的第三个参数可以传字典设置线程数测试阶段传nullptr即可。3.2 解码一帧并转成 QImage解码循环里最容易翻车的是avcodec_send_packet和avcodec_receive_frame的返回值处理。FFmpeg 新 API 要求先送 packet再收 frame收完可能还要送空 packet 冲刷。测试程序里只解一帧可以简化但返回值必须判断。bool Decoder::decodeOneFrame(QImage outImage) { AVPacket *pkt av_packet_alloc(); if (!pkt) { emit errorOccurred(av_packet_alloc failed); return false; } bool gotFrame false; while (!gotFrame av_read_frame(fmtCtx, pkt) 0) { if (pkt-stream_index ! videoStreamIndex) { av_packet_unref(pkt); continue; } int ret avcodec_send_packet(codecCtx, pkt); av_packet_unref(pkt); if (ret 0) { emit errorOccurred(avcodec_send_packet failed); break; } ret avcodec_receive_frame(codecCtx, frame); if (ret 0) { gotFrame true; } else if (ret AVERROR(EAGAIN)) { continue; } else { emit errorOccurred(avcodec_receive_frame failed); break; } } av_packet_free(pkt); if (!gotFrame) { return false; } // 分配 RGB 缓冲区并转换像素格式 int w frame-width; int h frame-height; if (!swsCtx) { swsCtx sws_getContext(w, h, codecCtx-pix_fmt, w, h, AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!swsCtx) { emit errorOccurred(sws_getContext failed); return false; } } int bufSize av_image_get_buffer_size(AV_PIX_FMT_RGB24, w, h, 1); rgbBuffer static_castuint8_t *(av_malloc(bufSize)); if (!rgbBuffer) { emit errorOccurred(av_malloc failed); return false; } av_image_fill_arrays(rgbFrame-data, rgbFrame-linesize, rgbBuffer, AV_PIX_FMT_RGB24, w, h, 1); sws_scale(swsCtx, frame-data, frame-linesize, 0, h, rgbFrame-data, rgbFrame-linesize); outImage QImage(rgbFrame-data[0], w, h, rgbFrame-linesize[0], QImage::Format_RGB888).copy(); av_free(rgbBuffer); rgbBuffer nullptr; av_frame_unref(frame); return true; }逻辑说明av_read_frame读到的 packet 可能不是视频流必须用stream_index过滤否则会把音频 packet 送给视频解码器直接报错。avcodec_send_packet之后要立刻av_packet_unref因为 send 之后 packet 的数据已经被解码器引用不 unref 会内存泄漏。avcodec_receive_frame返回EAGAIN表示当前没有帧可收需要继续送 packet这是正常流程不是错误。sws_getContext只创建一次宽高和像素格式变化时要重建。QImage构造时用copy()是因为rgbBuffer随后会被释放不 copy 会得到悬空指针。参数上SWS_BILINEAR是缩放算法测试阶段够用追求画质可以换SWS_BICUBIC。3.3 在 Qt 窗口里显示并触发解码主窗口里放一个QLabel把Decoder的decodeOneFrame结果设进去。测试程序可以只解一帧也可以加一个按钮连续解。注意不要在 UI 线程里做长时间解码否则界面会卡死。// main.cpp #include QApplication #include QLabel #include QPushButton #include QVBoxLayout #include QWidget #include decoder.h int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; QVBoxLayout *layout new QVBoxLayout(window); QLabel *label new QLabel; label-setMinimumSize(640, 360); label-setScaledContents(true); QPushButton *btn new QPushButton(Decode One Frame); layout-addWidget(label); layout-addWidget(btn); Decoder decoder; QObject::connect(decoder, Decoder::errorOccurred, [](const QString msg) { label-setText(msg); }); QObject::connect(btn, QPushButton::clicked, []() { if (!decoder.open(/path/to/test.mp4)) { return; } QImage img; if (decoder.decodeOneFrame(img)) { label-setPixmap(QPixmap::fromImage(img)); } }); window.resize(800, 600); window.show(); return app.exec(); }逻辑说明Decoder对象在栈上创建生命周期覆盖整个窗口避免信号槽连接后对象被销毁。open每次点击都调用测试阶段可以接受实际使用要加状态判断避免重复打开导致资源泄漏。QLabel的setScaledContents(true)让图像自适应控件大小方便观察花屏和比例问题。参数上测试文件路径写死即可换成命令行参数或文件对话框是后续的事。4. 避坑与排查Qt5 调 FFmpeg 最常见的 5 个翻车点4.1 编译通过但运行时报 “undefined symbol”现象qmake和make都成功运行时报undefined symbol: avcodec_send_packet或类似错误。原因通常是.pro里链接了 FFmpeg 库但运行时加载的是系统里另一个版本的库或者链接顺序不对。解决用ldd看可执行文件实际链接的库路径确认和编译时一致在.pro里把PKGCONFIG放在SOURCES之前确保链接参数顺序正确。如果系统里有多个 FFmpeg 版本用LD_LIBRARY_PATH指定运行时库路径但不要写进系统全局配置。4.2 解码一帧后程序崩溃在 av_frame_unref现象第一次解码正常第二次调用decodeOneFrame时崩溃。原因通常是frame没有在每次解码后av_frame_unref或者rgbBuffer被重复释放。解决每次avcodec_receive_frame成功后处理完数据立刻av_frame_unref(frame)rgbBuffer用av_malloc分配、av_free释放不要用new/delete混用。如果要在类里缓存rgbBuffer必须记录分配大小下次复用前先释放旧的。4.3 QImage 显示花屏或颜色错乱现象图像能显示但颜色偏绿或偏紫或者有斜条纹。原因通常是像素格式不匹配比如解码器输出AV_PIX_FMT_YUV420P转换时写成AV_PIX_FMT_RGB24但QImage用了Format_RGB888以外的格式。解决确认sws_getContext的目标格式和QImage的格式一致QImage::Format_RGB888对应AV_PIX_FMT_RGB24Format_RGB32对应AV_PIX_FMT_BGRA。如果图像有斜条纹检查linesize是否被正确传入QImage构造函数不能直接用width * 3代替。4.4 中文路径导致 avformat_open_input 失败现象英文路径正常中文路径返回失败。原因QString::toUtf8()之后传给 FFmpeg 的路径在部分系统上仍然被当作本地编码处理。解决在 Linux 下确认系统 locale 是 UTF-8用QFile::encodeName或直接path.toLocal8Bit()试如果文件系统编码不是 UTF-8需要先转换。测试阶段建议先用英文路径排除干扰确认链路通了再处理路径编码。4.5 界面卡死或内存持续上涨现象点击解码按钮后界面无响应或者连续解码时内存一直涨。原因解码在 UI 线程执行av_read_frame阻塞或者每次解码都av_malloc而不释放。解决把Decoder移到QThread里用信号槽跨线程传递QImage每次decodeOneFrame结束后释放临时 packet 和 bufferswsCtx只在分辨率变化时重建。测试程序里可以先用QCoreApplication::processEvents临时缓解但正式方案必须用线程。5. 进阶技巧把测试程序变成可复用的解码骨架5.1 用 QThread 把解码和显示分开测试程序跑通后下一步是把解码放到独立线程。常见做法是Decoder继承QObject用moveToThread移入QThread在解码线程里循环av_read_frame每解出一帧就emit frameReady(QImage)UI 线程只负责显示。注意QImage跨线程传递是值拷贝不会共享底层数据所以不需要加锁。线程退出时要发一个停止标志让解码循环跳出再quit和wait否则程序退出时可能崩溃在 FFmpeg 的清理函数里。5.2 控制解码速度与丢帧策略直接循环解码会跑满 CPU因为解码速度远快于显示速度。常见做法是用QTimer按帧率触发解码或者在解码线程里根据frame-pts和stream-time_base计算睡眠时间。测试阶段可以简单用QThread::msleep(33)模拟 30fps但正式方案要根据时间戳做音视频同步。丢帧策略上如果解码队列超过阈值可以丢弃非关键帧只保留最近一帧避免内存堆积。5.3 用表格对比不同像素格式的转换参数源格式目标格式QImage 格式sws 标志备注YUV420PRGB24Format_RGB888SWS_BILINEAR最常用兼容性好YUV420PBGRAFormat_ARGB32SWS_BILINEAR适合直接给 OpenGLNV12RGB24Format_RGB888SWS_FAST_BILINEAR硬件解码常见输出YUV422PRGB24Format_RGB888SWS_BICUBIC画质优先CPU 占用高表格里的参数不是固定的sws标志可以根据性能取舍。SWS_FAST_BILINEAR最快但画质一般SWS_BICUBIC画质好但慢。测试阶段先用SWS_BILINEAR确认链路通了再调。5.4 一个我常犯的错误忘记 avformat_close_input早期写测试程序时我只在析构里avcodec_free_context和av_frame_free忘了avformat_close_input。结果连续打开多个文件后文件描述符耗尽程序报 “Too many open files”。后来养成习惯open成功之后所有资源在析构里按“后申请先释放”的顺序清理avformat_close_input放在最后。这个习惯在长时间运行的音视频工具里尤其重要因为文件描述符泄漏不会立刻崩溃但会在跑几小时后突然翻车。希望帮到你。本文还有配套的精品资源点击获取