1 简介

在出差的过程中,曾经出现了一个微小人脸检测的需求,该算法的主要工作流程是从一张图片上通过算法识别出图片上的微小人脸。撇去算法如何实现,在这个微小人脸检测执行的过程中,图片的来源经过协商,希望通过海康威视摄像头的抓拍功能来实现,毕竟做安放平台摄像头是必不可少的设备,现场也采购了几个海康威视的人脸相机。这样也就定下来了实现的方案,即通过调用海康威视的设备SDK来实现摄像头的抓图的功能。然后通过HTTP连接的方式把该图片传送给算法识别,作为算法识别的数据来源,以满足微小人脸的测试需求。 而海康威视设备SDK为C++开发而成,而当时开发主要是使用Spring Boot框架,使用了Java开发语言,因此这便涉及到使用Java调用C++程序以调用C++的功能。

1.1 DLL含义

动态链接库(Dynamic Link Library)是一个包含可由多个程序,同时使用的代码和数据的库。

在百科上说,Comdlg32.dll执行与对话框有关的常见函数。因此,每个程序都可以使用该DLL中包含的功能来实现“打开对话框”。这有助于避免代码重写和存进内存的有效使用。通过使用dll,程序可以实现模块化,由相对独立的组件组成。因为模块是相互独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。

1.2 JNA作用

JNA框架由JNI发展而来,JNI最早是用来进行跨语言通信的框架,尤其使用Java调用C/C++交互的应用场景,当然这个文档要解决的通过Java程序调用C++开发的设备SDK也符合这样的场景。
JNI调用C/C++的过程如下图所示:

从上图可知,JNI调用的过程很复杂。
JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。使用JNI调用共享类库(.dll/.so文件)是非常麻烦的事情,既需要编写java代码,又要编写C语言的代理方法,这其中需要很多数据类型的转换,是让人非常头痛。JNA框架就是为了解决这些问题和繁琐的事情而开发的,它提供一组Java工具类用于在运行期动态访问系统本地共享类库而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射,大大降低了Java调用本体共享库的开发难度。JNA与.NET平台上的P/Invoke机制一样简单和方便。

简而言之,就是JNA在跨语言交互更有竞争力。

1.3 设备网络SDK

开始时,以为服务器会是Windows Server,因此在海康威视官网下载了win64的设备网络sdk包。笔者下载的win64下的SDK,解压之后目录结构如下:

2 海康威视SDK

2.1 SDK调用流程

在设备网络SDK使用手册中,可以看到设备网络SDK调用的主要流程包括:
初始化→注册→抓拍→反注册→反初始化

2.2 模块介绍

上述图示中虚线框是可选部分,不会影响其他流程和模块的功能使用。按实现功能的不同可以分成十个模块,实现每个模块的功能时初始化SDK、用户设备注册、注销设备和释放SDK资源这4个流程是必不可少的。

2.2.1 预览模块

该模块支持的功能从设备取实时码流,解码显示以及播放控制、抓图等功能。

2.2.2 回放和下载模块

可以通过按时间和按文件名的方式远程回放或者下载设备的录像文件,后续可以进行解码或者存储。同时还支持断点续传功能。

2.2.3 参数配置模块

设置和获取设备的参数,主要包括设备参数、网络参数、通道压缩参数、串口参数、报警参数、异常参数、交易信息和用户配置等参数信息。

2.2.4 远程设备维护模块

实现关闭设备、重启设备、恢复默认值、远程硬盘格式化、远程升级和配置文件导入/导出等维护工作。

2.2.5 语音对讲转发模块

实现和设备的语音数据对讲和语音数据获取,音频编码格式可以指定

2.2.6 报警模块

处理设备上传的各种报警信号。报警分为“布防”和“监听”两种方式,在采用监听方式并且不需要获取用户ID的情况下,报警模块可以无需进行“用户注册”操作步骤。

2.2.7 透明通道模块

透明通道是将IP数据报文解析后直接发送到串行口的一种技术。实际上起到了延伸串行设备控制距离的作用。可利用IP网络控制多种串行设备,如控制解码器、矩阵、报警主机、门禁、仪器仪表等串行设备,对用户来说,只看到点对点传输,无须关心网络传输过程,所以称为串口透明通道。 SDK提供485和232串口作为透明通道功能,其中要将232串口作为透明通道使用,首先必须在232串口的配置信息(NET_DVR_RS232CFG)中将工作模式选为透明通道,这样232串口才可作为透明通道使用。

2.2.8 云台控制模块

实现对云台的基本操作、预置点、巡航、轨迹和透明云台的控制。SDK将云台控制分为两种模式:一种是通过图像预览返回的句柄进行控制;另一种是无预览限制,通过用户注册ID号进行云台控制。

2.2.9 解码器功能模块

实现解码器设备的配置、解码控制等功能。SDK支持单路解码器和多路解码器,但目前以多路解码器为主流产品

2.2.10 行为识别模块

实现对智能产品的参数配置、报警上传和能力集获取等功能

2.3 摄像头抓拍

由此可以到,摄像机抓图需要依赖预览模块来实现。
其中,预览录像抓图模块流程图如下:

抓图实现分成两种方式:预览抓图和设备抓图,在实现时采用了设备抓图的方式,具体区别可以参见设备网络SDK使用手册.chm
相关的抓图接口如下:

    BOOL NET_DVR_CaptureJPEGPicture(LONG                 lUserID,LONG                 lChannel,  LPNET_DVR_JPEGPARA   lpJpegPara,char                 *sPicFileName);

3 源码实现

在源码实现时,显然要在Java程序中导入相关的dll,供程序JNA模块主动加载。由于在实现时服务器是固定的,可以把相关dll放在指定目录,供JNA模块加载即可。

3.1 注意事项

在解压后的设备SDK文件夹可以看到如下的结构

txt文件内容如下:

【注意事项】------------------------------------
1. 更新设备网络SDK时,SDK开发包【库文件】里的HCNetSDK.dll、HCCore.dll、PlayCtrl.dll、SuperRender.dll、AudioRender.dll、HCNetSDKCom文件夹等文件均要加载到程序里面,【HCNetSDKCom文件夹】(包含里面的功能组件dll库文件)需要和HCNetSDK.dll、HCCore.dll一起加载,放在同一个目录下,且HCNetSDKCom文件夹名不能修改。2. 如果自行开发软件不能正常实现相应功能,而且程序没有指定加载的dll库路径,请在程序运行的情况下尝试删除HCNetSDK.dll。如果可以删除,说明程序可能调用到系统盘Windows->System32目录下的dll文件,建议删除或者更新该目录下的相关dll文件;如果不能删除,dll文件右键选择属性确认SDK库版本。3. 如按上述步骤操作后还是不能实现相应功能,请根据NET_DVR_GetLastError返回的错误号判断原因。

3.2 HCNetSDK.java配置路径实现JNA载入dll

HCNetSDK.java文件存在于Java Demo中,但需要修改载入的路径。

package com.example.platform.sdk;import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.Union;
import com.sun.jna.examples.win32.GDI32.RECT;
import com.sun.jna.examples.win32.W32API;
import com.sun.jna.examples.win32.W32API.HWND;
import com.sun.jna.ptr.ByteByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.NativeLongByReference;
import com.sun.jna.ptr.ShortByReference;//SDK接口说明,HCNetSDK.dll
public interface HCNetSDK extends StdCallLibrary {//TODO 如果把dll打入jar包,则需要在运行时首先通过流把dll相关内容拷贝到本地目录,然后加载//TODO 比如说拷贝到系统本地目录C:\Windows\System32,此时加载目录为://TODO C:\Windows\System32\lib\HCNetSDKHCNetSDK INSTANCE = (HCNetSDK) Native.loadLibrary("D:\\Git\\screenshot\\src\\main\\java\\com\\example\\platform\\sdk\\lib\\HCNetSDK", HCNetSDK.class);/***宏定义***///常量public static final int MAX_NAMELEN = 16;    //DVR本地登陆名public static final int MAX_RIGHT = 32;    //设备支持的权限(1 -12表示本地权限,13-32表示远程权限)public static final int NAME_LEN = 32;    //用户名长度public static final int PASSWD_LEN = 16;    //密码长度
…
}

在这个过程中,有一个步骤较为关键,即Nativi.loadLibrary()函数的参数要通过绝对路径方式载入HCNetSDK,对应的文件HCNetSDK.dll文件。

这样在程序运行时,INSTANCE即为设备网络SDK的实例,通过该实例可以实现需要的操作。

3.3 抓图实现

3.3.1 controller

package com.example.platform.controller;import com.alibaba.fastjson.JSONObject;
import com.example.platform.common.CommonReturn;
import com.example.platform.model.LoginInfo;
import com.example.platform.sdk.HCNetSDK;
import com.example.platform.service.AsyncTask;
import com.sun.jna.NativeLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/*** 抓图** @Owner: SongQuanHeng* @Time: 2019/3/14-14:44* @Version:* @Change:*/
@RestController
public class ScreenShot {private final static Logger logger = LoggerFactory.getLogger(ScreenShot.class);private LoginInfo loginInfo;private String dirPath;private HCNetSDK.NET_DVR_DEVICEINFO_V30 deviceInfo;private NativeLong userID = new NativeLong(-1);static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;private final List<String> validKeys = new ArrayList<>(Arrays.asList("ip", "port", "userName", "password", "dirPath"));@Autowiredprivate AsyncTask asyncTask;// 负责初始化private boolean init() {logger.debug("Enter init");return hCNetSDK.NET_DVR_Init();}// 反初始化private boolean unInit() {return hCNetSDK.NET_DVR_Cleanup();}@RequestMapping("/connect")public String connect(@RequestBody JSONObject param) throws Exception {if (!hasValidKey(param)) {return CommonReturn.httpReturnFailure("参数有误");}if (userID.intValue()>0) {return CommonReturn.httpReturnFailure("设备已连接, 请断开连接然后重新连接");}this.getLoginInfoAndDir(param);if (!isExistDir(dirPath)) {return CommonReturn.httpReturnFailure("文件夹目录不存在");}// 去掉传入的dirPath末尾的\\\\while (dirPath.endsWith("\\")) {dirPath = dirPath.substring(0, dirPath.length()-1);}if (!init()) {int errorCode = hCNetSDK.NET_DVR_GetLastError();return CommonReturn.httpReturn(String.valueOf(errorCode), "初始化SDK失败");}deviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();userID = hCNetSDK.NET_DVR_Login_V30(loginInfo.getIp(), loginInfo.getPort(), loginInfo.getUserName(), loginInfo.getPassword(), deviceInfo);asyncTask.setConnected(true);asyncTask.executeAsyncTask(userID, dirPath);return CommonReturn.httpReturnSuccess();}@RequestMapping("/disconnect")public String disconnect() {asyncTask.setConnected(false);if (!hCNetSDK.NET_DVR_Logout(userID)){int errorCode = hCNetSDK.NET_DVR_GetLastError();logger.info("In disconnect NET_DVR_Logout errorCode: "+errorCode);return CommonReturn.httpReturnFailure("资源释放失败");}userID = new NativeLong(-1);if (!hCNetSDK.NET_DVR_Cleanup()){int errorCode = hCNetSDK.NET_DVR_GetLastError();logger.info("In disconnect NET_DVR_Cleanup errorCode: " + errorCode);return CommonReturn.httpReturnFailure("SDK反初始化失败");}return CommonReturn.httpReturnSuccess("断开连接,抓拍结束");}private boolean isExistDir(String directory) {File dir = new File(directory);return dir.exists() && dir.isDirectory();}private boolean hasValidKey(JSONObject param) {for (String key : validKeys) {if (!param.containsKey(key)){return false;}}return true;}private void getLoginInfoAndDir(JSONObject param) {loginInfo = new LoginInfo(param);dirPath = param.getString("dirPath").trim();}
}

Demo比较简单,connect作为Controller,可以接受REST请求,该请求的过程会进行初始化,用户注册,然后调用异步任务进行抓拍图像。其中抓图任务通过是通过注入的AsyncTask类实现的。

3.3.2 asyncTask

异步任务类源码如下:

package com.example.platform.service;import com.sun.jna.NativeLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import com.example.platform.sdk.HCNetSDK;/*** 异步任务执行类** @Owner: SongQuanHeng* @Time: 2019/3/14-17:02* @Version:* @Change:*/
@Service
public class AsyncTask {private final Logger logger = LoggerFactory.getLogger(this.getClass());private boolean connected = false;static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");public boolean isConnected() {return connected;}public void setConnected(boolean connected) {this.connected = connected;}@Asyncpublic void executeAsyncTask(NativeLong userID, String dirPath) throws Exception {logger.info("Enter executeAsyncTask");// 参数含义 参见chm文件HCNetSDK.NET_DVR_JPEGPARA jpegpara = new HCNetSDK.NET_DVR_JPEGPARA();jpegpara.wPicQuality = 3;jpegpara.wPicSize = 0xFF;logger.info(Thread.currentThread().getName());logger.info(dirPath);while (connected) {String fileName = getImgName();hCNetSDK.NET_DVR_CaptureJPEGPicture(userID, new NativeLong(1), jpegpara, dirPath+"\\"+fileName);int errCode = hCNetSDK.NET_DVR_GetLastError();if (errCode!=0) {logger.info("errorCode: "+errCode);}Thread.sleep(1000/4);logger.debug(dateFormat.format(new Date())+" produce Pic: "+dirPath+"\\"+fileName);}logger.info("Leave executeAsyncTask");}private String getImgName() {return UUID.randomUUID().toString()+".jpg";}
}

3.3.3 难点阐述

至此,即可实现Java调用C++功能(dll文件)的完整过程。其中最主要的工作即HCNetSDK.java文件的撰写,即Java的数据结构要映射到C/C++的数据结构的过程,在HCNetSDK.java中由海康威视的人做了很大一部分的工作,如果在实现某些特定的功能时,发现在HCNetSDK.java并未实现相应的数据结构,则需要开发人员自己去实现数据结构的映射,这是难点。但好在在摄像机抓拍图像是一个较为普通的工作,威视已经为我们提供了相应的数据结构映射。

boolean NET_DVR_CaptureJPEGPicture(NativeLong lUserID, NativeLong lChannel, NET_DVR_JPEGPARA lpJpegPara, String sPicFileName); //JNA
BOOL NET_DVR_CaptureJPEGPicture(LONG lUserID, LONG lChannel, LPNET_DVR_JPEGPARA lpJpegPara, char *sPicFileName); // C

表示图像质量的结构NET_DVR_JPEGPARA定义在HCNetSDK.java如下:

//图片质量public static class NET_DVR_JPEGPARA extends Structure {/*注意:当图像压缩分辨率为VGA时,支持0=CIF, 1=QCIF, 2=D1抓图,
当分辨率为3=UXGA(1600x1200), 4=SVGA(800x600), 5=HD720p(1280x720),6=VGA,7=XVGA, 8=HD900p
仅支持当前分辨率的抓图*/public short wPicSize;                /* 0=CIF, 1=QCIF, 2=D1 3=UXGA(1600x1200), 4=SVGA(800x600), 5=HD720p(1280x720),6=VGA*/public short wPicQuality;            /* 图片质量系数 0-最好 1-较好 2-一般 */}

3.3.4 JNA常用类型映射

有过跨语言、跨平台开发的程序员都知道、跨平台、跨语言交互的难点,就是不同语言之间数据类型不一致的问题。因为C/C++基本数据类型与Java数据类型不一致,因此必须采用某种机制让它们保持一致。JNA提供了一组基本数据类型的映射,帮助程序员进行跨语言交互的问题

Java primitive types (and their object equivalents) map directly to the native C type of the same size.

Native Type Size Java Type Common Windows Types
char 8-bit integer byte BYTE, TCHAR
short 16-bit integer short WORD
wchar_t 16/32-bit character char TCHAR
int 32-bit integer int DWORD
int boolean value boolean BOOL
long 32/64-bit integer NativeLong LONG
long long 64-bit integer long __int64
float 32-bit FP float
double 64-bit FP double
char* C string String LPTCSTR
void* pointer Pointer LPVOID, HANDLE, LPXXX

>Unsigned types use the same mappings as signed types. C enums are usually interchangeable with "int". 至于类型之间的对应,可以通过HCNetSDK.java文件和设备网络SDK使用手册.chm相互佐证,进而编写其他自定义的数据结构。

4 引用

JNA实战笔记汇总<一> 简单认识JNA|成功调用JNA

JNA理解

Windows下使用Java调用Hikvision设备网络SDK的使用指南相关推荐

  1. Linux下使用Java调用Hikvision设备网络SDK使用指南

    1 简介  由于在开发过程中,本来以为抓图项目会部署在Windows服务器上,但随着项目的进行发现项目需要部署在Linux系统,甚至是国产化平台银河麒麟上,但在部署时发现在国产化平台部署时出现缺包的问 ...

  2. java libusb_Windows下的Java访问USB设备解决之道(翻译Java libusb / libusb-win32 wrapper)收...

    Java libusb / libusb-win32 封装包 概要 Java libusb 封装包是对libsub和libusb-win32 USB library的Jave封装. libusb旨在创 ...

  3. 基于最新版设备网络SDK集成海康摄像头之Java Demo调试运行教程

    Java Demo运行截图: 由于涉及到空间隐私,部分位置会打码. 设备网络SDK 设备网络SDK是基于硬件的SDK,是基于设备私有网络通信协议开发的,为后端设备(嵌入式网络硬盘录像机.视频服务器). ...

  4. Windows下Libvirt Java API使用教程(二)- 接口使用说明

    介绍完libvirt Java API的部署工作: <Windows下Libvirt Java API使用教程(一)- 开发环境部署> 接下来我们就介绍一下接口的使用和代码样例. libv ...

  5. Windows下Libvirt Java API使用教程(三)- TLS认证访问和动态链接文件依赖

    之前已经介绍过了libvirt api的上手使用方式: <Windows下Libvirt Java API使用教程(二)- 接口使用说明> <Windows下Libvirt Java ...

  6. window与linux环境下,java调用c语言的方法

    windows环境下,java调用c语言的方法 1.环境 C语言编译器:工具比较多,本文使用的是gcc,带gcc的软件业比较丰富,我使用的是TDM-GCC, 注:网上很多使用的是MinGW,但我遇到了 ...

  7. windows下配置java

    WINDOWS下配置JAVA环境变量 JAVA需要的环境变量: JAVA需要配置的环境变量有三,分别是java_home环境变量.path环境变量和classpath环境变量. JAVA环境变量的意义 ...

  8. 基于Windows下处理Java错误:编码GBK的不可映射字符的解决方案

    基于Windows下处理Java错误:编码GBK的不可映射字符的解决方案 最近在研究Java,涉及命令行编译,使用notepad++编辑器,然后使用javac编译: 之前的几个文件没有中文的内容,都没 ...

  9. windows下查看硬盘序列号、设备序列号、操作系统版本和安装时间、系统启用时间等命令

    windows下查看硬盘序列号.设备序列号.操作系统版本和安装时间.系统启用时间等命令 最近由于工作需要查询一些硬盘序列号.设备序列号.操作系统版本和安装时间.系统启用时间等信息.所以对用到的命令进行 ...

最新文章

  1. android自定义离线地图,MapBox GL Android:已下载但未使用的自定义磁贴源的离线地图...
  2. 20220202--CTF刷题MISC方向--第5题--反编译
  3. “没有找到iliertutil.dll,因此这个应用程序未能启动。”文件拷贝 PE 或者 dos...
  4. kettle同步数据中文乱码问题解决
  5. 期刊投稿状态_在省级期刊上发表论文可以用于评定中级职称吗?
  6. mtk android 设置默认铃声,[转载]MTK修改铃声资源
  7. MFC 改变控件字体大小
  8. 监测http端口是否存活
  9. laravel 集合从父中移除_在 Laravel 7 中优雅使用 UUID 教程
  10. Android 系统签名实现的三种方式
  11. TensorFlow相关教程:使用tf.data加载图片
  12. 关于WiFi密码破解的一些心得
  13. 十进制转二进制、十六进制
  14. android q mix3,Android Q+5G 小米MIX3流畅播放8K视频
  15. 债券中的久期是什么意思
  16. “绿多多”绿色资产资讯:良设板+“空间优造”亮相雄安 绿色生态进击!
  17. What is road rage?
  18. Android横竖屏屏幕方向设置
  19. 工作中常见的开会问题
  20. 以WhatsApp为例,外贸人如何从0到1构建社交营销闭环全流程

热门文章

  1. python打包成exe导入文件_Pyinstaller(python打包为exe文件)
  2. e1载波的数据速率是_智能电表传输数据必须需要网线吗?--老王说表
  3. 中国电信正式发布5G定制网
  4. 远程连接redis一会又断开重连
  5. IT项目管理:IT项目集成管理
  6. CF- Educational Codeforces Round 97 (Rated for Div. 2)-1437B. Reverse Binary Strings【思维/贪心】
  7. cisco交换机安装操作系统ios
  8. 安卓调用日历提醒,并实现闹钟提醒功能,在miui上测试通过,日历提醒闹钟设置失败解决(附源码,源码已更新)
  9. 全民上云季,云服务器有哪些优势和特点?
  10. Digicert关于SSL证书域名验证(DCV)策略变更通知