简介:这份源码面向在 Linux 平台从事安防监控开发、系统集成与嵌入式视频应用的 C++ 开发者,提供基于 Qt 接入并播放海康摄像头视频流的完整示例工程,可用于理解网络取流、解码显示与界面交互的实现思路。压缩包共 220 个文件,约 1.2MB,以 62 个 h 头文件与 58 个 cpp 源文件为主体,另有 51 个 ui 界面文件及 png、bmp 等图标素材,并包含 pro、pri 工程配置与 qrc 资源文件,目录按登录、实时预览、录像回放、通道参数、远程参数等模块组织,便于按功能定位代码。已有 1153 人学习下载。读者可从中获得 RTSP 网络通信、Qt 多媒体播放与视频显示、多线程处理及错误日志等关键环节的参考实现,适合具备 C++ 与 Qt 基础、希望快速搭建监控客户端原型或进行二次开发的技术人员对照研读。
1. 拿到一份 Linux 下的监控视频 QT demo 源码,先别急着编译
很多人第一次接触 Linux 下的监控视频开发,是从一份能跑起来的 QT demo 源码开始的。它解决的不是“从零写一个播放器”的问题,而是把监控视频这条链路上最容易翻车的几段——设备取流、解码、帧转换、界面渲染——先串成一个能看见画面的最小闭环。适合谁?适合要在 Linux 桌面上做视频预览、想搞清楚监控 SDK 和 QT 怎么对接、又不想一上来就啃几百页接口文档的从业者。这份源码的价值不在代码量,而在于它把“能出画面”这件事的路径固定下来了,你照着改参数、换通道、调分辨率,就能验证自己的环境到底卡在哪一环。下面按“它是什么、怎么跑起来、坑在哪、怎么改”的顺序拆。
2. 拆开这份源码:模块划分与取流解码链路
监控视频 demo 看着简单,实际是一条从网络到像素的流水线。先把这条链路在脑子里立住,后面编译报错、黑屏、卡顿才知道往哪查。常见做法是把整份工程分成四层:设备接入层负责登录设备和拉流,解码层把码流还原成图像帧,转换层把 SDK 输出的原始格式转成 QT 能吃的 QImage,界面层用 QLabel 或自绘控件把帧刷上去。这份源码基本就是按这个思路组织的,只是每层实现得比较“最小可用”。
2.1 设备接入与取流:登录、通道、码流三件事
监控设备接入的核心参数就三个:设备地址、端口、通道号。demo 里一般会有一个配置结构体或 ini 文件,把 IP、端口、用户名、密码、通道号放进去。取流前必须先登录拿到会话句柄,再按通道启动预览或拉流。这里最容易忽略的是码流类型:主码流分辨率高、码率高,子码流轻量,预览窗口小的时候用子码流更稳。源码里如果写死了主码流,你在低配机器上跑就会觉得“怎么这么卡”,其实不是代码问题,是码流选错了。
登录和取流的调用顺序不能乱,常见写法是:
// 设备登录,拿到会话句柄 LONG lUserID = NET_DVR_Login_V40(&struLoginInfo, &struDeviceInfo); if (lUserID < 0) { // 登录失败,打印错误码,别只写一句“登录失败” printf("login failed, err=%d\n", NET_DVR_GetLastError()); return -1; } // 按通道启动预览,回调里拿码流 NET_DVR_PREVIEWINFO struPreviewInfo = {0}; struPreviewInfo.lChannel = 1; // 通道号,从 1 开始 struPreviewInfo.dwStreamType = 1; // 0 主码流,1 子码流 struPreviewInfo.dwLinkMode = 0; // 0 TCP,网络差可考虑 UDP struPreviewInfo.hPlayWnd = NULL; // 回调模式传 NULL LONG lRealHandle = NET_DVR_RealPlay_V40(lUserID, &struPreviewInfo, RealDataCallBack, NULL);这段代码的逻辑是:先登录换会话,再用会话去开预览,码流通过回调函数RealDataCallBack吐出来。参数上,dwStreamType决定清晰度和带宽的取舍,dwLinkMode决定传输方式,hPlayWnd传 NULL 表示不走 SDK 自绘窗口、改由回调自己处理——这一点很关键,因为你要把画面塞进 QT 控件,就必须走回调模式,让 SDK 把码流交给你而不是自己弹窗。错误码一定要打出来,监控 SDK 的失败原因全在NET_DVR_GetLastError()里,不打错误码排查基本靠猜。
2.2 解码与帧转换:从码流到 QImage 的关键一步
回调拿到的是压缩码流,不能直接显示,得先喂给解码器。demo 里通常用 SDK 自带的解码接口,或者接 FFmpeg。用 SDK 解码的好处是格式匹配、依赖少;用 FFmpeg 的好处是通用性强、能处理更多编码格式。源码如果用的是 SDK 解码,流程是:回调里把码流送进解码通道,解码器吐出 YUV 或 RGB 数据,再根据宽高和像素格式构造 QImage。
帧转换这段是黑屏和花屏的高发区。核心是像素格式对齐:解码出来常见是 YUV420P 或 NV12,QT 的 QImage 支持 Format_RGB32、Format_ARGB32 等,中间必须做一次色彩空间转换。如果源码里直接拿 YUV 数据往 QImage 里塞,画面一定是绿的或者花的。转换时还要注意行对齐,很多解码输出的每行字节数是按 4 或 16 对齐的,宽不是对齐倍数时会出现斜纹。
// 假设解码输出为 RGB24,宽 w、高 h QImage img(w, h, QImage::Format_RGB888); // 逐行拷贝,避免 stride 不一致导致错位 for (int y = 0; y < h; ++y) { memcpy(img.scanLine(y), src + y * stride, w * 3); } // 跨线程刷新必须走信号槽,不能直接操作控件 emit frameReady(img);逻辑说明:逐行拷贝而不是一次性 memcpy,是因为解码缓冲的每行跨度stride往往大于w * 3,整块拷贝会把行尾填充字节也带进来,画面就歪了。emit frameReady(img)是把图像交给 UI 线程,QT 控件只能在主线程碰,回调线程直接setPixmap是典型的翻车点。参数上,Format_RGB888要和实际解码输出一致,不一致就换格式或加转换,别硬塞。
2.3 QT 界面层:刷新节奏与控件选择
界面层决定观感。demo 一般用 QLabel 显示 QImage,简单直接;帧率高的时候 QLabel 的setPixmap会有明显开销,可以换成自绘 QWidget 重写paintEvent,或者用 QOpenGLWidget 把转换放到 GPU。刷新节奏也要控:解码出帧就刷,遇到网络抖动会忽快忽慢;常见做法是加一个定时器或帧队列,按固定间隔取最新帧显示,丢掉积压的旧帧。监控预览要的是“实时”,不是“每一帧都不丢”,积压帧只会让画面越来越延迟。这一层不用改太多,但要知道瓶颈可能在这,别一出问题就怀疑取流。
3. 在 Linux 上把 demo 跑起来:依赖、编译与首帧验证
源码能不能用,第一步是能不能编过、能不能出第一帧。Linux 下这类工程通常用 qmake 或 CMake,依赖 QT 开发包和监控 SDK 的 so 库。别一上来就改代码,先把环境对齐,编译通过再谈功能。
3.1 依赖清单与编译命令
先确认 QT 版本和 SDK 库的位数一致,32 位库配 64 位 QT 是编不过的。常见依赖包括 qtbase5-dev、对应版本的 SDK 动态库、以及解码库。编译前把 SDK 的 include 和 lib 路径写进工程文件。
# 以 qmake 工程为例,先看依赖是否齐全 qmake -v ldd /path/to/sdk/lib/*.so | grep "not found" # 编译 qmake MonitorDemo.pro make -j$(nproc)ldd那一步是提前排雷:SDK 的 so 如果缺依赖,运行时会直接报“cannot open shared object file”,编译期却看不出来。make -j用满核数加快编译,出错时先看第一条 error,后面的往往是连锁反应。工程文件里LIBS += -L$$PWD/sdk/lib -lHCCore -lPlayCtrl这类写法,路径要用绝对路径或$$PWD相对工程文件,别用相对当前终端的路径,否则换个目录就编不过。
3.2 运行期库路径与首帧验证
编过不等于跑得起来。Linux 下动态库搜索路径是运行期的事,常见做法是设LD_LIBRARY_PATH或在/etc/ld.so.conf.d/里登记。临时验证用前者就够。
export LD_LIBRARY_PATH=/path/to/sdk/lib:$LD_LIBRARY_PATH ./MonitorDemo跑起来后先看日志里登录是否成功、预览句柄是否有效,再看窗口有没有画面。首帧验证有个笨但有效的办法:把回调里收到的码流字节数打印出来,字节数在涨说明取流通了,画面不出就是解码或转换的问题;字节数不涨,问题在登录、通道或网络。这个二分法能省掉大量瞎猜。参数上,通道号从 1 开始不是从 0,填 0 在很多设备上直接失败;端口默认常见 8000,改过端口的设备要同步改配置。
4. 避坑与排查:黑屏、花屏、卡顿的定位顺序
这类 demo 的坑高度集中,按现象分三类,每类都有固定的排查顺序。下面几条是血泪经验里最常撞的,照着顺序查比乱改快得多。
4.1 现象:程序跑起来但窗口全黑
原因通常有三层:登录没成功、通道号或码流类型不对、回调没触发。解决顺序是先看登录返回值和错误码,再看预览句柄是否大于等于 0,最后在回调里打日志确认有没有数据进来。如果登录成功、句柄有效、回调也有数据,那黑屏就在解码或 UI 刷新,往下一节查。别一黑屏就去改渲染代码,方向错了白费功夫。
4.2 现象:有画面但颜色发绿、发花或斜纹
原因是像素格式不匹配或行跨度没对齐。解决:确认解码输出到底是 YUV420P、NV12 还是 RGB,QImage 的 Format 必须和转换后的格式一致;逐行拷贝时用解码器给的实际 stride,不要用width * 每像素字节硬算。斜纹基本都是 stride 问题,颜色错基本是色彩空间没转。
4.3 现象:画面能动但延迟越来越大、越跑越卡
原因是帧积压:解码出帧的速度大于 UI 刷新速度,旧帧堆在队列里。解决:显示端只取最新帧,队列满了丢旧帧;或者用定时器按固定帧率取帧。监控预览追求低延迟,丢帧比积压正确。另一个隐藏原因是主码流分辨率过高,换子码流立竿见影。
4.4 现象:编译通过但运行报找不到 so
原因是运行期库路径没配,或 SDK 库位数与系统/QT 不一致。解决:ldd逐个查依赖,缺的补上;位数不一致只能换对应版本的库,别想着混用。用file命令看 so 是 32 位还是 64 位,比猜快。
4.5 现象:多通道同时预览时崩溃或花屏
原因是多个回调线程共享了解码器或缓冲区,没做隔离。解决:每个通道独立解码上下文和缓冲,回调里加锁的范围尽量小,UI 刷新统一走信号槽排队。共享一个全局 QImage 是典型误用,多线程写同一块内存必花。
5. 二次开发:把 demo 改成能用的预览工具
demo 能出画面只是起点,真正落地要改成能配参数、能切通道、能长时间稳定跑的工具。这一步的改动量不大,但每处都对应一个实际需求。
5.1 配置外置与通道切换
把 IP、端口、账号、通道号、码流类型从代码里挪到 ini 或 json,启动时读、界面上能改。通道切换的正确做法是先停旧预览、释放句柄,再按新通道重新开,直接改通道号不重开是无效的。停预览要调对应的停止接口并释放句柄,只置空变量会泄漏会话,跑久了设备端会话数满就登不上了。
// 切换通道:先停后开,句柄必须释放 if (lRealHandle >= 0) { NET_DVR_StopRealPlay(lRealHandle); lRealHandle = -1; } // 再按新通道号重新 RealPlay逻辑上这是“资源先还再借”,参数上注意停止和释放是两步,很多 SDK 停止预览不等于释放会话,登出设备才算彻底回收。长时间运行的工具还要加断线重连:回调长时间无数据就触发重登,别指望网络永远稳。
5.2 稳定性验证:跑够时间才算数
验证一个监控预览工具,别只看打开那几秒。至少连续跑几小时,观察内存是否持续上涨、会话是否泄漏、断网恢复后能否自动重连。内存涨多半是每帧 new 了 QImage 没释放或缓冲没复用;会话泄漏看设备端在线用户数;重连失败看登出和重登的顺序有没有配对。我一般会挂一晚上再看,白天测出来的“稳定”经常是假象。从那以后我每次改完取流或解码相关代码,都强制让它空跑一夜再交付,希望帮到你。
本文还有配套的精品资源,点击获取