package com.ard.work.sdk.dh.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.terrain.domain.ArdTerrainMark; import com.ard.work.device.terrain.service.IArdTerrainMarkService; import com.ard.work.event.LoginEvent; import com.ard.work.sdk.common.GlobalVariable; import com.ard.work.sdk.common.osSelect; import com.ard.work.sdk.dh.lib.NetSDKLib; import com.ard.work.sdk.dh.lib.enumeration.EM_NEW_CONFIG; import com.ard.work.sdk.dh.lib.enumeration.NET_EM_CFG_OPERATE_TYPE; import com.ard.work.sdk.dh.lib.structure.CFG_INFRARED_INFO; import com.ard.work.sdk.dh.lib.structure.CFG_VIDEO_IN_FOCUS; import com.ard.work.sdk.dh.lib.structure.DH_OUT_PTZ_VIEW_RANGE_STATUS; import com.ard.work.sdk.dh.lib.structure.NET_VIDEOIN_DEFOG_INFO; import com.ard.work.sdk.dh.module.*; 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 jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.util.StopWatch; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.OptionalDouble; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static com.ard.work.sdk.dh.lib.NetSDKLib.*; import static com.ard.work.sdk.dh.lib.NetSDKLib.NET_PTZ_ControlType.NET_PTZ_POINT_MOVE_CONTROL; import static com.ard.work.sdk.dh.lib.NetSDKLib.NET_PTZ_ControlType.NET_PTZ_POINT_SET_CONTROL; import static com.ard.work.sdk.dh.lib.ToolKits.getErrorCodePrint; /** * @Description: 大华sdk策略 * @ClassName: DhSdkStrategy * @Author: 刘苏义 * @date: 2023年11月07日10:51:09 **/ @Service("dahuaSDK") @Slf4j public class DaHuaSDK implements CameraSDK { @Resource private ApplicationEventPublisher eventPublisher; @Resource private RedisService redisService; @Resource private RemoteFileService remoteFileService; @Resource private IArdTerrainMarkService ardTerrainMarkService; @Value("${sdk.tempDir}") private String tempDir; public static NetSDKLib netsdk = null; public static NetSDKLib configsdk = null; // 设备断线通知回调 private static final DisConnect disConnect = new DisConnect(); // 网络连接恢复 private static final HaveReConnect haveReConnect = new HaveReConnect(); @PostConstruct public void initSdk() { try { boolean b = CreateSDKInstance(); if (!b) { log.error("Create DhSDKInstance failed"); } boolean bInit = netsdk.CLIENT_Init(disConnect, null); if (!bInit) { log.error("Initialize DhSDK failed"); } //打开日志,可选 //NetSDKLib.LOG_SET_PRINT_INFO setLog = new NetSDKLib.LOG_SET_PRINT_INFO(); //File path = new File("./sdklog/"); //if (!path.exists()) { // path.mkdir(); //} //String logPath = path.getAbsoluteFile().getParent() + "\\sdklog\\" + ToolKits.getDate() + ".log"; //setLog.nPrintStrategy = 0; //setLog.bSetFilePath = 1; //System.arraycopy(logPath.getBytes(), 0, setLog.szLogFilePath, 0, logPath.getBytes().length); //System.out.println(logPath); //setLog.bSetPrintStrategy = 1; //bLogopen = netsdk.CLIENT_LogOpen(setLog); // if (!bLogopen) { // System.err.println("Failed to open NetSDK log"); // } // 设置断线重连回调接口,设置过断线重连成功回调函数后,当设备出现断线情况,SDK内部会自动进行重连操作 // 此操作为可选操作,但建议用户进行设置 netsdk.CLIENT_SetAutoReconnect(haveReConnect, null); //设置登录超时时间和尝试次数,可选 int waitTime = 2000; //登录请求响应超时时间设置为5S int tryTimes = 1; //登录时尝试建立链接1次 netsdk.CLIENT_SetConnectTime(waitTime, tryTimes); // 设置更多网络参数,NET_PARAM的nWaitTime,nConnectTryNum成员与CLIENT_SetConnectTime // 接口设置的登录设备超时时间和尝试次数意义相同,可选 NetSDKLib.NET_PARAM netParam = new NetSDKLib.NET_PARAM(); netParam.nConnectTime = 2000; // 登录时尝试建立链接的超时时间 netParam.nGetConnInfoTime = 3000; // 设置子连接的超时时间 netParam.nGetDevInfoTime = 3000;//获取设备信息超时时间,为0默认1000ms netsdk.CLIENT_SetNetworkParam(netParam); log.info("初始化大华sdk完成"); } catch (Exception EX) { log.error("初始化大华sdk失败", EX); } } /** * 登录 * 刘苏义 * 2023/10/17 8:28:13Wde2486Eds */ @Override public void login(ArdCamera camera) { String ip = camera.getIp(); Integer port = camera.getPort(); String username = camera.getUsername(); String password = camera.getPassword(); NetSDKLib.NET_DEVICEINFO_Ex deviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex(); NetSDKLib.LLong loginId = LoginModule.login(ip, port, username, password, deviceInfo); if (loginId.longValue() <= 0) { // 登录失败处理 handleLoginFail(camera, loginId); } else { // 登录成功处理 handleLoginSuccess(camera, loginId, deviceInfo); } // 发布登录事件 eventPublisher.publishEvent(new LoginEvent(camera)); } /** * 登录失败处理 */ private void handleLoginFail(ArdCamera camera, NetSDKLib.LLong loginId) { camera.setChanNum(0); camera.setLoginId(loginId.longValue()); camera.setState("0"); camera.setChannelList(null); log.warn("设备[{}:{}]登录失败,{}", camera.getIp(), camera.getPort(), getErrorCodePrint()); } /** * 登录成功处理 */ private void handleLoginSuccess(ArdCamera camera, NetSDKLib.LLong loginId, NetSDKLib.NET_DEVICEINFO_Ex deviceInfo) { camera.setState("1"); camera.setChanNum(deviceInfo.byChanNum); camera.setStartChan(1); camera.setLoginId(loginId.longValue()); //获取通道并设置 List channels = getChannels(camera); if (!channels.isEmpty()) { camera.setChannelList(channels); } log.info("设备登录成功 [{}:{}]", camera.getIp(), camera.getPort()); } //获取通道 @Override public List getChannels(ArdCamera camera) { if (camera.getLoginId() < 0) { return new ArrayList<>(); } NetSDKLib.LLong loginId = new NetSDKLib.LLong(camera.getLoginId()); List ardChannelList = new ArrayList<>(); for (int i = 1; i < camera.getChanNum() + 1; i++) { ArdChannel channel = new ArdChannel(); String chanName = ConfigModule.getChannelName(loginId, i).trim(); channel.setName(chanName.isEmpty() ? "通道" + i : chanName); channel.setDeviceId(camera.getId()); channel.setChanNo(i); //获取视频编码 CameraCmd cameraCmd = new CameraCmd(camera.getId(), channel.getChanNo()); cameraCmd.setLoginId(camera.getLoginId()); VideoCompressionCfg videoCompressionCfg = getVideoCompressionCfg(cameraCmd); if (StringUtils.isNotNull(videoCompressionCfg)) { channel.setVideoCode(videoCompressionCfg.getVideoEncType()); } if ("2".equals(camera.getType())) { //NVR过滤不在线的通道 if (!ConfigModule.queryCameraState(loginId, camera.getChanNum(), i)) { continue; } } String rtspUrl = String.format("rtsp://%s:%s@%s:%d/cam/realmonitor?channel=%d&subtype=0", camera.getUsername(), camera.getPassword(), camera.getIp(), camera.getRtspPort(), channel.getChanNo() ); channel.setLiveAddress(rtspUrl); ardChannelList.add(channel); } return ardChannelList; } /** * 注销 * 刘苏义 * 2023/10/17 8:28:13 */ @Override public void logout(String CameraId) { Long loginId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, CameraId); if (loginId == null) { return; } LoginModule.logout(new NetSDKLib.LLong(loginId)); } /** * 是否在线检测 * 刘苏义 * 2023/10/17 8:28:13 */ @Override public Boolean isOnLine(CameraCmd cmd) { try { LLong loginId = new LLong(cmd.getLoginId()); NetSDKLib.NET_WORKSTATE dh_ptz_location_info = new NetSDKLib.NET_WORKSTATE(); boolean b = ConfigModule.queryDevState(loginId, NET_DEVSTATE_ONLINE, dh_ptz_location_info); if (!b) { return false; } } catch (Exception ex) { log.error("检测在线异常:{}", ex.getMessage()); return false; } return true; } @Override public Boolean pTZControl(CameraCmd cmd) { String cameraId = cmd.getCameraId(); boolean enable = cmd.isEnable(); int chanNo = cmd.getChanNo(); // 通道号 int speed = cmd.getSpeed(); // 速度 int code = cmd.getCode(); // 指令 NetSDKLib.LLong loginId = new NetSDKLib.LLong(cmd.getLoginId()); int dwStop = enable ? 0 : 1; // 0 开启,1 关闭 int dwPTZCommand; // 1️⃣ 根据 code 映射控制类型 switch (code) { case 1: dwPTZCommand = NetSDKLib.NET_EXTPTZ_ControlType.NET_EXTPTZ_LEFTTOP; break; case 2: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_UP_CONTROL; break; case 3: dwPTZCommand = NetSDKLib.NET_EXTPTZ_ControlType.NET_EXTPTZ_RIGHTTOP; break; case 4: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_LEFT_CONTROL; break; case 5: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_POINT_LOOP_CONTROL; break; case 6: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_RIGHT_CONTROL; break; case 7: dwPTZCommand = NetSDKLib.NET_EXTPTZ_ControlType.NET_EXTPTZ_LEFTDOWN; break; case 8: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_DOWN_CONTROL; break; case 9: dwPTZCommand = NetSDKLib.NET_EXTPTZ_ControlType.NET_EXTPTZ_RIGHTDOWN; break; case 10: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_ZOOM_ADD_CONTROL; break; case 11: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_ZOOM_DEC_CONTROL; break; case 12: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_FOCUS_ADD_CONTROL; break; case 13: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_FOCUS_DEC_CONTROL; break; case 14: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_APERTURE_ADD_CONTROL; break; case 15: dwPTZCommand = NetSDKLib.NET_PTZ_ControlType.NET_PTZ_APERTURE_DEC_CONTROL; break; case 16: dwPTZCommand = NetSDKLib.CtrlType.CTRLTYPE_CTRL_RAINBRUSH_MOVEONCE; break; default: log.warn("【{}】PTZ控制指令不支持:{}", cameraId, code); return false; } // 2️⃣ 调用 SDK 控制 boolean success; if (code == 16) { // 雨刷控制单独处理 success = PtzControlModule.controlRainBrushControl(loginId, chanNo - 1, dwPTZCommand); } else { success = PtzControlModule.ptzControl(loginId, chanNo - 1, dwPTZCommand, speed, speed, 0, dwStop); } // 3️⃣ 判断控制结果 if (!success) { log.warn("【{}】PTZ控制失败,错误码:{}", cameraId, getErrorCodePrint()); return false; } return true; // 成功 } /** * 获取PTZ值 * 刘苏义 * 2023/10/17 8:28:13 */ @Override public PtzDto getPtz(CameraCmd cmd) { NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); NetSDKLib.NET_PTZ_LOCATION_INFO dh_ptz_location_info = new NetSDKLib.NET_PTZ_LOCATION_INFO(); boolean b = ConfigModule.queryDevState(loginId, NET_DEVSTATE_PTZ_LOCATION, dh_ptz_location_info); if (!b) { String errorMsg = String.format("相机【%s】获取ptz失败,%s", cmd.getCameraId(), getErrorCodePrint()); log.warn(errorMsg); return null; } // Pan (-1800~1800 → -180~180) float P = dh_ptz_location_info.nPTZPan / 10f; // Tilt (-1800~1800 → -180~180 → 0~360) float tilt = dh_ptz_location_info.nPTZTilt / -10f; // 如果设备方向要反 float T = Math.round(((tilt + 360f) % 360f) * 100f) / 100f; // Zoom (放大100倍 → 真实值) float Z = dh_ptz_location_info.nZoomValue / 100f; return new PtzDto(P, T, Z); } @Override public PtzScope getPtzScope(CameraCmd cmd) { NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); NET_PRODUCTION_DEFNITION productionDefinition = new NET_PRODUCTION_DEFNITION(); boolean b = ConfigModule.QueryProductionDefinition(loginId, productionDefinition); if (!b) { String errorMsg = String.format("获取ptz范围失败,%s", getErrorCodePrint()); log.warn(errorMsg); return null; } PtzScope ptzScope = new PtzScope(); ptzScope.setPMax((float) productionDefinition.nPtzHorizontalAngleMax); ptzScope.setPMin((float) productionDefinition.nPtzHorizontalAngleMin); ptzScope.setTMax((float) productionDefinition.nPtzVerticalAngleMax); ptzScope.setTMin((float) productionDefinition.nPtzVerticalAngleMin); return ptzScope; } //设置ptz信息 @Override public Boolean setPtz(CameraCmd cmd) { Integer chanNo = cmd.getChanNo(); PtzDto ptzDto = cmd.getPtzDto(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); try { int p = Math.round(ptzDto.getP() * 10); int t = Math.round(ptzDto.getT() * 10); t = (t > 900) ? (3600 - t) : (-t); float scale = ptzDto.getZ(); int z = convertZoomTo128Range(scale); boolean bool = netsdk.CLIENT_DHPTZControlEx2(loginId, chanNo - 1, NetSDKLib.NET_EXTPTZ_ControlType.NET_EXTPTZ_EXACTGOTO, p, t, z, 0, null); if (!bool) { String errorMsg = String.format("设置ptz失败,%s", getErrorCodePrint()); log.warn(errorMsg); return false; } return true; } catch (Exception ex) { log.error("设置ptz异常:{}", ex.getMessage()); return false; } } /** * 将float类型的实际倍率(0~86)转换为0~128区间的整数数值 * * @param actualZoom 实际倍率值(float类型,0~86) * @return 0~128区间的整数结果 */ public static int convertZoomTo128Range(float actualZoom) { // 边界值校验(避免超出范围) if (actualZoom < 0.0f) { return 0; } if (actualZoom > 86.0f) { return 128; } // 核心转换计算,四舍五入取整 double result = (double) actualZoom / 86.0 * 128.0; return (int) Math.round(result); } //设置零方位角 @Override public Boolean setZeroPtz(CameraCmd cmd) { Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); boolean bool = netsdk.CLIENT_DHPTZControlEx(loginId, chanNo - 1, NetSDKLib.NET_EXTPTZ_ControlType.NET_EXTPTZ_RESETZERO, 0, 0, 0, 0); if (!bool) { String errorMsg = String.format("设置零方位角失败,%s", getErrorCodePrint()); log.warn(errorMsg); return false; } return true; } //截图 存服务器 @Override public String picCutCate(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String bucketName = cmd.getBucketName() == null ? "pic" : cmd.getBucketName(); String type = cmd.getType() == null ? "other" : cmd.getType(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); String url = ""; String objectName = cameraId + "_" + chanNo + ".jpeg"; CompletableFuture urlFuture = new CompletableFuture<>(); fCaptureReceiveCB m_CaptureReceiveCB = new fCaptureReceiveCB(bucketName, objectName, type, urlFuture); CapturePictureModule.setSnapRevCallBack(m_CaptureReceiveCB); boolean success = CapturePictureModule.remoteCapturePicture(loginId, chanNo - 1); if (!success) { String errorMsg = String.format("截图失败,%s", getErrorCodePrint()); log.warn(errorMsg); return url; } // 阻塞等待,直到异步任务完成 try { return urlFuture.get(); } catch (InterruptedException | ExecutionException e) { log.error(e.getMessage()); } return url; } //截图 存服务器 @Override public String picCutCate(CameraCmd cmd, String bucketName, String objectName) { Integer chanNo = cmd.getChanNo(); String type = cmd.getType() == null ? "other" : cmd.getType(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); String url = ""; CompletableFuture urlFuture = new CompletableFuture<>(); fCaptureReceiveCB m_CaptureReceiveCB = new fCaptureReceiveCB(bucketName, objectName, type, urlFuture); CapturePictureModule.setSnapRevCallBack(m_CaptureReceiveCB); boolean success = CapturePictureModule.remoteCapturePicture(loginId, chanNo - 1); if (!success) { String errorMsg = String.format("截图失败,%s", getErrorCodePrint()); log.warn(errorMsg); return url; } // 阻塞等待,直到异步任务完成 try { return urlFuture.get(); } catch (InterruptedException | ExecutionException e) { log.error(e.getMessage()); } return url; } @Override public byte[] localCapture(CameraCmd cmd) { Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); CompletableFuture imageFuture = new CompletableFuture<>(); fCaptureReceiveBytes m_CaptureReceiveCB = new fCaptureReceiveBytes(imageFuture); CapturePictureModule.setSnapRevCallBack(m_CaptureReceiveCB); boolean success = CapturePictureModule.remoteCapturePicture(loginId, chanNo - 1); if (!success) { log.warn("截图失败,{}", getErrorCodePrint()); return null; } // 阻塞等待,直到异步任务完成 try { return imageFuture.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } @Override public Double correctPitch(CameraCmd cmd) { double pitchAngle = 0.0; try { Point targetPosition = cmd.getTargetPosition(); ArdTerrainMark ardTerrainMark = new ArdTerrainMark(); ardTerrainMark.setCameraId(cmd.getCameraId()); List ardTerrainMarks = ardTerrainMarkService.selectArdTerrainMarkList(ardTerrainMark); //过滤500米范围内的集合 ardTerrainMarks = ardTerrainMarks.stream().filter(n -> GisUtil.getDistance(targetPosition, 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; } //短时录像 @Override public String record(CameraCmd cmd) { try { String url = ""; String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String name = cameraId + "_" + chanNo; // 本地临时录像地址 String tempPicPath = tempDir + "/record/" + name + ".mp4"; String path = FileUtils.createFile(tempPicPath); boolean enable = cmd.isEnable(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); if (enable) { LLong lRealHandle; if (!GlobalVariable.previewMap.containsKey(name)) { lRealHandle = RealPlayModule.startRealPlay(loginId, chanNo - 1, path); if (lRealHandle.longValue() <= 0) { String errorMsg = String.format("取流失败,%s", getErrorCodePrint()); log.warn(errorMsg); return ""; } log.debug("取流成功"); GlobalVariable.previewMap.put(name, lRealHandle); } log.debug("录像开始"); } else { if (GlobalVariable.previewMap.containsKey(name)) { LLong lRealHandle = (LLong) (GlobalVariable.previewMap.get(name)); RealPlayModule.stopRealPlay(lRealHandle); GlobalVariable.previewMap.remove(name); } log.debug("录像停止"); } 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; NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); // 本地临时录像地址 String tempPicPath = tempDir + "/record/" + name + ".mp4"; String path = FileUtils.createFile(tempPicPath); LLong lRealHandle; if (!GlobalVariable.previewMap.containsKey(name)) { lRealHandle = RealPlayModule.startRealPlay(loginId, chanNo - 1, path); if (lRealHandle.longValue() <= 0) { String errorMsg = String.format("取流失败,%s", getErrorCodePrint()); log.warn(errorMsg); return false; } log.debug("取流成功"); GlobalVariable.previewMap.put(name, lRealHandle); } log.debug("录像开始"); return true; } catch (Exception ex) { log.error("开始录像异常:{}", ex.getMessage()); return false; } } @Override public String recordStop(CameraCmd cmd) { String url = ""; try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); String name = cameraId + "_" + chanNo; // 本地临时录像地址 String tempPicPath = tempDir + "/record/" + name + ".mp4"; String path = FileUtils.createFile(tempPicPath); LLong lRealHandle; if (GlobalVariable.previewMap.containsKey(name)) { lRealHandle = (LLong) (GlobalVariable.previewMap.get(name)); RealPlayModule.stopRealPlay(lRealHandle); GlobalVariable.previewMap.remove(name); log.debug("停止当前录像"); } //ffmpeg转码 StopWatch watchStop = new StopWatch(); watchStop.start(); String newpath = FileUtils.createFile(tempDir + "/record/" + name + "_c.mp4"); FFmpegUtils.transcodeToMP4(path, newpath); watchStop.stop(); log.warn("转码耗时:{}", watchStop.getTotalTimeMillis()); //存入minio String bucketName = "record"; String fileName = cameraId + "_" + chanNo; String type = cmd.getType(); // MultipartFile multipartFile = MultipartFileUtil.filePathToMultipartFile(newpath, objectName, // MimeTypeUtils.VIDEO_MP4); File tempFile = new File(newpath); MultipartFile multipartFile = new FileMultipartFile(tempFile, fileName, MimeTypeUtils.VIDEO_MP4); R sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { url = sysFileR.getData().getUrl(); log.info("上传文件成功:{}", url); } } catch (Exception ex) { log.error("录像异常:{}", ex.getMessage()); } return url; } /** * 引导目标位置 * 刘苏义 * 2023/10/17 8:27:48 */ @Override public Boolean guideTargetPosition(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); try { ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId); if (StringUtils.isNull(ardCamera)) { log.warn("相机不存在:{}", 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); //修正俯仰 double correctPitch = correctPitch(cmd); int p = (int) (cameraPTZ.getP() * 10); int t = (int) ((cameraPTZ.getT() + correctPitch) * 10); t = (t > 900) ? (3600 - t) : (-t); int z = (int) (cameraPTZ.getZ().floatValue()); boolean bool = netsdk.CLIENT_DHPTZControlEx(loginId, chanNo - 1, NetSDKLib.NET_EXTPTZ_ControlType.NET_EXTPTZ_EXACTGOTO, p, t, z, 0); if (!bool) { String errorMsg = String.format("设置ptz失败,%s", getErrorCodePrint()); log.warn(errorMsg); return false; } return true; } catch (Exception ex) { log.error("引导目标位置异常:{}", ex.getMessage()); return false; } } //转至预置点 @Override public Boolean gotoPreset(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); Integer presetIndex = cmd.getPresetIndex(); NetSDKLib.LLong loginId = new NetSDKLib.LLong(cmd.getLoginId()); try { boolean success = netsdk.CLIENT_DHPTZControlEx( loginId, chanNo - 1, NET_PTZ_POINT_MOVE_CONTROL, 0, presetIndex, 0, 0 ); if (!success) { log.warn("【{}】转至预置点失败: {}", cameraId, getErrorCodePrint()); return false; } log.info("【{}】转至预置点成功: {}", cameraId, presetIndex); return true; } catch (Exception ex) { log.error("【{}】转至预置点异常: {}", cameraId, ex.getMessage(), ex); return false; } } // 设置预置位 @Override public Boolean setPreset(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); Integer presetIndex = cmd.getPresetIndex(); NetSDKLib.LLong loginId = new NetSDKLib.LLong(cmd.getLoginId()); try { boolean success = netsdk.CLIENT_DHPTZControlEx(loginId, chanNo - 1, NET_PTZ_POINT_SET_CONTROL, 0, presetIndex, 0, 0); if (!success) { log.warn("【{}】设置预置点失败: {}", cameraId, getErrorCodePrint()); return false; } log.info("【{}】设置预置点成功: {}", cameraId, presetIndex); return true; } catch (Exception ex) { log.error("【{}】设置预置点异常: {}", cameraId, ex.getMessage(), ex); return false; } } //切换聚焦模式 @Override public Boolean controlFocusMode(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); boolean enable = cmd.isEnable(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); CFG_VIDEO_IN_FOCUS cfg_video_in_focus = new CFG_VIDEO_IN_FOCUS(); cfg_video_in_focus.nVideoInFocusRealNum = 3; // 配置使用个数 for (int i = 0; i < 3; i++) { cfg_video_in_focus.stVideoInFocusUnit[i].nSensitivity = 1; // 聚焦灵敏度, 0-高, 1-默认, 2-低 cfg_video_in_focus.stVideoInFocusUnit[i].nIRCorrection = 2; // 红外光聚焦修正, 0-不修正, 1-修正, 2-自动修正 cfg_video_in_focus.stVideoInFocusUnit[i].nFocusLimit = 10000; // 聚焦极限值, 单位毫米 if (enable) { // 手动聚焦 cfg_video_in_focus.stVideoInFocusUnit[i].nMode = 4; cfg_video_in_focus.stVideoInFocusUnit[i].emFocusMode = 0; log.debug("当前为手动聚焦模式"); } else { // 自动聚焦 cfg_video_in_focus.stVideoInFocusUnit[i].nMode = 2; cfg_video_in_focus.stVideoInFocusUnit[i].emFocusMode = 1; log.debug("当前为自动聚焦模式"); } } cfg_video_in_focus.nChannelIndex = chanNo - 1; boolean bool = ConfigModule.SetDevConfig(loginId, chanNo - 1, NetSDKLib.CFG_CMD_VIDEOIN_FOCUS, cfg_video_in_focus); if (!bool) { String errorMsg = String.format("切换聚焦模式失败,%s", getErrorCodePrint()); log.warn(errorMsg); return false; // 或者直接抛异常,视你统一风格 } log.debug("切换聚焦模式成功"); return true; } //获取聚焦模式 @Override public Boolean getFocusMode(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); NET_VIDEOIN_FOCUSMODE_INFO focusModeInfo = new NET_VIDEOIN_FOCUSMODE_INFO(); int emCfgOpType = NET_EM_CFG_OPERATE_TYPE.NET_EM_CFG_VIDEOIN_FOCUSMODE; boolean success = ConfigModule.GetConfig(loginId, chanNo - 1, emCfgOpType, focusModeInfo); if (!success) { String errorMsg = String.format("获取聚焦模式失败,%s", getErrorCodePrint()); log.warn(errorMsg); return false; } // 判断手动/自动 // 0=关闭, 1=辅助, 2=自动, 3=半自动, 4=手动 return focusModeInfo.emFocusMode == 4; // true=手动聚焦, false=自动聚焦 } @Override public Boolean controlPTHeater(CameraCmd cmd) { throw new CameraSDKException( CameraSDKException.ErrorCode.CMD_NOT_SUPPORT.getCode(), cmd.getCameraId(), "功能暂不支持" ); } @Override public Boolean controlCameraDeicing(CameraCmd cmd) { throw new CameraSDKException( CameraSDKException.ErrorCode.CMD_NOT_SUPPORT.getCode(), cmd.getCameraId(), "功能暂不支持" ); } @Override public Boolean getPTZLockInfo(CameraCmd cmd) { throw new CameraSDKException( CameraSDKException.ErrorCode.CMD_NOT_SUPPORT.getCode(), cmd.getCameraId(), "功能暂不支持" ); } @Override public Boolean setPTZLockInfo(CameraCmd cmd) { throw new CameraSDKException( CameraSDKException.ErrorCode.CMD_NOT_SUPPORT.getCode(), cmd.getCameraId(), "功能暂不支持" ); } // 切换透雾 @Override public Boolean controlDefog(CameraCmd cmd) { Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); try { String command = EM_NEW_CONFIG.CFG_CMD_VIDEOINDEFOG.getValue(); NET_VIDEOIN_DEFOG_INFO defogInfo = new NET_VIDEOIN_DEFOG_INFO(); boolean success = ConfigModule.SetDevConfig(loginId, chanNo - 1, command, defogInfo); if (!success) { log.warn("【{}】切换透雾失败: {}", cmd.getCameraId(), getErrorCodePrint()); } return success; } catch (Exception ex) { log.error("切换透雾异常:{}", ex.getMessage()); return false; } } //查询是否开启透雾 @Override public Boolean isEnableDefog(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); try { String command = EM_NEW_CONFIG.CFG_CMD_VIDEOINDEFOG.getValue(); NET_VIDEOIN_DEFOG_INFO netVideoinDefogInfo = new NET_VIDEOIN_DEFOG_INFO(); boolean success = ConfigModule.GetNewDevConfig(loginId, chanNo - 1, command, netVideoinDefogInfo); if (!success) { log.warn("查询透雾失败: {}", getErrorCodePrint()); return false; } return netVideoinDefogInfo.bCamDefogEnable; } catch (Exception ex) { log.error("查询透雾异常: {}", ex.getMessage()); return false; } } //切换红外 @Override public Boolean controlInfrared(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); try { CFG_INFRARED_INFO cfgInfraredInfo = new CFG_INFRARED_INFO(); boolean success = ConfigModule.SetDevConfig(loginId, chanNo - 1, CFG_CMD_INFRARED_CONFIG, cfgInfraredInfo); 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(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); try { CFG_INFRARED_INFO cfgInfraredInfo = new CFG_INFRARED_INFO(); boolean success = ConfigModule.GetDevConfig(loginId, chanNo - 1, CFG_CMD_INFRARED_CONFIG, cfgInfraredInfo); if (!success) { log.warn("【{}】查询是否开启红外失败: {}", cameraId, getErrorCodePrint()); return false; } // emInfraredMode 可以根据设备文档判断 0/1 表示关闭/开启 return cfgInfraredInfo.emInfraredMode != 0; } catch (Exception ex) { log.error("【{}】查询是否开启红外异常", cameraId, ex); return false; } } //获取聚焦值 @Override public Integer getFocusPos(CameraCmd cmd) { try { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new NetSDKLib.LLong(cmd.getLoginId()); NET_PTZ_LOCATION_INFO net_ptz_location_info = new NET_PTZ_LOCATION_INFO(); net_ptz_location_info.nChannelID = chanNo - 1; boolean success = ConfigModule.queryDevState(loginId, NET_DEVSTATE_PTZ_LOCATION, net_ptz_location_info); if (!success) { log.warn("获取聚焦值失败,{}", getErrorCodePrint()); return null; // 返回 null 表示失败,Controller 层处理 } return net_ptz_location_info.nFocusMapValue; } catch (Exception ex) { log.error("获取聚焦值异常: {}", ex.getMessage()); return null; // 返回 null 表示失败 } } /** * 设置聚焦值(大华实现) */ @Override public Boolean setFocusPos(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); boolean enable = cmd.isEnable(); NetSDKLib.LLong loginId = new NetSDKLib.LLong(cmd.getLoginId()); try { CFG_LIGHTING_INFO cfg = new CFG_LIGHTING_INFO(); cfg.nLightingDetailNum = 1; // 开启/关闭聚焦模式 cfg.stuLightingDetail[0].emMode = enable ? EM_CFG_LIGHTING_MODE.EM_CFG_LIGHTING_MODE_MANUAL : EM_CFG_LIGHTING_MODE.EM_CFG_LIGHTING_MODE_OFF; boolean success = ConfigModule.SetDevConfig(loginId, chanNo - 1, CFG_CMD_LIGHTING, cfg); if (!success) { log.warn("【{}】设置聚焦值失败: {}", cameraId, getErrorCodePrint()); return false; } return true; } catch (Exception e) { log.error("【{}】设置聚焦值异常: {}", cameraId, e.getMessage(), e); return false; } } //获取码流压缩参数 @Override public VideoCompressionCfg getVideoCompressionCfg(CameraCmd cmd) { String cameraId = cmd.getCameraId(); Integer chanNo = cmd.getChanNo(); NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); CFG_ENCODE_INFO cfg_encode_info = new CFG_ENCODE_INFO(); cfg_encode_info.nChannelID = chanNo - 1; boolean b = ConfigModule.GetNewDevConfig(loginId, chanNo - 1, CFG_CMD_ENCODE, cfg_encode_info); if (!b) { String errorMsg = String.format("获取码流压缩参数失败,%s", getErrorCodePrint()); log.warn(errorMsg); return null; } int emCompression = cfg_encode_info.stuMainStream[0].stuVideoFormat.emCompression;//视频格式 String videoEncType; switch (emCompression) { case 0: videoEncType = "MPEG4"; break; case 1: videoEncType = "MS-MPEG4"; break; case 2: videoEncType = "MPEG2"; break; case 3: videoEncType = "MPEG1"; break; case 4: videoEncType = "H.263"; break; case 5: videoEncType = "MJPG"; break; case 6: videoEncType = "FCC-MPEG4"; break; case 7: videoEncType = "标准h264"; break; case 8: videoEncType = "标准h265"; break; case 9: videoEncType = "SVAC"; break; default: videoEncType = "未知"; break; } int nBitRate = cfg_encode_info.stuMainStream[0].stuVideoFormat.nBitRate; int nWidth = cfg_encode_info.stuMainStream[0].stuVideoFormat.nWidth; int nHeight = cfg_encode_info.stuMainStream[0].stuVideoFormat.nHeight; String resolution = nWidth + "*" + nHeight; float nFrameRate = cfg_encode_info.stuMainStream[0].stuVideoFormat.nFrameRate; VideoCompressionCfg videoCompressionCfg = new VideoCompressionCfg(); videoCompressionCfg.setVideoBitrate(String.valueOf(nBitRate));//比特率 videoCompressionCfg.setResolution(resolution);//分辨率 videoCompressionCfg.setVideoEncType(videoEncType);//编码 videoCompressionCfg.setFrameRate(String.valueOf(nFrameRate));//帧率 return videoCompressionCfg; } //获取云台可视域 @Override public PtzParamDTO getGisInfo(CameraCmd cmd) { try { NetSDKLib.LLong loginId = new LLong(cmd.getLoginId()); DH_OUT_PTZ_VIEW_RANGE_STATUS dh_out_ptz_view_range_status = new DH_OUT_PTZ_VIEW_RANGE_STATUS(); boolean b = ConfigModule.queryRemotDevState(loginId, 0, NET_DEVSTATE_PTZ_VIEW_RANGE, dh_out_ptz_view_range_status); if (!b) { String errorMsg = String.format("获取云台可视域失败,%s", getErrorCodePrint()); log.warn(errorMsg); return new PtzParamDTO(); } float nAngelH = (float) dh_out_ptz_view_range_status.nAngelH / 10; float nAngelV = (float) dh_out_ptz_view_range_status.nAngelV / 10; PtzParamDTO ptzParamDTO = new PtzParamDTO(); PtzDto ptzDto = getPtz(cmd); if (ptzDto != null) { ptzParamDTO.setP(ptzDto.getP()); ptzParamDTO.setT(ptzDto.getT()); ptzParamDTO.setZ(ptzDto.getZ()); } else { ptzParamDTO.setP(0f); ptzParamDTO.setT(0f); ptzParamDTO.setZ(0f); } ptzParamDTO.setFHorFieldAngle(nAngelH); ptzParamDTO.setFVerFieldAngle(nAngelV); return ptzParamDTO; } catch (Exception ex) { log.error("获取云台可视域异常:{}", ex.getMessage()); return new PtzParamDTO(); } } //3d定位 @Override public Boolean set3DPosition(CameraCmd cmd) { throw new CameraSDKException( CameraSDKException.ErrorCode.CMD_NOT_SUPPORT.getCode(), cmd.getCameraId(), "功能暂不支持" ); } @Override public DeviceCFG getSystemInfo(CameraCmd cmd) { return null; } // 设备断线回调: 当设备出现断线时,SDK会调用该函数 private static class DisConnect implements NetSDKLib.fDisConnect { @Override public void invoke(LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser) { log.warn("Device[{}:{}] DisConnect!", pchDVRIP, nDVRPort); } } // 网络连接恢复回调:设备重连成功回调,当已断线的设备重连成功时,SDK会调用该函数 private static class HaveReConnect implements NetSDKLib.fHaveReConnect { @Override public void invoke(LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser) { log.warn("ReConnect Device[{}:{}]", pchDVRIP, nDVRPort); } } // 抓图接收回调:当抓图成功,sdk会调用该函数 public class fCaptureReceiveCB implements NetSDKLib.fSnapRev { private final String objectName; private final String bucketName; private final String type; private final CompletableFuture urlFuture; // 用于存储URL的 CompletableFuture public fCaptureReceiveCB(String bucketName, String objectName, String type, CompletableFuture urlFuture) { this.bucketName = bucketName; this.objectName = objectName; this.type = type; this.urlFuture = urlFuture; } @Override public void invoke(LLong lLoginID, Pointer pBuf, int RevLen, int EncodeType, int CmdSerial, Pointer dwUser) { if (pBuf != null && RevLen > 0) { byte[] buf = pBuf.getByteArray(0, RevLen); // 定义文件信息 String fileName = objectName; String tempDir = System.getProperty("java.io.tmpdir") + "/snap_temp/"; File tempDirFile = new File(tempDir); if (!tempDirFile.exists()) { tempDirFile.mkdirs(); } File tempFile = new File(tempDir + fileName); try { // 写入临时文件 try (FileOutputStream fos = new FileOutputStream(tempFile)) { fos.write(buf); fos.flush(); } // 使用 FileMultipartFile 转换 MultipartFile multipartFile = new FileMultipartFile(tempFile, fileName, MimeTypeUtils.IMAGE_JPEG); // 上传到 MinIO R sysFileR = remoteFileService.upload(multipartFile, bucketName, type); if (sysFileR.getCode() == R.SUCCESS) { String url = sysFileR.getData().getUrl(); log.info("上传文件成功:{}", url); urlFuture.complete(url); } else { log.error("上传文件失败: {}", sysFileR.getMsg()); urlFuture.completeExceptionally(new RuntimeException("上传失败: " + sysFileR.getMsg())); } } catch (Exception e) { log.error("处理截图失败", e); urlFuture.completeExceptionally(e); } finally { // 清理临时文件 if (tempFile.exists()) { tempFile.delete(); } } } else { urlFuture.completeExceptionally(new RuntimeException("截图数据为空")); } } } public static class fCaptureReceiveBytes implements NetSDKLib.fSnapRev { private final CompletableFuture imageFuture; public fCaptureReceiveBytes(CompletableFuture imageFuture) { this.imageFuture = imageFuture; } @Override public void invoke(LLong lLoginID, Pointer pBuf, int RevLen, int EncodeType, int CmdSerial, Pointer dwUser) { if (pBuf == null || RevLen <= 0) { return; } byte[] imageBytes = pBuf.getByteArray(0, RevLen); imageFuture.complete(imageBytes); } } /** * 动态库加载 - 大华 NetSDK + ConfigSDK */ private static boolean CreateSDKInstance() { if (netsdk == null || configsdk == null) { synchronized (NetSDKLib.class) { String netSdkDllPath = ""; String configSdkDllPath = ""; try { String userDir = System.getProperty("user.dir"); if (osSelect.isWindows()) { netSdkDllPath = userDir + "\\lib\\dh\\win64\\dhnetsdk.dll"; configSdkDllPath = userDir + "\\lib\\dh\\win64\\dhconfigsdk.dll"; } else if (osSelect.isLinux()) { if (osSelect.isArm64()) { netSdkDllPath = userDir + "/lib/dh/linux-arm64/libdhnetsdk.so"; configSdkDllPath = userDir + "/lib/dh/linux-arm64/libdhconfigsdk.so"; } else if (osSelect.isX64()) { netSdkDllPath = userDir + "/lib/dh/linux64/libdhnetsdk.so"; configSdkDllPath = userDir + "/lib/dh/linux64/libdhconfigsdk.so"; } else { throw new UnsupportedOperationException("❌ 不支持的 Linux 架构: " + osSelect.getArch()); } } else { throw new UnsupportedOperationException("❌ 不支持的操作系统: " + osSelect.getOsName()); } // 检查文件是否存在 if (!new File(netSdkDllPath).exists()) { System.err.println("❌ dhnetsdk 库文件不存在: " + netSdkDllPath); return false; } if (!new File(configSdkDllPath).exists()) { System.err.println("❌ dhconfigsdk 库文件不存在: " + configSdkDllPath); return false; } System.out.println("✅ 正在加载 NetSDK: " + netSdkDllPath); netsdk = Native.loadLibrary(netSdkDllPath, NetSDKLib.class); System.out.println("✅ 正在加载 ConfigSDK: " + configSdkDllPath); configsdk = Native.loadLibrary(configSdkDllPath, NetSDKLib.class); System.out.println("✅ SDK 加载完成"); } catch (Exception ex) { System.err.println("❌ SDK 加载失败: \n异常: " + ex.getMessage()); ex.printStackTrace(); return false; } } } return true; } @Override public List> getCameraVideoLable(Long userId, CameraCmd cmd)throws ServiceException{ return null; } }