Java二次開發海康SDK-對接門禁機

你比從前快樂; 發表於 2021-12-01
Java

寫在最前

SDK版本:CH-HCNetSDKV6.1.6.45_build20210302_win64

參考文件:海康SDK使用手冊_V6.1

對接測試裝置型號:DS-K1T671M

裝置序列號:E50247795

 

業務目標

使用門禁裝置實現對人臉的抓拍,將抓拍的人臉與其對應的資料進行上傳。

 

業務流程圖:

Java二次開發海康SDK-對接門禁機

 

 

業務流程節點解釋:

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

 

Java二次開發海康SDK-對接門禁機

 

 

2.建立好springboot專案,將這個程式包裡面的庫檔案引進去。

Java二次開發海康SDK-對接門禁機

 

3.將程式包裡面它提供的 HCNetSDK.java 複製到你的專案裡面,並修改你剛才放的庫檔案路徑,注意以.dll結尾

Java二次開發海康SDK-對接門禁機

 

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。這樣才能正確接收裝置傳過來的資料,也能得到上傳的圖片及其對應的人員資訊。