简介:这份资源是面向C#开发者的海康威视摄像头二次开发示例包,适合已掌握C#基础、希望快速接入海康SDK实现设备控制与视频流处理的工程师参考。包内共187个文件,以92个dll动态库、11个cs源码、8个exe可执行程序及若干config、manifest、resx、sln、csproj等工程配置为主,压缩包约23.38MB,完整保留了Visual Studio解决方案的目录结构。示例围绕设备初始化与连接、实时视频流获取与显示、单帧图像抓取、分辨率与帧率等参数设置、移动侦测等事件响应以及错误处理机制展开,并附有日志与缓存文件,便于对照调试。目前已有3903人学习下载,读者可借此理解SDK接口的封装方式,将设备管理、视频流处理与图像捕获等模块迁移到自己的监控项目中,减少从零摸索的成本。
1. 从一份 C# Demo 说起:海康威视摄像头二次开发到底在做什么
手上拿到一个叫「海康威视摄像头C#Demo.rar」的压缩包,很多人第一反应是解压、双击 sln、F5 跑起来看画面。但真正落到项目里,你会发现 Demo 能出画面只是起点,后面还有一堆事:多路摄像头怎么同时预览、录像文件怎么按时间段回放、云台怎么控制、报警事件怎么订阅、断线重连怎么做。这些才是海康威视 C# 二次开发的核心工作量。
海康威视摄像头本身跑的是 RTSP/ONVIF 这类通用协议,但厂商为了让你用满它的能力(预置点、智能分析、报警布防、码流切换),提供了一套原生 SDK,也就是常说的 HCNetSDK。C# 没法直接调 C 风格的动态库,所以官方和社区都做了 C# 封装,Demo 里通常就是这套封装加几个 WinForm 界面。这篇笔记不讲空泛概念,就顺着「拿到 Demo 之后怎么把它变成能上生产的东西」这条线走:SDK 怎么初始化、预览和回放怎么落地、多路场景怎么管、踩过的坑在哪。适合已经能跑通 Demo、准备往实际上位机或安防平台里集成的 C# 开发者。
2. 海康 SDK 的 C# 封装结构:先搞懂再动手改
2.1 HCNetSDK 的调用链路和 C# 封装层
海康原生 SDK 是一组 DLL:HCNetSDK.dll是主库,HCCore.dll负责组件注册,PlayCtrl.dll管解码播放,OpenNetStream.dll是另一种取流方式。C 语言里你直接LoadLibrary加GetProcAddress,或者链接 lib 文件。C# 走的是 P/Invoke,用[DllImport("HCNetSDK.dll")]声明每个要用的函数。
Demo 里的封装一般分三层:最底层是HCNetSDK.cs这种巨型文件,把几百个结构体和函数签名翻译成 C#;中间层是业务封装,比如CameraService、RealPlayService;最上层是 WinForm 或 WPF 界面。你要改代码,八成动的是中间层,底层签名基本不用碰,除非遇到结构体对齐问题。
一个典型的初始化调用长这样:
// 初始化 SDK,整个进程只需调用一次 bool initResult = HCNetSDK.NET_DVR_Init(); if (!initResult) { // 拿错误码,排查是缺 DLL 还是组件没注册 uint errCode = HCNetSDK.NET_DVR_GetLastError(); Console.WriteLine($"SDK 初始化失败,错误码:{errCode}"); return; } // 设置连接超时和重连,单位毫秒 HCNetSDK.NET_DVR_SetConnectTime(2000, 1); HCNetSDK.NET_DVR_SetReconnect(10000, true);NET_DVR_Init必须在所有其他调用之前执行,而且一个进程只调一次。NET_DVR_SetConnectTime的第一个参数是单次连接超时,第二个是尝试次数;NET_DVR_SetReconnect的第一个参数是重连间隔,第二个布尔值决定是否启用。这两个不设,默认值在弱网环境下会很难受,登录卡半天。
2.2 登录、预览、回放三个核心流程的代码骨架
登录是拿NET_DVR_USER_LOGIN_INFO结构体填 IP、端口、用户名、密码,然后调NET_DVR_Login_V40,返回一个lUserID,后续所有操作都靠这个句柄。注意密码字段是byte数组不是 string,中文密码或特殊字符要按编码转。
预览用NET_DVR_PREVIEWINFO,关键字段是通道号lChannel、码流类型dwStreamType(0 主码流、1 子码流)、显示模式dwLinkMode(0 TCP、1 UDP、2 多播、3 RTP)。拿到lRealPlayHandle后,如果要在自己的控件里画,就设回调fRealDataCallBack收码流,再交给PlayCtrl解码;如果图省事,直接传窗口句柄让 SDK 自己画。
// 登录设备 HCNetSDK.NET_DVR_USER_LOGIN_INFO loginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO(); loginInfo.sDeviceAddress = "192.168.1.64"; loginInfo.wPort = 8000; loginInfo.sUserName = "admin"; loginInfo.sPassword = "yourpassword"; HCNetSDK.NET_DVR_DEVICEINFO_V40 deviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40(); int userId = HCNetSDK.NET_DVR_Login_V40(ref loginInfo, ref deviceInfo); if (userId < 0) { Console.WriteLine($"登录失败:{HCNetSDK.NET_DVR_GetLastError()}"); } // 启动预览,子码流省带宽 HCNetSDK.NET_DVR_PREVIEWINFO previewInfo = new HCNetSDK.NET_DVR_PREVIEWINFO(); previewInfo.lChannel = 1; previewInfo.dwStreamType = 1; previewInfo.dwLinkMode = 0; previewInfo.hPlayWnd = IntPtr.Zero; // 用回调自己渲染 int playHandle = HCNetSDK.NET_DVR_RealPlay_V40(userId, ref previewInfo, null, IntPtr.Zero);回放走的是另一套:先NET_DVR_FindFile_V40按时间段查文件,拿到文件列表后NET_DVR_PlayBackByTime_V40启动回放,再通过NET_DVR_PlayBackControl_V40控制暂停、快进、拖拽。回放和预览的句柄是分开的,别混用。
提示:登录返回的
userId和预览返回的playHandle都要在退出时按顺序释放,先停预览再登出,否则下次登录可能报资源占用。
3. 多路摄像头并发管理:句柄、线程和资源回收
3.1 多路预览的句柄管理和线程模型
单路跑通不难,难的是 8 路、16 路同时预览。每路预览占一个playHandle,每路登录占一个userId。如果你用同一个账号登录多次,海康设备默认可能限制并发登录数,常见做法是登录一次拿一个userId,然后用不同lChannel启动多路预览。这样句柄少,管理简单。
线程模型上,SDK 的回调是在它自己的线程里触发的,你千万别在回调里直接更新 UI 控件,WinForm 会抛跨线程异常。正确做法是回调里把数据丢进队列或ConcurrentQueue,UI 线程用定时器或Invoke取。解码播放如果每路都开一个PlayCtrl实例,CPU 会飙,子码流加硬解码能压下来。
// 一个 userId 带多路预览,通道号区分 int[] channels = { 1, 2, 3, 4 }; List<int> playHandles = new List<int>(); foreach (int ch in channels) { HCNetSDK.NET_DVR_PREVIEWINFO info = new HCNetSDK.NET_DVR_PREVIEWINFO(); info.lChannel = ch; info.dwStreamType = 1; // 统一走子码流 info.dwLinkMode = 0; info.hPlayWnd = IntPtr.Zero; int handle = HCNetSDK.NET_DVR_RealPlay_V40(userId, ref info, realDataCallback, IntPtr.Zero); if (handle >= 0) playHandles.Add(handle); }realDataCallback是RealDataCallBack委托,签名里带lRealHandle、dwDataType、pBuffer、dwBufSize。dwDataType要判断:0 是原始码流,1 是私有头,2 是解码后 YUV,3 是音频。多数场景你只处理 0 和 1,把码流喂给PlayCtrl。
3.2 断线重连和资源释放的正确姿势
网络抖动、设备重启、交换机抽风,预览断了是常态。SDK 自带NET_DVR_SetReconnect只对部分场景有效,更稳的是自己监听异常回调NET_DVR_SetExceptionCallBack_V30,收到EXCEPTION_REALPLAY或EXCEPTION_RECONNECT后主动停掉旧句柄、重新登录、重新起预览。
资源释放顺序很关键:先NET_DVR_StopRealPlay停预览,再NET_DVR_Logout登出,最后进程退出时NET_DVR_Cleanup。漏掉任何一步,跑久了句柄泄漏,设备那边也会残留会话。我一般写个CameraSession类,把 userId、playHandle、通道号绑在一起,实现IDisposable,用using或显式Dispose保证释放。
public void Dispose() { foreach (var handle in playHandles) { if (handle >= 0) HCNetSDK.NET_DVR_StopRealPlay(handle); } playHandles.Clear(); if (userId >= 0) { HCNetSDK.NET_DVR_Logout(userId); userId = -1; } }注意:
NET_DVR_Cleanup只在程序退出时调一次,别在每次断开时调,否则后续所有 SDK 调用都会失败。
4. 避坑与排查:那些让 Demo 跑不起来的细节
4.1 现象:初始化返回 false,错误码 1 或 41
原因通常是 DLL 没放对位置或组件没注册。海康 SDK 依赖HCNetSDK.dll、HCCore.dll、PlayCtrl.dll、libcrypto等一堆文件,而且分 32 位和 64 位。你的 C# 项目平台目标如果是 Any CPU,在 64 位系统上跑成 64 位进程,却放了 32 位 DLL,直接失败。错误码 41 一般是组件没注册,需要以管理员运行regsvr32注册HCCore.dll,或者把 SDK 目录加到 PATH。
解决:项目属性里把平台目标固定成 x64 或 x86,和 DLL 位数一致;把所有依赖 DLL 复制到输出目录;确认HCNetSDKCom子目录也在。
4.2 现象:登录成功但预览黑屏
黑屏但句柄有效,八成是码流类型或显示模式不对。有些设备主码流是 H.265,你的PlayCtrl版本不支持,就黑屏。换成子码流(dwStreamType = 1)通常能出画面。另外dwLinkMode用 UDP 在跨网段时容易丢包花屏,改 TCP(0)更稳。
还有一种情况是hPlayWnd传了IntPtr.Zero但没设回调,SDK 不知道往哪画,自然黑屏。要么给窗口句柄,要么设fRealDataCallBack自己解码。
4.3 现象:多路预览跑几分钟后程序卡死
这是典型的回调线程阻塞。如果你在realDataCallback里做了耗时操作(写文件、更新 UI、加锁),SDK 的回调线程被拖住,后续码流堆积,最终卡死。回调里只做最轻的事:拷贝数据到队列,立刻返回。解码和渲染放到独立线程。
另外PlayCtrl的PlayM4_InputData如果缓冲区满了会阻塞,要判断返回值,满了就丢帧而不是死等。
4.4 现象:回放拖拽进度条后画面卡住
回放拖拽要用NET_DVR_PlayBackControl_V40的NET_DVR_PLAYBACK_SETPOS命令,传目标时间。但很多人在拖拽后没有重新触发播放,或者时间格式没转对。海康的时间结构体NET_DVR_TIME是年、月、日、时、分、秒六个 DWORD,别用DateTime直接强转。
还有,回放句柄和预览句柄不能共用同一个PlayCtrl端口,要各自独立。
4.5 现象:程序退出后设备还显示在线,再次登录失败
这是没调NET_DVR_Logout或没调NET_DVR_Cleanup。设备端会话有超时,但短时间反复启动调试,会话数占满就登不上了。养成习惯:每个userId配一个try/finally,finally 里登出;程序退出事件里调NET_DVR_Cleanup。
5. 从 Demo 到可用工具:几个提升稳定性的进阶技巧
5.1 用配置文件驱动多设备,而不是硬编码
Demo 里 IP、端口、账号往往写死在代码里。实际项目设备一多,必须外置。我一般用一个 JSON 数组描述设备列表,启动时遍历登录。这样加设备不用重新编译。
public class CameraConfig { public string Ip { get; set; } public int Port { get; set; } = 8000; public string User { get; set; } public string Password { get; set; } public int[] Channels { get; set; } } // 读取配置 var configs = JsonSerializer.Deserialize<List<CameraConfig>>(File.ReadAllText("cameras.json")); foreach (var cfg in configs) { // 登录并按 Channels 起预览 }密码别明文存,至少做个简单加密或者用 Windows 凭据管理器。配置文件里通道号用数组,方便一台 NVR 下挂多个摄像头。
5.2 用异常回调做统一重连,而不是每路自己写
NET_DVR_SetExceptionCallBack_V30是全局的,所有设备的异常都从这里出来。回调参数里有lUserID和lHandle,你可以根据这两个值反查是哪台设备哪路预览,然后统一走重连逻辑。这样比每路预览各自 try-catch 干净得多。
HCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, IntPtr.Zero, (dwType, lUserID, lHandle, pUser) => { if (dwType == HCNetSDK.EXCEPTION_REALPLAY || dwType == HCNetSDK.EXCEPTION_RECONNECT) { // 根据 lUserID 和 lHandle 找到对应会话,标记需要重连 ReconnectManager.MarkDirty(lUserID, lHandle); } }, IntPtr.Zero);重连不要立刻做,加个退避,比如 3 秒后重试,失败再等 6 秒,避免设备刚重启就被打爆。
5.3 验证方案是否靠谱的三个检查点
第一,拔网线 30 秒再插上,看预览是否自动恢复,恢复时间是否在可接受范围。第二,同时开 16 路子码流跑 2 小时,看内存和句柄数是否稳定,任务管理器里 GDI 对象别持续涨。第三,程序异常退出(直接杀进程)后立刻重启,看能否正常登录,验证设备端会话是否被正确清理。
这三个点过了,基本能上生产环境。我自己踩过最深的坑就是回调里写日志文件,跑一晚上磁盘 IO 把回调线程堵死,第二天画面全黑。后来所有回调只入内存队列,落盘交给独立线程,再没出过。希望帮到你。
本文还有配套的精品资源,点击获取