写在最前
SDK版本:CH-HCNetSDKV6.1.6.45_build20210302_win64
参考文档:海康SDK使用手册_V6.1
对接测试设备型号:DS-K1T671M
设备序列号:E50247795
业务目标
使用门禁设备实现对人脸的抓拍,将抓拍的人脸与其对应的数据进行上传。
业务流程图:
业务流程节点解释:
1.初始化SDK(NET_DVR_Init):进行海康提供开发库的载入,使用海康官方提供的文件库,进入之后,修改载入路径就可以了。
2.设置报警回调函数(NET_DVR_SetDVRMessageCallBack_V31):初始完SDK之后,进行报警回调函数的设置,当设备进行人脸抓拍之后,上传报警信息到SDK,触发回调函数进行内部业务逻辑处理。对于(门禁设备)人脸侦测,回调函数中的报警类型(lCommand)为COMM_ALARM_ACS,,报警信息(pAlarmInfo)对应结构体:NET_DVR_ACS_ALARM_INFO。
3.用户注册(NET_DVR_Login_V40):填写设备对应的设备参数,进行设备的注册,注册成功会返回一个lUserID,使用这个lUserID进行下面一系列的操作。
4.获取设备能力集(NET_DVR_GetDeviceAbility):能力集类型DEVICE_ABILITY_INFO,获取智能通道分析能力集可以判断设备是否支持相关功能。(可选功能)
5.设置人脸抓拍参数(NET_DVR_SetDVRConfig) (可选功能)
6.获取人脸抓拍参数(NET_DVR_GetDVRConfig) (可选功能)
7.报警布防(NET_DVR_SetupAlarmChan_v41):布防即建立设备跟客户端之间报警上传的连接通道,这样设备发生报警之后通过该连接上传报警信息,SDK在报警回调函数中接收和处理报警信息数据即可。如果设备同时支持人脸侦测和人脸抓拍方式,调用该接口时,NET_DVR_SETUPALARM_PARAM布防参数中byFaceAlarmDetection赋值为0即选择设备上传的报警信息类型为人脸抓拍类型。
注意:在报警布防中需要设置连接的参数,设置不对或没有设置会提示连接设备失败。
8.报警回调函数里面接收和处理数据:报警类型:COMM_ALARM_ACS,报警信息结构体:NET_DVR_ACS_ALARM_INFO。对设备上传来的数据信息进行接收
9.报警撤防(NET_DVR_CloseAlarmChan_v30)
10.注销用户(NET_DVR_logout)
11.释放SDK资源(NET_DVR_Cleanup):关闭连接通道,释放资源。
代码示例
1.首先根据你需要开发的系统去海康官网下载对应的程序包。
比如我的win64
2.创建好springboot项目,将这个程序包里面的库文件引进去。
3.将程序包里面它提供的 HCNetSDK.java 复制到你的项目里面,并修改你刚才放的库文件路径,注意以.dll结尾
4.接下来就是写 demo 测试连接
package com.example.testsdk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; /** * @author LH * @date 2021/11/29 10:37 */ public class startHCNetAlarm { private static final Logger LOGGER = LoggerFactory.getLogger(startHCNetAlarm.class); // 载入sdk库文件 static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE; public static void main(String[] args) throws IOException { HCNetAlarm hcNetAlarm = new HCNetAlarm(); // 资源初始化 int row = hcNetAlarm.initDevice(); if (row == 1) { LOGGER.info("初始化失败"); } // 设置连接超时时间与重连功能 hCNetSDK.NET_DVR_SetConnectTime(2000, 1); hCNetSDK.NET_DVR_SetReconnect(10000, true); // 设备注册,注册成功返回一个唯一标识符 lUserID,根据这个进行设备的其它操作 int luserID = hcNetAlarm.deviceRegister(-1, "填你设备的ip地址", "设备用户名", "设备密码", "设备端口,一般默认8000"); System.out.println(luserID); // 设置报警回调函数,建立报警上传通道(启用布防) int lAlarmHandle = hcNetAlarm.setupAlarmChan(luserID, -1); // 检查设备状态(是否在线),打印设备信息 hcNetAlarm.onlineState(luserID); // 设备抓拍功能, // hcNetAlarm.getDVRPic(luserID); try { // 等待设备上传报警信息 LOGGER.info("等待设备上传报警信息===================="); Thread.sleep(100 * 60 * 60); } catch (InterruptedException e) { e.printStackTrace(); } // 撤销布防上传通道 hcNetAlarm.closeAlarmChan(lAlarmHandle); // 注销 释放sdk资源 hcNetAlarm.logout(luserID); System.out.println("====== 设备注销 ======"); } }
package com.example.testsdk; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import com.sun.jna.ptr.IntByReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; /** * @author LH * @date 2021/11/29 9:06 */ public class HCNetAlarm { private static final Logger LOGGER = LoggerFactory.getLogger(HCNetAlarm.class); // 载入sdk库文件 static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE; // 设备登录信息 HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO(); // 设备信息 HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40(); // 已登录设备的IP String m_sDeviceIP; // 设备用户名 String m_sUsername; // 设备密码 String m_sPassword; // 报警回调函数实现 public static HCNetSDK.FMSGCallBack_V31 fMSFCallBack_V31; /** * sdk初始化 * * @return */ public int initDevice() { if (!hCNetSDK.NET_DVR_Init()) { // sdk初始化失败 return 1; } return 0; } /** * 注销 * * @param lUserID 设备注册成功唯一标识符 */ public void logout(int lUserID) { // 注销 hCNetSDK.NET_DVR_Logout(lUserID); // 释放sdk资源 hCNetSDK.NET_DVR_Cleanup(); } /** * 设备注册 * * @param ip 设备ip * @param name 设备名 * @param password 设备密码 */ public int deviceRegister(int lUserID, String ip, String name, String password, String port) { // 设备注册之前先进行判断,注销已注册的设备 if (lUserID > -1) { // 先注销 hCNetSDK.NET_DVR_Logout(lUserID); lUserID = -1; } // ip地址 m_sDeviceIP = ip; m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN]; System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length()); // 设备用户名 m_sUsername = name; m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN]; System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length()); // 设备密码 m_sPassword = password; m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN]; System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length()); m_strLoginInfo.wPort = (short) Integer.parseInt(port); // 是否异步登录:0 - 否,1 - 是 m_strLoginInfo.bUseAsynLogin = false; m_strLoginInfo.write(); // 设备注册调用 NET_DVR_Login_V40,注册成功得到唯一标识符 lUserID // 设备注册失败,调用 NET_DVR_GetLastError,根据错误号判断错误类型 lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo); if (lUserID == -1) { LOGGER.info("设备注册失败,错误号:", hCNetSDK.NET_DVR_GetLastError()); return -1; } else { LOGGER.info("设备注册成功"); return lUserID; } } /** * 设置报警信息回调函数,根据上传的数据进行回调触发 */ public class FMSGCallBack_V31 implements HCNetSDK.FMSGCallBack_V31 { // lCommand 上传消息类型,这个是设备上传的数据类型,比如现在测试的门禁设备,回传回来的是 COMM_ALARM_ACS = 0x5002; 门禁主机报警信息 // pAlarmer 报警设备信息 // pAlarmInfo 报警信息 根据 lCommand 来选择接收的报警信息数据结构 // dwBufLen 报警信息缓存大小 // pUser 用户数据 @Override public boolean invoke(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) { alarmDataHandle(lCommand, pAlarmer, pAlarmInfo, dwBufLen, pUser); return true; } } /** * 建立布防上传通道,用于传输数据 * * @param lUserID 唯一标识符 * @param lAlarmHandle 报警处理器 */ public int setupAlarmChan(int lUserID, int lAlarmHandle) { // 根据设备注册生成的lUserID建立布防的上传通道,即数据的上传通道 if (lUserID == -1) { LOGGER.info("请先注册"); return lUserID; } if (lAlarmHandle < 0) { // 设备尚未布防,需要先进行布防 if (fMSFCallBack_V31 == null) { fMSFCallBack_V31 = new FMSGCallBack_V31(); Pointer pUser = null; if (!hCNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fMSFCallBack_V31, pUser)) { LOGGER.info("设置回调函数失败!", hCNetSDK.NET_DVR_GetLastError()); } } // 这里需要对设备进行相应的参数设置,不设置或设置错误都会导致设备注册失败 HCNetSDK.NET_DVR_SETUPALARM_PARAM m_strAlarmInfo = new HCNetSDK.NET_DVR_SETUPALARM_PARAM(); m_strAlarmInfo.dwSize = m_strAlarmInfo.size(); // 智能交通布防优先级:0 - 一等级(高),1 - 二等级(中),2 - 三等级(低) m_strAlarmInfo.byLevel = 1; // 智能交通报警信息上传类型:0 - 老报警信息(NET_DVR_PLATE_RESULT), 1 - 新报警信息(NET_ITS_PLATE_RESULT) m_strAlarmInfo.byAlarmInfoType = 1; // 布防类型(仅针对门禁主机、人证设备):0 - 客户端布防(会断网续传),1 - 实时布防(只上传实时数据) m_strAlarmInfo.byDeployType = 1; // 抓拍,这个类型要设置为 0 ,最重要的一点设置 m_strAlarmInfo.byFaceAlarmDetection = 0; m_strAlarmInfo.write(); // 布防成功,返回布防成功的数据传输通道号 lAlarmHandle = hCNetSDK.NET_DVR_SetupAlarmChan_V41(lUserID, m_strAlarmInfo); if (lAlarmHandle == -1) { LOGGER.info("设备布防失败,错误码=========={}", hCNetSDK.NET_DVR_GetLastError()); // 注销 释放sdk资源 logout(lUserID); return lAlarmHandle; } else { LOGGER.info("设备布防成功"); return lAlarmHandle; } } return lAlarmHandle; } /** * 报警撤防 * * @param lAlarmHandle 报警处理器 */ public int closeAlarmChan(int lAlarmHandle) { if (lAlarmHandle > -1) { if (hCNetSDK.NET_DVR_CloseAlarmChan_V30(lAlarmHandle)) { LOGGER.info("撤防成功"); lAlarmHandle = -1; return lAlarmHandle; } return lAlarmHandle; } return lAlarmHandle; } /** * 接收设备上传的报警信息,进行上传数据的业务逻辑处理 * * @param lCommand 上传消息类型 * @param pAlarmer 报警设备信息 * @param pAlarmInfo 报警信息 * @param dwBufLen 报警信息缓存大小 * @param pUser 用户数据 */ public void alarmDataHandle(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) { System.out.println("报警监听中================================"); System.out.println(pAlarmInfo); String sAlarmType = new String(); String[] newRow = new String[3]; //报警时间 Date today = new Date(); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String[] sIP = new String[2]; sAlarmType = new String("lCommand=0x") + Integer.toHexString(lCommand); // lCommand是传的报警类型 switch (lCommand) { // 摄像头实时人脸抓拍上传 case HCNetSDK.COMM_UPLOAD_FACESNAP_RESULT: // 分配存储空间 HCNetSDK.NET_VCA_FACESNAP_RESULT strFaceSnapInfo = new HCNetSDK.NET_VCA_FACESNAP_RESULT(); strFaceSnapInfo.write(); Pointer pFaceSnapInfo = strFaceSnapInfo.getPointer(); // 写入传入数据 pFaceSnapInfo.write(0, pAlarmInfo.getByteArray(0, strFaceSnapInfo.size()), 0, strFaceSnapInfo.size()); strFaceSnapInfo.read(); sAlarmType = sAlarmType + ":人脸抓拍上传[人脸评分:" + strFaceSnapInfo.dwFaceScore + ",年龄:" + strFaceSnapInfo.struFeature.byAge + ",性别:" + strFaceSnapInfo.struFeature.bySex + "]"; newRow[0] = dateFormat.format(today); // 报警类型 newRow[1] = sAlarmType; // 报警设备IP地址 sIP = new String(strFaceSnapInfo.struDevInfo.struDevIP.sIpV4).split("\0", 2); newRow[2] = sIP[0]; LOGGER.info("人脸抓拍========{}", Arrays.toString(newRow)); // 设置日期格式 SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); // new Date()为获取当前系统时间 String time = df.format(new Date()); // 人脸图片写文件 File file = new File(System.getProperty("user.dir") + "\\pic1\\"); if (!file.exists()) { file.mkdir(); } try { FileOutputStream big = new FileOutputStream(System.getProperty("user.dir") + "\\pic1\\" + time + "background.jpg"); if (strFaceSnapInfo.dwFacePicLen > 0) { if (strFaceSnapInfo.dwFacePicLen > 0) { try { big.write(strFaceSnapInfo.pBuffer2.getByteArray(0, strFaceSnapInfo.dwBackgroundPicLen), 0, strFaceSnapInfo.dwBackgroundPicLen); big.close(); } catch (IOException e) { e.printStackTrace(); } } } } catch (FileNotFoundException e) { e.printStackTrace(); } break; // 门禁主机类型实时人脸抓拍上传,走这里 case HCNetSDK.COMM_ALARM_ACS: // 分配存储空间 System.out.println("============ 这是门禁主机的报警信息 ============"); HCNetSDK.NET_DVR_ACS_ALARM_INFO strFaceSnapInfo1 = new HCNetSDK.NET_DVR_ACS_ALARM_INFO(); strFaceSnapInfo1.write(); Pointer pFaceSnapInfo1 = strFaceSnapInfo1.getPointer(); // 写入传入数据 pFaceSnapInfo1.write(0, pAlarmInfo.getByteArray(0, strFaceSnapInfo1.size()), 0, strFaceSnapInfo1.size()); strFaceSnapInfo1.read(); // 设置日期格式 SimpleDateFormat df1 = new SimpleDateFormat("yyyyMMddHHmmss"); // new Date()为获取当前系统时间 String time1 = df1.format(new Date()); // 人脸图片写文件 File file1 = new File(System.getProperty("user.dir") + "\\pic3\\"); if (!file1.exists()) { file1.mkdir(); } try { FileOutputStream big = new FileOutputStream(System.getProperty("user.dir") + "\\pic3\\" + time1 + ".jpg"); if (strFaceSnapInfo1.dwPicDataLen > 0) { System.out.println("========== 图片有数据 ========"); if (strFaceSnapInfo1.dwPicDataLen > 0) { try { System.out.println("============ 图片上传成功 ============="); big.write(strFaceSnapInfo1.pPicData.getByteArray(0, strFaceSnapInfo1.dwPicDataLen), 0, strFaceSnapInfo1.dwPicDataLen); big.close(); System.out.println("设备唯一编码=================" + strFaceSnapInfo1.struAcsEventInfo.byDeviceNo); System.out.println("数据采集时间=================" + strFaceSnapInfo1.struTime.dwYear + strFaceSnapInfo1.struTime.dwMonth + strFaceSnapInfo1.struTime.dwDay + strFaceSnapInfo1.struTime.dwHour + strFaceSnapInfo1.struTime.dwMinute + strFaceSnapInfo1.struTime.dwSecond); System.out.println("人员工号=================" + strFaceSnapInfo1.struAcsEventInfo.dwEmployeeNo); System.out.println("人员姓名=================" + strFaceSnapInfo1.sNetUser); System.out.println("通进类型(0:入场,1:离场)=================" + strFaceSnapInfo1.struAcsEventInfo.dwDoorNo); System.out.println("图片唯一标识(工号加时间)=================" + strFaceSnapInfo1.struAcsEventInfo.dwEmployeeNo + time1 + ".jpg"); System.out.println("人员类型(0:白名单,1:访客,2:黑名单)=================" + strFaceSnapInfo1.struAcsEventInfo.byCardType); } catch (IOException e) { e.printStackTrace(); } } } } catch (FileNotFoundException e) { e.printStackTrace(); } break; default: newRow[0] = dateFormat.format(today); // 报警类型 newRow[1] = sAlarmType; // 报警设备IP地址 sIP = new String(pAlarmer.sDeviceIP).split("\0", 2); newRow[2] = sIP[0]; LOGGER.info("其他报警信息=========={}", Arrays.toString(newRow)); break; } } // 抓拍图片 public static void getDVRPic(int userId) throws IOException { // 设置通道号,其中 1 正常,-1不正常 NativeLong chanLong = new NativeLong(1); // 返回Boolean值,判断是否获取设备能力 HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30(); if (!hCNetSDK.NET_DVR_GetDVRWorkState_V30(userId, devwork)) { System.out.println("返回设备状态失败"); } // JPEG图像信息结构体 HCNetSDK.NET_DVR_JPEGPARA jpeg = new HCNetSDK.NET_DVR_JPEGPARA(); jpeg.wPicSize = 2; // 设置图片的分辨率 jpeg.wPicQuality = 2; // 设置图片质量 IntByReference a = new IntByReference(); SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss"); Date date = new Date(); int random = (int) (Math.random() * 1000); String fileNameString = sdf.format(date) + random + ".jpg"; // 设置字节缓存 ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024); // 抓图到文件 boolean is = hCNetSDK.NET_DVR_CaptureJPEGPicture(userId, chanLong.intValue(), jpeg, fileNameString); if (is) { System.out.println("图片抓取成功,返回长度:" + a.getValue()); } else { System.out.println("图片抓取失败:" + hCNetSDK.NET_DVR_GetLastError()); } } /** * 设备状态,是否在线,打印设备信息 */ public Boolean onlineState(int lUserID) { HCNetAlarm hcNetAlarm = new HCNetAlarm(); int row = hcNetAlarm.initDevice(); if (row == 1) { LOGGER.info("初始化失败"); } // 检查设备在线状态 LOGGER.info("设备信息========={}", hcNetAlarm.m_strDeviceInfo.struDeviceV30); boolean isOnLine = hCNetSDK.NET_DVR_RemoteControl(lUserID, 20005, null, 0); LOGGER.info("checkDeviceOnLine---isOnLine============{}", isOnLine); return isOnLine; } }
写在结尾
遇到的问题:无法上传图片。(官方文档是个坑)
可能原因:刚开始以为是设备不支持抓拍功能。
解决方式:一遍一遍地阅读官方文档,换了一个又一个接口,最后发现,官方文档上提示的抓拍功能流程图是基于海康摄像头的,但是我使用的设备是海康的门禁设备,两者虽然大体相似,但是还是有不同之处,对于不同的设备需要进行不同的判断。
如这次使用的设备是门禁设备,首先根据触发回调返回的lCommand 进行设备区分,本次测试返回的 lCommand = 0x5002, 即门禁主机报警信息,然后去官方文档上查看对应的sdk接收信息体,为NET_DVR_ACS_ALARM_INFO。这样才能正确接收设备传过来的数据,也能得到上传的图片及其对应的人员信息。