简介:一套基于Qt框架的海康威视摄像头预览Demo,面向视频监控与Qt开发者,演示设备实时预览与播放。压缩包共55个文件,约9.08MB,包含dll动态库、lib导入库、exe可执行程序以及cpp/h源码,并附带pro工程文件和ui界面文件,可直接查看工程结构并二次开发。Demo主要用到Qt图形视图框架和多媒体模块,同时集成海康威视HCNetSDK等网络SDK,涵盖摄像头初始化、登录、实时预览和界面绑定等关键环节,有助于快速掌握设备参数配置与视频流显示流程,适合刚接触安防客户端开发的工程师参考。已有244人学习浏览,资源目录逻辑清晰,库与源码分离,便于对照调用关系和界面搭建思路,是一份精简实用的入门Demo。
1. 这个Demo里真正值钱的不是界面,是那三个DLL
海康威视的Qt预览Demo,压缩包名字里带1998168.com和haikang,解压之后第一眼看上去全是dll和lib,Qt源码反而只有几个文件。不少第一次接触的人会以为这是个界面示例工程,实际上这个包的核心价值在于它完整串起了海康威视设备接入的三层链路:HCNetSDK.dll负责设备发现、登录和信令控制,PlayCtrl.dll负责解码和渲染,中间再靠Qt的窗口句柄把视频画面嵌进自己的界面。换句话说,你把它当成一个「海康SDK调用的最小可运行模板」来读,比当Qt教程更有价值。
这个Demo适合两类人:一类是刚拿到海康设备、想在Qt里做实时预览的C++开发者;另一类是已经能跑通预览、但想搞清楚NET_DVR_RealPlay_V40返回的播放句柄和窗口句柄之间关系的人。它不涉及复杂的算法或图像处理,纯粹是SDK集成工程,但恰恰是这类工程最容易在发布部署时栽跟头——依赖DLL缺失、库版本不匹配、窗口句柄传错。下文按调用链顺序拆解,重点关注能直接复用的代码模式和排错手段。
2. 认识包内关键文件:HCNetSDK.dll、PlayCtrl.dll 与 Qt 工程骨架
2.1 动态库与静态库的角色划分
解压后看到的文件分三类:DLL运行库、LIB导入库、Qt工程源码。角色分配如下表:
| 文件 | 类型 | 作用 | 说明 |
|---|---|---|---|
| HCNetSDK.dll / .lib | 动态库 / 导入库 | 设备注册、登录、参数配置、报警回调 | 所有海康设备通信的入口 |
| PlayCtrl.dll | 动态库 | 视频流解码、播放控制、抓图、OSD叠加 | 预览画面渲染的核心 |
| HCPreview.dll / HCCore.dll | 动态库 | 预览功能的封装层 | 部分版本SDK需要,注意版本配套 |
| StreamTransClient.dll | 动态库 | 流媒体转发客户端 | 涉及远程取流时加载 |
| AudioRender.dll / AudioIntercom.dll | 动态库 | 音频渲染与对讲 | 有音频需求时依赖 |
| iconv.dll / libiconv2.dll | 第三方库 | 字符编码转换 | 处理设备返回的GBK编码字符串 |
| libxml2.dll | 第三方库 | XML解析 | 部分设备能力集解析用 |
| QtDemoTest.pro / main.cpp / mainwindow.cpp | Qt工程 | 界面与逻辑封装 | 重点看 mainwindow.cpp |
.pro.user文件是Qt Creator的本地用户配置,里面记录的是生成好的构建目录和编译器路径。这个文件通常不能跨机器直接使用,因为它绑定了本机Qt版本和编译套件路径。拿到的Demo如果打开后提示套件失效,直接删掉.pro.user文件,用当前环境重新构建即可。
2.2 Qt工程初始化与SDK初始化顺序
先看main.cpp里的基础结构,标准Qt入口,但在构造主窗口之前,SDK初始化动作已经发生:
#include <QApplication> #include "mainwindow.h" #include "HCNetSDK.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); // 初始化SDK,返回false说明DLL加载失败 bool initSuccess = NET_DVR_Init(); if (!initSuccess) { return -1; } // 设置连接超时与尝试次数,调试阶段建议给大一点 NET_DVR_SetConnectTime(5000, 1); NET_DVR_SetReconnect(10000, true); MainWindow w; w.show(); int ret = a.exec(); // 程序退出前释放SDK资源 NET_DVR_Cleanup(); return ret; }NET_DVR_Init是海康SDK的全局初始化函数,它会加载设备列表、初始化网络模块和日志模块。这里有个容易被忽略的点:如果程序运行目录下缺少HCNetSDK.dll或它的依赖项,NET_DVR_Init会静默失败返回false。在main函数里直接return -1而没有日志输出,新手查起来会一头雾水。我一般会在初始化失败时追加一行qWarning()输出,同时检查sdkLog目录下生成的日志文件。
2.3 工程文件与DLL的部署关系
.pro文件决定编译产物位置,但DLL的部署不在.pro里,需要手动处理。这个Demo里debug和release目录各放了一份DLL,说明作者是用「拷贝DLL到输出目录」的方式完成运行时依赖的。更规范的做法是用QMAKE_POST_LINK自动化拷贝:
# 在 .pro 文件中追加 DLL_SRC = $$PWD/HCNetSDK.dll \ $$PWD/PlayCtrl.dll \ $$PWD/HCPreview.dll DLL_DEST = $$OUT_PWD/debug win32 { CONFIG(debug, debug|release) { for(dll, DLL_SRC) { QMAKE_POST_LINK += $$quote(copy /Y $$shell_path($$dll) $$shell_path($$DLL_DEST) $$escape_expand(\n\t)) } } }这样每次构建后自动把需要的DLL复制到输出目录,避免手动拷贝遗漏。特别注意PlayCtrl.dll必须和HCNetSDK.dll在同一个目录,而且要保证版本匹配——海康的预览播放库跟主SDK是配套发布的,混用版本会出现登录成功但预览黑屏的现象。
3. 设备登录与实时预览的完整调用链
3.1 设备信息结构体与登录接口
海康从某个SDK版本开始主推NET_DVR_Login_V40,它替代了老旧的NET_DVR_Login_V30。V40版本使用NET_DVR_USER_LOGIN_INFO结构体传入设备地址、端口、用户名密码,同时支持会话连接方式设置:
#include "HCNetSDK.h" NET_DVR_DEVICEINFO_V40 deviceInfo = {0}; NET_DVR_USER_LOGIN_INFO loginInfo = {0}; strcpy(loginInfo.sDeviceAddress, "192.168.1.64"); loginInfo.wPort = 8000; strcpy(loginInfo.sUserName, "admin"); strcpy(loginInfo.sPassword, "password123"); // 通过回调输出登录结果和错误码 NET_DVR_SetLoginInfoCallback(LoginResultCallback, nullptr); long userId = NET_DVR_Login_V40(&loginInfo, &deviceInfo); if (userId < 0) { DWORD errorCode = NET_DVR_GetLastError(); qWarning() << "登录失败, 错误码:" << errorCode; return; }端口8000是海康设备默认的SDK通信端口,不是RTSP的554端口。设备序列号、通道数量从deviceInfo.struDeviceV30.byChanNum读取。如果设备固件比较老,只支持NET_DVR_Login_V30,那需要换成NET_DVR_DEVICEINFO_V30结构体,但接口形式类似。
回调函数LoginResultCallback的定义需要注意返回类型:
void CALLBACK LoginResultCallback(LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo, void *pUser) { if (dwResult == 0) { qDebug() << "用户" << lUserID << "登录成功"; } else { DWORD errorCode = NET_DVR_GetLastError(); qDebug() << "登录失败, 错误码:" << errorCode; } }dwResult为0表示成功,非0时调用NET_DVR_GetLastError()获取具体错误码。实践中,NET_DVR_GetLastError返回的错误码更容易出现在回调里而不是NET_DVR_Login_V40的返回值上,所以回调不能省。
3.2 实时预览:从登录句柄到视频画面
登录成功拿到userId之后,预览走NET_DVR_RealPlay_V40,这是海康SDK对接窗口句柄的关键一步。核心代码如下:
NET_DVR_PREVIEWINFO previewInfo = {0}; previewInfo.lChannel = 1; // 通道号,从1开始 previewInfo.dwStreamType = 0; // 0-主码流,1-子码流 previewInfo.dwLinkMode = 0; // 0-TCP方式 previewInfo.hPlayWnd = (HWND)ui->videoWidget->winId(); // 关键:Qt窗口句柄 LONG previewHandle = NET_DVR_RealPlay_V40(userId, &previewInfo, nullptr, nullptr, 0); if (previewHandle < 0) { DWORD errorCode = NET_DVR_GetLastError(); qWarning() << "启动预览失败, 错误码:" << errorCode; }hPlayWnd是预览画面渲染的目标窗口句柄。在Qt里,QWidget::winId()拿到的是这个控件的原生窗口句柄,但有个坑:如果videoWidget尚未显示,winId()可能返回0或者一个无效句柄。因此务必在show()事件之后再去启动预览,或者在构造函数里先调用videoWidget->winId()强制创建原生窗口。
dwLinkMode参数决定取流方式。0是TCP,适合局域网内的稳定传输;1是UDP,延迟低但可能丢包。跨公网访问时推荐TCP。如果设备支持RTSP over TCP,也可以把dwLinkMode设为2,但Demo默认的0通常够用。
3.3 停止预览与资源释放顺序
预览停止的顺序决定了程序是否会在退出时崩溃。很多人直接调NET_DVR_Cleanup(),如果预览句柄还占着,轻则内存泄漏,重则崩溃。正确顺序是:先停止预览,再注销登录,最后清理SDK全局资源:
void MainWindow::stopPreview() { if (m_previewHandle >= 0) { NET_DVR_StopRealPlay(m_previewHandle); m_previewHandle = -1; } if (m_userId >= 0) { NET_DVR_Logout(m_userId); m_userId = -1; } }NET_DVR_StopRealPlay是阻塞调用,它会等待解码线程退出。如果界面线程在这里卡住,八成是解码库还在渲染队列里有未处理完的帧。遇到这种情况,可以先隐藏播放窗口再调用停止。
4. PlayCtrl.dll 的绘图机制与画面优化
4.1 为什么界面只绘黑框不见画面
PlayCtrl.dll 是海康的播放库,负责将设备传回的PS流或H.264裸流解码并绘制到指定窗口。预览接口把hPlayWnd传进去之后,解码库直接在该窗口上进行硬件加速绘制。
黑屏问题几乎都出在窗口句柄上。有两个常见原因:一是传入的是QWidget对象指针而不是winId(),导致绘制失败;二是界面启用了Qt的合成器,QWidget默认不带WA_NativeWindow属性,winId()拿到的可能是代理窗口。解决办法是在预览前设置:
ui->videoWidget->setAttribute(Qt::WA_NativeWindow); ui->videoWidget->setAttribute(Qt::WA_PaintOnScreen); HWND hwnd = (HWND)ui->videoWidget->winId();WA_PaintOnScreen告诉Qt这个控件不使用Qt自身的绘制管线,直接把系统原生的WM_PAINT交给外部引擎,这样PlayCtrl的GDI绘制才能正常上屏。设置之后控件的paintEvent不再被Qt调用,如果需要叠加文字标签,得用独立子窗口盖在上面,而不能重写paintEvent。
4.2 解码播放库的日志和版本排查
这个DEMO的sdkLog目录下有个SdkLog_1_W.log文件,PlayCtrl.dll 和海康主SDK都会往这个文件里写日志。排查预览黑屏时可以按以下流程走:
- 打开
SdkLog_1_W.log,找到最近一次NET_DVR_RealPlay_V40调用的记录 - 看日志里是否有
error=...字节流,通常错误码含义和NET_DVR_GetLastError()一致 - 确认
PlayCtrl.dll版本与HCNetSDK.dll版本所属同一发布包
常见错误码速查表:
| 错误码 | 含义 | 处理建议 |
|---|---|---|
| 23 | 设备登录失败 | 检查用户名密码和端口 |
| 29 | 通道号错误 | 确认设备实际通道数 |
| 66 | 不支持的码流类型 | 尝试切换主/子码流 |
| 70 | 播放库未初始化 | 检查PlayCtrl.dll是否加载 |
另外注意HCNetSDKCom目录,里面放的是组件解压后的散装DLL。海康SDK在运行时可能自动解压组件到该目录,如果程序没有该目录的读写权限,NET_DVR_Init会失败。发布时把这个目录一起带上,并确保安装路径可写。
4.3 延迟优化与画面尺寸控制
Demo默认的预览是原始分辨率输出,大屏显示没问题,但嵌入到仪表盘里就显得臃肿。海康SDK没有直接的缩放接口,缩放靠的是窗口大小自适应。但码流分辨率会影响解码负载,所以更合理的做法是在NET_DVR_PREVIEWINFO里直接指定dwStreamType = 1(子码流),子码流是CIF或D1分辨率,解码开销小得多。
如果必须主码流,但画面撕裂严重,可以尝试把回调模式从MODE_FRAME_CALLBACK切到MODE_PLAY,前者把每一帧原始码流回传给应用层,后者在SDK内部控制解码并直接上屏,更省CPU。这个参数在NET_DVR_RealPlay_V40的dwPreviewMode字段中设置。
5. 基于Demo扩展:抓图、对讲和音频的接入验证
5.1 用PlayCtrl接口做本地JPEG抓图
PlayCtrl.dll 提供的PlayM4_GetJPEG可以抓取当前显示帧直接存为JPEG文件。这是在Demo基础上扩展最常用的功能,代码模式如下:
// 假设已经通过 PlayM4_GetPort 获取到播放端口号 // 实际OpenStream后即可调用 BYTE *jpegBuffer = new BYTE[1024 * 1024]; DWORD jpegSize = 0; BOOL ret = PlayM4_GetJPEG(playPort, jpegBuffer, 1024 * 1024, &jpegSize); if (ret) { QFile file("snapshot.jpg"); file.open(QIODevice::WriteOnly); file.write((char *)jpegBuffer, jpegSize); file.close(); } delete[] jpegBuffer;抓图的关键在于playPort是从预览句柄关联过来的。预览流程调用NET_DVR_RealPlay_V40内部已经建立了播放库端口,但要在应用层拿到这个端口号,需要在预览启动前使用PlayM4_GetPort(&playPort)分配端口,然后再预览。步骤如下:
- 创建播放端口:
PlayM4_GetPort(&m_port) - 用
NET_DVR_RealPlay_V40启动预览,同时NET_DVR_SetRealDataCallBack注册码流回调 - 在码流回调里把数据喂给
PlayM4_InputData(m_port, data, len) - 需要抓图时调用
PlayM4_GetJPEG(m_port, ...)
5.2 音频对讲的接入验证
AudioIntercom.dll和AudioRender.dll是对讲功能依赖的两个库。Demo里没有直接的语音对讲界面,但HCNetSDK.h里能看到NET_DVR_StartVoiceCom_V30的声明,这个接口用于向设备发起双向语音对讲。启动对讲前需要调用NET_DVR_Init并确保麦克风设备可用:
NET_DVR_AUDIO_INTERCOM_INFO audioInfo = {0}; audioInfo.dwSize = sizeof(audioInfo); strcpy(audioInfo.sAudioFileName, ""); // 留空表示使用实时采集 LONG voiceHandle = NET_DVR_StartVoiceCom_V30(m_userId, 1, &audioInfo); if (voiceHandle < 0) { DWORD errorCode = NET_DVR_GetLastError(); qWarning() << "语音对讲启动失败:" << errorCode; }对讲功能里最容易出问题的是音频格式不匹配。海康设备默认使用G.711编码,但Windows上采集的PCM数据要先转换成G.711才能推送,否则对方听到的是刺耳噪音。这个转换算法在SDK文档里有参考代码,但Demo里没提供。如果只做预览不做对讲,可以直接跳过这两个DLL的依赖,反而减少部署体积。
5.3 版本兼容性最终验证清单
拿到这个Demo并成功跑通之后,建议按以下清单做一轮最终验证,确保它从Demo变成可交付的模板:
- 换一台不同型号的海康设备测试登录和预览,确认没有硬编码IP或设备型号
- 拔掉网线再插回,确认
NET_DVR_SetReconnect的自动重连生效 - 将程序复制到一台没有安装过海康SDK的干净Windows机器上,确认DLL依赖完整
- 用
dumpbin /dependents QtDemoTest.exe查看导入表,确认所有依赖项都有对应DLL在目录中
最后这个检查很值得养成本能。海康SDK组件多、依赖关系隐蔽,HCNetSDK.dll依赖HCCore.dll,HCPreview.dll又依赖HCNetSDK.dll的特定导出函数。
本文还有配套的精品资源,点击获取