首页 / 资讯中心 / 文章详情

Linux下海康监控视频QT demo:从编译到二次开发全解析

Linux下海康监控视频QT demo:从编译到二次开发全解析 ★ FEATURED ARTICLE
简介这份源码面向在 Linux 平台从事视频监控应用开发的 C 开发者与系统集成人员提供基于 Qt 框架接入并播放海康摄像头视频流的完整示例工程可用于学习网络取流、解码显示与界面交互的实现思路。压缩包共 220 个文件约 1.2MB以 62 个 h 头文件、58 个 cpp 源文件和 51 个 ui 界面文件为主体另有 png、bmp 等界面素材及 pri、pro 工程配置覆盖登录、实时预览、录像回放、通道参数与远程参数配置等模块。已有 1153 人学习下载说明其在同类需求中具备一定参考价值。读者可从中了解 RTSP 取流、QMultimedia 播放、多线程处理与错误日志等关键环节的组织方式适合具备 C 与 Qt 基础、希望快速搭建监控客户端原型的开发者对照研读。1. 拿到一份 Linux 下的监控视频 QT demo先别急着编译前阵子帮朋友看一个现场问题一台工控机上跑着 Linux接了两路网络摄像头界面用 QT 写的视频窗口偶尔黑屏重启程序又好了。他手里只有一份源码包标题写着「Linux下海康监控视频QTdemo源码」没有 README没有编译脚本说明解压出来一堆 .pro、.cpp、.h。这种包在安防集成、边缘设备、NVR 客户端二次开发里很常见——它不是一个成品软件而是一个能跑通「登录设备 → 取流 → 解码 → 上屏」最小闭环的工程骨架。适合谁适合要在 Linux 上做监控预览界面、又不想从零啃 SDK 文档的开发者也适合想搞清楚 QT 里视频渲染线程怎么和 SDK 回调配合的人。下面按「这项目长什么样 → 怎么跑起来 → 核心链路怎么写的 → 怎么改」的顺序拆。2. 工程结构与技术选型一个 QT 客户端是怎么把 SDK 包进来的2.1 目录骨架与模块划分这类 demo 的目录通常不会太深解压后大致是这样一个形态不同版本命名会有出入但职责划分基本一致HikQtDemo/ ├── HikQtDemo.pro # qmake 工程入口 ├── main.cpp # QApplication 启动 ├── mainwindow.h/.cpp/.ui # 主窗口视频显示 设备列表 ├── hikdevice.h/.cpp # SDK 封装层登录、预览、登出 ├── videorender.h/.cpp # 渲染层把 YUV/裸流画到 QWidget 上 ├── include/ # SDK 头文件 │ ├── HCNetSDK.h │ └── PlayM4.h ├── lib/ # SDK 动态库 │ ├── libhcnetsdk.so │ ├── libPlayCtrl.so │ └── HCNetSDKCom/ # 组件库目录运行时必须存在 └── res/ # 图标、样式关键点在于hikdevice和videorender这两层的分离。hikdevice只负责和 SDK 打交道不碰任何 QT 控件videorender只负责把一帧图像画出来不知道数据从哪来。中间靠回调函数和信号槽连接。这种分层不是洁癖是血泪经验——SDK 的回调线程不是 QT 主线程直接在回调里操作 QWidget 会崩必须走跨线程信号。2.2 技术栈与依赖关系组件作用备注QT 5.xWidgets界面、事件循环、信号槽用 qmake 构建不依赖 QtQuick设备 SDKHCNetSDK登录、实时预览、云台控制提供 .so 与头文件播放库PlayCtrl/PlayM4解码、YUV 回调与 SDK 配套版本需匹配Linux 系统库pthread、dlopenSDK 内部依赖链接时注意选 QT Widgets 而不是 QML是因为监控预览界面元素少、对刷新时序要求硬Widgets 的paintEvent配合QImage更可控。构建用 qmake 而非 CMake是这类老工程的惯性——.pro文件里直接写LIBS -L$$PWD/lib -lhcnetsdk改路径方便。2.3 .pro 文件里必须写对的三处# HikQtDemo.pro QT core gui widgets TARGET HikQtDemo TEMPLATE app # 1. 头文件搜索路径 INCLUDEPATH $$PWD/include # 2. 链接 SDK 动态库注意顺序被依赖的放后面 LIBS -L$$PWD/lib -lPlayCtrl -lhcnetsdk LIBS -lpthread -ldl # 3. 运行时库路径避免每次手动设 LD_LIBRARY_PATH QMAKE_RPATHDIR $$PWD/libQMAKE_RPATHDIR这一行很多人会漏。漏了的后果是编译通过、一运行就报error while loading shared libraries: libhcnetsdk.so: cannot open shared object file。加上它可执行文件里会写入 rpath运行时自动去lib/找库。-ldl也不能省SDK 内部用dlopen加载HCNetSDKCom下的组件缺了它链接阶段就过不去。3. 从零跑通环境准备、编译与第一路画面3.1 环境准备与 SDK 库的摆放先确认系统里有 QT 开发环境。以常见的发行版为例装qtbase5-dev、qttools5-dev-tools和build-essential即可。然后处理 SDK把厂商给的 Linux 64 位库整体拷进lib/注意HCNetSDKCom这个子目录要原样保留它是组件库SDK 初始化时会按相对路径去加载少一个文件就会在NET_DVR_Init之后返回错误码。# 检查动态库依赖是否齐全 ldd lib/libhcnetsdk.so | grep not found # 给可执行权限部分压缩包会丢权限 chmod x lib/*.soldd输出里如果出现not found说明系统缺基础库按提示装对应的libxxx包。这一步别跳过很多人编译过了却在运行阶段卡住就是库依赖没查。3.2 编译与首次运行qmake HikQtDemo.pro make -j$(nproc) ./HikQtDemo首次运行大概率是空界面因为设备 IP、端口、账号密码还写在代码里。找到hikdevice.cpp里的登录参数改成现场设备的真实值// hikdevice.cpp 片段 NET_DVR_USER_LOGIN_INFO loginInfo {0}; NET_DVR_DEVICEINFO_V40 devInfo {0}; strcpy(loginInfo.sDeviceAddress, 192.168.1.64); // 设备 IP loginInfo.wPort 8000; // SDK 端口非网页端口 strcpy(loginInfo.sUserName, admin); strcpy(loginInfo.sPassword, 设备密码); loginInfo.bUseAsynLogin 0; // 同步登录便于拿返回值 LONG lUserID NET_DVR_Login_V40(loginInfo, devInfo); if (lUserID 0) { qDebug() login failed, err NET_DVR_GetLastError(); }wPort填的是 SDK 服务端口默认 8000不是浏览器访问的 80 端口这是新手最常填错的一个参数。bUseAsynLogin设 0 走同步登录结果立刻能判断调试阶段建议保持同步正式产品再考虑异步避免界面卡顿。登录失败时NET_DVR_GetLastError()的返回值要会查用户名密码错、IP 不通、端口错错误码各不相同别一律当成「设备坏了」。3.3 启动预览与渲染链路登录成功后调NET_DVR_RealPlay_V40发起预览把播放句柄交给播放库解码解码回调里拿到 YUV 数据再转成QImage投递到界面线程。// 预览启动 NET_DVR_PREVIEWINFO preview {0}; preview.lChannel 1; // 通道号从 1 开始 preview.dwStreamType 0; // 0 主码流1 子码流 preview.dwLinkMode 0; // TCP preview.hPlayWnd NULL; // 不用 SDK 自带窗口自己渲染 LONG lRealHandle NET_DVR_RealPlay_V40(lUserID, preview, NULL, NULL);hPlayWnd传 NULL 是关键决策让 SDK 不创建自己的窗口数据走回调由 QT 自己画。这样界面风格统一也方便做多画面拼接。代价是要自己处理解码和格式转换代码量比直接给窗口句柄多但可控性高得多。4. 核心链路拆解回调、解码与跨线程上屏4.1 解码回调到 QImage 的转换播放库解码后通过回调吐出 YUV420P 数据需要转成 RGB 才能给QImage。demo 里通常用一个转换函数// videorender.cpp QImage yuv420pToImage(const unsigned char* yuv, int w, int h) { QImage img(w, h, QImage::Format_RGB888); for (int y 0; y h; y) { for (int x 0; x w; x) { int Y yuv[y * w x]; int U yuv[w * h (y / 2) * (w / 2) (x / 2)] - 128; int V yuv[w * h * 5 / 4 (y / 2) * (w / 2) (x / 2)] - 128; int R Y 1.402 * V; int G Y - 0.344 * U - 0.714 * V; int B Y 1.772 * U; img.setPixel(x, y, qRgb(qBound(0,R,255), qBound(0,G,255), qBound(0,B,255))); } } return img; }这段是纯 CPU 逐像素转换1080P 下单帧就要几十毫秒多路直接卡死。实际项目里我一般不会这么写而是用sws_scaleFFmpeg或播放库自带的PlayM4_ConvertToBmpFile类接口做硬件/汇编加速。demo 保留这个写法是为了逻辑清晰、不引入额外依赖读代码时知道它是性能瓶颈就行别照抄进生产。4.2 回调线程与界面线程的隔离SDK 的解码回调运行在它自己的线程里直接update()或操作控件会触发 QT 的线程断言。正确做法是把图像通过信号槽投递// videorender.h class VideoRender : public QWidget { Q_OBJECT public slots: void onFrameReady(QImage img); // 在界面线程执行 protected: void paintEvent(QPaintEvent*) override; private: QImage m_frame; QMutex m_mutex; }; // 回调线程里 void decodeCallback(...) { QImage img yuv420pToImage(...); emit frameReady(img); // 跨线程自动排队安全 }emit一个带QImage的信号QT 会自动做队列连接前提是连接类型为Qt::QueuedConnection或跨线程自动判定。QImage是隐式共享的拷贝成本低适合这样传。paintEvent里加锁读m_frame再drawImage避免回调写、界面读的竞争。4.3 资源释放顺序不能乱退出时的顺序错了会崩在析构里这是监控 demo 最常见的翻车点// 正确顺序先停预览再登出最后清理 SDK if (lRealHandle 0) { NET_DVR_StopRealPlay(lRealHandle); lRealHandle -1; } if (lUserID 0) { NET_DVR_Logout(lUserID); lUserID -1; } NET_DVR_Cleanup();必须先StopRealPlay再Logout反过来的话 SDK 内部还在往回调里送数据而播放句柄已经失效轻则报错重则段错误。NET_DVR_Cleanup放在最后且整个进程只调一次。把这些收进一个releaseAll()函数窗口closeEvent和析构都调它用标志位防重入。5. 避坑与排查现场最容易翻车的五个点5.1 现象程序启动即报找不到 .so原因lib/没进运行时搜索路径或HCNetSDKCom子目录被漏拷。解决确认.pro里有QMAKE_RPATHDIR用ldd逐个查依赖HCNetSDKCom必须和主库同级存在。临时验证可以export LD_LIBRARY_PATH$PWD/lib:$LD_LIBRARY_PATH再跑。5.2 现象登录返回 -1错误码提示密码错但密码是对的原因密码里含特殊字符strcpy后被截断或设备开了锁定策略。解决确认密码长度不超 SDK 限制连续失败后等设备解锁用厂商客户端先验证同一组账号能登录排除设备侧问题。5.3 现象画面能出但几秒后卡住原因解码回调里做了耗时操作比如逐像素转换 直接repaint回调线程被拖慢SDK 内部缓冲溢出丢帧。解决回调里只做数据拷贝和emit转换和绘制放到界面线程必要时降码流dwStreamType改 1 走子码流。5.4 现象多路预览时界面卡顿、内存持续上涨原因每路都新建了转换缓冲却没释放或信号槽连接用了Qt::DirectConnection导致回调线程直接操作界面。解决检查new出来的缓冲是否有对应delete连接显式写Qt::QueuedConnection限制同时预览路数超出走轮询。5.5 现象退出时崩溃在NET_DVR_Cleanup原因还有预览句柄或登录句柄没释放就清理了 SDK。解决按 4.3 的顺序释放releaseAll()加防重入标志closeEvent里先releaseAll()再accept()。6. 二次开发改造把 demo 变成能交付的预览模块拿到这份 demo真正要做的不是改界面颜色而是把它抽成一个可复用的预览组件。我的做法是定义一个IPreviewSource抽象接口把「登录、开流、停流、取一帧」四个动作抽出来海康 SDK 只是其中一个实现。这样以后换别的品牌设备只加一个实现类界面层不动。class IPreviewSource { public: virtual ~IPreviewSource() default; virtual bool login(const QString ip, int port, const QString user, const QString pwd) 0; virtual bool startPreview(int channel, int streamType) 0; virtual void stopPreview() 0; virtual void logout() 0; signals: virtual void frameReady(QImage) 0; // 实际用 QObject 基类承载 };验证改造是否成功别只看「能出画面」。我一般跑三个检查一是连续开关预览 50 次看句柄数是否稳定ls /proc/pid/fd | wc -l二是拔网线再插回看是否有重连逻辑而不是直接黑屏三是把窗口最小化再恢复确认paintEvent不会因为m_frame为空而画花屏。这三个场景过了才算能上现场。还有一个具体技巧把 SDK 的日志打开。NET_DVR_SetLogToFile(3, ./sdklog, TRUE)会在本地落日志文件出问题时不用猜直接看 SDK 自己记了什么。这个开关在排查「登录成功但开流失败」时特别有用比在代码里到处加qDebug高效得多。从那以后我每次接这类 SDK demo都强制先跑一遍「登录—开流—关流—登出」的循环脚本再动业务代码确认底层链路是干净的。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站