ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

海康威视SDK Java二次开发:基于JNA实现摄像头预览与门禁联动

海康威视SDK Java二次开发:基于JNA实现摄像头预览与门禁联动 简介该资源是一套基于Java与海康威视SDK的二次开发项目面向需要完成毕业设计、课程设计或企业级项目开发的Java开发者。项目实现了网络摄像头与门禁系统的核心联动涵盖设备注册登录、局域网设备发现、门禁人员列表与人脸信息管理、门禁卡及人脸下发、事件布防与照片上传、实时帧获取以及RTSP/SDK推流等完整功能可直接作为功能基线进行扩展。压缩包共223个文件以174个Java源码文件为主辅以配置文件、依赖JAR包、Dockerfile等整体体积1.53MB结构清晰便于编译调试与二次开发。项目源码经过严格测试代码规范性与可读性较好适合参考其模块划分和SDK调用方式也可快速移植到实际场景中。该资源目前已有203人学习适合中高级Java开发者用于快速理解海康威视设备接入流程与门禁业务逻辑。1. 为什么用海康威视SDK做网络摄像头和门禁系统的Java二次开发接安防项目时很容易有一种错觉网络摄像头拉个RTSP流门禁就是继电器控制。理论上这两件事单独做都不难但一旦要放进同一个系统里按卡号联动录像、按事件联动布防这种组合方案就会在并发和回调上翻车。用Java直接调海康威视SDK做二次开发是毕业设计和课程设计里最常见的选型因为SDK本身是C接口Java通过JNA加载后能同时覆盖摄像头预览、录像回放、抓图、门禁开关、刷卡事件上报等能力。这里说的“SDK二次开发”不是用现成网页插件而是在自己代码里控制前端设备。适合有Java基础、但没碰过设备协议的人也适合想绕过联网平台直接对接海康硬件的人。2. 准备Java开发环境用JNA加载HCNetSDK并登录设备2.1 海康SDK的部件和JNA调用路径海康威视提供的设备网络SDK通常叫HCNetSDKWindows下会看到HCNetSDK.dll、HCCore.dll、PlayCtrl.dll等文件Linux下是libhcnetsdk.so和libcrypto相关库。播放和语音对讲还依赖额外的播放库所以做摄像头预览时光把主库加载进Java还不够播放部分的DLL也要能被操作系统找到。Java本身不能直接调用dll我用JNAJava Native Access做桥接它比JNI省去大量手写C头文件转换的工作。JNA 5.x对指针和结构体的处理已经很成熟适合快速完成毕设和中小型项目。在Maven项目里加入JNA依赖是第一步我用5.12.0兼容Java 8及以上dependency groupIdnet.java.dev.jna/groupId artifactIdjna/artifactId version5.12.0/version /dependency依赖引入后先确认SDK目录里有HCNetSDK.dll或libhcnetsdk.so并把SDK的dll目录加到启动参数或系统环境变量PATH里。在Windows下常见的坑是把dll放在项目目录里但没加原生路径导致UnsatisfiedLinkError。我一般会把SDK库文件和依赖的HCCore等放在/libs目录运行参数加-Djava.library.path./libs保证主库和副库能被找到。2.2 用JNA声明HCNetSDK核心接口C接口在Java里需要一个匹配的interface继承LibraryJDK动态代理会帮我们生成对应的原生调用。下面是最小可用的接口声明覆盖登录、注销、初始化和获取错误码import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Structure; import com.sun.jna.Union; import java.util.Arrays; public interface HCNetSDK extends Library { HCNetSDK INSTANCE Native.load(HCNetSDK, HCNetSDK.class); boolean NET_DVR_Init(); boolean NET_DVR_Cleanup(); int NET_DVR_Login_V40(NET_DVR_USER_LOGIN_INFO pLoginInfo, NET_DVR_DEVICEINFO_V40 lpDeviceInfo); boolean NET_DVR_Logout(int lUserID); int NET_DVR_GetLastError(); }Native.load(HCNetSDK, ...)会按平台自动找HCNetSDK.dll或libhcnetsdk.so。必须注意接口里的方法名、参数顺序要和C头文件一致比如登录函数是NET_DVR_Login_V40不是旧版的NET_DVR_Login。结构体也需要映射这里的NET_DVR_USER_LOGIN_INFO在C里是一个很长的结构JNA里定义时字段顺序不能改否则后面登录会报内存错乱。下面的写法是推荐方式Structure.FieldOrder({sDeviceAddress, wPort, sUserName, sPassword}) public static class NET_DVR_USER_LOGIN_INFO extends Structure { public byte[] sDeviceAddress new byte[129]; public short wPort; public byte[] sUserName new byte[64]; public byte[] sPassword new byte[64]; public NET_DVR_USER_LOGIN_INFO() { super(); } Override protected java.util.ListString getFieldOrder() { return Arrays.asList(sDeviceAddress, wPort, sUserName, sPassword); } }C头文件里的登录结构还有bUseTransport、cbLoginResult等字段但没有全部映射也能登录前提是字段长度和顺序从第一个字段开始一致。有时网上下载的简版结构不完整照样能跑因为前四个字节段正好是地址、端口、用户名、密码。真正做项目时我会从SDK自带头文件里把整个结构逐字段粘过来按FieldOrder排序防止登录成功后写内存越界。2.3 初始化与登录设备的参数设置登录是所有后续操作的前提。设备默认端口是8000用户名和密码是设备激活时设置的。下面这段代码可以测试SDK是否正常工作public class SdkDemo { private final HCNetSDK hik HCNetSDK.INSTANCE; public int login(String ip, short port, String user, String pwd) { if (!hik.NET_DVR_Init()) { throw new RuntimeException(SDK初始化失败); } HCNetSDK.NET_DVR_USER_LOGIN_INFO loginInfo new HCNetSDK.NET_DVR_USER_LOGIN_INFO(); loginInfo.sDeviceAddress Arrays.copyOf(ip.getBytes(), 129); loginInfo.wPort port; loginInfo.sUserName Arrays.copyOf(user.getBytes(), 64); loginInfo.sPassword Arrays.copyOf(pwd.getBytes(), 64); HCNetSDK.NET_DVR_DEVICEINFO_V40 deviceInfo new HCNetSDK.NET_DVR_DEVICEINFO_V40(); int userId hik.NET_DVR_Login_V40(loginInfo, deviceInfo); if (userId -1) { System.out.println(登录失败错误码 hik.NET_DVR_GetLastError()); } return userId; } }Arrays.copyOf(byte[], int)把Java字符串补零到定长字节数组这是JNA处理C定长字符数组的常见方式。不能直接赋String类型因为结构体里声明的是byte[]。登录返回的userId是一次会话句柄后续预览、布防、门禁控制都要带上它。参数取值参数类型说明sDeviceAddressbyte[129]设备IP或域名最大128个字符wPortshort设备SDK端口默认8000sUserNamebyte[64]管理员或操作员用户名sPasswordbyte[64]对应用户的密码lpDeviceInfo结构体指针登录后由SDK填充设备类型、通道数等如果返回 -1用NET_DVR_GetLastError()拿错误码。常见的有7表示网络连接失败17表示用户密码错误23表示设备忙碌。调试阶段先把设备IP ping通保证网络能访问8000端口再去看业务代码。3. 用Java实现摄像头预览和门禁控制3.1 摄像头预览通道、主码流与窗口句柄海康SDK预览有两种模式一是让SDK直接解码到窗口句柄适合桌面客户端二是通过回调拿码流数据在Java里转成图片或交给FFmpeg再处理。毕设里最简单的方案是窗口句柄预览但Java Swing/AWT里需要拿到原生窗口句柄才能交给SDK。这个操作在Windows下可以用JNA的Native.getWindowHandle下面的代码基于AWT的Canvas组件import com.sun.jna.Pointer; import java.awt.Canvas; import javax.swing.JFrame; JFrame frame new JFrame(预览); Canvas canvas new Canvas(); frame.add(canvas); frame.setSize(640, 360); frame.setVisible(true); long hwnd Native.getWindowHandle(canvas);拿到句柄后设置NET_DVR_CLIENTINFO参数并调用预览接口HCNetSDK.NET_DVR_CLIENTINFO clientInfo new HCNetSDK.NET_DVR_CLIENTINFO(); clientInfo.lChannel 1; // 通道号 clientInfo.lStreamType 0; // 0为主码流1为子码流 clientInfo.hPlayWnd new Pointer(hwnd); clientInfo.bBlocked true; int playHandle hik.NET_DVR_RealPlay(userId, clientInfo, null, null, true); if (playHandle -1) { System.out.println(预览失败错误码 hik.NET_DVR_GetLastError()); }lStreamType参数直接关系到带宽占用主码流清晰但占资源子码流适合多画面轮询实际项目里会按场景切换。bBlocked为true时预览调用会阻塞到播放窗口创建完成改为false时异步返回后者在并发预览场景更常用。预览结束后用NET_DVR_StopRealPlay(playHandle)释放会话。如果用的是Spring Boot这类无界面环境窗口句柄不可用那就改用回调模式把NET_DVR_RealPlay的第四个参数改成回调函数SDK会把码流帧数据交给Java层。回调节点里不要再做耗时操作否则视频会卡顿常见做法是直接丢到阻塞队列由消费线程处理。3.2 门禁控制开门、关门和常开的SDK命令门禁设备的登录方式和摄像头一致只是登录后走的是门禁控制接口。常用的控制函数是NET_DVR_ControlGateway参数含义比预览更直观boolean ret hik.NET_DVR_ControlGateway(userId, 1, 1, 0); if (!ret) { System.out.println(开门失败错误码 hik.NET_DVR_GetLastError()); }第一个参数是登录会话ID第二个参数是门号从1开始第三个参数是命令第四个参数是锁ID一般填0。调用前一定要确认这台设备用NET_DVR_ControlGateway还是NET_DVR_ControlDoor不同固件版本命名不同我习惯先看SDK头文件里NET_DVR_LinkDoor这类结构确认当前型号支持的函数名和命令码。命令码取值通常如下命令码含义0关门1开门2常开保持打开3解除常开4触发警报门禁控制看起来是一句话的事但排错比摄像头更难因为没有图像反馈。我一般会在调用前先布防再执行一次开门然后去读设备状态。设备状态用NET_DVR_GetDVRState或NET_DVR_GetGatewayStatus轮询具体函数每个型号也有差异。如果开门没反应先看错误码其次检查门号和锁ID最后检查门磁报警是否被服务器占用。3.3 把报警回调转换为业务事件门禁刷卡和摄像头报警都是主动上报不能靠轮询。海康SDK通过NET_DVR_SetDVRMessageCallBack_V31注册报警回调这个回调工作在SDK内部线程里不能长时间阻塞。下面是一个最小回调实现HCNetSDK.FMSGCallBack_V31 alarmCallback (alarmType, pAlarmInfo, pAlarmBuffer, dwBufSize) - { // 立即交给业务线程池不在回调线程里做耗时操作 executor.execute(() - handleAlarm(alarmType, pAlarmInfo)); return 0; }; boolean ret hik.NET_DVR_SetDVRMessageCallBack_V31(alarmCallback, null);回调的第一个参数alarmType会区分设备类型比如门禁事件、网络摄像机事件、移动侦测等。pAlarmInfo是一个Pointer需要按设备类型转成对应结构体。比如门禁报警常用NET_DVR_ACS_ALARM_INFO里面包含卡号、门号、事件类型、事件时间。定义结构体时依然要严格按照官方头文件字段顺序一门禁报警结构体在JAVA里展开可能有几十个字段我一般只映射头部十几个但必须保证顺序不跳。实际项目中我会在handleAlarm里把事件封装成自己的POJO然后扔进消息队列或直接入库。事件的业务逻辑和SDK代码分开这样后续换设备或升级SDK时只需改回调层。4. 门禁刷卡事件和摄像头联动联动的进阶处理4.1 在报警回调里提取卡号门禁系统最常见的需求是刷卡记录。报警回调里的结构体按NET_DVR_ACS_ALARM_INFO解析时关键字段如下Structure.FieldOrder({dwSize, dwMajor, dwMinor, dwTime, byCardNo}) public static class NET_DVR_ACS_ALARM_MIN extends Structure { public int dwSize; public int dwMajor; public int dwMinor; public NET_DVR_TIME dwTime; public byte[] byCardNo new byte[32]; }byCardNo保存的是BCD码或ASCII码卡号转成字符串时要注意有些设备返回32字节补零数组直接new String(byCardNo).trim()就能得到卡号有些设备是16字节BCD码需要自己转16进制字符串。拿到卡号后再结合dwMajor和dwMinor判断是合法刷卡还是非法卡最后把数据写入业务表。String cardNo new String(minInfo.byCardNo, StandardCharsets.US_ASCII).trim(); int eventType minInfo.dwMinor; log.info(door event: doorNo{}, eventType{}, cardNo{}, minInfo.dwMinor, eventType, cardNo);4.2 三个影响稳定性的参数坑症状原因处理登录后预览正常但门禁控制返回超时门禁设备同时被多个客户端连接或设备未布防先撤防再控制或断开另一路客户端连接回调收不到刷卡事件没有调用布防接口或回调函数被GC回收在类中保存回调实例并在登录后调用布防JNA结构体解析出的卡号带乱码字段顺序不全或者结构体大小不够对照头文件补全字段用getFieldOrder验证顺序第一个坑在毕业设计里特别常见海康门禁一体机默认只允许一个管理端在线你用官方客户端调试过你的Java程序就登录不上了。第二个坑是Java特有陷阱回调对象是局部变量时很快被垃圾回收JNA底层就会崩溃。解决方法是把它保存在某个单例或长生命周期对象里。第三个坑排查起来比较痛苦建议先改用long字段试而不是急着调结构体因为内存对齐变化会导致整体偏移错误。4.3 用一条日志验证联动是否生效验证摄像头和门禁联动不一定非要看到画面。把报警回调和视频录像做成联动时我习惯在回调里打印一条固定格式日志然后用脚本观察。比如在handleAlarm里输出door event: doorNo1, eventType1, cardNo20240501。启动项目后在设备管理页面手动远程开门或刷卡然后看服务日志tail -f /var/log/access.log | grep door event如果能在几秒内看到日志说明报警回调链路通。接着再去检查录像是否生成就能把问题范围从“SDK没接好”缩小到“录像模块没触发”。若日志没有输出先用官方客户端布防并触发一次报警确认设备本身能上送事件再回来查Java回调定义。这样逐步把设备侧和代码侧隔离开比盲目改代码快得多。本文还有配套的精品资源点击获取
返回列表