现在制造业很多都是用的海康的摄像头,作为程序员有时候需要对接海康摄像头,实现门禁访问控制,监控预览,录像文件下载等功能。
一、开发环境准备
在海康官网下载sdk开发文档及库文件:
https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10
根据软件部署的平台选择对应的版本,这里以win64版本为例。
将下载好的库文件复制粘贴加入到java项目中,并改名为lib文件夹。
注意:demo里的examples.jar和jna.jar也需要放在lib文件下,以方便调试。
然后在project structure中加入lib库文件中。
将下载下来的demo中的文件复制粘贴到项目文件中,填入对应的摄像头的账号密码。执行程序,若显示登录成功,则表示调试成功。
二、实现java调用设备接口
打开下载的《设备网络sdk使用手册.chm》作为参考,在该手册中java如何对接海康摄像头有详细说明。
由于设备网络sdk是封装的动态链接库(windows的dll或者linux的so),各种开发语言对接sdk,都是通过加载动态库链接,调用动态库中的接口实现功能模块对接,因此,设备网络sdk的对接不区分开发语言,而且对接的流程和对应的接口都是通用的,各种语言调用动态库的方式有所不同。
java语言是通过jna的方式调用动态链接库中的接口,实现在java语言中调用c/c++语言封装的接口。
因此,java的类文件不需要编写任何业务代码实现某接口来调用设备,而是声明一个接口就能调用设备的功能了。
这只需要在一个java接口中描述目标native library的函数与结构,jna将自动实现java接口到native function的映射,而不需要编写任何native/jni代码,大大降低了java调用动态链接库的开发难度。
jna调用c/c++的过程大致如下:
(一)加载动态链接库
通过java调用海康官方提供的设备功能,首先需要自定义一个接口加载dll文件,比如demo中是声明hcnetsdk
的接口,该接口继承library 或 stdcalllibrary。
默认的是继承library ,如果动态链接库里的函数是以stdcall方式输出的,那么就继承stdcalllibrary。接口内部需要一个公共静态常量:instance,通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部dll/so的函数。
instance常量通过 native.loadlibrary() api函数获得,(新版本的jna中,常量是通过native.load()函数获取的)该函数有2个参数:
// sdk接口说明,hcnetsdk.dll public interface hcnetsdk extends stdcalllibrary { hcnetsdk instance = (hcnetsdk) native.loadlibrary("e:\\demo_test\\java_demo\\jna_test\\lib\\hcnetsdk.dll", hcnetsdk.class); // 动态库中结构体、接口描述 }
这里是采用的是绝对路径,为防止项目工程路径的变化,可以改为获取动态路径。
public class xxdemo { static hcnetsdk hcnetsdk = null; //动态库加载,根据软件所属操作系统的工程文件目录动态获取库文件路径 private static boolean createsdkinstance() { if (hcnetsdk == null) { synchronized (hcnetsdk.class) { string strdllpath = ""; try { if (osselect.iswindows()) //win系统加载库路径 strdllpath = system.getproperty("user.dir") + "\\lib\\hcnetsdk.dll"; else if (osselect.islinux()) //linux系统加载库路径 strdllpath = system.getproperty("user.dir") + "/lib/libhcnetsdk.so"; hcnetsdk = (hcnetsdk) native.loadlibrary(strdllpath, hcnetsdk.class); } catch (exception ex) { system.out.println("loadlibrary: " + strdllpath + " error: " + ex.getmessage()); return false; } } } return true; } public static void main(string[] args) throws interruptedexception { //调用函数之前先加载动态链接库 if (hcnetsdk == null) { if (!createsdkinstance()) { system.out.println("load sdk fail"); return; } } } }
类似的,别的dll库文件的接口也是如此进行声明。
// 播放库函数声明,playctrl.dll public interface playctrl extends stdcalllibrary{ playctrl instance = (playctrl) native.loadlibrary("e:\\demo_test\\java_demo\\jna_test\\lib\\playctrl.dll", playctrl.class); // 播放库中结构体,接口描述 }
(二)结构体、接口重定义
dll和so是c/c++语言函数的集合和容器,与java中的接口概念吻合,所以jna把dll文件和so文件看成一个个接口。
在jna中定义一个接口就是相当于了定义一个dll/so文件的描述文件,该接口代表了动态链接库中发布的所有函数,对于程序不需要的函数,可以不在接口中声明。
例如,官方demo中的hcnetsdk的java接口声明了hcnetsdk.dll/so文件很多的接口。在实际开发中可以不需要声明这么多接口,只取某些需要的接口重新进行分组。
若在《设备网络sdk使用手册》查找功能时发现某些接口在demo中没有,则可以在hcnetsdk.h
头文件搜索对应的接口,然后在java中声明该接口的定义。
1.类型映射
接口中使用的函数必须与链接库中的函数原型保持一致,因为c/c++的类型与java的类型是不一样的,动态库中的c/c++的数据类型必须转换成java对应类型,这就是类型映射(type mappings)。
默认的类型映射可以参考jna官网的类型映射表。(国内镜像链接 https://gitee.com/mirrors/jna/)
2.结构体和类的转换
java中没有结构体(struct)这种数据类型,jna为我们提供了structure这个类,只要继承该类,就可实现java结构体,相当于转换成java中的类。
例如,net_dvr_user_login_info结构体需要在hcnetsdk接口中进行重定义,转换方式如下:
//c++中net_dvr_user_login_info结构体定义 typedef struct{ char sdeviceaddress[net_dvr_dev_address_max_len]; byte byusetransport; // 是否启用能力集透传,0--不启用透传,默认,1--启用透传 word wport; char susername[net_dvr_login_username_max_len]; char spassword[net_dvr_login_passwd_max_len]; floginresultcallback cbloginresult; void *puser; bool buseasynlogin; byte byproxytype; // 0:不使用代理,1:使用标准代理,2:使用ehome代理 byte byuseutctime; // 0-不进行转换,默认,1-接口上输入输出全部使用utc时间,sdk完成utc时间与设备时区的转换,2-接口上输入输出全部使用平台本地时间,sdk完成平台本地时间与设备时区的转换 byte byloginmode; // 0-private 1-isapi 2-自适应 byte byhttps; // 0-不适用tls,1-使用tls 2-自适应 long iproxyid; // 代理服务器序号,添加代理服务器信息时,相对应的服务器数组下表值 byte byverifymode; // 认证方式,0-不认证,1-双向认证,2-单向认证;认证仅在使用tls的时候生效; byte byres3[119]; }net_dvr_user_login_info,*lpnet_dvr_user_login_info; // 宏定义 #define net_dvr_dev_address_max_len 129 #define net_dvr_login_username_max_len 64 #define net_dvr_login_passwd_max_len 64
注意转换之后,java类中的属性都为public,这意味着可以直接进行属性的修改和读取,而不需要进行get和set方法的声明和操作。
// sdk接口说明,hcnetsdk.dll public interface hcnetsdk extends stdcalllibrary { hcnetsdk instance = (hcnetsdk) native.loadlibrary("e:\\demo_test\\java_demo\\jna_test\\lib\\hcnetsdk.dll", hcnetsdk.class); // 动态库中结构体、接口描述 public static class net_dvr_user_login_info extends structure{ public byte[] sdeviceaddress = new byte[net_dvr_dev_address_max_len]; public byte byusetransport; public short wport; public byte[] susername = new byte[net_dvr_login_username_max_len]; public byte[] spassword = new byte[net_dvr_login_passwd_max_len]; public floginresultcallback cbloginresult; public pointer puser; public boolean buseasynlogin; public byte byproxytype; // 0:不使用代理,1:使用标准代理,2:使用ehome代理 public byte byuseutctime; // 0-不进行转换,默认,1-接口上输入输出全部使用utc时间,sdk完成utc时间与设备时区的转换,2-接口上输入输出全部使用平台本地时间,sdk完成平台本地时间与设备时区的转换 public byte byloginmode; // 0-private 1-isapi 2-自适应 public byte byhttps; // 0-不适用tls,1-使用tls 2-自适应 public int iproxyid; // 代理服务器序号,添加代理服务器信息时,相对应的服务器数组下表值 public byte byverifymode; // 认证方式,0-不认证,1-双向认证,2-单向认证;认证仅在使用tls的时候生效; public byte[] byres2 = new byte[119]; // 结构体中重写getfieldorder方法,fieldorder顺序要和结构体中定义的顺序保持一致 @override protected list getfieldorder(){ return arrays.aslist("sdeviceaddress","byusetransport","wport","susername","spassword", "cbloginresult","puser","buseasynlogin","byproxytype","byuseutctime", "byloginmode","byhttps","iproxyid","byverifymode","byres2"); } } // 常量(宏)定义 public static final int net_dvr_dev_address_max_len = 129; public static final int net_dvr_login_username_max_len = 64; public static final int net_dvr_login_passwd_max_len = 64; }
3.接口转换
在hcnetsdk接口中声明的方法要和开发包中hcnetsdk.h的头文件中声明的函数对应上,其中方法名、参数列表、返回值都要和hcnetsdk.h中的函数对应,hcnetsdk.h头文件的函数转换到java中声明,转换方式如下所示:
/******************************** sdk接口函数声明 *********************************/ // 初始化sdk,调用其他sdk函数的前提 net_dvr_api bool __stdcall net_dvr_init(); // 启用日志文件写入接口 net_dvr_api bool __stdcall net_dvr_setlogtofile(dword nloglevel ,char* strlogdir, bool bautodel); // 返回最后操作的错误码 net_dvr_api dword __stdcall net_dvr_getlasterror(); // 释放sdk资源,在程序结束之前调用 net_dvr_api bool __stdcall net_dvr_cleanup(); // 登录接口 net_dvr_api long __stdcall net_dvr_login_v40( lpnet_dvr_user_login_info plogininfo, lpnet_dvr_deviceinfo_v40 lpdeviceinfo ); // 用户注销 net_dvr_api bool __stdcall net_dvr_logout(long luserid); // 回调函数声明,登录状态回调函数 typedef void (callback *floginresultcallback) ( long luserid, dword dwresult, lpnet_dvr_deviceinfo_v30 lpdeviceinfo , void* puser );
转换到java中时,注意类型转换
// sdk接口说明,hcnetsdk.dll public interface hcnetsdk extends stdcalllibrary { hcnetsdk instance = (hcnetsdk) native.loadlibrary("e:\\demo_test\\java_demo\\jna_test\\lib\\hcnetsdk.dll", hcnetsdk.class); /*** api函数声明 ***/ // 初始化sdk,调用其他sdk函数的前提 boolean net_dvr_init(); // 启用日志文件写入接口 boolean net_dvr_setlogtofile(int blogenable , string strlogdir, boolean bautodel); // 返回最后操作的错误码 int net_dvr_getlasterror(); // 释放sdk资源,在程序结束之前调用 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); // 回调函数申明 public static interface floginresultcallback extends stdcallcallback{ // 登录状态回调函数 public int invoke(int luserid,int dwresult,net_dvr_deviceinfo_v30 lpdeviceinfo,pointer puser); } }
4.方法调用
经过上述的操作,jna工程已经创建完成,结构体和函数也在hcnetsdk
接口类中进行了转换,后续就可以在主类中实现调用。
以下为官方demo中的示例,以实现用户注册功能模块为例,解释了接口调用的流程。
public class jna_test { // 接口的实例,通过接口实例调用外部dll/so的函数 static hcnetsdk hcnetsdk = hcnetsdk.instance; // 用户登录返回句柄 static int luserid; int ierr = 0; public static void main(string[] args) throws interruptedexception { jna_test test01 = new jna_test(); // 初始化 boolean initsuc = hcnetsdk.net_dvr_init(); if (initsuc != true) { system.out.println("初始化失败"); } // 打印sdk日志 hcnetsdk.net_dvr_setlogtofile(3, ".\\sdklog\\", false); // 用户登陆操作 test01.login_v40("192.168.1.64",(short)8000,"admin","test12345"); /* *实现sdk中其余功能模快 */ thread.sleep(5000); //用户注销,释放sdk test01.logout(); } /** * * @param m_sdeviceip 设备ip地址 * @param wport 端口号,设备网络sdk登录默认端口8000 * @param m_susername 用户名 * @param m_spassword 密码 */ public void login_v40(string m_sdeviceip,short wport,string m_susername,string m_spassword) { /* 注册 */ // 设备登录信息 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(); 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_strlogininfo.wport =wport ; 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_strlogininfo.spassword = new byte[hcnetsdk.net_dvr_login_passwd_max_len]; system.arraycopy(m_spassword.getbytes(), 0, m_strlogininfo.spassword, 0, m_spassword.length()); // 是否异步登录:false- 否,true- 是 m_strlogininfo.buseasynlogin = false; // write()调用后数据才写入到内存中 m_strlogininfo.write(); luserid = hcnetsdk.net_dvr_login_v40(m_strlogininfo, m_strdeviceinfo); if (luserid == -1) { system.out.println("登录失败,错误码为" + hcnetsdk.net_dvr_getlasterror()); return; } else { system.out.println("登录成功!"); // read()后,结构体中才有对应的数据 m_strdeviceinfo.read(); return; } } //设备注销 sdk释放 public void logout() { if (luserid>=0) { if (hcnetsdk.net_dvr_logout(luserid) == false) { system.out.println("注销失败,错误码为" + hcnetsdk.net_dvr_getlasterror()); } system.out.println("注销成功"); hcnetsdk.net_dvr_cleanup(); return; } else{ system.out.println("设备未登录"); hcnetsdk.net_dvr_cleanup(); return; } } }
我自己也写了个demo,放在gitee仓库了。可以提供参考:https://gitee.com/zachlong/java-hik-camera
总结
到此这篇关于java对接海康摄像头的文章就介绍到这了,更多相关java对接海康摄像头内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论