拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Qt+MinGW32+FFmpeg+RTSP:Windows平台视频拉流播放编译实战

简介面向Windows下Qt与FFmpeg开发RTSP视频流显示的工程示例适合需要快速集成实时视频预览的C开发者。压缩包共103个文件以81个头文件与5个动态库、5个静态库为主另含4个源码文件、界面文件、图标及工程配置等整体约11.58MB结构精简便于直接查阅。目前已有1163人学习下载广受同类项目开发者关注。代码无冗余封装取消影子构建后在Windows下可完整编译运行视频响应速度优于VLC、QTAV等播放器。支持三通道同显一个视频流可单击截图保存至最后通道也支持通道双击最大化便于多路监控或比对场景。还附带编译好的可执行文件与FFmpeg运行库节省环境配置时间。Linux用户只需替换对应FFmpeg库文件即可移植参考虽未包含存储与回放功能但作为实时显示基础框架具有实用价值。 “qtmingw32windowsffmpegrtsp保证编译可用”这个标题组合在我的收藏夹里躺了有一阵子了也是很多做Windows桌面视频应用的开发者绕不开的一条路。说白了就是用Qt写界面用FFmpeg做视频解码和RTSP拉流再用MinGW32这套编译器把工程编译成一个能在Windows上直接跑的exe。这种需求在安防监控、工业上位机、远程画面预览这类项目里太常见了几乎每个月都能在技术群看到有人在问。但真正动手搭的时候坑是一个接一个Qt装错版本、FFmpeg库选不对、编译器链接器对不上、拉流没画面甚至直接闪退问题五花八门。这篇文章就把我在真实项目里折腾出来的完整方案记下来从环境搭建到工程配置从核心代码到编译排错把那些坑提前帮你趟平。打算做视频播放器、或者要给设备写RTSP监控客户端的开发者都可以直接照着操作整套流程是经过实际验证的。1. 方案选型Qt、MinGW32、FFmpeg这套组合怎么定位1.1 这套组合要解决什么场景一个视频类桌面工具核心诉求通常是三件事一个看得过去的操作界面、一个稳定的视频流拉取通道、一套可跨平台的消息和线程机制。Qt负责第一件和第三件FFmpeg负责第二件MinGW32则负责把这些东西在Windows上编译成一个可执行文件。RTSP拉流本身不复杂FFmpeg的libavformat库把RTSP协议的握手、包解析、音视频流分离全部封装好了我们只要打开协议、读包、送解码器、取帧再做一次像素格式转换就能得到一帧可以往Qt界面上贴的图像。这里的关键不在协议本身而在于怎么让这几个库在Windows环境下正确链接、正确部署让程序在别人机器上也能双击就跑。实际项目里这类工具往往要对接海康、大华或者各种第三方RTSP流媒体服务器地址格式差别不小有的要求TCP传输有的默认UDP还有的带鉴权参数。这也是为什么代码里必须预留灵活的配置入口不能写死传输方式。1.2 为什么选中MinGW32而不是MSVC或MinGW64很多人在选型阶段就纠结到不行。我的建议是如果你不是已经在用MSVC配合Visual Studio做界面、且确定FFmpeg相关依赖都能找到兼容版本那么在Qt FFmpeg这条技术路线上MinGW32是省心程度最高的选择。原因也实在。第一FFmpeg官方社区里面向Windows的预编译包大量以MinGW为编译环境MSVC版本的库需要额外去第三方站点找而且经常要和C运行时库的版本做匹配出问题很难查。第二MinGW32生成的exe不依赖Visual Studio那套庞大的运行库部署起来干净很多。第三在32位环境下FFmpeg的内存分配逻辑和各个模块的初始化和老一代视频采集设备SDK的兼容性都更好很多工控机上的摄像头SDK至今只提供32位库这点在做集成时相当关键。MinGW64在纯64位场景下性能确实更好但换来的是兼容性风险一旦你的工程里某个不起眼的静态库还是32位的整个链接过程就直接卡死。反过来32位程序在64位Windows上运行一点问题没有。所以“能用32位就不折腾64位”这是我踩过几次坑后的真实体会。2. 环境搭建下载、安装、匹配一步不到位后面全完2.1 Qt与MinGW32的安装配合Qt这块我建议直接用Qt 5.15.2 LTS版本配套的编译器选MinGW 32-bit。注意选择安装组件时除了勾选Qt库本身还要展开Tools节点把MinGW编译器一并选上否则打开Qt Creator连构建套件Kit都配不全。安装时有一点容易被忽略组件的勾选会直接影响编译路径。比如你只装了MSVC 2019 64-bit然后又单独下载了MinGW编译器手动去Kit设置里拼出来也是能编译的但路径问题、环境变量问题一旦出错了排查成本会高很多。更稳妥的做法是让Qt官方安装器把Qt库和对应的MinGW编译器作为一套整体装好至少在版本匹配上不会出错。装完以后到“选项 - Kits - 编译器”里确认一下MinGW的编译器路径已经自动识别。如果没识别出来手动把编译器路径指到MinGW的bin目录下的gcc.exe和g.exe。这一步很多人忽略最后编译时Qt Creator报“compiler cannot run”绝大多数情况就是编译器没配对或者路径里的环境变量没生效。2.2 FFmpeg开发版与共享版的区别和选择FFmpeg在Windows下的下载包分很多种最容易让人搞混的是dev和shared这两类。dev包里面是头文件include目录和静态链接库lib目录编译链接阶段要用的就是这些shared包里面是动态链接库DLL和一整套exe工具运行阶段必须有这些DLL。实际下载时还需要注意选32位版本和项目的MinGW32保持一致。可以从gyan.dev或者BtbN的Windows builds页面下载选择一个稳定release版本比如4.4或者5.1系列而不要追最新版。FFmpeg太新的版本往往编译工具链要求更高和Qt附带的MinGW版本可能不匹配。下载完成后解压到项目里的第三方目录比如3rdparty/ffmpeg目录结构大概是3rdparty/ffmpeg/ include/ libavcodec/ libavformat/ libavutil/ libswscale/ lib/ libavcodec.dll.a libavformat.dll.a libavutil.dll.a libswscale.dll.a bin/ avcodec-59.dll avformat-59.dll avutil-57.dll ...注意MinGW的lib目录里库文件是.dll.a后缀的导入库不是MSVC那种.lib文件。这一点决定了GCC链接时会用哪些文件。如果你的FFmpeg包是从MSVC版站点下载的那里面提供的是.lib文件在MinGW下链接会直接报错“file not recognized”很多人都卡在这里。2.3 运行目录的DLL部署策略编译成功的exe放到别的机器上如果报“找不到avformat-59.dll”多半是没有把FFmpeg建目录下的所有DLL复制到exe同目录。简单粗暴的姿势就是整个bin目录的DLL全拷过去尤其别只拷自己以为用到的那几个因为FFmpeg各模块之间也有依赖漏掉一个都会启动失败。Qt自己的运行库则用自带工具部署。在Qt安装目录的bin下有一个windeployqt.exe命令行进入exe目录执行windeployqt RtspPlayer.exe它会自动把Qt的core、gui、widgets等运行库和平台插件拷到exe同目录。有用户问为什么FFmpeg没有类似的部署工具那是两码事FFmpeg的DLL还是得手动拷。3. 工程配置.pro文件决定编译成败3.1 合理的目录结构工程文件不建议把所有头文件和库堆在一层目录里时间长了自己都分不清。建议按下面这种结构组织RtspPlayer/ RtspPlayer.pro src/ main.cpp MainWindow.h MainWindow.cpp VideoThread.h VideoThread.cpp 3rdparty/ ffmpeg/ include/ lib/ bin/ bin/整个项目只把3rdparty作为只读依赖不往里面混入自己写的代码。这样以后换FFmpeg版本只需要整体替换3rdparty/ffmpeg不必动工程文件。3.2 .pro文件详解核心的.pro文件长这样QT core gui greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET RtspPlayer TEMPLATE app CONFIG c11 # FFmpeg 头文件目录 INCLUDEPATH $$PWD/3rdparty/ffmpeg/include # FFmpeg 链接库 LIBS -L$$PWD/3rdparty/ffmpeg/lib \ -lavformat \ -lavcodec \ -lavutil \ -lswscale \ -lswresample # 编译后的可执行文件输出到 bin DESTDIR $$PWD/bin # 自动拷贝 FFmpeg DLL 到运行目录 FFMPEG_DLL_PATH $$PWD/3rdparty/ffmpeg/bin QMAKE_POST_LINK $$quote(cmd /c copy /y $$FFMPEG_DLL_PATH\\*.dll $$DESTDIR\\)最后两行的QMAKE_POST_LINK是个很实用的配置编译完自动把DLL复制到输出目录省得每次手动拷。路径里的反斜杠注意用双反斜杠转义。如果是在其他shell环境可能需要调整命令但Windows下用cmd /c是稳的。3.3 库链接顺序的门道MinGW的GCC链接器对库的顺序非常敏感。链接时处于左边的库如果引用了右边库的符号能正确找到反过来就找不到。所以写LIBS时要把依赖别人的一方写在前面被依赖的基础库写在后面。在我上面的配置里avformat依赖avcodec和avutilavcodec又依赖avutil所以顺序必须是avformat、avcodec、avutil。如果把avutil写在avformat前面编译时大概率会冒出一堆undefined reference to avformat_xxx而这个错误很容易让人误以为是库文件没放好。可以在.cpp文件里这样引入FFmpeg头文件为了兼容C和C的符号规则extern C { #include libavformat/avformat.h #include libavcodec/avcodec.h #include libswscale/swscale.h }不包这个extern C链接时也会遇到找不到符号的问题因为C会把函数名做名字修饰name mangling而FFmpeg的库是C编译的。这个细节在新手项目里出现频率极高。4. RTSP拉流与播放的实现4.1 初始化和打开RTSP流新版FFmpeg4.x以后已经不需要调用av_register_all()了但网络初始化还是必须的。打开RTSP流之前先调用一次网上初始化avformat_network_init();然后设置连接参数。RTSP传输方式建议显式指定为TCP否则默认走UDP在弱网环境下很容易出现花屏、马赛克甚至拉不到流的问题。稳定性和实时性的权衡上内网监控场景TCP更可靠AVFormatContext *fmtCtx nullptr; AVDictionary *options nullptr; av_dict_set(options, rtsp_transport, tcp, 0); av_dict_set(options, stimeout, 5000000, 0); QByteArray urlBytes url.toUtf8(); int ret avformat_open_input(fmtCtx, urlBytes.constData(), nullptr, options); if (ret 0) { char errbuf[128] {0}; av_strerror(ret, errbuf, sizeof(errbuf)); qDebug() 打开RTSP流失败: errbuf; return; }注意stimeout单位是微秒这里设成5秒意思是超时5秒断开避免网络抖动时程序一直挂起。打开成功后紧接着avformat_find_stream_info获取流信息再用av_find_best_stream找到视频流索引。RTSP流通常不止一路可能是视频加音频不找对索引后面读包读到的可能就是音频数据。4.2 解码线程与画面显示这里强烈建议不要把av_read_frame放在Qt的UI线程里跑否则解码一卡整个界面就假死了。最常用的方案是写一个继承QThread的工作线程循环读包、解码、发送信号主线程更新QLabel。大致骨架如下void VideoThread::run() { // 前面步骤open input、find stream info、open decoder…… AVPacket *pkt av_packet_alloc(); AVFrame *frame av_frame_alloc(); while (!m_stopFlag av_read_frame(fmtCtx, pkt) 0) { if (pkt-stream_index m_videoIdx) { if (avcodec_send_packet(m_codecCtx, pkt) 0) { while (avcodec_receive_frame(m_codecCtx, frame) 0) { // 把YUV转换成RGB sws_scale(m_swsCtx, frame-data, frame-linesize, 0, m_codecCtx-height, m_rgbFrame-data, m_rgbFrame-linesize); QImage image(m_rgbFrame-data[0], m_codecCtx-width, m_codecCtx-height, QImage::Format_RGB888); emit frameReady(image.copy()); // 跨线程发送信号 } } } av_packet_unref(pkt); } // 清理资源 }这里有两件事让我吃了不少亏。第一FFmpeg解码出来默认是YUV420PQLabel不认这个格式必须用sws_getContext建一个转换上下文再用sws_scale转成RGB24。第二QImage对象在emit出去的时候要做一次深拷贝image.copy()否则工作线程下一轮循环复用buffer后UI线程拿到的图像已经被覆盖了画面会闪。信号连接放到MainWindow的构造函数里connect(m_thread, VideoThread::frameReady, this, MainWindow::onFrameReady); void MainWindow::onFrameReady(const QImage img) { ui-videoLabel-setPixmap( QPixmap::fromImage(img).scaled(ui-videoLabel-size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); }4.3 关键参数rtsp_transport与超时设置上一节提到的参数我单独拎出来再说一下。RTSP默认走UDP好处是实时性更好但坏处是丢包时画面会碎掉而且有些网络环境屏蔽UDP大包根本拉不出来。用TCP能避免大部分网络问题代价是延迟稍高摄像头内网直连的场景完全感知不到。参数设置时不光有rtsp_transport还有一个常见的max_delay这个参数对播放流畅度有影响。如果发现画面总是卡顿可以试着把它调大一点av_dict_set(options, max_delay, 500000, 0);数值单位是微秒500000就是0.5秒。值得注意的是这些参数不传也是可以打开的默认行为和具体构建版本的FFmpeg配置有关显式设置以后行为才有保证。我还碰到过一种情况RTSP流地址是H.265编码而目标机器解码能力弱画面经常花屏。这种情况和FFmpeg本身关系不大更多是解码吞吐量和显示刷新率的匹配问题需要做抽帧降帧率或者换个支持硬解码的构建版本。这里不展开但遇到类似问题时心里要有个概念。5. 常见编译错误与排查5.1 编译期问题速查表把我在实际操作中遇到的高频问题和解决办法整理成一张表方便快速对照报错信息原因解决办法cannot find -lavformat-L路径不对或者库里没有对应的导入库文件检查3rdparty/ffmpeg/lib路径下是否存在libavformat.dll.a且位数一致undefined reference to avformat_open_input链接顺序不对或者头文件没包extern C调整LIBS顺序把avformat放最前检查extern C花括号范围file not recognized: File format not recognized拿到了MSVC版的.lib文件MinGW无法用换MinGW版的FFmpeg开发包确保lib目录是.dll.a后缀cannot run compilerQt Creator的Kit里编译器路径未正确识别到工具-选项-Kits-编译器里重新配置MinGW的gcc/gno such file or directory: libavformat/avformat.hINCLUDEPATH没写对确认.pro里INCLUDEPATH指向/include目录程序启动直接崩溃 / 弹窗提示缺失DLLFFmpeg的DLL没拷贝到exe同目录检查bin目录下DLL数量和版本把3rdparty/ffmpeg/bin里所有DLL拷过去这里面有一个特别隐蔽的32位和64位混用。Qt装了64位的MinGWFFmpeg却下载了32位的库链接时不一定立刻报错但程序一运行就可能崩溃。我建议选型时就把位数钉死在一个方向上检查3rdparty/ffmpeg/lib里的导入库可以用file命令或者objdump确认架构。5.2 运行期问题与调试技巧编译通过只是第一步运行期的问题更考验耐心。最常见的现象是程序能启动但拉流失败控制台没有任何输出。这种情况先在代码里把av_strerror的错误信息打印出来再确认整个链路的每个包是否正常。如果是卡在avformat_open_input那一步先排除网络因素用VLC播放器连接同一个RTSP地址看能不能正常播放。VLC能播而自己代码播不了多半是参数设置差了什么。优先检查rtsp_transport是不是需要设成tcp以及是不是需要额外的鉴权参数。解码开始后如果只有黑屏没有崩溃检查一下sws_scale的调用参数。我遇到过把width和height传反的情况输出画面是一种变形的条纹状看起来像花屏但其实是尺寸不对。QImage构造时宽度高度顺序写反也是一个常见原因。调试时建议先做一件事把qDebug()挂到关键节点比如打开流成功、找到解码器、拿到第一帧。在avcodec_receive_frame返回0时打印一次帧宽高比对摄像头实际分辨率能快速发现数据是否异常。我第一次跑通一个1080p的RTSP源时就是这样一步步定位到画面被压缩变形的。6. 写在最后的几点经验这套环境在Windows下跑通之后再用MSYS2去编译更新版本的FFmpeg也会顺手很多因为两者的工具链是同源的。分享一个很多人不知道的细节Qt Creator里如果发现断点打不上、调试器不生效检查一下Kit里的Debugger路径是不是指向了MinGW的gdb.exe。默认安装的Qt一般把Debugger配置好了但如果你曾经动过环境变量或者从旧版本升级过来这个配置经常失效。别问我是怎么知道的。还有一点项目里尽量用QThread的第一种写法就是继承QThread并实现run()虽然网上很多人说应该用moveToThread加信号槽的方式但在简单的RTSP拉流场景里直接继承QThread代码结构更直观也更容易控制解码循环的终止逻辑。线程退出时记得在析构函数里调用requestInterruption再wait一下保证资源干净释放。最后再给一个可以立刻去测试的RTSP地址rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4这是一个公开测试流很适合验证你的环境是否已经跑通。拿它做完链路测试后再换成自己项目里的真实摄像头地址整个调试过程就不会有太多疑问了。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门