package com.ard.work.sdk.hik.service; import com.ard.common.core.constant.CacheConstants; import com.ard.common.core.domain.R; import com.ard.common.core.exception.ServiceException; import com.ard.common.core.exception.device.CameraSDKException; import com.ard.common.core.utils.StringUtils; import com.ard.common.core.utils.file.FileMultipartFile; import com.ard.common.core.utils.file.FileUtils; import com.ard.common.core.utils.file.MimeTypeUtils; import com.ard.common.redis.service.RedisService; import com.ard.system.api.RemoteFileService; import com.ard.system.api.domain.SysFile; import com.ard.work.api.domian.*; import com.ard.work.device.camera.domain.PtzParamDTO; import com.ard.work.device.camera.service.IArdCameraPtzScopeService; import com.ard.work.device.terrain.domain.ArdTerrainMark; import com.ard.work.device.terrain.service.IArdTerrainMarkService; import com.ard.work.event.LoginEvent; import com.ard.work.point.tower.domain.ArdTower; import com.ard.work.point.tower.service.IArdTowerService; import com.ard.work.point.well.service.IArdWellService; import com.ard.work.sdk.common.GlobalVariable; import com.ard.work.sdk.common.ResolutionEnum; import com.ard.work.sdk.common.SdkErrorCodeEnum; import com.ard.work.sdk.common.osSelect; import com.ard.work.sdk.hik.lib.ExceptionCallBack; import com.ard.work.sdk.hik.lib.HCNetSDK; import com.ard.work.sdk.hp.cache.CameraSerialCache; import com.ard.work.sdk.model.DeviceCFG; import com.ard.work.sdk.model.PtzScope; import com.ard.work.sdk.service.CameraSDK; import com.ard.work.utils.FFmpegUtils; import com.ard.work.utils.gis.GisUtil; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.ptr.IntByReference; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.util.StopWatch; import org.springframework.web.multipart.MultipartFile; import java.awt.geom.Point2D; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; import static com.ard.work.sdk.dh.lib.ToolKits.getErrorCodePrint; import static com.ard.work.sdk.hik.lib.HCNetSDK.*; /** * @Description: 海康抽象类 * @ClassName: DhSdkStrategy * @Author: 刘苏义 * @Date: 2023年11月07日10:51:09 **/ @Slf4j public abstract class AbstractHikVisionSDK implements CameraSDK { @Resource private RemoteFileService remoteFileService; @Resource private IArdTerrainMarkService ardTerrainMarkService; @Resource private IArdCameraPtzScopeService ardCameraPtzScopeService; @Resource private RedisService redisService; @Value("${sdk.tempDir}") private String tempDir; @Resource private ApplicationEventPublisher eventPublisher; @Resource private IArdTowerService ardTowerService; @Resource private IArdWellService ardWellService; protected static HCNetSDK hCNetSDK = null; private static HCNetSDK.FExceptionCallBack fExceptionCallBack;//异常回调 static { initSdk(); } public static 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完成"); } /** * 登录(海康) * 刘苏义 * 2024/9/5 */ @Override public void login(ArdCamera camera) { String ip = camera.getIp(); String username = camera.getUsername(); String password = camera.getPassword(); short port = camera.getPort().shortValue(); // 设备信息输出参数 NET_DVR_DEVICEINFO_V40 deviceInfo = new NET_DVR_DEVICEINFO_V40(); NET_DVR_USER_LOGIN_INFO loginInfo = new NET_DVR_USER_LOGIN_INFO(); // 设置登录信息 loginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN]; System.arraycopy(ip.getBytes(), 0, loginInfo.sDeviceAddress, 0, ip.length()); loginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN]; System.arraycopy(username.getBytes(), 0, loginInfo.sUserName, 0, username.length()); loginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN]; System.arraycopy(password.getBytes(), 0, loginInfo.sPassword, 0, password.length()); loginInfo.wPort = port; loginInfo.byVerifyMode = 0; loginInfo.byLoginMode = 0; loginInfo.bUseAsynLogin = false; loginInfo.write(); // 调用海康 SDK 登录 int loginId = hCNetSDK.NET_DVR_Login_V40(loginInfo, deviceInfo); if (loginId < 0) { handleLoginFail(camera); } else { // 登录成功处理 handleLoginSuccess(camera, loginId, deviceInfo); } // 发布登录事件 eventPublisher.publishEvent(new LoginEvent(camera)); } /** * 登录失败处理 */ private void handleLoginFail(ArdCamera camera) { camera.setChanNum(0); camera.setLoginId(-1L); camera.setState("0"); camera.setChannelList(null); int errCode = hCNetSDK.NET_DVR_GetLastError(); log.warn("设备[{}:{}]登录失败,错误码:{} 原因:{}", camera.getIp(), camera.getPort(), errCode, SdkErrorCodeEnum.getDescByCode(errCode)); } /** * 登录成功处理 */ private void handleLoginSuccess(ArdCamera camera, int loginId, NET_DVR_DEVICEINFO_V40 deviceInfo) { camera.setLoginId((long) loginId); camera.setState("1"); int chanNum = deviceInfo.struDeviceV30.byChanNum; int startChan = deviceInfo.struDeviceV30.byStartDChan + 1; // 如果是终端或者超脑,需要特殊处理 if ("2".equals(camera.getType()) || "3".equals(camera.getType())) { chanNum = deviceInfo.struDeviceV30.byIPChanNum == 0 ? 64 : deviceInfo.struDeviceV30.byIPChanNum; startChan = deviceInfo.struDeviceV30.byStartDChan; } camera.setChanNum(chanNum); camera.setStartChan(startChan); GlobalVariable.hikLoginMap.put(loginId, camera); // 获取最新通道 List channels = getChannels(camera); if (!channels.isEmpty()) { camera.setChannelList(channels); } log.info("设备登录成功 [{}:{}]", camera.getIp(), camera.getPort()); } /** * 注销登录 * * @return boolean * @author 刘苏义 * @date 2024/9/5 16:58 */ @Override public void logout(String cameraId) { Long loginId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId); if (loginId == null) { return; } Integer lSerialHandle = CameraSerialCache.get(cameraId); if (lSerialHandle != null) { CameraSerialCache.remove(cameraId); if (hCNetSDK.NET_DVR_SerialStop(lSerialHandle)) { log.info("【{}】关闭透明串口成功", cameraId); } } hCNetSDK.NET_DVR_Logout(loginId.intValue()); } /** * 是否在线 * * @param cmd 相机命令 */ @Override public Boolean isOnLine(CameraCmd cmd) { try { int userId = cmd.getLoginId().intValue(); return hCNetSDK.NET_DVR_RemoteControl(userId, NET_DVR_CHECK_USER_STATUS, null, 0); } catch (Exception ex) { log.error("检测在线异常:{}", ex.getMessage()); return false; } } /** * 带速度的云台控制操作 * * @param cmd 相机命令 * @return true 表示控制命令发送成功 * @throws CameraSDKException SDK调用失败时抛出 */ @Override public Boolean pTZControl(CameraCmd cmd) { String cameraId = cmd.getCameraId(); int userId = cmd.getLoginId().intValue(); // 已保证登录成功 int dwStop = cmd.isEnable() ? 0 : 1; int dwPTZCommand; switch (cmd.getCode()) { case 1: dwPTZCommand = HCNetSDK.UP_LEFT; break; case 2: dwPTZCommand = HCNetSDK.TILT_UP; break; case 3: dwPTZCommand = HCNetSDK.UP_RIGHT; break; case 4: dwPTZCommand = HCNetSDK.PAN_LEFT; break; case 5: dwPTZCommand = HCNetSDK.RUN_SEQ; break; case 6: dwPTZCommand = HCNetSDK.PAN_RIGHT; break; case 7: dwPTZCommand = HCNetSDK.DOWN_LEFT; break; case 8: dwPTZCommand = HCNetSDK.TILT_DOWN; break; case 9: dwPTZCommand = HCNetSDK.DOWN_RIGHT; break; case 10: dwPTZCommand = HCNetSDK.ZOOM_IN; break; case 11: dwPTZCommand = HCNetSDK.ZOOM_OUT; break; case 12: dwPTZCommand = HCNetSDK.FOCUS_NEAR; break; case 13: dwPTZCommand = HCNetSDK.FOCUS_FAR; break; case 14: dwPTZCommand = HCNetSDK.IRIS_OPEN; break; case 15: dwPTZCommand = HCNetSDK.IRIS_CLOSE; break; case 16: dwPTZCommand = HCNetSDK.WIPER_PWRON; break; default: return false; // 或者记录日志 } boolean success = hCNetSDK.NET_DVR_PTZControlWithSpeed_Other( userId, cmd.getChanNo(), dwPTZCommand, dwStop, cmd.getSpeed() ); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); log.warn("【{}】PTZ控制失败,错误码:{}, 原因:{}", cameraId, errorCode, SdkErrorCodeEnum.getDescByCode(errorCode)); } return success; } /** * 设置聚焦值 * * @param cmd 相机命令 * @return true 成功,false 失败 */ @Override public Boolean setFocusPos(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer channelNum = cmd.getChanNo(); Integer dwFocusPos = cmd.getDwFocusPos(); int userId = cmd.getLoginId().intValue(); try { HCNetSDK.NET_DVR_STD_CONFIG stdCfg = new HCNetSDK.NET_DVR_STD_CONFIG(); HCNetSDK.NET_DVR_GIS_INFO gisInfo = new HCNetSDK.NET_DVR_GIS_INFO(); gisInfo.struPtzPosEx.dwFocus = dwFocusPos; stdCfg.read(); IntByReference channelRef = new IntByReference(channelNum); stdCfg.lpInBuffer = gisInfo.getPointer(); stdCfg.lpCondBuffer = channelRef.getPointer(); stdCfg.dwCondSize = 4; stdCfg.dwInSize = gisInfo.size(); stdCfg.write(); // 写入前必须调用 boolean success = hCNetSDK.NET_DVR_SetSTDConfig(userId, NET_DVR_GET_GISINFO, stdCfg); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("【{}】设置聚焦值失败,错误码:{},原因:{}", cameraId, errorCode, errorDesc); return false; } return true; } catch (Exception e) { log.error("【{}】设置聚焦值异常: {}", cameraId, e.getMessage(), e); return false; } } /** * 获取聚焦值 * * @param cmd 相机命令 */ @Override public Integer getFocusPos(CameraCmd cmd) { try { String cameraId = cmd.getCameraId(); Integer channelNum = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); HCNetSDK.NET_DVR_STD_CONFIG stdCfg = new HCNetSDK.NET_DVR_STD_CONFIG(); HCNetSDK.NET_DVR_GIS_INFO gisInfo = new HCNetSDK.NET_DVR_GIS_INFO(); stdCfg.read(); IntByReference channelRef = new IntByReference(channelNum); stdCfg.lpCondBuffer = channelRef.getPointer(); stdCfg.dwCondSize = 4; stdCfg.lpOutBuffer = gisInfo.getPointer(); stdCfg.dwOutSize = gisInfo.size(); stdCfg.write(); // 设置前需要 write() boolean success = hCNetSDK.NET_DVR_GetSTDConfig(userId, NET_DVR_GET_GISINFO, stdCfg); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("获取聚焦值失败,错误码:{},原因:{}", errorCode, errorDesc); return null; // 返回 null 表示失败 } gisInfo.read(); return gisInfo.struPtzPosEx.dwFocus; } catch (Exception ex) { log.error("获取聚焦值异常:{}", ex.getMessage()); return null; // 返回 null 表示失败 } } /** * 设置预置点 * * @param cmd 相机命令 */ @Override public Boolean setPreset(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer channelNum = cmd.getChanNo(); Integer presetIndex = cmd.getPresetIndex(); int userId = cmd.getLoginId().intValue(); try { boolean success = hCNetSDK.NET_DVR_PTZPreset_Other(userId, channelNum, SET_PRESET, presetIndex); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("【{}】预置点设置失败,错误码:{},原因:{}", cameraId, errorCode, errorDesc); return false; } log.info("【{}】预置点设置成功: {}", cameraId, presetIndex); return true; } catch (Exception ex) { log.error("【{}】设置预置点异常: {}", cameraId, ex.getMessage(), ex); return false; } } /** * 转到预置点 * * @param cmd 相机命令 */ @Override public Boolean gotoPreset(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer channelNum = cmd.getChanNo(); Integer presetIndex = cmd.getPresetIndex(); int userId = cmd.getLoginId().intValue(); try { boolean success = hCNetSDK.NET_DVR_PTZPreset_Other(userId, channelNum, GOTO_PRESET, presetIndex); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("【{}】调用预置点失败,错误码:{}, 原因:{}", cameraId, errorCode, errorDesc); return false; } log.info("【{}】调用预置点成功: {}", cameraId, presetIndex); return true; } catch (Exception ex) { log.error("【{}】调用预置点异常: {}", cameraId, ex.getMessage(), ex); return false; } } /** * 获取通道压缩参数 * * @param cmd 相机命令 * @return VideoCompressionCfg * @author 刘苏义 * @date 2024/9/5 16:59 */ @Override public VideoCompressionCfg getVideoCompressionCfg(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); NET_DVR_COMPRESSIONCFG_V30 compressionCfg = new 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) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("获取通道压缩参数失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return null; } 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; } /** * 获取ptz信息 * * @param cmd 相机命令 * @return AjaxResult * @author 刘苏义 * @date 2024/9/5 16:59 */ @Override public PtzDto getPtz(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); NET_DVR_PTZPOS m_ptzPosCurrent = new NET_DVR_PTZPOS(); Pointer point = m_ptzPosCurrent.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); m_ptzPosCurrent.write(); boolean bool = hCNetSDK.NET_DVR_GetDVRConfig(userId, HCNetSDK.NET_DVR_GET_PTZPOS, chanNo, point, m_ptzPosCurrent.size(), ibrBytesReturned); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("获取ptz失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return null; } m_ptzPosCurrent.read(); // DecimalFormat df = new DecimalFormat("0.0");//设置保留位数 //16进制转Integer后除10,保留小数点1位 //实际显示的PTZ值是获取到的十六进制值的十分之一, //如获取的水平参数P的值是0x1750,实际显示的P值为175度; //获取到的垂直参数T的值是0x0789,实际显示的T值为78.9度; //获取到的变倍参数Z的值是0x1100,实际显示的Z值为110倍。 BigDecimal b = BigDecimal.valueOf((float) Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wPanPos)) / 10); BigDecimal c = BigDecimal.valueOf((float) Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wTiltPos)) / 10); BigDecimal d = BigDecimal.valueOf((float) Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wZoomPos)) / 10); float p = b.setScale(1, RoundingMode.HALF_UP).floatValue(); float t = c.setScale(1, RoundingMode.HALF_UP).floatValue(); float z = d.setScale(1, RoundingMode.HALF_UP).floatValue(); return new PtzDto(p, t, z); } /** * 获取高精度ptz信息 * * @param cmd 相机命令 * @return AjaxResult * @author 刘苏义 * @date 2024/9/5 17:00 */ @Override public PtzDto getPtzHigh(CameraCmd cmd) { try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); IntByReference channelRef = new IntByReference(chanNo); Pointer channelPointer = channelRef.getPointer(); NET_DVR_STD_CONFIG lpConfigParam6696 = new NET_DVR_STD_CONFIG(); NET_DVR_PTZABSOLUTEEX_CFG lpPTZAbsoluteEX_cfg = new NET_DVR_PTZABSOLUTEEX_CFG(); lpConfigParam6696.lpCondBuffer = channelPointer; lpConfigParam6696.dwCondSize = 4; lpConfigParam6696.lpInBuffer = null; lpConfigParam6696.dwInSize = 0; lpConfigParam6696.lpOutBuffer = lpPTZAbsoluteEX_cfg.getPointer(); lpConfigParam6696.dwOutSize = lpPTZAbsoluteEX_cfg.size(); lpConfigParam6696.write(); boolean res = hCNetSDK.NET_DVR_GetSTDConfig(userId, HCNetSDK.NET_DVR_GET_PTZABSOLUTEEX, lpConfigParam6696); if (!res) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("获取高精度PTZ失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return null; } lpPTZAbsoluteEX_cfg.read(); double p = lpPTZAbsoluteEX_cfg.struPTZCtrl.fPan; double t = lpPTZAbsoluteEX_cfg.struPTZCtrl.fTilt; double z = lpPTZAbsoluteEX_cfg.struPTZCtrl.fZoom; //保留小数点1位 BigDecimal bdPan = new BigDecimal(p).setScale(1, RoundingMode.HALF_UP); BigDecimal bdTilt = BigDecimal.valueOf(t < 0 ? t + 360 : t).setScale(1, RoundingMode.HALF_UP); BigDecimal bdZoom = new BigDecimal(z).setScale(1, RoundingMode.HALF_UP); return new PtzDto(bdPan.floatValue(), bdTilt.floatValue(), bdZoom.floatValue()); } catch (Exception ex) { log.error("获取高精度PTZ绝对位置异常:{}", ex.getMessage()); return null; } } /** * 设置ptz信息 * * @param cmd 相机命令 * @return AjaxResult * @author 刘苏义 * @date 2024/9/5 17:00 */ @Override public Boolean setPtz(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer channelNum = cmd.getChanNo(); PtzDto ptzDto = cmd.getPtzDto(); int userId = cmd.getLoginId().intValue(); NET_DVR_PTZPOS m_ptzPosCurrent = new NET_DVR_PTZPOS(); m_ptzPosCurrent.wAction = 1; try { String p = String.valueOf((int) (ptzDto.getP() * 10)); String t = String.valueOf((int) (ptzDto.getT() * 10)); String z = String.valueOf((int) (ptzDto.getZ() * 10)); m_ptzPosCurrent.wPanPos = (short) (Integer.parseInt(p, 16)); m_ptzPosCurrent.wTiltPos = (short) (Integer.parseInt(t, 16)); m_ptzPosCurrent.wZoomPos = (short) (Integer.parseInt(z, 16)); Pointer point = m_ptzPosCurrent.getPointer(); m_ptzPosCurrent.write(); boolean bool = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_PTZPOS, channelNum, point, m_ptzPosCurrent.size()); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("设置PTZ参数失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return false; } return true; } catch (Exception ex) { log.error(ex.getMessage()); return false; } } /** * 设置高精度ptz信息(透传) * 解决新固件2通道无法自动聚焦问题 * * @param cmd 相机命令 * @return AjaxResult * @author 刘苏义 * @date 2024/9/5 17:04 */ @Override public Boolean setPtzHighT(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); PtzDto ptzDto = cmd.getPtzDto(); // 校验范围 validatePtzRange(cmd); int userId = cmd.getLoginId().intValue(); HCNetSDK.NET_DVR_XML_CONFIG_INPUT inputParam = new HCNetSDK.NET_DVR_XML_CONFIG_INPUT(); inputParam.dwSize = inputParam.size(); HCNetSDK.BYTE_ARRAY requestUrl = new HCNetSDK.BYTE_ARRAY(256); String urlString = "PUT /ISAPI/PTZCtrl/channels/" + chanNo + "/absoluteEx"; System.arraycopy(urlString.getBytes(), 0, requestUrl.byValue, 0, urlString.length()); requestUrl.write(); inputParam.lpRequestUrl = requestUrl.getPointer(); inputParam.dwRequestUrlLen = requestUrl.byValue.length; inputParam.dwRecvTimeOut = 5000; Float T = (ptzDto.getT() >= 300.0f) ? (ptzDto.getT() - 360.0f) : ptzDto.getT(); String xmlString = "" + "\n" + "\n" + "" + T + "\n" + "" + ptzDto.getP() + "\n" + "" + ptzDto.getZ() + "\n" + "100\n" + "100\n" + ""; int iStringSize = xmlString.length(); HCNetSDK.BYTE_ARRAY ptrByte = new HCNetSDK.BYTE_ARRAY(iStringSize); System.arraycopy(xmlString.getBytes(), 0, ptrByte.byValue, 0, xmlString.length()); ptrByte.write(); inputParam.lpInBuffer = ptrByte.getPointer(); inputParam.dwInBufferSize = ptrByte.byValue.length; inputParam.write(); HCNetSDK.BYTE_ARRAY outputXml = new HCNetSDK.BYTE_ARRAY(16 * 1024); outputXml.read(); HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT outputParam = new HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT(); outputParam.dwSize = outputParam.size(); outputParam.lpOutBuffer = outputXml.getPointer(); outputParam.dwOutBufferSize = outputXml.byValue.length; HCNetSDK.BYTE_ARRAY setStatusBuff = new HCNetSDK.BYTE_ARRAY(16 * 1024); setStatusBuff.read(); outputParam.lpStatusBuffer = setStatusBuff.getPointer(); outputParam.dwStatusSize = setStatusBuff.byValue.length; outputParam.write(); boolean bool = hCNetSDK.NET_DVR_STDXMLConfig(userId, inputParam, outputParam); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("设置透传高精度PTZ失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return false; } return true; } //非透传 旧设备使用 新固件会导致2通道不自动聚焦 @Override public Boolean setPtzHigh(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); PtzDto ptzDto = cmd.getPtzDto(); // 校验范围 validatePtzRange(cmd); int userId = cmd.getLoginId().intValue(); IntByReference channelRef = new IntByReference(chanNo); Pointer ChannelNoPointer = channelRef.getPointer(); NET_DVR_STD_CONFIG lpConfigParam6697 = new NET_DVR_STD_CONFIG(); NET_DVR_PTZABSOLUTEEX_CFG lpPTZAbsoluteEX_cfgInfo = new NET_DVR_PTZABSOLUTEEX_CFG(); lpConfigParam6697.lpCondBuffer = ChannelNoPointer; lpConfigParam6697.dwCondSize = 4; BYTE_ARRAY m_szStatusBuf = new BYTE_ARRAY(4096 * 4); lpConfigParam6697.lpStatusBuffer = m_szStatusBuf.getPointer(); lpConfigParam6697.dwStatusSize = 4096 * 4; lpPTZAbsoluteEX_cfgInfo.dwSize = lpPTZAbsoluteEX_cfgInfo.size(); lpPTZAbsoluteEX_cfgInfo.struPTZCtrl.fPan = ptzDto.getP(); float t = ptzDto.getT(); float t1 = t > 300 ? (t - 360) : t; BigDecimal bigDecimal = new BigDecimal(t1); lpPTZAbsoluteEX_cfgInfo.struPTZCtrl.fTilt = bigDecimal.setScale(2, RoundingMode.HALF_UP).floatValue(); lpPTZAbsoluteEX_cfgInfo.struPTZCtrl.fZoom = ptzDto.getZ(); //lpPTZAbsoluteEX_cfgInfo.struPTZCtrl.dwFocus = 100000;//这个曾注掉也不行 lpPTZAbsoluteEX_cfgInfo.dwFocalLen = 100000; lpPTZAbsoluteEX_cfgInfo.fHorizontalSpeed = (float) 1000; lpPTZAbsoluteEX_cfgInfo.fVerticalSpeed = (float) 1000; lpPTZAbsoluteEX_cfgInfo.byZoomType = 0; lpPTZAbsoluteEX_cfgInfo.write(); lpConfigParam6697.lpInBuffer = lpPTZAbsoluteEX_cfgInfo.getPointer(); lpConfigParam6697.dwInSize = lpPTZAbsoluteEX_cfgInfo.dwSize; lpConfigParam6697.write(); boolean bool = hCNetSDK.NET_DVR_SetSTDConfig(userId, NET_DVR_SET_PTZABSOLUTEEX, lpConfigParam6697); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("设置高精度PTZ失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return false; } return true; } /** * 引导目标位置 * * @param cmd 相机命令 * @return AjaxResult * @author 刘苏义 * @date 2024/9/5 17:04 */ @Override public Boolean guideTargetPosition(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); Float baseYaw = cmd.getBaseYaw(); int userId = cmd.getLoginId().intValue(); NET_DVR_PTZPOS m_ptzPosCurrent = new NET_DVR_PTZPOS(); m_ptzPosCurrent.wAction = 1; ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId); if (StringUtils.isNull(ardCamera)) { log.warn("未找到相机信息,cameraId:{}", cameraId); return false; } Point cameraPosition = new Point(ardCamera.getLongitude(), ardCamera.getLatitude(), ardCamera.getAltitude()); cameraPosition.validate(); // 校验目标位置 Point targetPositions = cmd.getTargetPosition(); targetPositions.validate(); // 校验目标位置 PtzDto cameraPTZ = GisUtil.getCameraPTZ(cameraPosition, targetPositions, 20, 150); //若为车载光电,需要根据修改P值 if ("4".equals(ardCamera.getType())) { cameraPTZ.setP((cameraPTZ.getP() - baseYaw + 360) % 360); } // pt校验范围 cmd.setPtzDto(cameraPTZ); validatePtzRange(cmd); String p = String.valueOf((int) (cameraPTZ.getP() * 10)); //修正俯仰 double correctPitch = correctPitch(cmd); String t = String.valueOf((int) ((cameraPTZ.getT() + correctPitch) * 10)); String z = String.valueOf((int) (cameraPTZ.getZ() * 10)); m_ptzPosCurrent.wPanPos = (short) (Integer.parseInt(p, 16)); m_ptzPosCurrent.wTiltPos = (short) (Integer.parseInt(t, 16)); m_ptzPosCurrent.wZoomPos = (short) (Integer.parseInt(z, 16)); Pointer point = m_ptzPosCurrent.getPointer(); m_ptzPosCurrent.write(); boolean bool = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_PTZPOS, chanNo, point, m_ptzPosCurrent.size()); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("设置ptz失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return false; } return true; } /** * 获取ptz锁定信息 * * @param cmd 相机命令 * @return AjaxResult * @author 刘苏义 * @date 2024/9/5 17:06 */ @Override public Boolean getPTZLockInfo(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer channelNum = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); NET_DVR_PTZ_LOCKCFG lockCfg = new NET_DVR_PTZ_LOCKCFG(); Pointer point = lockCfg.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); lockCfg.write(); boolean bool = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_PTZLOCKCFG, channelNum, point, lockCfg.size(), ibrBytesReturned); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("获取云台锁定失败,错误码:{},原因:{}", errorCode, errorDesc); throw new CameraSDKException(errorCode, cameraId, errorDesc); } lockCfg.read(); int byWorkMode = lockCfg.byWorkMode; log.debug("PTZ锁定模式: {}", byWorkMode); //云台锁定控制:0- 解锁,1- 锁定 return byWorkMode == 0; } @Override public Boolean setPTZLockInfo(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); Boolean isLock = cmd.isEnable(); NET_DVR_PTZ_LOCKCFG lockCfg = new NET_DVR_PTZ_LOCKCFG(); lockCfg.byWorkMode = isLock ? (byte) 1 : (byte) 0; // 0- 解锁 false,1- 锁定 true lockCfg.write(); boolean bool = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_PTZLOCKCFG, chanNo, lockCfg.getPointer(), lockCfg.size()); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("设置云台锁定失败,错误码:{},原因:{}", errorCode, errorDesc); throw new CameraSDKException(errorCode, cameraId, errorDesc); } return true; } /** * 设置零方位角 * * @param cmd 相机命令 * @return AjaxResult * @author 刘苏义 * @date 2024/9/5 17:08 */ @Override public Boolean setZeroPtz(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer channelNum = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); NET_DVR_INITIALPOSITIONCTRL initializations = new NET_DVR_INITIALPOSITIONCTRL(); initializations.dwSize = initializations.size(); initializations.byWorkMode = 0; initializations.dwChan = Short.parseShort(channelNum.toString()); Pointer point = initializations.getPointer(); initializations.write(); boolean bool = hCNetSDK.NET_DVR_RemoteControl(userId, NET_DVR_PTZ_INITIALPOSITIONCTRL, point, initializations.size()); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("设置零方位角失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return false; } return true; } /** * 获取球机PTZ参数取值范围 * * @param cmd 相机命令 * @return AjaxResult * @author 刘苏义 * @date 2024/9/5 17:12 */ @Override public PtzScope getPtzScope(CameraCmd cmd) { Integer chanNo = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); NET_DVR_PTZSCOPE m_ptzPosCurrent = new NET_DVR_PTZSCOPE(); Pointer point = m_ptzPosCurrent.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); m_ptzPosCurrent.write(); boolean bool = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_PTZSCOPE, chanNo, point, m_ptzPosCurrent.size(), ibrBytesReturned); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("获取ptz范围失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return null; } m_ptzPosCurrent.read(); PtzScope ptzScope = new PtzScope(); ptzScope.setPMax(Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wPanPosMax)) / 10.0f); ptzScope.setPMin(Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wPanPosMin)) / 10.0f); ptzScope.setTMax(Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wTiltPosMax)) / 10.0f); ptzScope.setTMin(Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wTiltPosMin)) / 10.0f); ptzScope.setZMax(Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wZoomPosMax)) / 10.0f); ptzScope.setZMin(Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wZoomPosMin)) / 10.0f); return ptzScope; } /** * 校验 PTZ 参数是否在设备支持的范围内 * * @param cmd 相机命令,需包含 PTZ 参数 * @throws CameraSDKException 校验失败时抛出异常 */ protected void validatePtzRange(CameraCmd cmd) { String cameraId = cmd.getCameraId(); // 从数据库获取 PTZ 范围 PtzScope ptzScope = ardCameraPtzScopeService.getPtzScopeByCameraId(cameraId); if (ptzScope == null) { log.warn("获取 PTZ 范围失败: " + cameraId); throw new CameraSDKException(CameraSDKException.ErrorCode.OVER_GUIDANCE_RANGE.getCode(), cameraId, "获取 PTZ 范围失败,请检查设备配置"); } PtzDto ptzDto = cmd.getPtzDto(); if (ptzDto == null) { log.warn("PTZ 控制参数为空: " + cameraId); throw new CameraSDKException(CameraSDKException.ErrorCode.CMD_NOT_SUPPORT.getCode(), cameraId, "PTZ 控制参数为空"); } float p = ptzDto.getP(); float t = ptzDto.getT(); float z = ptzDto.getZ() < 1 ? 1 : ptzDto.getZ(); // 修正 z 小于 1 的情况 // 水平角度校验 if (p < ptzScope.getPMin() || p > ptzScope.getPMax()) { log.warn("水平角度超出范围:" + p + ",允许范围:" + ptzScope.getPMin() + " ~ " + ptzScope.getPMax()); throw new CameraSDKException(CameraSDKException.ErrorCode.OVER_GUIDANCE_RANGE.getCode(), cameraId, "水平角度超出范围:" + ptzScope.getPMin() + " ~ " + ptzScope.getPMax()); } // 俯仰角度校验 if (t <= 90) { if (t > ptzScope.getTMax()) { log.warn("俯仰角度超出向上范围:" + t + ",最大允许:" + ptzScope.getTMax()); throw new CameraSDKException(CameraSDKException.ErrorCode.OVER_GUIDANCE_RANGE.getCode(), cameraId, "俯仰角度超出向上范围:" + ptzScope.getTMax()); } } else if (t >= 300 && t <= 360) { if (t < ptzScope.getTMin()) { log.warn("俯仰角度超出向下范围:" + t + ",最小允许:" + ptzScope.getTMin()); throw new CameraSDKException(CameraSDKException.ErrorCode.OVER_GUIDANCE_RANGE.getCode(), cameraId, "俯仰角度超出向下范围:" + ptzScope.getTMin()); } } else { log.warn("俯仰角度非法:" + t); throw new CameraSDKException(CameraSDKException.ErrorCode.BEYOND_VISIBILITY_RANGE.getCode(), cameraId, "俯仰角度非法:" + t); } // 变倍校验 if (z > ptzScope.getZMax()) { log.warn("变倍超出范围:" + z + ",最大允许:" + ptzScope.getZMax()); throw new CameraSDKException(CameraSDKException.ErrorCode.OVER_GUIDANCE_RANGE.getCode(), cameraId, "变倍超出范围:" + ptzScope.getZMax()); } } /** * 切换透雾 * * @param cmd 相机命令 */ @Override public Boolean controlDefog(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); boolean enable = cmd.isEnable(); int userId = cmd.getLoginId().intValue(); try { // 获取前端参数 NET_DVR_CAMERAPARAMCFG_EX cameraParam = new NET_DVR_CAMERAPARAMCFG_EX(); Pointer point = cameraParam.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); boolean gotParam = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_CCDPARAMCFG_EX, chanNo, point, cameraParam.size(), ibrBytesReturned); if (!gotParam) { log.warn("【{}】获取前端参数失败: {}", cameraId, getErrorCodePrint()); return false; } cameraParam.read(); log.debug("当前透雾模式: {}", cameraParam.struDefogCfg.byMode); // 设置透雾配置 NET_DVR_DEFOGCFG defogCfg = new NET_DVR_DEFOGCFG(); if (enable) { defogCfg.byMode = 2; // 常开模式 defogCfg.byLevel = 100; // 0-100 } else { defogCfg.byMode = 0; // 不启用 } cameraParam.struDefogCfg = defogCfg; cameraParam.write(); boolean success = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_CCDPARAMCFG_EX, chanNo, point, cameraParam.size()); if (!success) { log.warn("【{}】切换透雾失败: {}", cameraId, getErrorCodePrint()); return false; } log.debug("【{}】切换透雾成功", cameraId); return true; } catch (Exception ex) { log.error("切换透雾异常:{}", ex.getMessage()); return false; } } @Override public Boolean isEnableDefog(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); Integer userId = cmd.getLoginId().intValue(); try { NET_DVR_CAMERAPARAMCFG_EX strutCameraParam = new NET_DVR_CAMERAPARAMCFG_EX(); Pointer point = strutCameraParam.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); boolean success = hCNetSDK.NET_DVR_GetDVRConfig( userId, NET_DVR_GET_CCDPARAMCFG_EX, chanNo, point, strutCameraParam.size(), ibrBytesReturned ); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("【{}】获取前端参数失败,错误码:{}, 原因:{}", cameraId, errorCode, errorDesc); return false; // SDK 层只返回 true/false } strutCameraParam.read(); log.debug("【{}】是否开启透雾:{}", cameraId, strutCameraParam.struDefogCfg.byMode); return strutCameraParam.struDefogCfg.byMode != 0; } catch (Exception e) { log.error("【{}】查询透雾异常: {}", cameraId, e.getMessage()); return false; } } /** * 切换红外 * * @param cmd 相机命令 * @return com.ard.common.core.web.domain.AjaxResult * @author 刘苏义 * @date 2024/9/5 17:21 */ @Override public Boolean controlInfrared(CameraCmd cmd) { String cameraId = cmd.getCameraId(); boolean enable = cmd.isEnable(); Integer chanNo = cmd.getChanNo(); Integer userId = cmd.getLoginId().intValue(); try { NET_DVR_CAMERAPARAMCFG_EX cameraParamCfg = new NET_DVR_CAMERAPARAMCFG_EX(); Pointer point = cameraParamCfg.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); // 获取当前参数 boolean b_GetCameraParam = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_CCDPARAMCFG, chanNo, point, cameraParamCfg.size(), ibrBytesReturned); if (!b_GetCameraParam) { log.warn("【{}】获取前端参数失败: {}", cameraId, getErrorCodePrint()); return false; } cameraParamCfg.read(); log.debug("当前红外状态:{}", cameraParamCfg.struDayNight.byDayNightFilterType == 1 ? "夜晚" : "白天"); // 设置红外 NET_DVR_DAYNIGHT dayNight = new NET_DVR_DAYNIGHT(); dayNight.byDayNightFilterType = (byte) (enable ? 1 : 0); // 夜晚/白天 dayNight.bySwitchScheduleEnabled = 1; dayNight.byDayNightFilterTime = 60; cameraParamCfg.struDayNight = dayNight; cameraParamCfg.write(); boolean success = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_CCDPARAMCFG, chanNo, point, cameraParamCfg.size()); if (!success) { log.warn("【{}】切换红外失败: {}", cameraId, getErrorCodePrint()); return false; } log.info("【{}】切换红外成功", cameraId); return true; } catch (Exception e) { log.error("【{}】切换红外异常: {}", cameraId, e.getMessage()); return false; } } //查询是否开启红外 @Override public Boolean isEnableInfrared(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); Integer userId = cmd.getLoginId().intValue(); try { NET_DVR_CAMERAPARAMCFG_EX cameraParamCfg = new NET_DVR_CAMERAPARAMCFG_EX(); Pointer point = cameraParamCfg.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); boolean success = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_CCDPARAMCFG, chanNo, point, cameraParamCfg.size(), ibrBytesReturned); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("【{}】获取前端参数失败,错误码:{},原因:{}", cameraId, errorCode, errorDesc); return false; } cameraParamCfg.read(); // 1表示夜晚/红外开启,0表示白天/红外关闭 return cameraParamCfg.struDayNight.byDayNightFilterType == 1; } catch (Exception ex) { log.error("【{}】查询红外开关异常", cameraId, ex); return false; } } //聚焦开关 @Override public Boolean controlFocusMode(CameraCmd cmd) { String cameraId = cmd.getCameraId(); boolean enable = cmd.isEnable(); Integer channelNum = cmd.getChanNo(); Integer userId = cmd.getLoginId().intValue(); NET_DVR_FOCUSMODE_CFG focusModeCfg = new NET_DVR_FOCUSMODE_CFG(); Pointer point = focusModeCfg.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); // 获取当前聚焦模式 boolean got = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_FOCUSMODECFG, channelNum, point, focusModeCfg.size(), ibrBytesReturned); if (!got) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("获取前端参数失败,错误码:{},原因:{}", errorCode, errorDesc); return false; } focusModeCfg.read(); log.debug("当前聚焦模式:{}", focusModeCfg.byFocusMode); // 设置聚焦模式 if (enable) { focusModeCfg.byFocusMode = 1; // 手动聚焦 focusModeCfg.byAutoFocusMode = 0; } else { focusModeCfg.byFocusMode = 2; // 自动聚焦 focusModeCfg.byAutoFocusMode = 1; } focusModeCfg.byFocusDefinitionDisplay = 1; focusModeCfg.byFocusSpeedLevel = 3; focusModeCfg.write(); boolean setOk = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_FOCUSMODECFG, channelNum, point, focusModeCfg.size()); if (!setOk) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("设置聚焦模式失败,错误码:{},原因:{}", errorCode, errorDesc); return false; } log.debug("设置聚焦模式成功"); return true; } //获取聚焦模式 @Override public Boolean getFocusMode(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer channelNum = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); NET_DVR_FOCUSMODE_CFG strutFocusMode = new NET_DVR_FOCUSMODE_CFG(); Pointer point = strutFocusMode.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); boolean b_GetCameraParam = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_FOCUSMODECFG, channelNum, point, strutFocusMode.size(), ibrBytesReturned); if (!b_GetCameraParam) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("获取聚焦模式失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); throw new CameraSDKException(errorCode, cameraId, errorMsg); } strutFocusMode.read(); // 判断手动/自动聚焦 // 0-自动,1-手动,2-半自动 return strutFocusMode.byFocusMode == 1; // true=手动 } //云台加热开关 @Override public Boolean controlPTHeater(CameraCmd cmd) { boolean enable = cmd.isEnable(); Integer channelNum = cmd.getChanNo(); Integer userId = cmd.getLoginId().intValue(); int dwStop = enable ? 0 : 1; // 0=开启, 1=关闭 boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(userId, channelNum, HEATER_PWRON, dwStop); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("设置云台加热失败,错误码:{},原因:{}", errorCode, errorDesc); return false; } log.debug("设置云台加热成功"); return true; } //镜头加热开关 @Override public Boolean controlCameraDeicing(CameraCmd cmd) { String cameraId = cmd.getCameraId(); boolean enable = cmd.isEnable(); Integer channelNum = cmd.getChanNo(); Integer userId = cmd.getLoginId().intValue(); NET_DVR_DEVSERVER_CFG devServerCfg = new NET_DVR_DEVSERVER_CFG(); IntByReference ibrBytesReturned = new IntByReference(0); // 获取当前配置 boolean b_GetCameraParam = hCNetSDK.NET_DVR_GetDVRConfig( userId, NET_DVR_GET_DEVSERVER_CFG, channelNum, devServerCfg.getPointer(), devServerCfg.size(), ibrBytesReturned); if (!b_GetCameraParam) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("获取前端参数失败,错误码:{},原因:{}", errorCode, errorDesc); return false; } devServerCfg.read(); log.debug("当前镜头除冰状态:{}", devServerCfg.byEnableDeicing); // 设置除冰状态 devServerCfg.byEnableDeicing = enable ? (byte) 1 : (byte) 0; devServerCfg.write(); boolean bool = hCNetSDK.NET_DVR_SetDVRConfig( userId, NET_DVR_SET_DEVSERVER_CFG, channelNum, devServerCfg.getPointer(), devServerCfg.size()); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("设置镜头除冰失败,错误码:{},原因:{}", errorCode, errorDesc); return false; } log.debug("设置镜头除冰成功"); return true; } //截图 返给前端 @Override public byte[] localCapture(CameraCmd cmd) { Integer channelNum = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); // 1. 校验设备工作状态 NET_DVR_WORKSTATE_V30 workState = new NET_DVR_WORKSTATE_V30(); if (!hCNetSDK.NET_DVR_GetDVRWorkState_V30(userId, workState)) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("获取设备工作状态失败,错误码:{},原因:{}", errorCode, errorDesc); return null; } // 2. JPEG 参数 NET_DVR_JPEGPARA jpegPara = new NET_DVR_JPEGPARA(); jpegPara.wPicSize = 0; // 使用当前分辨率 jpegPara.wPicQuality = 0; // 最好质量 // 3. 抓图到内存 int bufferSize = 1024 * 1024; // 1MB ByteBuffer jpegBuffer = ByteBuffer.allocate(bufferSize); IntByReference outLen = new IntByReference(0); boolean success = hCNetSDK.NET_DVR_CaptureJPEGPicture_NEW( userId, channelNum, jpegPara, jpegBuffer, bufferSize, outLen ); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); log.warn("抓图失败,错误码:{},原因:{}", errorCode, errorDesc); return null; } // 4. 只取有效长度的数据 byte[] imageBytes = new byte[outLen.getValue()]; jpegBuffer.rewind(); jpegBuffer.get(imageBytes); return imageBytes; } //截图 存服务器 @Override public String picCutCate(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String type = cmd.getType() == null ? "other" : cmd.getType(); String bucketName = cmd.getBucketName() == null ? "pic" : cmd.getBucketName(); int userId = cmd.getLoginId().intValue(); //图片信息 NET_DVR_JPEGPARA jpeg = new NET_DVR_JPEGPARA(); //设置图片分辨率 jpeg.wPicSize = 0; //设置图片质量:0-最好,1-较好,2-一般 jpeg.wPicQuality = 0; IntByReference a = new IntByReference(); //设置图片大小 ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024); // 抓图到内存 boolean bool = hCNetSDK.NET_DVR_CaptureJPEGPicture_NEW(userId, chanNo, jpeg, jpegBuffer, 1024 * 1024, a); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("抓图失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return ""; } log.debug("hikSdk(抓图)-结果状态值(0表示成功):{}", hCNetSDK.NET_DVR_GetLastError()); // 获取实际图片数据 int actualLength = a.getValue(); byte[] imageBytes; if (actualLength > 0 && actualLength < 1024 * 1024) { imageBytes = new byte[actualLength]; System.arraycopy(jpegBuffer.array(), 0, imageBytes, 0, actualLength); } else { imageBytes = jpegBuffer.array(); } // 存储到minio String objectName = cameraId + "_" + chanNo + ".jpeg"; String url = ""; File tempFile = null; try { // 创建临时文件 tempFile = File.createTempFile("snap_", ".jpeg"); java.nio.file.Files.write(tempFile.toPath(), imageBytes); // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, objectName, MimeTypeUtils.IMAGE_JPEG); R sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); } } catch (Exception e) { log.error("上传失败", e); } finally { // 删除临时文件 if (tempFile != null && tempFile.exists()) { tempFile.delete(); } } return url; } //截图 存服务器 @Override public String picCutCate(CameraCmd cmd, String bucketName, String objectName) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String type = cmd.getType() == null ? "other" : cmd.getType(); Integer userId = cmd.getLoginId().intValue(); //图片信息 NET_DVR_JPEGPARA jpeg = new NET_DVR_JPEGPARA(); //设置图片分辨率 jpeg.wPicSize = 0; //设置图片质量:0-最好,1-较好,2-一般 jpeg.wPicQuality = 0; IntByReference a = new IntByReference(); //设置图片大小 ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024); // 抓图到内存,单帧数据捕获并保存成JPEG存放在指定的内存空间中 boolean bool = hCNetSDK.NET_DVR_CaptureJPEGPicture_NEW(userId, chanNo, jpeg, jpegBuffer, 1024 * 1024, a); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("抓图失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return ""; } // 获取实际图片数据(只复制有效长度) int actualLength = a.getValue(); byte[] imageBytes; if (actualLength > 0 && actualLength < 1024 * 1024) { imageBytes = new byte[actualLength]; System.arraycopy(jpegBuffer.array(), 0, imageBytes, 0, actualLength); } else { imageBytes = jpegBuffer.array(); } String url = ""; File tempFile = null; try { // 创建临时文件 tempFile = File.createTempFile("snap_", ".jpeg"); java.nio.file.Files.write(tempFile.toPath(), imageBytes); // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, objectName, MimeTypeUtils.IMAGE_JPEG); R sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); } } catch (Exception e) { log.error("上传失败", e); } finally { if (tempFile != null && tempFile.exists()) { tempFile.delete(); } } return url; } //短时录像 @Override public String record(CameraCmd cmd) { String url = ""; File tempFile = null; try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String name = cameraId + "_" + chanNo; // 本地临时录像地址 String path = FileUtils.createFile(tempDir + "/record/" + name + ".mp4"); boolean enable = cmd.isEnable(); Integer userId = cmd.getLoginId().intValue(); //强制I帧结构体对象 NET_DVR_I_FRAME netDvrIFrame = new NET_DVR_I_FRAME(); netDvrIFrame.read(); netDvrIFrame.dwChannel = chanNo; netDvrIFrame.byStreamType = 0; netDvrIFrame.dwSize = netDvrIFrame.size(); netDvrIFrame.write(); if (!hCNetSDK.NET_DVR_RemoteControl(userId, 3402, netDvrIFrame.getPointer(), netDvrIFrame.dwSize)) { log.error("强制I帧 错误码为:{}", hCNetSDK.NET_DVR_GetLastError()); } //预览参数 NET_DVR_PREVIEWINFO previewInfo = new NET_DVR_PREVIEWINFO(); previewInfo.read(); previewInfo.lChannel = chanNo; previewInfo.dwStreamType = 0; previewInfo.dwLinkMode = 0; previewInfo.hPlayWnd = null; previewInfo.bBlocked = 0; previewInfo.byNPQMode = 0; previewInfo.write(); if (enable) { // 开始录像 if (!GlobalVariable.previewMap.containsKey(name)) { int lRealHandle = hCNetSDK.NET_DVR_RealPlay_V40(userId, previewInfo, null, null); if (lRealHandle == -1) { log.error("取流失败:{}", hCNetSDK.NET_DVR_GetLastError()); return ""; } log.debug("取流成功"); GlobalVariable.previewMap.put(name, lRealHandle); } if (!hCNetSDK.NET_DVR_SaveRealData_V30((int) GlobalVariable.previewMap.get(name), 2, path)) { log.error("保存视频文件到临时文件夹失败 错误码为:{}", hCNetSDK.NET_DVR_GetLastError()); return ""; } log.debug("录像开始"); } else { // 停止录像 if (GlobalVariable.previewMap.containsKey(name)) { Integer lRealHandle = (Integer) GlobalVariable.previewMap.get(name); hCNetSDK.NET_DVR_StopRealPlay(lRealHandle); GlobalVariable.previewMap.remove(name); } log.debug("录像停止"); // 检查录像文件是否存在 File videoFile = new File(path); if (!videoFile.exists() || videoFile.length() == 0) { log.error("录像文件不存在或为空: {}", path); return ""; } // 存入minio - 使用 FileMultipartFile String bucketName = "record"; String objectName = cameraId + "_" + chanNo + ".mp4"; // 创建临时文件对象(实际上 path 已经是文件路径) tempFile = new File(path); // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, objectName, MimeTypeUtils.VIDEO_MP4); R sysFileR = remoteFileService.upload(multipartFile, bucketName); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.debug("上传文件成功:{}", url); } } return url; } catch (Exception ex) { log.error("录像异常:{}", ex.getMessage()); return ""; } } @Override public Boolean recordStart(CameraCmd cmd) { try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String name = cameraId + "_" + chanNo; // 本地临时录像地址 String path = FileUtils.createFile(tempDir + "/record/" + name + ".mp4"); Integer userId = cmd.getLoginId().intValue(); //强制I帧结构体对象 NET_DVR_I_FRAME netDvrIFrame = new NET_DVR_I_FRAME(); //新建结构体对象 netDvrIFrame.read(); netDvrIFrame.dwChannel = chanNo;//因为上文代码中设置了通道号,按照上文中的设置 netDvrIFrame.byStreamType = 0; netDvrIFrame.dwSize = netDvrIFrame.size(); netDvrIFrame.write(); if (!hCNetSDK.NET_DVR_RemoteControl(userId, 3402, netDvrIFrame.getPointer(), netDvrIFrame.dwSize)) { int code = hCNetSDK.NET_DVR_GetLastError(); log.error("设置强制I帧错误:{}", SdkErrorCodeEnum.getDescByCode(code) + "(" + code + ")"); } //预览参数 NET_DVR_PREVIEWINFO previewInfo = new NET_DVR_PREVIEWINFO(); previewInfo.read(); previewInfo.lChannel = chanNo; previewInfo.dwStreamType = 0;//码流类型:0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推 previewInfo.dwLinkMode = 0;//连接方式:0-TCP方式,1-UDP方式,2-多播方式,3-RTP方式,4-RTP/RTSP,5-RTP/HTTP,6-HRUDP(可靠传输),7 // -RTSP/HTTPS,8-NPQ previewInfo.hPlayWnd = null;//播放窗口的句柄,为NULL表示不解码显示。 previewInfo.bBlocked = 0;//0- 非阻塞取流,1-阻塞取流 previewInfo.byNPQMode = 0;//NPQ模式:0-直连模式,1-过流媒体模式 previewInfo.write(); if (GlobalVariable.previewMap.containsKey(name)) { Integer lRealHandle = (Integer) GlobalVariable.previewMap.get(name); hCNetSDK.NET_DVR_StopRealPlay(lRealHandle); GlobalVariable.previewMap.remove(name); log.debug("停止当前录像"); } int lRealHandle = hCNetSDK.NET_DVR_RealPlay_V40(userId, previewInfo, null, null); if (lRealHandle == -1) { log.error("取流失败:{}", hCNetSDK.NET_DVR_GetLastError()); return false; } log.debug("取流成功"); GlobalVariable.threadMap.put(cameraId, Thread.currentThread().getName()); GlobalVariable.previewMap.put(name, lRealHandle); if (!hCNetSDK.NET_DVR_SaveRealData_V30((int) GlobalVariable.previewMap.get(name), 2, path)) { log.error("保存视频文件到临时文件夹失败 错误码为:{}", hCNetSDK.NET_DVR_GetLastError()); return false; } log.debug("录像开始"); return true; } catch (Exception ex) { log.error("开始录像异常:{}", ex.getMessage()); return false; } } @Override public String recordStop(CameraCmd cmd) { String url = ""; File videoFile = null; File transcodeFile = null; try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String name = cameraId + "_" + chanNo; // 本地临时录像地址 String path = FileUtils.createFile(tempDir + "/record/" + name + ".mp4"); // 停止录像 if (GlobalVariable.previewMap.containsKey(name)) { Integer lRealHandle = (Integer) GlobalVariable.previewMap.get(name); hCNetSDK.NET_DVR_StopRealPlay(lRealHandle); GlobalVariable.previewMap.remove(name); } log.debug("录像停止"); // 检查原录像文件是否存在 videoFile = new File(path); if (!videoFile.exists() || videoFile.length() == 0) { log.error("录像文件不存在或为空: {}", path); return ""; } // ffmpeg转码 StopWatch watchStop = new StopWatch(); watchStop.start(); String newpath = FileUtils.createFile(tempDir + "/record/" + name + "_c.mp4"); transcodeFile = new File(newpath); FFmpegUtils.transcodeToMP4(path, newpath); watchStop.stop(); log.warn("转码耗时:{}", watchStop.getTotalTimeMillis()); // 检查转码后的文件是否存在 if (!transcodeFile.exists() || transcodeFile.length() == 0) { log.error("转码文件不存在或为空: {}", newpath); return ""; } // 存入minio - 使用 FileMultipartFile String bucketName = "record"; String objectName = cameraId + "_" + chanNo + ".mp4"; String type = cmd.getType(); MultipartFile multipartFile = new FileMultipartFile(transcodeFile, objectName, MimeTypeUtils.VIDEO_MP4); R sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.info("上传文件成功:{}", url); } return url; } catch (Exception ex) { log.error("录像异常:{}", ex.getMessage()); return ""; } finally { // 清理临时文件 try { if (videoFile != null && videoFile.exists()) { videoFile.delete(); log.debug("删除原录像文件: {}", videoFile.getPath()); } if (transcodeFile != null && transcodeFile.exists()) { transcodeFile.delete(); log.debug("删除转码文件: {}", transcodeFile.getPath()); } } catch (Exception e) { log.warn("删除临时文件失败", e); } } } //获取IP通道 @Override public List getChannels(ArdCamera camera) { //获取通道 List channelList = new ArrayList<>(); int loginId = camera.getLoginId().intValue(); int startChan = camera.getStartChan(); int chanNum = camera.getChanNum(); // 1. 资源复用:在循环外创建结构体和引用,减少 GC 压力 NET_DVR_PICCFG_V40 strPicCfg = new NET_DVR_PICCFG_V40(); int structSize = strPicCfg.size(); strPicCfg.dwSize = structSize; // 初始化大小 IntByReference pInt = new IntByReference(0); for (int i = 0; i < chanNum; i++) { { int chanNo = i + startChan; ArdChannel channel = new ArdChannel(); strPicCfg.dwSize = structSize; strPicCfg.write(); boolean success = hCNetSDK.NET_DVR_GetDVRConfig(loginId, HCNetSDK.NET_DVR_GET_PICCFG_V40, chanNo, strPicCfg.getPointer(), structSize, pInt); if (!success) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("获取通道【{}】失败,错误码:%s,原因:%s", chanNo, errorCode, errorDesc); log.debug(errorMsg); continue; } // 3. 读取结果 strPicCfg.read(); // 获取通道名称 String channelName; try { // 尝试 GBK,如果全是乱码或空,可根据业务需求尝试 UTF-8 (视具体设备固件而定) channelName = new String(strPicCfg.sChanName, "GBK").trim(); } catch (UnsupportedEncodingException e) { channelName = "通道" + chanNo; log.error("字符集解码失败", e); } if (channelName.isEmpty()) { channelName = "通道" + chanNo; } channel.setDeviceId(camera.getId()); channel.setName(channelName); channel.setChanNo(chanNo); // 获取视频编码 CameraCmd cameraCmd = new CameraCmd(camera.getId(), chanNo); cameraCmd.setLoginId(camera.getLoginId()); VideoCompressionCfg videoCompressionCfg = getVideoCompressionCfg(cameraCmd); if (StringUtils.isNotNull(videoCompressionCfg)) { channel.setVideoCode(videoCompressionCfg.getVideoEncType()); } // 获取rtsp地址 String rtspUrl = String.format("rtsp://%s:%s@%s:%d/h264/ch%d/main/av_stream", camera.getUsername(), camera.getPassword(), camera.getIp(), camera.getRtspPort(), channel.getChanNo() ); channel.setLiveAddress(rtspUrl); channelList.add(channel); } } return channelList; } //获取GIS信息数据 @Override public PtzParamDTO getGisInfo(CameraCmd cmd) { Integer chanNo = cmd.getChanNo(); Integer userId = cmd.getLoginId().intValue(); NET_DVR_STD_CONFIG stdCfg = new NET_DVR_STD_CONFIG(); NET_DVR_GIS_INFO gisInfo = new NET_DVR_GIS_INFO(); stdCfg.read(); IntByReference channelRef = new IntByReference(chanNo); stdCfg.lpCondBuffer = channelRef.getPointer(); stdCfg.dwCondSize = 4; stdCfg.lpOutBuffer = gisInfo.getPointer(); stdCfg.dwOutSize = gisInfo.size(); stdCfg.write();//设置前之前要write() boolean bool = hCNetSDK.NET_DVR_GetSTDConfig(userId, NET_DVR_GET_GISINFO, stdCfg); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("获取GIS信息数据失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return new PtzParamDTO(); } gisInfo.read(); PtzParamDTO ptzParamDTO = new PtzParamDTO(); ptzParamDTO.setP(gisInfo.struPtzPos.fPanPos); ptzParamDTO.setT(gisInfo.struPtzPos.fTiltPos < 0 ? gisInfo.struPtzPos.fTiltPos + 360 : gisInfo.struPtzPos.fTiltPos); ptzParamDTO.setZ(gisInfo.struPtzPos.fZoomPos); ptzParamDTO.setFHorFieldAngle(gisInfo.fHorizontalValue); ptzParamDTO.setFVerFieldAngle(gisInfo.fVerticalValue); return ptzParamDTO; } @Override public Double correctPitch(CameraCmd cmd) { double pitchAngle = 0.0; try { Point targetPositions = cmd.getTargetPosition(); ArdTerrainMark ardTerrainMark = new ArdTerrainMark(); ardTerrainMark.setCameraId(cmd.getCameraId()); List ardTerrainMarks = ardTerrainMarkService.selectArdTerrainMarkList(ardTerrainMark); //过滤500米范围内的集合 ardTerrainMarks = ardTerrainMarks.stream().filter(n -> GisUtil.getDistance(targetPositions, new Point(n.getLongitude(), n.getLatitude(), n.getAltitude())) <= 500).collect(Collectors.toList()); //计算T平均值 OptionalDouble averageT = ardTerrainMarks.stream().mapToDouble(ArdTerrainMark::getT).average(); // 输出 if (averageT.isPresent()) { pitchAngle = averageT.getAsDouble(); log.debug("修正俯仰值:{}", pitchAngle); } } catch (Exception ex) { log.error("修正俯仰值异常:{}", ex.getMessage()); } return pitchAngle; } //3D定位 @Override public Boolean set3DPosition(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); PointFrame pointFrame = cmd.getPointFrame(); int userId = cmd.getLoginId().intValue(); NET_DVR_POINT_FRAME netDvrPointFrame = new NET_DVR_POINT_FRAME(); netDvrPointFrame.xTop = pointFrame.getXTop(); netDvrPointFrame.yTop = pointFrame.getYTop(); netDvrPointFrame.xBottom = pointFrame.getXBottom(); netDvrPointFrame.yBottom = pointFrame.getYBottom(); netDvrPointFrame.write(); boolean bool = hCNetSDK.NET_DVR_PTZSelZoomIn_EX(userId, chanNo, netDvrPointFrame); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("3D定位失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return false; } return true; } @Override public DeviceCFG getSystemInfo(CameraCmd cmd) { Integer chanNo = cmd.getChanNo(); int userId = cmd.getLoginId().intValue(); NET_DVR_DEVICECFG_V40 netDvrDevicecfgV40 = new NET_DVR_DEVICECFG_V40(); netDvrDevicecfgV40.write(); Pointer point = netDvrDevicecfgV40.getPointer(); IntByReference ibrBytesReturned = new IntByReference(0); boolean bool = hCNetSDK.NET_DVR_GetDVRConfig(userId, HCNetSDK.NET_DVR_GET_DEVICECFG_V40, chanNo, point, netDvrDevicecfgV40.size(), ibrBytesReturned); if (!bool) { int errorCode = hCNetSDK.NET_DVR_GetLastError(); String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode); String errorMsg = String.format("获取系统信息失败,错误码:%s,原因:%s", errorCode, errorDesc); log.warn(errorMsg); return null; } netDvrDevicecfgV40.read(); DeviceCFG deviceCFG = new DeviceCFG(); deviceCFG.setSDVRName(new String(netDvrDevicecfgV40.sDVRName, StandardCharsets.UTF_8)); deviceCFG.setDwSoftwareVersion(parseVersion(netDvrDevicecfgV40.dwSoftwareVersion)); log.debug("获取系统信息成功:{}", deviceCFG); return deviceCFG; } public static String parseVersion(int dwSoftwareVersion) { int major, minor, patch; if (dwSoftwareVersion >= 0x03000000) { // V3.0以上 major = (dwSoftwareVersion >> 24) & 0xFF; // 高8位 minor = (dwSoftwareVersion >> 16) & 0xFF; // 次高8位 patch = dwSoftwareVersion & 0xFFFF; // 低16位 } else { // V3.0以下 major = (dwSoftwareVersion >> 16) & 0xFFFF; // 高16位 minor = dwSoftwareVersion & 0xFFFF; // 低16位 patch = 0; // 无修复版本号 } return String.format("%d.%d.%d", major, minor, patch); } /** * 动态库加载 */ 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; } @Override public List> getCameraVideoLable(Long userId, CameraCmd cmd) throws ServiceException { Long loginId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cmd.getCameraId()); if (loginId == null) { throw new ServiceException("相机不在线"); } cmd.setLoginId(loginId); PtzParamDTO data = getGisInfo(cmd); ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cmd.getCameraId()); List ardWellResult = ardWellService.selectArdWellByUserId(userId); if (ardCamera.getTowerId() == null) { throw new ServiceException("该光电没有挂接塔"); } ArdTower ardTower = ardTowerService.selectArdTowerById(ardCamera.getTowerId()); /*Float p = (Float) data.get("p"); Float t = (Float) data.get("t"); Float fHorFieldAngle = (Float) data.get("fHorFieldAngle");// 水平视场角 Float fVerFieldAngle = (Float) data.get("fVerFieldAngle");// 垂直视场角*/ Float p = data.getP(); Float t = data.getT(); Float fHorFieldAngle = data.getFHorFieldAngle();// 水平视场角 Float fVerFieldAngle = data.getFVerFieldAngle();// 垂直视场角 if (t > 359.0) { throw new ServiceException("俯仰角过小"); } //小三角形腰 Double xy = Math.tan(Math.PI / 2 - (Math.PI * 2 - Math.PI * t / 180) - Math.PI * fVerFieldAngle / 180 / 2) * ardCamera.getAltitude() / Math.cos(Math.PI * fHorFieldAngle / 180 / 2); //大三角形腰 Double dy = Math.tan(Math.PI / 2 - (Math.PI * 2 - Math.PI * t / 180) + Math.PI * fVerFieldAngle / 180 / 2) * ardCamera.getAltitude() / Math.cos(Math.PI * fHorFieldAngle / 180 / 2); if (dy < 0) { dy = -1 * dy; } if (xy < 0) { xy = -1 * xy; } Double lonA = ardCamera.getLongitude() + xy * Math.sin((p + fHorFieldAngle / 2) * Math.PI / 180) * 180 / (Math.PI * 6371229 * Math.cos(ardCamera.getLatitude() * Math.PI / 180)); Double latA = ardCamera.getLatitude() + xy * Math.cos((p + fHorFieldAngle / 2) * Math.PI / 180) / (Math.PI * 6371229 / 180); System.out.println("A点经度:" + lonA); System.out.println("A点纬度:" + latA); Double lonB = ardCamera.getLongitude() + xy * Math.sin((p - fHorFieldAngle / 2) * Math.PI / 180) * 180 / (Math.PI * 6371229 * Math.cos(ardCamera.getLatitude() * Math.PI / 180)); Double latB = ardCamera.getLatitude() + xy * Math.cos((p - fHorFieldAngle / 2) * Math.PI / 180) / (Math.PI * 6371229 / 180); System.out.println("B点经度:" + lonB); System.out.println("B点纬度:" + latB); Double lonC = ardCamera.getLongitude() + dy * Math.sin((p + fHorFieldAngle / 2) * Math.PI / 180) * 180 / (Math.PI * 6371229 * Math.cos(ardCamera.getLatitude() * Math.PI / 180)); Double latC = ardCamera.getLatitude() + dy * Math.cos((p + fHorFieldAngle / 2) * Math.PI / 180) / (Math.PI * 6371229 / 180); System.out.println("C点经度:" + lonC); System.out.println("C点纬度:" + latC); Double lonD = ardCamera.getLongitude() + dy * Math.sin((p - fHorFieldAngle / 2) * Math.PI / 180) * 180 / (Math.PI * 6371229 * Math.cos(ardCamera.getLatitude() * Math.PI / 180)); Double latD = ardCamera.getLatitude() + dy * Math.cos((p - fHorFieldAngle / 2) * Math.PI / 180) / (Math.PI * 6371229 / 180); System.out.println("D点经度:" + lonD); System.out.println("D点纬度:" + latD); //Double dg = Math.tan(Math.PI/2 - (Math.PI * 2 - Math.PI * t/180) - Math.PI * fVerFieldAngle/180/2) // *ardCamera.getAltitude();//大三角形高 //Double xg = Math.tan(Math.PI/2 - (Math.PI * 2 - Math.PI * t/180) + Math.PI * fVerFieldAngle/180/2) // *ardCamera.getAltitude();//小三角形高 //设置多边形 List pts = new ArrayList(); pts.add(new Point2D.Double(lonA, latA)); pts.add(new Point2D.Double(lonB, latB)); pts.add(new Point2D.Double(lonD, latD)); pts.add(new Point2D.Double(lonC, latC)); List innerList = new ArrayList();//查找多边形内兴趣点 for (ArdWell ardWell : ardWellResult) { Point2D.Double point = new Point2D.Double(ardWell.getLongitude(), ardWell.getLatitude()); if (IsPtInPoly(point, pts)) { innerList.add(ardWell); } } List> result = new ArrayList(); if (ardTower.getAltitude() > 5) {//有高程 for (ArdWell ardWell : innerList) { Map map = new HashMap(); double distance_Target = RealDistance(ardCamera.getLongitude(), ardCamera.getLatitude(), ardWell.getLongitude(), ardWell.getLatitude()); double Angle_A = GetAngle(ardCamera.getLongitude(), ardCamera.getLatitude(), ardWell.getLongitude(), ardWell.getLatitude()); //double Angle_E = Math.atan((ardCameras.getAltitude() - 0) / distance_Target) * 180 / Math.PI;//无高程 double Angle_E = Math.atan((ardCamera.getAltitude() + ardTower.getAltitude() - ardWell.getAltitude()) / distance_Target) * 180 / Math.PI;//有高程 Angle_A = Angle_A - (p - fHorFieldAngle / 2);//视场角内方位 Angle_E = Angle_E - (360 - t - fVerFieldAngle / 2);//视场角内俯仰 if (!(Angle_E / fVerFieldAngle < 0.1 || Angle_A / fHorFieldAngle < 0.08 || Angle_A / fHorFieldAngle > 0.92)) {//最上,最左,最右去掉 map.put("id", ardWell.getId()); map.put("wellId", ardWell.getWellId()); map.put("horFieldProportion", Angle_A / fHorFieldAngle);//横向占比 map.put("verFieldProportion", Angle_E / fVerFieldAngle);//纵向占比 result.add(map); } } } else {//无高程 for (ArdWell ardWell : innerList) { Map map = new HashMap(); double distance_Target = RealDistance(ardCamera.getLongitude(), ardCamera.getLatitude(), ardWell.getLongitude(), ardWell.getLatitude()); double Angle_A = GetAngle(ardCamera.getLongitude(), ardCamera.getLatitude(), ardWell.getLongitude(), ardWell.getLatitude()); double Angle_E = Math.atan((ardCamera.getAltitude() - 0) / distance_Target) * 180 / Math.PI;//无高程 //double Angle_E = Math.atan((ardCameras.getAltitude() + ardTowers.getAltitude() - ardAlarmpointsWell // .getAltitude()) / distance_Target) * 180 / Math.PI;//有高程 Angle_A = Angle_A - (p - fHorFieldAngle / 2);//视场角内方位 Angle_E = Angle_E - (360 - t - fVerFieldAngle / 2);//视场角内俯仰 if (!(Angle_E / fVerFieldAngle < 0.1 || Angle_A / fHorFieldAngle < 0.08 || Angle_A / fHorFieldAngle > 0.92)) {//最上,最左,最右去掉 map.put("id", ardWell.getId()); map.put("wellId", ardWell.getWellId()); map.put("horFieldProportion", Angle_A / fHorFieldAngle);//横向占比 map.put("verFieldProportion", Angle_E / fVerFieldAngle);//纵向占比 result.add(map); } } } return result; } public static double RealDistance(double base_pos_longitude, double base_pos_latitude, double tar_pos_longitude, double tar_pos_latitude) { double er; double f; double g; double l; double sg; double sl; double sf; double s; double c; double w; double r; double d; double h1; double h2; double fl; er = 6378.137; fl = 1 / 298.257; f = (base_pos_latitude + tar_pos_latitude) * Math.PI / 360; g = (base_pos_latitude - tar_pos_latitude) * Math.PI / 360; l = (base_pos_longitude - tar_pos_longitude) * Math.PI / 360; sg = Math.sin(g); sl = Math.sin(l); sf = Math.sin(f); sg = Math.pow(sg, 2); sl = Math.pow(sl, 2); sf = Math.pow(sf, 2); s = sg * (1 - sl) + (1 - sf) * sl; c = (1 - sg) * (1 - sl) + sf * sl; w = Math.atan(Math.sqrt(s / c)); r = Math.sqrt(s * c) / w; d = 2 * w * er; h1 = (3 * r - 1) / 2 / c; h2 = (3 * r + 1) / 2 / s; return 1000 * d * (1 + fl * (h1 * sf * (1 - sg) - h2 * (1 - sf) * sg));//84 } public static double GetAngle(double base_pos_longitude, double base_pos_latitude, double tar_pos_longitude, double tar_pos_latitude) { double lat1 = base_pos_latitude * Math.PI / 180; double lon1 = base_pos_longitude * Math.PI / 180; double lat2 = tar_pos_latitude * Math.PI / 180; double lon2 = tar_pos_longitude * Math.PI / 180; double d = Math.sin(lat2) * Math.sin(lat1) + Math.cos(lat2) * Math.cos(lat1) * Math.cos(lon2 - lon1); if (d != 1) { d = Math.asin(Math.cos(lat2) * Math.sin(lon2 - lon1) / Math.sqrt(1 - d * d)); d = d * 180 / Math.PI; } else { d = 0; } if (lat1 > lat2) { d = 180 - d; } else { if (lon1 > lon2) { d = 360 + d; } } return d; } public boolean IsPtInPoly(Point2D.Double point, List pts) { int N = pts.size(); boolean boundOrVertex = true; //如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true int intersectCount = 0;//cross points count of x double precision = 2e-10; //浮点类型计算时候与0比较时候的容差 Point2D.Double p1, p2;//neighbour bound vertices Point2D.Double p = point; //当前点 p1 = pts.get(0);//left vertex for (int i = 1; i <= N; ++i) {//check all rays if (p.equals(p1)) { return boundOrVertex;//p is an vertex } p2 = pts.get(i % N);//right vertex if (p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)) {//ray is outside of our interests p1 = p2; continue;//next ray left point } if (p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)) {//ray is crossing over by the algorithm // (common part of) if (p.y <= Math.max(p1.y, p2.y)) {//x is before of ray if (p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)) {//overlies on a horizontal ray return boundOrVertex; } if (p1.y == p2.y) {//ray is vertical if (p1.y == p.y) {//overlies on a vertical ray return boundOrVertex; } else {//before ray ++intersectCount; } } else {//cross point on the left side double xinters = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;//cross point of y if (Math.abs(p.y - xinters) < precision) {//overlies on a ray return boundOrVertex; } if (p.y < xinters) {//before ray ++intersectCount; } } } } else {//special case when ray is crossing through the vertex if (p.x == p2.x && p.y <= p2.y) {//p crossing over p2 Point2D.Double p3 = pts.get((i + 1) % N); //next vertex if (p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)) {//p.x lies between p1.x & p3.x ++intersectCount; } else { intersectCount += 2; } } } p1 = p2;//next ray left point } if (intersectCount % 2 == 0) {//偶数在多边形外 return false; } else { //奇数在多边形内 return true; } } }