最近公司一个项目要对接海康的SDK, 落到了我手里, 折磨了我一个月, 写个博客来吐槽, 本篇只通过报警布防介绍对接海康SDK, 实时预览和视频回放下次一定。
一. 简介
设备网络SDK是基于设备私有网络通信协议开发的,为海康威视各类硬件产品服务的配套模块,用于远程访问和控制设备的软件二次开发。
二. 下载
下载地址: https://open.hikvision.com/download
从地址中选择硬件产品, 选择合适的版本下载即可。内含SDK动态库, 开发文档, Demo示例
三. 使用流程

- 虚线指向为费必要操作;
- 功能模块可以单选多选不选;
SDK资源和设备操作为必要操作, 否则会无法操作或者没有效果.
四. 对接Demo
本人使用Java开发报警预防相关, 则以此为例, 在Spring Boot项目中对接海康SDK.
1. 官方Demo
为方便理清思路, 将官方Demo的主要流程代码放置在此
官方Dmeo:
在使用之前请确保按照说明文档中的方法, 将相关文件和文件夹放置妥当
public static void main(String[] args) throws InterruptedException {
// 加载SDK资源, 获取资源实例 if (hCNetSDK == null) {
if (!CreateSDKInstance()) {
System.out.println("Load SDK fail"); return; } } // 初始化 hCNetSDK.NET_DVR_Init(); // 加载日志 hCNetSDK.NET_DVR_SetLogToFile(3, "../sdklog", false); //设置报警回调函数 if (fMSFCallBack_V31 == null) {
fMSFCallBack_V31 = new FMSGCallBack_V31(); Pointer pUser = null; if (!hCNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fMSFCallBack_V31, pUser)) {
System.out.println("设置回调函数失败!"); return; } else {
System.out.println("设置回调函数成功!"); } } /* 设备上传的报警信息是COMM_VCA_ALARM(0x4993)类型, 在SDK初始化之后增加调用NET_DVR_SetSDKLocalCfg(enumType为NET_DVR_LOCAL_CFG_TYPE_GENERAL)设置通用参数NET_DVR_LOCAL_GENERAL_CFG的byAlarmJsonPictureSeparate为1, 将Json数据和图片数据分离上传,这样设置之后,报警布防回调函数里面接收到的报警信息类型为COMM_ISAPI_ALARM(0x6009), 报警信息结构体为NET_DVR_ALARM_ISAPI_INFO(与设备无关,SDK封装的数据结构),更便于解析。*/ HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG struNET_DVR_LOCAL_GENERAL_CFG = new HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG(); struNET_DVR_LOCAL_GENERAL_CFG.byAlarmJsonPictureSeparate = 1; //设置JSON透传报警数据和图片分离 struNET_DVR_LOCAL_GENERAL_CFG.write(); Pointer pStrNET_DVR_LOCAL_GENERAL_CFG = struNET_DVR_LOCAL_GENERAL_CFG.getPointer(); hCNetSDK.NET_DVR_SetSDKLocalCfg(17, pStrNET_DVR_LOCAL_GENERAL_CFG); // 设备登录 Alarm.Login_V40(0, "10.17.35.41", (short) 8000, "admin", "abcd1234"); // 设备布防 Alarm.SetAlarm(0); while (true) {
//这里加入控制台输入控制,是为了保持连接状态,当输入Y表示布防结束 System.out.print("请选择是否撤出布防(Y/N):"); Scanner input = new Scanner(System.in); String str = input.next(); if (str.equals("Y")) {
break; } } Alarm.Logout(0); }
此处注意官方
Demo中的while(true), 下面会有使用和相关说明
2. 应用于SpringBoot项目中
(1) 配置
@Data @Configuration @ConfigurationProperties(prefix = "hik") public class HCNetSDKConfig {
public static HCNetSDKConfig HC_NET_SDK_CONFIG; / * 登录的设备 */ public static List<DeviceApp> devices = new LinkedList<>(); / * SDK资源路径 */ private String sdkPath; / * 日志文件路径 */ private String logPath; / * 是否自动登录 */ private boolean deviceAutoLogin; / * 设备用户名 */ private String username; / * 设备密码 */ private String password; / * 超脑ip */ private String superBrainIp; @PostConstruct public void postConstruct() {
HC_NET_SDK_CONFIG = this; } }
- 项目的设备较少, 在配置类中用一个
List作为设备存档, 具体存档方式应以具体项目具体选择.- 项目中的所有设备用户名和密码一致, 所以此处使用配置作为数据项, 具体数据来源应以具体项目为准
配置文件:
hik: sdk-path: D:\\workspace\\idea\\hazard-chemical-web\\sdk device-auto-login: true super-brain-ip: 192.168.1.45 username: admin password:
(2) 应用
此处寡人是将此资源比作了一个应用, 所以代码中会有App字样
设备相关:
设备信息类:
@Data @Accessors(chain = true) public class Device {
private String ip; private short port; private String username; private String password; private boolean userAsync; private String deviceName; private String id; }
设备应用类:
@Data public class DeviceApp {
private static Logger log = LoggerFactory.getLogger(DeviceApp.class); private HCNetSDK hCNetSDK; / * 设备信息 */ private Device device; private int lUserID = -1; //用户句柄 private int lDChannel = -1; //IP通道号 private int lAlarmHandle = -1; // 报警布防句柄 / * 注销标记 */ private boolean logout; public DeviceApp(HCNetSDK hcNetSDK, Device device){
this.hCNetSDK = hcNetSDK; this.device = device; } / * 设备登录 * @return */ public boolean deviceLogin() {
if (!this.logout) {
return true; } log.info(device.getIp() + ": 设备登录!"); //登录设备,每一台设备分别登录; 登录句柄是唯一的,可以区分设备 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();//设备信息 String m_sDeviceIP = device.getIp();//设备ip地址 m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN]; System.arraycopy(m_sDeviceIP.getBytes(StandardCharsets.UTF_8), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length()); String m_sUsername = device.getUsername();//设备用户名 m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN]; System.arraycopy(m_sUsername.getBytes(StandardCharsets.UTF_8), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length()); String m_sPassword = device.getPassword();//设备密码 m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN]; System.arraycopy(m_sPassword.getBytes(StandardCharsets.UTF_8), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length()); m_strLoginInfo.wPort = device.getPort(); //SDK端口 m_strLoginInfo.bUseAsynLogin = device.isUserAsync(); //是否异步登录:0- 否,1- 是 m_strLoginInfo.write(); this.lUserID = this.hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo); if (this.lUserID == -1) {
log.error("登录失败,错误码为" + this.hCNetSDK.NET_DVR_GetLastError()); this.logout = true; return false; } else {
log.info(m_sDeviceIP + ":设备登录成功! " + "设备序列号:" + new String(m_strDeviceInfo.struDeviceV30.sSerialNumber).trim()); this.logout = false; m_strDeviceInfo.read(); } //byStartDChan为IP通道起始通道号, 预览回放NVR的IP通道时需要根据起始通道号进行取值 this.lDChannel = m_strDeviceInfo.struDeviceV30.byStartDChan; return true; } / * 设备注销 * @return */ public boolean deviceLogout() {
log.info("设备注销!"); this.logout = hCNetSDK.NET_DVR_Logout(lUserID); return this.logout; } / * 布防 * @param device */ public static void setAlarm(DeviceApp device) {
log.info("布放!"); //尚未布防,需要布防 if (device.getLAlarmHandle() < 0) {
//报警布防参数设置 HCNetSDK.NET_DVR_SETUPALARM_PARAM_V50 m_strAlarmInfo = new HCNetSDK.NET_DVR_SETUPALARM_PARAM_V50(); m_strAlarmInfo.dwSize = m_strAlarmInfo.size(); m_strAlarmInfo.byLevel = 1; //布防等级 m_strAlarmInfo.byAlarmInfoType = 1; // 智能交通报警信息上传类型:0- 老报警信息(NET_DVR_PLATE_RESULT),1- 新报警信息(NET_ITS_PLATE_RESULT) m_strAlarmInfo.byDeployType = 1; //布防类型:0-客户端布防,1-实时布防 m_strAlarmInfo.write(); String s = xmlData(); log.info("xml 参数: {}", s); Pointer mode = new Memory(s.length() + 1); byte[] bytes = s.getBytes(StandardCharsets.UTF_8); mode.write(0, bytes, 0, bytes.length); int lAlarmHandle = device.getHCNetSDK().NET_DVR_SetupAlarmChan_V50(device.getLUserID(), m_strAlarmInfo, mode, s.length() + 1); // int lAlarmHandle = device.getHCNetSDK().NET_DVR_SetupAlarmChan_V41(device.getLUserID(), m_strAlarmInfo); device.setLAlarmHandle(lAlarmHandle); log.info("lAlarmHandle: " + lAlarmHandle); if (lAlarmHandle == -1) {
log.error("布防失败,错误码为" + device.getHCNetSDK().NET_DVR_GetLastError()); } else {
log.info("布防成功"); } } else {
log.info("设备已经布防,请先撤防!"); } } @PreDestroy public void destroy() {
log.info("设备销毁!"); if (this.logout) {
return; } for (int i = 0;!this.logout && i < 3 ; i++) {
this.logout = deviceLogout(); } } / * 设备xml配置 * @return */ private static String xmlData() {
return "
"
+ "
list
" + "
"
+ "
"
+ "
fielddetection,
" + "
binary
" + "" + "
"
+ "
linedetection
" + "
binary
" + "" + "
"
+ "
group
" + "
binary
" + "" + "" + "
"
+ "" + "
"
+ "" + ""; } }
资源应用:
@Data @Component public class HCNetSDKApp {
private static Logger log = LoggerFactory.getLogger(HCNetSDKApp.class); @Autowired private HCNetSDKConfig hcNetSDKConfig; @Autowired private IVideoService videoService; / * 释放资源标记 */ private boolean cleanup = true; private HCNetSDK hCNetSDK; private FExceptionCallBack_Imp fExceptionCallBack; / * 初始化SDK资源 * @return * @throws CreateSDKException */ public boolean initSDKInstance() throws CreateSDKException {
if (!this.cleanup) {
return true; } log.info("SDK初始化!"); // 创建SDK实例 boolean createSDK = CreateSDKInstance(); if (!createSDK) {
log.error("创建SDK实例失败!"); throw new CreateSDKException("创建SDK实例失败!"); } else {
log.info("创建SDK实例成功!"); } // SDK资源初始化 boolean initSDK = hCNetSDK.NET_DVR_Init(); if (!initSDK) {
log.error("初始化SDK资源失败!"); hCNetSDK.NET_DVR_Cleanup(); throw new CreateSDKException("初始化SDK资源失败!"); } else {
this.cleanup = false; log.info("初始化SDK资源成功!"); } fExceptionCallBack = new FExceptionCallBack_Imp(); Pointer pUser = null; if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
log.info("初始化异常消息回调失败!"); // 初始化异常消息回调失败, 释放SDK资源 hCNetSDK.NET_DVR_Cleanup(); this.cleanup = true; throw new CreateSDKException("初始化异常消息回调失败!"); } else {
log.info("初始化异常消息回调成功!"); } //启动SDK写日志 hCNetSDK.NET_DVR_SetLogToFile(3, hcNetSDKConfig.getLogPath(), false); return true; } / * 自动登录 */ public void autoDevicesLogin() {
AtomicBoolean flag = new AtomicBoolean(false); QueryWrapper<VideoShowModel> wrapper = new QueryWrapper<>(); wrapper.isNotNull("IP"); wrapper.ne("IP", ""); List<Video> videos = videoService.getBaseMapper().selectList(wrapper); List<Device> devices = videos.stream().map(item -> {
if (hcNetSDKConfig.getSuperBrainIp().equals(item.getIp())) {
flag.set(true); } Device device = new Device(); device.setIp(item.getIp()); device.setId(item.getSheetId()); return device; }).collect(Collectors.toList()); if (!flag.get()) {
Device device = new Device(); device.setIp(hcNetSDKConfig.getSuperBrainIp()); devices.add(device); } devicesLogin(devices); } / * 设备登录 */ public void devicesLogin(List<Device> devices) {
for (Device device : devices) {
deviceLogin(device); } } / * 设备登录 * @param device * @return */ public DeviceApp deviceLogin(Device device) {
try {
DeviceApp deviceApp = new DeviceApp(this.hCNetSDK, device); // 设备登录注册 boolean deviceLogin = deviceApp.deviceLogin(); if (!deviceLogin) {
log.error("注册设备失败!"); throw new CreateSDKException("注册设备失败!"); } else {
log.info("注册设备成功!"); } HCNetSDKConfig.devices.add(deviceApp); log.info("已登录设备数量: {}", HCNetSDKConfig.devices.size()); return deviceApp; } catch (CreateSDKException e) {
e.printStackTrace(); return null; } } / * 释放SDK资源 * * @return */ public boolean cleanup() {
log.info("释放SDK资源!"); this.cleanup = hCNetSDK.NET_DVR_Cleanup(); return this.cleanup; } @PreDestroy public void destroy() {
log.info("SDK实例销毁!"); if (this.cleanup) {
return; } for (int i = 0; !this.cleanup && i < 3; i++) {
if (!HCNetSDKConfig.devices.isEmpty()) {
HCNetSDKConfig.devices.forEach(item -> {
boolean b = item.deviceLogout(); }); } } HCNetSDKConfig.devices.clear(); cleanup(); } static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {
public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
log.error("异常事件类型: {}", dwType); log.error("异常用户主键: {}", lUserID); log.error("异常处理: {}", lHandle); log.error("异常Pointer: {}", pUser); } } / * 动态库加载 * * @return */ private boolean CreateSDKInstance() {
log.info("创建SDK实例!"); String strDllPath = ""; log.info("hc net sdk config: {}", hcNetSDKConfig); try {
if (OSSelectUtil.isWindows()) //win系统加载库路径 strDllPath = hcNetSDKConfig.getSdkPath() + "\\windows\\HCNetSDK.dll"; else if (OSSelectUtil.isLinux()) //Linux系统加载库路径 strDllPath = hcNetSDKConfig.getSdkPath() + "/linux/libhcnetsdk.so"; this.hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class); this.cleanup = false; return true; } catch (Exception ex) {
log.error("loadLibrary: " + strDllPath + " Error: " + ex.getMessage()); return false; } } }
说明:
- 资源应用使用了
Spring的组件注解@Component, 因为改资源只需要加载一次即可, 可以引用Spring的单例模式- 注意: 在之前的配置项中,
sdk-path使用了\\作为目录分隔符, 本项目是在windows服务器上运行, 所以使用windows目录格式, 尝试过使用/和\两种方式, 都失败了,具体原因,我知道不知道,你自己体会吧.- 此处的设备登录相关内容是本人项目中查询相关设备,然后调用设备中的登录方法,不一定非要在此处,可将其作为相关案例使用, 需根据项目要求进行合理处理.
(3) 流程
流程启动类
@Service public class AlarmListenerServiceImpl implements IAlarmListenerService {
@Autowired private HCNetSDKApp hcNetSDKApp; @PostConstruct public void startListener() {
AlarmListenerTask task = new AlarmListenerTask(hcNetSDKApp); Thread thread = new Thread(task); thread.setDaemon(true); thread.start(); } }
- 启动方式有很多, 可以选择手动启动, 也可以选择项目运行时启动, 此处项目中采用项目启动时启动
- 项目启动时执行某些方法的方式有多种, 可参照博文: Springboot启动后执行方法的四种方式
- 此处使用新建守护线程的方式启动报警布防功能, 下方代码展示后便会知晓.
流程任务类
public class AlarmListenerTask implements Runnable {
private static Logger log = LoggerFactory.getLogger(AlarmListenerTask.class); private HCNetSDKApp hcNetSDKApp; public AlarmListenerTask(HCNetSDKApp hcNetSDKApp) {
this.hcNetSDKApp = hcNetSDKApp; } @Override public void run() {
try {
if (hcNetSDKApp.getHCNetSDK() == null) {
boolean b = hcNetSDKApp.initSDKInstance(); } } catch (CreateSDKException e) {
log.error("SDK实例创建失败!"); } FMSGCallBack_V31 fMSFCallBack_V31 = new FMSGCallBack_V31(); Pointer pUser = null; if (!hcNetSDKApp.getHCNetSDK().NET_DVR_SetDVRMessageCallBack_V50(0, fMSFCallBack_V31, pUser)) {
log.error("设置回调函数失败!"); return; } else {
log.info("设置回调函数成功!"); } /* 设备上传的报警信息是COMM_VCA_ALARM(0x4993)类型, 在SDK初始化之后增加调用NET_DVR_SetSDKLocalCfg(enumType为NET_DVR_LOCAL_CFG_TYPE_GENERAL)设置通用参数NET_DVR_LOCAL_GENERAL_CFG的byAlarmJsonPictureSeparate为1, 将Json数据和图片数据分离上传,这样设置之后,报警布防回调函数里面接收到的报警信息类型为COMM_ISAPI_ALARM(0x6009), 报警信息结构体为NET_DVR_ALARM_ISAPI_INFO(与设备无关,SDK封装的数据结构),更便于解析。*/ HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG struNET_DVR_LOCAL_GENERAL_CFG = new HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG(); struNET_DVR_LOCAL_GENERAL_CFG.byAlarmJsonPictureSeparate = 1; //设置JSON透传报警数据和图片分离 struNET_DVR_LOCAL_GENERAL_CFG.write(); Pointer pStrNET_DVR_LOCAL_GENERAL_CFG = struNET_DVR_LOCAL_GENERAL_CFG.getPointer(); hcNetSDKApp.getHCNetSDK().NET_DVR_SetSDKLocalCfg(17, pStrNET_DVR_LOCAL_GENERAL_CFG); // 设备登录 hcNetSDKApp.autoDevicesLogin(); log.info("布防设备数量: {}", HCNetSDKConfig.devices.size()); // 设备布防 HCNetSDKConfig.devices.forEach(DeviceApp::setAlarm); // 维持线程 while (true) {
} } }
- 此任务类中的
run方法中便是整个布防流程while(true){}这个位置的代码即是前面官方Demo中的while代码的修改
关于为什么新建一个守护线程处理流程:
SDK的模块功能需要维持一条线程保持运转,官方Demo中的while就是为了维持线程, 即使它被卡在while处无法继续向下进行,
猜测:SDK为当前线程创建了守护线程进行布防监听, 当前线程中断了,布防的守护线程就挂掉了.main线程在加载资源后就会结束, 本人在初次尝试时即使用main执行流程,结果是报警回调后续便不再触发,所以选择新建一条线程来进行流程操作.- 使用守护线程是因为代码中有死循环代码, 守护线程在主线程结束后便会被杀死.但前两天看博文,好像
spring项目在结束程序时会杀死所有线程,具体我不太了解,希望有大佬解答.
如果有幸有人能看到这篇博文, 请转告身边的人, 不要为难开发人员了, 他们不是万能的, 劝大家善良
日 期 : 2022 − 07 − 01 \color{#00FF00}{日期:2022-07-01} 日期:2022−07−01
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/221575.html原文链接:https://javaforall.net
