java對接海康攝像頭的完整步驟記錄
現(xiàn)在制造業(yè)很多都是用的??档臄z像頭,作為程序員有時候需要對接??禂z像頭,實現(xiàn)門禁訪問控制,監(jiān)控預覽,錄像文件下載等功能。
一、開發(fā)環(huán)境準備
在??倒倬W(wǎng)下載SDK開發(fā)文檔及庫文件:
https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10
根據(jù)軟件部署的平臺選擇對應的版本,這里以win64版本為例。

將下載好的庫文件復制粘貼加入到java項目中,并改名為lib文件夾。

注意:demo里的examples.jar和jna.jar也需要放在lib文件下,以方便調(diào)試。

然后在project structure中加入lib庫文件中。

將下載下來的demo中的文件復制粘貼到項目文件中,填入對應的攝像頭的賬號密碼。執(zhí)行程序,若顯示登錄成功,則表示調(diào)試成功。

二、實現(xiàn)java調(diào)用設備接口
打開下載的《設備網(wǎng)絡SDK使用手冊.chm》作為參考,在該手冊中java如何對接海康攝像頭有詳細說明。

由于設備網(wǎng)絡SDK是封裝的動態(tài)鏈接庫(Windows的dll或者Linux的so),各種開發(fā)語言對接SDK,都是通過加載動態(tài)庫鏈接,調(diào)用動態(tài)庫中的接口實現(xiàn)功能模塊對接,因此,設備網(wǎng)絡SDK的對接不區(qū)分開發(fā)語言,而且對接的流程和對應的接口都是通用的,各種語言調(diào)用動態(tài)庫的方式有所不同。
java語言是通過JNA的方式調(diào)用動態(tài)鏈接庫中的接口,實現(xiàn)在java語言中調(diào)用C/C++語言封裝的接口。
因此,java的類文件不需要編寫任何業(yè)務代碼實現(xiàn)某接口來調(diào)用設備,而是聲明一個接口就能調(diào)用設備的功能了。
這只需要在一個java接口中描述目標native library的函數(shù)與結(jié)構(gòu),JNA將自動實現(xiàn)Java接口到native function的映射,而不需要編寫任何Native/JNI代碼,大大降低了Java調(diào)用動態(tài)鏈接庫的開發(fā)難度。
JNA調(diào)用C/C++的過程大致如下:

(一)加載動態(tài)鏈接庫
通過java調(diào)用??倒俜教峁┑脑O備功能,首先需要自定義一個接口加載dll文件,比如demo中是聲明HCNetSDK的接口,該接口繼承Library 或 StdCallLibrary。
默認的是繼承Library ,如果動態(tài)鏈接庫里的函數(shù)是以stdcall方式輸出的,那么就繼承StdCallLibrary。接口內(nèi)部需要一個公共靜態(tài)常量:INSTANCE,通過這個常量,就可以獲得這個接口的實例,從而使用接口的方法,也就是調(diào)用外部dll/so的函數(shù)。
INSTANCE常量通過 Native.loadLibrary() API函數(shù)獲得,(新版本的jna中,常量是通過Native.load()函數(shù)獲取的)該函數(shù)有2個參數(shù):
// 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);
// 動態(tài)庫中結(jié)構(gòu)體、接口描述
}
這里是采用的是絕對路徑,為防止項目工程路徑的變化,可以改為獲取動態(tài)路徑。
public class XXDemo {
static HCNetSDK hCNetSDK = null;
//動態(tài)庫加載,根據(jù)軟件所屬操作系統(tǒng)的工程文件目錄動態(tài)獲取庫文件路徑
private static boolean createSDKInstance() {
if (hCNetSDK == null) {
synchronized (HCNetSDK.class) {
String strDllPath = "";
try {
if (osSelect.isWindows())
//win系統(tǒng)加載庫路徑
strDllPath = System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll";
else if (osSelect.isLinux())
//Linux系統(tǒng)加載庫路徑
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 {
//調(diào)用函數(shù)之前先加載動態(tài)鏈接庫
if (hCNetSDK == null) {
if (!createSDKInstance()) {
System.out.println("Load SDK fail");
return;
}
}
}
}
類似的,別的dll庫文件的接口也是如此進行聲明。
// 播放庫函數(shù)聲明,PlayCtrl.dll
public interface PlayCtrl extends StdCallLibrary{
PlayCtrl INSTANCE = (PlayCtrl) Native.loadLibrary("E:\\DEMO_TEST\\JAVA_Demo\\JNA_TEST\\lib\\PlayCtrl.dll", PlayCtrl.class);
// 播放庫中結(jié)構(gòu)體,接口描述
}(二)結(jié)構(gòu)體、接口重定義
dll和so是C/C++語言函數(shù)的集合和容器,與Java中的接口概念吻合,所以JNA把dll文件和so文件看成一個個接口。
在JNA中定義一個接口就是相當于了定義一個DLL/SO文件的描述文件,該接口代表了動態(tài)鏈接庫中發(fā)布的所有函數(shù),對于程序不需要的函數(shù),可以不在接口中聲明。
例如,官方demo中的HCNetSDK的java接口聲明了HCNetSDK.dll/so文件很多的接口。在實際開發(fā)中可以不需要聲明這么多接口,只取某些需要的接口重新進行分組。
若在《設備網(wǎng)絡SDK使用手冊》查找功能時發(fā)現(xiàn)某些接口在demo中沒有,則可以在HCNetSDK.h頭文件搜索對應的接口,然后在java中聲明該接口的定義。

1.類型映射
接口中使用的函數(shù)必須與鏈接庫中的函數(shù)原型保持一致,因為C/C++的類型與Java的類型是不一樣的,動態(tài)庫中的C/C++的數(shù)據(jù)類型必須轉(zhuǎn)換成java對應類型,這就是類型映射(Type Mappings)。
默認的類型映射可以參考JNA官網(wǎng)的類型映射表。(國內(nèi)鏡像鏈接 https://gitee.com/mirrors/jna/)

2.結(jié)構(gòu)體和類的轉(zhuǎn)換
Java中沒有結(jié)構(gòu)體(struct)這種數(shù)據(jù)類型,JNA為我們提供了Structure這個類,只要繼承該類,就可實現(xiàn)java結(jié)構(gòu)體,相當于轉(zhuǎn)換成java中的類。
例如,NET_DVR_USER_LOGIN_INFO結(jié)構(gòu)體需要在HCNetSDK接口中進行重定義,轉(zhuǎn)換方式如下:
//C++中NET_DVR_USER_LOGIN_INFO結(jié)構(gòu)體定義
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-不進行轉(zhuǎn)換,默認,1-接口上輸入輸出全部使用UTC時間,SDK完成UTC時間與設備時區(qū)的轉(zhuǎn)換,2-接口上輸入輸出全部使用平臺本地時間,SDK完成平臺本地時間與設備時區(qū)的轉(zhuǎn)換
BYTE byLoginMode; // 0-Private 1-ISAPI 2-自適應
BYTE byHttps; // 0-不適用tls,1-使用tls 2-自適應
LONG iProxyID; // 代理服務器序號,添加代理服務器信息時,相對應的服務器數(shù)組下表值
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
注意轉(zhuǎn)換之后,java類中的屬性都為public,這意味著可以直接進行屬性的修改和讀取,而不需要進行g(shù)et和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);
// 動態(tài)庫中結(jié)構(gòu)體、接口描述
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-不進行轉(zhuǎn)換,默認,1-接口上輸入輸出全部使用UTC時間,SDK完成UTC時間與設備時區(qū)的轉(zhuǎn)換,2-接口上輸入輸出全部使用平臺本地時間,SDK完成平臺本地時間與設備時區(qū)的轉(zhuǎn)換
public byte byLoginMode; // 0-Private 1-ISAPI 2-自適應
public byte byHttps; // 0-不適用tls,1-使用tls 2-自適應
public int iProxyID; // 代理服務器序號,添加代理服務器信息時,相對應的服務器數(shù)組下表值
public byte byVerifyMode; // 認證方式,0-不認證,1-雙向認證,2-單向認證;認證僅在使用TLS的時候生效;
public byte[] byRes2 = new byte[119];
// 結(jié)構(gòu)體中重寫getFieldOrder方法,F(xiàn)ieldOrder順序要和結(jié)構(gòu)體中定義的順序保持一致
@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.接口轉(zhuǎn)換
在HCNetSDK接口中聲明的方法要和開發(fā)包中HCNetSDK.h的頭文件中聲明的函數(shù)對應上,其中方法名、參數(shù)列表、返回值都要和HCNetSDK.h中的函數(shù)對應,HCNetSDK.h頭文件的函數(shù)轉(zhuǎn)換到java中聲明,轉(zhuǎn)換方式如下所示:
/******************************** SDK接口函數(shù)聲明 *********************************/
// 初始化SDK,調(diào)用其他SDK函數(shù)的前提
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資源,在程序結(jié)束之前調(diào)用
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);
// 回調(diào)函數(shù)聲明,登錄狀態(tài)回調(diào)函數(shù)
typedef void (CALLBACK *fLoginResultCallBack) (
LONG lUserID, DWORD dwResult,
LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo ,
void* pUser );
轉(zhuǎn)換到java中時,注意類型轉(zhuǎn)換
// 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函數(shù)聲明 ***/
// 初始化SDK,調(diào)用其他SDK函數(shù)的前提
boolean NET_DVR_Init();
// 啟用日志文件寫入接口
boolean NET_DVR_SetLogToFile(int bLogEnable , String strLogDir, boolean bAutoDel);
// 返回最后操作的錯誤碼
int NET_DVR_GetLastError();
// 釋放SDK資源,在程序結(jié)束之前調(diào)用
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);
// 回調(diào)函數(shù)申明
public static interface FLoginResultCallback extends StdCallCallback{
// 登錄狀態(tài)回調(diào)函數(shù)
public int invoke(int lUserID,int dwResult,NET_DVR_DEVICEINFO_V30 lpDeviceinfo,Pointer pUser);
}
}
4.方法調(diào)用
經(jīng)過上述的操作,JNA工程已經(jīng)創(chuàng)建完成,結(jié)構(gòu)體和函數(shù)也在HCNetSDK接口類中進行了轉(zhuǎn)換,后續(xù)就可以在主類中實現(xiàn)調(diào)用。

以下為官方demo中的示例,以實現(xiàn)用戶注冊功能模塊為例,解釋了接口調(diào)用的流程。
public class jna_test {
// 接口的實例,通過接口實例調(diào)用外部dll/so的函數(shù)
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");
/*
*實現(xiàn)SDK中其余功能???
*/
Thread.sleep(5000);
//用戶注銷,釋放SDK
test01.Logout();
}
/**
*
* @param m_sDeviceIP 設備ip地址
* @param wPort 端口號,設備網(wǎng)絡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()調(diào)用后數(shù)據(jù)才寫入到內(nèi)存中
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()后,結(jié)構(gòu)體中才有對應的數(shù)據(jù)
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
總結(jié)
到此這篇關于java對接??禂z像頭的文章就介紹到這了,更多相關java對接??禂z像頭內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java源碼解析HashMap的tableSizeFor函數(shù)
今天小編就為大家分享一篇關于Java源碼解析HashMap的tableSizeFor函數(shù),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01
spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式
這篇文章主要介紹了spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
一文詳解Spring Aop @After(后置通知)的使用場景
@After 是 Spring AOP 中的另一種通知(Advice)類型,通常被稱為“后置通知”或“最終通知”,本文將通過詳細的代碼示例給大家介紹一下Spring Aop @After(后置通知)的使用場景,需要的朋友可以參考下2025-06-06

