简介这是一个用QT/C编写的开源监控视频项目面向Qt应用开发、音视频流处理及安防监控方向的开发者。项目围绕实时视频的获取、解码与界面展示展开清晰演示了QT多媒体模块、网络通信和多线程协同工作的完整链路。压缩包内共有1361个文件整体约202.5MB除了h/cpp源文件、ui界面设计和pro工程配置外还包含dll动态库、lib依赖库及VLC播放组件同时带有png/ico界面资源与编译中间文件能够直接导入工程查看完整结构。项目已融入自定义封装模块和播放器主界面可以学习视频渲染控件使用、后台解码线程设计以及播放控制逻辑同时包含资源与辅助文件便于了解工程整体运作也方便在此基础上进行功能扩展和二次开发。目前已有455人学习下载是一份兼顾源码阅读与实战参考的QT监控项目资料。 这两年开源项目做了不少但真正让我觉得“有点意思”的还是手头这个用 QT 写的监控视频项目。起因其实挺简单早年做安防相关的边缘设备时经常被客户吐槽客户端难用、界面卡顿、又没法快速集成到现有系统里。后来我索性自己动手用 QT 把这套东西从头写了一遍从视频采集、实时预览到回放存储全部做成模块化再开源出去。没想到 GitHub 上陆续有人 star、提 issue还有人把它接进了自己的门店监控汇总系统里。今天就把这个项目从思路到落地拆开揉碎了讲一遍。我会把所有选型理由、核心实现、发布打包和常见坑都写出来尽量做到源码之外还有一份“人话版图纸”。不管你是刚接触 QT 的桌面端开发者还是想给监控视频项目加行为分析、音频频谱展示这类进阶功能这篇内容都能帮上忙。1. 项目整体设计与思路拆解1.1 为什么选 QT 而不是 Web 或 Python 方案做监控视频项目第一反应可能是 Python OpenCV或者 Web 播放器套壳。我也走过这两条路但最终都换成了 QT原因有三个。首先是线程模型和 I/O 密集场景的匹配度。监控项目不是只弹一个视频窗口那么简单通常要同时跑多路 RTSP 拉流、解码、显示、录像写入、移动侦测。Python 虽然有 GIL 之外的 multiprocessing但进程间通信的复杂度会拖慢开发而 QT 的 QThread、信号槽、事件循环天生就是为这种“并发 UI 交互”设计的多个网络流线程 解码线程 UI 主线程之间靠信号槽传递帧数据非常顺手。其次是跨平台发布的一致性。QT 的 widget 和 qml 在 Windows、Linux、macOS 上表现几乎一致这点对做设备端工具、门店客户端特别重要。我在 Ubuntu 上调试好的界面放到一台 Windows 工控机上只需要重新编译一遍就能跑字体、布局、缩放基本不用专门调。最后是控件生态足够接近桌面原生应用。看视频时附带画时间轴、截图列表、CPU 占用曲线这类辅助面板QT 的 QCustomPlot、QTableView、QChart 都能直接嵌入不用像 Web 那样绕一层 websocket 去操作 DOM。1.2 监控类项目里的通用难点这类项目普遍有四个难点解码性能、显示延迟、录像写入、异常恢复。其中延迟问题最容易被忽略它不只是解码慢导致的更多时候是帧在缓冲区里排队积压。我用生产者-消费者队列来处理拉流线程只管把原始数据包塞进队列解码线程按固定帧率取如果队列长度超过预设阈值就丢弃旧帧只保留最新一帧。这样在低性能设备上画面虽然会掉帧但不会越拖越慢保证“实时性优先”。架构上我采用了标准的分层方式采集层封装 RTSP 拉流、本地文件读取、USB 摄像头。处理层解码、格式转换、行为检测扩展、音频频谱分析。存储层按小时写 MP4 片段建立索引文件供回放检索。展示层主窗口、多画面分屏、告警面板、图表分析。每一层只通过接口对外通信。比如采集层换个摄像头品牌处理层完全不受影响替换成海康威视的 RTSP 地址时只需要改一下 URL 拼接规则x.x.x.x:554/Streaming/Channels/101 这种格式在项目里预留了配置文件入口。2. 核心细节解析与实操要点2.1 视频帧格式转换与高性能显示QT 默认的 QImage 支持的像素格式有限而 FFmpeg 解码出来的通常是 YUV420P。如果直接转成 QImage::Format_RGB888性能损失很大。实测在 4 路 1080p 同时预览时QImage 格式转换会占到 30% 以上的 CPU。我的做法是用显卡渲染将 YUV420P 上传到纹理再通过 shader 做色彩空间转换。具体路径是解码得到 AVFrame 后用 QOpenGLTexture 分别创建 Y、U、V 三个纹理在 fragment shader 里完成 BT.601 或 BT.709 到 RGB 的换算。这样 CPU 端只负责 memcpyGPU 负责上屏16 路 720p 预览时主线程占用依然很低。如果你不想碰 OpenGL也可以退一步使用QVideoFrame::convertToImage但只对单路预览时启用多路场景强制走 shader。这个开关我放在配置项里默认 auto根据当前预览路数自动切换。2.2 自定义绘图与音频分析联动项目热搜里很多人搜“qt qcustomplot 时域图转频域图”我在这套监控项目里也集成了这个模块不过不是用于视频帧而是用于音频频谱展示和设备震动分析。监控摄像头通常带音频输入调试机房噪音或者远程判断设备故障时频谱图非常直观。时域转频域我用了 kissfft因为它是纯 C 语言实现无依赖、可静态编译非常适合嵌入到 QT 项目。流程是拿到 PCM 数据后加汉宁窗再传给 kissfft 做前向变换取模的平方得到功率谱最后映射到 QCustomPlot 的 QCPGraph 上。实际开发中有一个重要教训QCustomPlot 不适合高频刷新全量数据。如果每 30ms 重新设置一次整个曲线的数据点在低配电脑上会产生明显卡顿因为每条曲线几万个 QCPData 的 append 加 replot 太耗时。我的优化思路是减少绘图数据量只显示峰值包络并且降低采样点数。例如 4096 个 FFT bin 先聚合成 256 个频段每个频段取最大值这样绘图数据结构小得多曲线形状也不会失真太多。2.3 人员行为检测与多模态扩展监控视频项目当前最火的诉求就是“人员行为检测”。我在项目里预留了检测模块的扩展点检测器抽象成IDetector接口核心方法只有两个init(model_path)和detect(frame, timestamp)。目前内置了一个基于 YOLOv5 的检测器推理后端用 ONNX Runtime在 CPU 上每秒能跑 20 帧左右识别目标类型包括人员、车辆、跌倒姿态。多模态这块我做得比较简单但也验证可行视频帧走视觉模型音频帧和视频流时间戳对齐后走一个轻量的音频分类器比如识别呼喊声、玻璃破碎声。最后两个检测器的结果汇入一条告警消息附带上截图和前后 5 秒的音频片段。这套方案比单纯视觉要实用得多夜间摄像头图像质量差时音频告警往往还能兜底。3. 实操过程与核心环节实现3.1 环境搭建与第三方库选择我开发时用的是 Qt 5.15.2 MSVC2019 64bit第三方库只依赖 FFmpeg 和 OpenCV。编译 FFmpeg 时只需要 libavformat、libavcodec、libavutil、libswscale 四个库不需要 libavdevice因为拉流走的是 RTSP over TCP属于网络输入。OpenCV 主要用于行为检测模块的前处理和后处理如缩放、归一化、非极大值抑制。如果你用国内镜像下载 Qt 源码包通常会更快但我建议尽量用在线安装器选装需要的模块。安装完记得把bin目录加到环境变量里否则后续用windeployqt打包时偶尔会找不到libEGL.dll一类的文件。3.2 视频采集模块代码结构下面是一段简化的拉流线程代码展示如何以低延迟方式从 FFmpeg 读取帧void RtspStreamThread::run() { avformat_network_init(); AVFormatContext* fmtCtx avformat_alloc_context(); AVDictionary* opts nullptr; av_dict_set(opts, rtsp_transport, tcp, 0); av_dict_set(opts, stimeout, 5000000, 0); if (avformat_open_input(fmtCtx, url_.c_str(), nullptr, opts) ! 0) { emit errorOccurred(cannot open stream); return; } if (avformat_find_stream_info(fmtCtx, nullptr) 0) { emit errorOccurred(cannot find stream info); return; } int videoIdx av_find_best_stream(fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); AVCodecParameters* codecParams fmtCtx-streams[videoIdx]-codecpar; const AVCodec* decoder avcodec_find_decoder(codecParams-codec_id); AVCodecContext* codecCtx avcodec_alloc_context3(decoder); avcodec_parameters_to_context(codecCtx, codecParams); avcodec_open2(codecCtx, decoder, nullptr); AVPacket packet; while (!stopRequested_) { if (av_read_frame(fmtCtx, packet) 0) { // 网络中断或EOF重连策略 break; } if (packet.stream_index videoIdx) { avcodec_send_packet(codecCtx, packet); AVFrame* frame av_frame_alloc(); if (avcodec_receive_frame(codecCtx, frame) 0) { emit frameReady(frame); } } av_packet_unref(packet); } // cleanup... }核心是把rtsp_transport设为 tcp避免 UDP 丢包造成的画面花屏。stimeout设为 5 秒摄像头掉线后不用等太久就能回到重连逻辑。3.3 UI 与后端线程的可靠通信解码线程不能直接操作任何 QWidget 控件我的做法是自绘控件接收新帧信号class VideoWidget : public QOpenGLWidget { Q_OBJECT public slots: void setFrame(const QVideoFrame frame) { frame_ frame; update(); } protected: void paintGL() override { // 绑定纹理并绘制 renderFrame(frame_); } }; // 连接方式 connect(decoderThread, DecoderThread::frameReady, videoWidget, VideoWidget::setFrame, Qt::QueuedConnection);注意这里必须用Qt::QueuedConnection因为发送方和接收方不在同一个线程。如果默认的 AutoConnection 在解码线程里执行的上下文触发信号接收槽在接收线程执行时如果发送方线程还在继续循环发下一帧会导致 UI 线程事件堆积。更稳妥的做法是在setFrame里先做一次“帧去重”只保留当前最新的待处理帧丢弃缓冲区里的旧帧。3.4 发布打包与部署发布 QT 桌面项目最容易踩的坑就是运行时 dll 不全。在 Windows 上我用windeployqt并附加 FFmpeg、OpenCV 的 dll 和插件目录。打包命令一般不复杂但它生成的 plugins 目录里如果少了 platforms就会出现典型的报错this application failed to start because no Qt platform plugin could be initialized。原因就是qwindows.dll没被正确部署或者程序和工作目录不对导致 QT 找不到 plugins。为了保证不丢文件我写了一个简单的发布脚本先构建 release 版本执行windeployqt --release --no-translations app.exe再把 FFmpeg 的 dll 手动复制到 exe 同目录最后把 OpenCV 的opencv_worlddll 复制进去。检查用 depends 工具或直接双击测试最好在不同磁盘目录下跑一次排除当前目录依赖。Linux 下相对省心用linuxdeployqt或手动把 so 文件放好就行。在 ubuntu 20.04 交叉编译时要注意 QT 版本和系统 GLIBC 版本是否匹配我遇到过一次在麒麟 x86 系统上部署时缺少 GLIBC_2.29 的兼容问题解决办法是回到内置 5.15.2 版本或换用更低版本要求的编译链。4. 常见问题与排查技巧实录4.1 视频预览卡顿和延迟过大现象是画面能显示但延迟可能超过 3 秒。这种问题多发于使用了 TCP 拉流但缓冲队列太深。排查时我先打印avformat_find_stream_info后得到的 delay 字段并且检查解码线程里队列里的积压帧数。解决方法是把队列最大值设为 3超过则清空并拉最新帧。实时预览的延迟体验会从 3-5 秒降到 0.5 秒以内。还有一个常见原因是显示控件用了QPainter::drawImage绘制 QImage。每次都要把解码后的帧转 RGB再把数据拷贝到内存绘图设备CPU 高是必然的。换成 OpenGL 纹理渲染后1080p 单路预览 CPU 占用从 35% 降到 11%差异非常明显。4.2 QCustomPlot 频域图刷新卡顿这个问题我一开始也没太在意直到在 i5-7200U 的设备上测试音频频谱一开整机界面就开始掉帧。定位后原因就是 QCustomPlot 每次加一次数据点都会触发内部范围变化并且重画所有内容。优化后我把数据刷新频率降到 15Hz只更新可见区间内的点并把 QCP::aeFast 作图模式打开。卡顿基本消除CPU 占用还低了 10%。4.3 RTSP 断线重连与时间戳同步项目里用了比较稳定的重连机制单个拉流线程负责断线重连重连次数限制和退避策略用配置文件控制。同时给每帧打上收到时的系统时间戳而不是只依赖 RTSP 流里的 PTS这样回放和告警片段的时间轴才一致。有个细节是多路摄像头如果镜头角度不同音频延迟又可能不同。我目前实现的是“事件级”多模态同步视觉和音频各自输出事件再由聚合器通过时间窗口合并并不是按帧做精确对齐这对于行为告警场景已经够用。4.4 高分屏与界面缩放的适配在 2K、4K 显示器上QT 界面容易出现字体模糊或窗口比例失调。核心处理方法是设置平台属性QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);但这只解决了图标模糊。如果界面有固定大小的控件需要在main()里根据逻辑分辨率动态调整字体与布局比例。针对不同摄像头预览路数我专门做了三种布局方案单画面、四分屏、九分屏。每种方案都用QSplitter和QGridLayout适配避免 UI 从 1920 缩放到 1366 时出现按钮溢出。5. 开源发布与后续协作思路5.1 源码目录与文档准备开源前我梳理了源码结构确保别人 clone 后能快速上手。最终目录是这样的project/ ├─ include/ # 公共头文件与接口定义 ├─ src/ # 核心逻辑 │ ├─ stream/ # 拉流解码线程 │ ├─ detect/ # 行为检测模块 │ ├─ display/ # 自绘摄像头控件 │ └─ storage/ # 录像与索引 ├─ ui/ # widget 与 qml 界面定义 ├─ tools/ # 打包发布脚本 ├─ docs/ # 编译说明和架构文档 └─ CMakeLists.txtREADME 里除了写在 Windows/Linux 下如何编译、需要什么版本的依赖还放一条命令直接拉取 FFmpeg 和 OpenCV 并设置好环境变量。这比让读者自己下载 SDK 再配置省事很多也是开源项目能收到 issue 数量少的直接原因之一。另外许可证我选了 GPL-3.0因为本项目依赖的部分组件是 GPL 的。许可证选择我通常建议如果是纯自己学习用 MIT 或 Apache-2.0 简单如果用了 GPL 依赖或希望后续修改也开源那就 GPL-3.0。不要自己发明许可证也不要混用多个许可证容易把下游使用者搞懵。5.2 监控视频行为分析的扩展方向这套项目已经具备从“监控视频查看”升级为“安全监控人员行为分析”的基础条件。开源之后我认为最值得沿着两个方向深化一是接入更精细的多模态行为识别模型例如时空图卷积网络做骨架序列行为识别二是做多门店/多摄像头的集中管理用代理服务汇总各门店边缘端的告警事件总部平台只查看事件和录像片段。与云服务打通时可以考虑把告警截图和短视频片段通过 HTTP POST 上报到服务端在服务端再做一次人工审核或二次识别。这部分不需要在 QT 端做强体验只需要提供标准的上传接口并把本地缓存目录做好轮转。5.3 我自己在维护这个项目时的体会维护一个开源项目和写一个自己用的工具是完全不同的两件事。你会发现别人对你的代码有各种奇奇怪怪的使用方式其中 60% 的需求都可以靠文档解决30% 可以通过合理的接口设计避免真正值得改进的只有 10%。我现在新增功能前会先问自己“这个需求是否有普适性”比如有人希望支持 GB28181 国标接入这个我犹豫了很久因为工作量太大但最终决定放在 roadmap 里因为确实有相当一部分用户在私有化部署时需要对接平台。而像“把 UI 换成纯黑色涂装”这种需求通常直接礼貌拒绝不会占用主线时间。最后再分享一个小技巧做监控视频项目时不要在一开始就追求功能大而全。先把一路 RTSP 从拉流到显示跑通再考虑多路、存储、检测和音频分析。项目每加一层能力就用自带的性能监测面板看一次 CPU、内存和延迟。这样你在开源后收到的反馈才更多是功能建议而不是性能投诉。本文还有配套的精品资源点击获取