package com.ard.device.component.sdk.hik.service.impl;
|
|
import com.ard.common.core.domain.AjaxResult;
|
import com.ard.common.utils.StringUtils;
|
import com.ard.device.camera.domain.ArdCamera;
|
import com.ard.device.camera.service.IArdCameraService;
|
import com.ard.device.channel.domain.ArdChannel;
|
import com.ard.device.camera.domain.CameraCmd;
|
import com.ard.device.channel.domain.VideoCompressionCfg;
|
import com.ard.device.channel.service.IArdChannelService;
|
import com.ard.device.component.sdk.common.GlobalVariable;
|
import com.ard.device.component.sdk.common.ResolutionEnum;
|
import com.ard.device.component.sdk.common.SdkErrorCodeEnum;
|
import com.ard.device.component.sdk.common.osSelect;
|
import com.ard.device.component.sdk.event.LoginEvent;
|
import com.ard.device.component.sdk.hik.lib.ExceptionCallBack;
|
import com.ard.device.component.sdk.hik.lib.HCNetSDK;
|
import com.ard.device.component.sdk.service.CameraSDK;
|
import com.sun.jna.Native;
|
import com.sun.jna.NativeLong;
|
import com.sun.jna.Pointer;
|
import com.sun.jna.ptr.IntByReference;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.stereotype.Service;
|
import javax.annotation.PostConstruct;
|
import javax.annotation.Resource;
|
import java.io.*;
|
import java.util.*;
|
|
import static com.ard.device.component.sdk.hik.lib.HCNetSDK.NET_DVR_CHECK_USER_STATUS;
|
|
/**
|
* @Description: 海康sdk策略
|
* @ClassName: DhSdkStrategy
|
* @Author: 刘苏义
|
* @Date: 2023年11月07日10:51:09
|
**/
|
@Service
|
@Slf4j(topic = "hikSdk")
|
public class HikVisionSDK implements CameraSDK {
|
|
@Resource
|
private ApplicationEventPublisher eventPublisher;
|
private static HCNetSDK hCNetSDK = null;
|
private static HCNetSDK.FExceptionCallBack fExceptionCallBack;//异常回调
|
|
@PostConstruct
|
public void initSdk() {
|
if (hCNetSDK == null) {
|
if (!CreateSDKInstance()) {
|
System.out.println("Load SDK fail");
|
return;
|
}
|
}
|
// 初始化
|
if (!hCNetSDK.NET_DVR_Init()) {
|
log.debug("SDK初始化失败");
|
}
|
if (fExceptionCallBack == null) {
|
fExceptionCallBack = new ExceptionCallBack();//异常回调
|
//设置异常回调函数(可在回调函数中获取设备上下线状态等)
|
if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, null)) {
|
log.debug("Set fExceptionCallBack function fail");
|
} else {
|
log.debug("Set fExceptionCallBack function successfully!");
|
}
|
}
|
//设置连接时间与重连时间
|
hCNetSDK.NET_DVR_SetConnectTime(2000, 1);
|
hCNetSDK.NET_DVR_SetReconnect(5000, true);
|
log.info("初始化海康sdk完成");
|
}
|
|
/**
|
* 登录
|
*/
|
@Override
|
public AjaxResult login(ArdCamera camera) {
|
try {
|
String m_sDeviceIP = camera.getIp();
|
String m_sUsername = camera.getUsername();
|
String m_sPassword = camera.getPassword();
|
short m_sPort = camera.getPort().shortValue();
|
//设备信息, 输出参数
|
HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();
|
HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();
|
// 注册设备-登录参数,包括设备地址、登录用户、密码等
|
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.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());
|
m_strLoginInfo.wPort = m_sPort;
|
m_strLoginInfo.byVerifyMode = 0;
|
m_strLoginInfo.byLoginMode = 0;
|
//是否异步登录:0- 否,1- 是 windowsSDK里是true和false
|
m_strLoginInfo.bUseAsynLogin = false;
|
m_strLoginInfo.write();
|
//同步登录
|
int loginId = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);
|
if (loginId < 0) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
camera.setChanNum(0);
|
camera.setLoginId(-1L);
|
camera.setState("0");
|
LoginEvent loginEvent = new LoginEvent(camera);
|
eventPublisher.publishEvent(loginEvent);
|
log.warn("设备[{}:{}]登录失败: {}({})", camera.getIp(), camera.getPort(),
|
SdkErrorCodeEnum.getDescByCode(errorCode), errorCode);
|
return AjaxResult.warn("设备[" + camera.getIp() + ":" + camera.getPort() + "]登录失败: " + SdkErrorCodeEnum.getDescByCode(errorCode) + "(" + errorCode + ")");
|
}
|
log.info("Login Success 【 {}:{} 】", camera.getIp(), camera.getPort());
|
|
GlobalVariable.loginMap.put(camera.getId(), loginId);
|
GlobalVariable.hikLoginMap.put(loginId, camera);
|
camera.setLoginId((long) loginId);
|
camera.setState("1");
|
int chanNum = m_strDeviceInfo.struDeviceV30.byChanNum;
|
int startChan = m_strDeviceInfo.struDeviceV30.byStartDChan + 1;
|
if (camera.getType().equals("2")) {
|
chanNum = m_strDeviceInfo.struDeviceV30.byIPChanNum;
|
startChan = m_strDeviceInfo.struDeviceV30.byStartDChan;
|
}
|
camera.setChanNum(chanNum);
|
camera.setStartChan(startChan);
|
//获取最新通道
|
List<ArdChannel> cameraChannelList = getChannels(camera);
|
if (!cameraChannelList.isEmpty()) {
|
camera.setChannelList(cameraChannelList);
|
}
|
LoginEvent loginEvent = new LoginEvent(camera);
|
eventPublisher.publishEvent(loginEvent);
|
return AjaxResult.success("设备登录成功");
|
} catch (Exception ex) {
|
log.error("注册设备异常", ex);
|
return AjaxResult.error("注册设备异常" + ex.getMessage());
|
}
|
}
|
|
/**
|
* 注销
|
*/
|
@Override
|
public boolean logout(String cameraId) {
|
if (!GlobalVariable.loginMap.containsKey(cameraId)) {
|
return false;
|
}
|
Integer userId = (Integer) GlobalVariable.loginMap.get(cameraId);
|
boolean b = hCNetSDK.NET_DVR_Logout(userId);
|
if (b) {
|
GlobalVariable.loginMap.remove(cameraId);
|
}
|
return b;
|
}
|
|
@Override
|
public boolean isOnLine(CameraCmd cmd) {
|
try {
|
String cameraId = cmd.getCameraId();
|
if (!GlobalVariable.loginMap.containsKey(cameraId)) {
|
return false;
|
}
|
Integer userId = (Integer) GlobalVariable.loginMap.get(cameraId);
|
return hCNetSDK.NET_DVR_RemoteControl(userId, NET_DVR_CHECK_USER_STATUS, null, 0);
|
} catch (Exception ex) {
|
log.error("检测在线异常:{}", ex.getMessage());
|
return false;
|
}
|
}
|
|
/**
|
* 获取通道压缩参数
|
*
|
*/
|
@Override
|
public VideoCompressionCfg getVideoCompressionCfg(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
if (!GlobalVariable.loginMap.containsKey(cameraId)) {
|
return null;
|
}
|
Integer userId = (Integer) GlobalVariable.loginMap.get(cameraId);
|
|
HCNetSDK.NET_DVR_COMPRESSIONCFG_V30 compressionCfg = new HCNetSDK.NET_DVR_COMPRESSIONCFG_V30();
|
compressionCfg.write();
|
Pointer point = compressionCfg.getPointer();
|
IntByReference ibrBytesReturned = new IntByReference(0);
|
|
boolean bool = hCNetSDK.NET_DVR_GetDVRConfig(userId, HCNetSDK.NET_DVR_GET_COMPRESSCFG_V30, chanNo, point,
|
compressionCfg.size(), ibrBytesReturned);
|
if (bool) {
|
compressionCfg.read();
|
|
//视频编码类型:0-私有264,1-标准h264,2-标准mpeg4,7-M-JPEG,8-MPEG2,9-SVAC,10-标准h265,0xfe- 自动(和源一致),0xff-无效
|
String videoEncType;
|
switch (compressionCfg.struNormHighRecordPara.byVideoEncType) {
|
case 0:
|
videoEncType = "私有264";
|
break;
|
case 1:
|
videoEncType = "标准h264";
|
break;
|
case 2:
|
videoEncType = "标准mpeg4";
|
break;
|
case 7:
|
videoEncType = "M-JPEG";
|
break;
|
case 8:
|
videoEncType = "MPEG2";
|
break;
|
case 9:
|
videoEncType = "SVAC";
|
break;
|
case 10:
|
videoEncType = "标准h265";
|
break;
|
default:
|
videoEncType = "未知";
|
break;
|
}
|
//视频码率
|
String videoBitrate;
|
switch (compressionCfg.struNormHighRecordPara.dwVideoBitrate) {
|
case 0:
|
videoBitrate = "保留";
|
break;
|
case 1:
|
videoBitrate = "16(保留)";
|
break;
|
case 2:
|
videoBitrate = "32";
|
break;
|
case 3:
|
videoBitrate = "48";
|
break;
|
case 4:
|
videoBitrate = "64";
|
break;
|
case 5:
|
videoBitrate = "80";
|
break;
|
case 6:
|
videoBitrate = "96";
|
break;
|
case 7:
|
videoBitrate = "128";
|
break;
|
case 8:
|
videoBitrate = "160";
|
break;
|
case 9:
|
videoBitrate = "192";
|
break;
|
case 10:
|
videoBitrate = "224";
|
break;
|
case 11:
|
videoBitrate = "256";
|
break;
|
case 12:
|
videoBitrate = "320";
|
break;
|
case 13:
|
videoBitrate = "384";
|
break;
|
case 14:
|
videoBitrate = "448";
|
break;
|
case 15:
|
videoBitrate = "512";
|
break;
|
case 16:
|
videoBitrate = "640";
|
break;
|
case 17:
|
videoBitrate = "768";
|
break;
|
case 18:
|
videoBitrate = "896";
|
break;
|
case 19:
|
videoBitrate = "1024";
|
break;
|
case 20:
|
videoBitrate = "1280";
|
break;
|
case 21:
|
videoBitrate = "1536";
|
break;
|
case 22:
|
videoBitrate = "1792";
|
break;
|
case 23:
|
videoBitrate = "2048K";
|
break;
|
case 24:
|
videoBitrate = "3072";
|
break;
|
case 25:
|
videoBitrate = "4096";
|
break;
|
case 26:
|
videoBitrate = "8192";
|
break;
|
case 27:
|
videoBitrate = "16384";
|
break;
|
default:
|
videoBitrate = "其他";
|
break;
|
}
|
//分辨率
|
String resolution =
|
ResolutionEnum.fromCode(compressionCfg.struNormHighRecordPara.byResolution).getResolution();
|
//帧率
|
String nFrameRate;
|
switch (compressionCfg.struNormHighRecordPara.dwVideoFrameRate) {
|
case 0:
|
nFrameRate = "0";
|
break;
|
case 1:
|
nFrameRate = "1/16";
|
break;
|
case 2:
|
nFrameRate = "1/8";
|
break;
|
case 3:
|
nFrameRate = "1/4";
|
break;
|
case 4:
|
nFrameRate = "1/2";
|
break;
|
case 5:
|
nFrameRate = "1";
|
break;
|
case 6:
|
nFrameRate = "2";
|
break;
|
case 7:
|
nFrameRate = "4";
|
break;
|
case 8:
|
nFrameRate = "6";
|
break;
|
case 9:
|
nFrameRate = "8";
|
break;
|
case 10:
|
nFrameRate = "10";
|
break;
|
case 11:
|
nFrameRate = "12";
|
break;
|
case 12:
|
nFrameRate = "16";
|
break;
|
case 13:
|
nFrameRate = "20";
|
break;
|
case 14:
|
nFrameRate = "15";
|
break;
|
case 15:
|
nFrameRate = "18";
|
break;
|
case 16:
|
nFrameRate = "22";
|
break;
|
case 17:
|
nFrameRate = "25";
|
break;
|
case 18:
|
nFrameRate = "30";
|
break;
|
case 19:
|
nFrameRate = "35";
|
break;
|
case 20:
|
nFrameRate = "40";
|
break;
|
default:
|
nFrameRate = "未知";
|
break;
|
}
|
VideoCompressionCfg videoCompressionCfg = new VideoCompressionCfg();
|
videoCompressionCfg.setVideoBitrate(videoBitrate);//比特率
|
videoCompressionCfg.setResolution(resolution);//分辨率
|
videoCompressionCfg.setVideoEncType(videoEncType);//编码
|
videoCompressionCfg.setFrameRate(nFrameRate);//帧率
|
return videoCompressionCfg;
|
} else {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
log.warn("获取配置失败:{} ", SdkErrorCodeEnum.getDescByCode(errorCode) + "(" + errorCode + ")");
|
return null;
|
}
|
}
|
|
/**
|
* 获取通道
|
*/
|
public List<ArdChannel> getChannels(ArdCamera camera) {
|
//获取通道
|
List<ArdChannel> channelList = new ArrayList<>();
|
try {
|
IntByReference ibrBytesReturned = new IntByReference(0);//获取IP接入配置参数
|
HCNetSDK.NET_DVR_IPPARACFG_V40 ipParaCfg = new HCNetSDK.NET_DVR_IPPARACFG_V40();
|
ipParaCfg.write();
|
//lpIpParaConfig 接收数据的缓冲指针
|
Pointer lpIpParaConfig = ipParaCfg.getPointer();
|
hCNetSDK.NET_DVR_GetDVRConfig(camera.getLoginId().intValue(), HCNetSDK.NET_DVR_GET_IPPARACFG_V40, 0,
|
lpIpParaConfig, ipParaCfg.size(), ibrBytesReturned);
|
ipParaCfg.read();
|
//log.debug("起始数字通道号:" + m_strIpParaCfg.dwStartDChan);
|
for (int iChanNo = 0; iChanNo < camera.getChanNum(); iChanNo++) {
|
ArdChannel channel = new ArdChannel();
|
int chanNo = iChanNo + camera.getStartChan();
|
HCNetSDK.NET_DVR_PICCFG_V40 strPicCfg = new HCNetSDK.NET_DVR_PICCFG_V40();
|
strPicCfg.dwSize = strPicCfg.size();
|
strPicCfg.write();
|
Pointer pStrPicCfg = strPicCfg.getPointer();
|
NativeLong lChannel = new NativeLong(chanNo);
|
IntByReference pInt = new IntByReference(0);
|
hCNetSDK.NET_DVR_GetDVRConfig(camera.getLoginId().intValue(), HCNetSDK.NET_DVR_GET_PICCFG_V40,
|
lChannel.intValue(), pStrPicCfg, strPicCfg.size(), pInt);
|
// log.error("获取图像参数失败,错误码:" + hCNetSDK.NET_DVR_GetLastError());
|
strPicCfg.read();
|
ipParaCfg.struStreamMode[iChanNo].read();
|
if (ipParaCfg.struStreamMode[iChanNo].byGetStreamType == 0) {
|
ipParaCfg.struStreamMode[iChanNo].uGetStream.setType(HCNetSDK.NET_DVR_IPCHANINFO.class);
|
ipParaCfg.struStreamMode[iChanNo].uGetStream.struChanInfo.read();
|
String channelName = new String(strPicCfg.sChanName, "GBK").trim();
|
channelName = channelName.isEmpty() ? "通道" + chanNo : channelName;
|
channel.setDeviceId(camera.getId());
|
channel.setName(channelName);
|
channel.setChanNo(chanNo);
|
//获取视频编码
|
CameraCmd cameraCmd = new CameraCmd(camera.getId(), chanNo);
|
VideoCompressionCfg videoCompressionCfg = getVideoCompressionCfg(cameraCmd);
|
if (StringUtils.isNotNull(videoCompressionCfg)) {
|
channel.setVideoCode(videoCompressionCfg.getVideoEncType());
|
}
|
//NVR过滤离线通道
|
if (camera.getType().equals("3") || camera.getType().equals("2")) {
|
if (ipParaCfg.struStreamMode[iChanNo].uGetStream.struChanInfo.byEnable != 1) {
|
continue;
|
}
|
}
|
channelList.add(channel);
|
}
|
}
|
} catch (Exception ex) {
|
log.error("获取IP通道异常:{}", ex.getMessage(), ex);
|
}
|
return channelList;
|
}
|
|
/**
|
* 动态库加载
|
*/
|
private static boolean CreateSDKInstance() {
|
if (hCNetSDK == null) {
|
synchronized (HCNetSDK.class) {
|
String strDllPath = "";
|
try {
|
String userDir = System.getProperty("user.dir");
|
|
if (osSelect.isWindows()) {
|
strDllPath = userDir + "\\lib\\hik\\win64\\HCNetSDK.dll";
|
} else if (osSelect.isLinux()) {
|
if (osSelect.isArm64()) {
|
strDllPath = userDir + "/lib/hik/linux-arm64/libhcnetsdk.so";
|
} else if (osSelect.isX64()) {
|
strDllPath = userDir + "/lib/hik/linux64/libhcnetsdk.so";
|
} else {
|
throw new UnsupportedOperationException("❌ 不支持的 Linux 架构: " + osSelect.getArch());
|
}
|
} else {
|
throw new UnsupportedOperationException("❌ 不支持的操作系统: " + osSelect.getOsName());
|
}
|
|
// 校验文件是否存在
|
File sdkFile = new File(strDllPath);
|
if (!sdkFile.exists()) {
|
System.err.println("❌ SDK 动态库不存在: " + strDllPath);
|
return false;
|
}
|
|
System.out.println("✅ 尝试加载动态库: " + strDllPath);
|
hCNetSDK = Native.loadLibrary(strDllPath, HCNetSDK.class);
|
System.out.println("✅ SDK 加载成功!");
|
} catch (Exception ex) {
|
System.err.println("❌ SDK 加载失败: " + strDllPath + "\n异常信息: " + ex.getMessage());
|
ex.printStackTrace();
|
return false;
|
}
|
}
|
}
|
return true;
|
}
|
}
|