package com.ard.work.sdk.hp.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.event.HepuLoginEvent;
|
import com.ard.work.event.LoginEvent;
|
import com.ard.work.sdk.common.GlobalVariable;
|
import com.ard.work.sdk.common.SdkErrorCodeEnum;
|
import com.ard.work.sdk.fjr.netty.FjrNettyClient;
|
import com.ard.work.sdk.fjr.netty.ThermalCommand;
|
import com.ard.work.sdk.hik.lib.HCNetSDK;
|
import com.ard.work.sdk.hik.service.AbstractHikVisionSDK;
|
import com.ard.work.sdk.hp.cache.CameraPTZCache;
|
import com.ard.work.sdk.hp.cache.CameraSerialCache;
|
import com.ard.work.sdk.model.PtzScope;
|
import com.ard.work.utils.ArdTool;
|
import com.ard.work.utils.FFmpegUtils;
|
import com.ard.work.utils.gis.GisUtil;
|
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.stereotype.Service;
|
import org.springframework.util.StopWatch;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import java.io.File;
|
import java.util.ArrayList;
|
import java.util.List;
|
import java.util.concurrent.CompletableFuture;
|
|
import static com.ard.work.sdk.dh.lib.ToolKits.getErrorCodePrint;
|
import static com.ard.work.sdk.hik.lib.HCNetSDK.*;
|
|
/**
|
* @Description: 合浦sdk策略 继承海康抽象类
|
* @ClassName: HpSDK
|
* @Author: 刘苏义
|
* @Date: 2026年03月06日13:05:09
|
**/
|
@Service("hpSDK")
|
@Slf4j
|
public class HpSDK extends AbstractHikVisionSDK {
|
|
@Resource
|
private RedisService redisService;
|
|
@Resource
|
private ApplicationEventPublisher eventPublisher;
|
|
@Value("${sdk.tempDir}")
|
private String tempDir;
|
|
@Resource
|
private RemoteFileService remoteFileService;
|
|
|
@Resource
|
private FjrNettyClient nettyClient;
|
|
//登录
|
@Override
|
public void login(ArdCamera camera) {
|
// --- 步骤 1: 登录主设备---
|
long masterLoginId = doSingleCameraLogin(camera);
|
if (masterLoginId < 0) {
|
int errCode = hCNetSDK.NET_DVR_GetLastError();
|
log.warn("设备[{}:{}]登录失败,错误码:{} 原因:{}", camera.getIp(), camera.getPort(), errCode,
|
SdkErrorCodeEnum.getDescByCode(errCode));
|
handleLoginFail(camera);
|
eventPublisher.publishEvent(new LoginEvent(camera));
|
return; // 主设备失败,直接终止,无需尝试子设备
|
}
|
camera.setLoginId(masterLoginId);
|
int serialHandle = initTransparentSerial(camera);
|
if (serialHandle < 0) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
log.warn("【{}:{}】建立透明通道失败,错误码:{} 原因:{}", camera.getIp(), camera.getPort(), errorCode,
|
SdkErrorCodeEnum.getDescByCode(errorCode));
|
handleLoginFail(camera);
|
eventPublisher.publishEvent(new LoginEvent(camera));
|
return;// 建立透明串口失败,直接终止,无需尝试子设备
|
}
|
CameraSerialCache.put(camera.getId(), serialHandle);
|
log.info("【{}:{}】建立透明通道成功,serialHandle:{}", camera.getIp(), camera.getPort(), serialHandle);
|
// 更新缓存
|
redisService.setCacheMapValue(CacheConstants.CAMERA_ONLINE, camera.getId(), masterLoginId);
|
log.debug("主设备 [{}] 登录成功,ID: {}", camera.getIp(), masterLoginId);
|
|
// --- 步骤 2: 登录机芯节点 ---
|
List<ArdCameraHepu> nodes = camera.getHepuNodes();
|
List<CompletableFuture<Integer>> futures = new ArrayList<>();
|
// 1. 为每个子节点创建并行登录任务
|
for (ArdCameraHepu node : nodes) {
|
futures.add(CompletableFuture.supplyAsync(() -> {
|
try {
|
int nodeLoginId = -1;
|
switch (node.getNodeType()) {
|
//可见光
|
case "1":
|
nodeLoginId = hikNodeLogin(node);
|
break;
|
//热红外
|
case "2":
|
nodeLoginId = fjrNodeLogin(node);
|
break;
|
}
|
if (nodeLoginId > 0) {
|
node.setLoginId((long) nodeLoginId);
|
node.setStatus("1");
|
redisService.setCacheMapValue(CacheConstants.CAMERA_ONLINE, node.getId(), (long) nodeLoginId);
|
} else {
|
node.setLoginId(-1L);
|
node.setStatus("0");
|
// 发布合浦节点登录事件
|
redisService.deleteCacheMapValue(CacheConstants.CAMERA_ONLINE, node.getId());
|
}
|
// 发布合浦节点登录事件
|
eventPublisher.publishEvent(new HepuLoginEvent(node));
|
return nodeLoginId;
|
} catch (Exception e) {
|
log.error("子节点 {} 登录异常", node.getRemark(), e);
|
return -1;
|
}
|
}));
|
}
|
// 2. 等待完成
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
// 3. 统计成功数 (ID > 0 即为成功)
|
long successCount = futures.stream().filter(f -> f.join() > 0).count();
|
boolean allSuccess = (successCount == nodes.size());
|
// 4. 只有全部成功
|
if (allSuccess) {
|
handleLoginSuccess(camera);
|
} else {
|
handleLoginFail(camera);
|
}
|
eventPublisher.publishEvent(new LoginEvent(camera));
|
}
|
|
//登录失败处理
|
private void handleLoginFail(ArdCamera camera) {
|
camera.setChanNum(0);
|
camera.setLoginId(-1L);
|
camera.setState("0");
|
camera.setChannelList(null);
|
}
|
|
//登录成功处理
|
private void handleLoginSuccess(ArdCamera camera) {
|
camera.setState("1");
|
camera.setStartChan(1);
|
camera.setChanNum(2);
|
|
// 获取最新通道
|
List<ArdChannel> channels = getChannels(camera);
|
if (!channels.isEmpty()) {
|
camera.setChannelList(channels);
|
}
|
log.info("设备登录成功 [{}:{}]", camera.getIp(), camera.getPort());
|
}
|
|
// --- 公共核心登录方法 ---
|
|
/**
|
* 执行通用的 SDK 登录操作
|
*
|
* @param ip 设备 IP
|
* @param port 端口
|
* @param username 用户名
|
* @param password 密码
|
* @return 登录句柄 (成功 > 0, 失败 <= 0)
|
*/
|
private int doLoginCore(String ip, short port, String username, String password) {
|
// 设备信息输出参数
|
NET_DVR_DEVICEINFO_V40 deviceInfo = new NET_DVR_DEVICEINFO_V40();
|
NET_DVR_USER_LOGIN_INFO loginInfo = new NET_DVR_USER_LOGIN_INFO();
|
|
// 1. 初始化字节数组 (防止空指针或长度不足)
|
loginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
|
loginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
|
loginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
|
|
// 2. 填充数据 (注意处理字符串长度不超过数组最大长度的情况,虽然通常不会超)
|
byte[] ipBytes = ip.getBytes();
|
byte[] userBytes = username.getBytes();
|
byte[] pwdBytes = password.getBytes();
|
|
System.arraycopy(ipBytes, 0, loginInfo.sDeviceAddress, 0, Math.min(ipBytes.length,
|
loginInfo.sDeviceAddress.length));
|
System.arraycopy(userBytes, 0, loginInfo.sUserName, 0, Math.min(userBytes.length, loginInfo.sUserName.length));
|
System.arraycopy(pwdBytes, 0, loginInfo.sPassword, 0, Math.min(pwdBytes.length, loginInfo.sPassword.length));
|
|
// 3. 设置其他参数
|
loginInfo.wPort = port;
|
loginInfo.byVerifyMode = 0; // 默认验证模式
|
loginInfo.byLoginMode = 0; // 默认登录模式
|
loginInfo.bUseAsynLogin = false; // 同步登录
|
|
// 4. 写入内存 (JNA 必需步骤)
|
loginInfo.write();
|
|
// 5. 执行登录
|
int loginId = hCNetSDK.NET_DVR_Login_V40(loginInfo, deviceInfo);
|
|
// 可选:如果登录失败,可以在这里统一记录错误码日志,或者由调用方处理
|
if (loginId <= 0) {
|
int errCode = hCNetSDK.NET_DVR_GetLastError();
|
// 注意:这里不要打太频繁的日志,因为并发登录时失败可能会刷屏,建议由上层捕获后记录
|
// log.debug("登录失败 IP:{} Port:{} ErrCode:{}", ip, port, errCode);
|
}
|
|
return loginId;
|
}
|
|
// --- 主相机登录 (调用核心方法) ---
|
private int doSingleCameraLogin(ArdCamera camera) {
|
return doLoginCore(
|
camera.getIp(),
|
camera.getPort().shortValue(),
|
camera.getUsername(),
|
camera.getPassword()
|
);
|
}
|
|
// --- 合浦海康节点机芯登录 (调用核心方法) ---
|
private int hikNodeLogin(ArdCameraHepu node) {
|
return doLoginCore(
|
node.getIp(),
|
node.getPort().shortValue(),
|
node.getUsername(),
|
node.getPassword()
|
);
|
}
|
|
// --- 合浦富吉瑞节点机芯登录 (调用核心方法) ---
|
private int fjrNodeLogin(ArdCameraHepu node) {
|
ArdCamera camera = new ArdCamera(node.getIp(), node.getPort(), node.getUsername(), node.getPassword());
|
camera.setId(node.getId());
|
boolean success = nettyClient.connect(camera);
|
if (!success) {
|
log.error("设备 {} 登录失败", camera.getIp());
|
return -1;
|
}
|
return (int) (System.currentTimeMillis() / 1000);
|
}
|
|
// 注销登录
|
@Override
|
public void logout(String cameraId) {
|
// 1. 注销主设备
|
Long loginId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (loginId != null) {
|
Integer lSerialHandle = CameraSerialCache.get(cameraId);
|
if (lSerialHandle != null) {
|
CameraSerialCache.remove(cameraId);
|
if (hCNetSDK.NET_DVR_SerialStop(lSerialHandle)) {
|
log.info("【{}】关闭透明串口成功", cameraId);
|
}
|
}
|
redisService.deleteCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
hCNetSDK.NET_DVR_Logout(loginId.intValue());
|
}
|
// 2. 注销合浦节点1
|
Long node1 = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId + "_1");
|
if (node1 != null) {
|
redisService.deleteCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId + "_1");
|
hCNetSDK.NET_DVR_Logout(node1.intValue());
|
}
|
// 3. 注销合浦节点2
|
Long node2 = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId + "_2");
|
if (node2 != null) {
|
redisService.deleteCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId + "_2");
|
// fjrSDK.logout(node2.intValue());
|
}
|
}
|
|
// 初始化透明串口
|
private int initTransparentSerial(ArdCamera ardCamera) {
|
NET_DVR_SERIALSTART_V40 serialStartV40 = new NET_DVR_SERIALSTART_V40();
|
serialStartV40.dwSize = serialStartV40.size(); // 结构体大小
|
serialStartV40.dwSerialType = 2; // 串口类型:1-232 2-485
|
serialStartV40.bySerialNum = 0; // 串口号:0表示第一个串口
|
serialStartV40.write(); // 写入内存
|
|
Pointer lpInBuffer = serialStartV40.getPointer();
|
int dwInBufferSize = serialStartV40.size();
|
HpSerialDataCallBack serialDataCallback = new HpSerialDataCallBack(ardCamera);
|
|
Pointer pUser = null;
|
|
return hCNetSDK.NET_DVR_SerialStart_V40(ardCamera.getLoginId().intValue(), lpInBuffer,
|
dwInBufferSize,
|
serialDataCallback, pUser);
|
}
|
|
|
// 透明串口发送指令
|
private Boolean SerialSend(int m_lSerialHandle, byte[] data) {
|
// 1. 定义查询指令
|
boolean b = hCNetSDK.NET_DVR_SerialSend(m_lSerialHandle, 1, data, data.length);
|
if (!b) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
log.error("透传指令发送失败,错误码:{}", errorCode);
|
return false;
|
}
|
return true;
|
}
|
|
//透明串口云台控制
|
public Boolean serialControl(CameraCmd cmd) {
|
|
String cameraId = cmd.getCameraId();
|
Integer serialHandle = CameraSerialCache.get(cameraId);
|
if (serialHandle == null) {
|
log.error("【{}】未找到对应的串口通道", cameraId);
|
return false;
|
}
|
|
byte CMD_HEAD = (byte) 0xFF;
|
byte ADDRESS = 0x01;
|
|
byte COMMAND1 = 0x00;
|
byte COMMAND2 = 0x00;
|
|
int speed = cmd.getSpeed() * 10;
|
if (speed <= 0) speed = 0x10;
|
if (speed > 0x3F) speed = 0x3F;
|
|
byte dataH = (byte) speed;
|
byte dataL = (byte) speed;
|
|
String action = "";
|
|
switch (cmd.getCode()) {
|
|
case 1:
|
COMMAND2 = 0x0C;
|
action = "左上";
|
break;
|
|
case 2:
|
COMMAND2 = 0x08;
|
action = "上";
|
break;
|
|
case 3:
|
COMMAND2 = 0x0A;
|
action = "右上";
|
break;
|
|
case 4:
|
COMMAND2 = 0x04;
|
action = "左";
|
break;
|
|
case 6:
|
COMMAND2 = 0x02;
|
action = "右";
|
break;
|
|
case 7:
|
COMMAND2 = 0x14;
|
action = "左下";
|
break;
|
|
case 8:
|
COMMAND2 = 0x10;
|
action = "下";
|
break;
|
|
case 9:
|
COMMAND2 = 0x12;
|
action = "右下";
|
break;
|
|
case 10:
|
COMMAND2 = 0x20;
|
dataH = 0;
|
dataL = 0;
|
action = "变倍+";
|
break;
|
|
case 11:
|
COMMAND2 = 0x40;
|
dataH = 0;
|
dataL = 0;
|
action = "变倍-";
|
break;
|
|
case 12:
|
COMMAND1 = 0x01;
|
dataH = 0;
|
dataL = 0;
|
action = "近焦";
|
break;
|
|
case 13:
|
COMMAND1 = (byte) 0x80;
|
dataH = 0;
|
dataL = 0;
|
action = "远焦";
|
break;
|
|
case 14:
|
COMMAND1 = 0x02;
|
action = "光圈开";
|
break;
|
|
case 15:
|
COMMAND1 = 0x04;
|
action = "光圈关";
|
break;
|
|
default:
|
log.warn("【{}】未知PTZ控制指令: {}", cameraId, cmd.getCode());
|
return false;
|
}
|
|
if (!cmd.isEnable()) {
|
COMMAND1 = 0x00;
|
COMMAND2 = 0x00;
|
dataH = 0;
|
dataL = 0;
|
action = action + " 停止";
|
}
|
|
int sum = (ADDRESS & 0xFF)
|
+ (COMMAND1 & 0xFF)
|
+ (COMMAND2 & 0xFF)
|
+ (dataH & 0xFF)
|
+ (dataL & 0xFF);
|
|
byte checkSum = (byte) (sum & 0xFF);
|
|
byte[] bytes = new byte[]{
|
CMD_HEAD,
|
ADDRESS,
|
COMMAND1,
|
COMMAND2,
|
dataH,
|
dataL,
|
checkSum
|
};
|
|
// 打印完整指令
|
log.info("【{}】PTZ控制 -> 动作:{} 速度:{} 指令:{}",
|
cameraId,
|
action,
|
speed,
|
ArdTool.bytesToHex(bytes));
|
|
Boolean result = SerialSend(serialHandle, bytes);
|
|
if (!result) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
log.warn("【{}】PTZ串口发送失败 错误码:{} 原因:{}",
|
cameraId,
|
errorCode,
|
SdkErrorCodeEnum.getDescByCode(errorCode));
|
}
|
|
return result;
|
}
|
|
/**
|
* 带速度的云台控制操作
|
*
|
* @param cmd 相机命令
|
* @return true 表示控制命令发送成功
|
* @throws CameraSDKException SDK调用失败时抛出
|
*/
|
@Override
|
public Boolean pTZControl(CameraCmd cmd) {
|
Integer chanNo = cmd.getChanNo();
|
// 2. 核心分流逻辑
|
// 假设 Code 10-13 是变倍和调焦相关指令
|
// 其他指令(1-9)是云台转动
|
boolean isRotationCommand = (cmd.getCode() >= 1 && cmd.getCode() <= 9);
|
if (isRotationCommand) {
|
//云台旋转指令走透明串口协议
|
return serialControl(cmd);
|
}
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cmd.getCameraId());
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikControl(cmd);
|
case 2:
|
return fjrControl(cmd);
|
}
|
return false;
|
}
|
|
public Boolean hikControl(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
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.intValue(), cmd.getChanNo(), dwPTZCommand, dwStop, cmd.getSpeed()
|
);
|
|
if (!success) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
log.warn("【{}】PTZ控制失败,错误码:{}, 原因:{}", cameraId, errorCode,
|
SdkErrorCodeEnum.getDescByCode(errorCode));
|
}
|
|
return success;
|
}
|
|
public Boolean fjrControl(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer speed = cmd.getSpeed();
|
// 1. 先判断是否是“停止”动作
|
// 如果 isEnable 为 false,说明按钮松开了,必须发送停止指令
|
if (!cmd.isEnable()) {
|
ThermalCommand stopCmd = ThermalCommand.createFocusStopCmd(cameraId);
|
nettyClient.send(cameraId, stopCmd);
|
if (cmd.getCode() == 10 || cmd.getCode() == 11) {
|
// 2. 延迟 300ms 发送自动调焦
|
ThermalCommand autoFocusCmd = ThermalCommand.createAutoFocusCmd(cameraId);
|
nettyClient.sendDelay(cameraId, autoFocusCmd, 1000);
|
}
|
return true;
|
}
|
// 1. 创建一个统一速度指令
|
ThermalCommand uniformSpeedCmd = ThermalCommand.createUniformSpeedCmd(cameraId, speed);
|
nettyClient.send(cameraId, uniformSpeedCmd);
|
// 2. 如果是“开始”动作 (isEnable == true),再判断方向
|
switch (cmd.getCode()) {
|
case 10: // 变倍+
|
ThermalCommand zoomPlus = ThermalCommand.createZoomCmd(cameraId, true);
|
nettyClient.send(cameraId, zoomPlus);
|
break;
|
|
case 11: // 变倍-
|
ThermalCommand zoomMinus = ThermalCommand.createZoomCmd(cameraId, false);
|
nettyClient.send(cameraId, zoomMinus);
|
break;
|
|
case 12: // 调焦-
|
// 这里假设你的 createFocusCmd 接受 boolean,true代表+,false代表-
|
ThermalCommand cmdMinus = ThermalCommand.createFocusCmd(cameraId, false);
|
nettyClient.send(cameraId, cmdMinus);
|
break;
|
|
case 13: // 调焦+
|
ThermalCommand cmdPlus = ThermalCommand.createFocusCmd(cameraId, true);
|
nettyClient.send(cameraId, cmdPlus);
|
break;
|
|
default:
|
System.err.println("未知的指令码: " + cmd.getCode());
|
return false;
|
}
|
|
return true;
|
}
|
|
// 切换透雾
|
@Override
|
public Boolean controlDefog(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikControlDefog(cmd);
|
case 2:
|
throw new ServiceException("设备不支持");
|
}
|
return false;
|
}
|
|
private Boolean hikControlDefog(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
boolean enable = cmd.isEnable();
|
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.intValue(), 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.intValue(), 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();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikIsEnableDefog(cmd);
|
case 2:
|
throw new ServiceException("设备不支持");
|
}
|
return false;
|
}
|
|
private Boolean hikIsEnableDefog(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
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.intValue(),
|
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;
|
}
|
}
|
|
// 切换红外
|
@Override
|
public Boolean controlInfrared(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikControlInfrared(cmd);
|
case 2:
|
throw new ServiceException("设备不支持");
|
}
|
return false;
|
}
|
|
private Boolean hikControlInfrared(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
boolean enable = cmd.isEnable();
|
Integer chanNo = cmd.getChanNo();
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
|
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.intValue(), 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.intValue(), 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();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikIsEnableInfrared(cmd);
|
case 2:
|
throw new ServiceException("设备不支持");
|
}
|
return false;
|
}
|
|
private Boolean hikIsEnableInfrared(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
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.intValue(), 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();
|
Integer chanNo = cmd.getChanNo();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikControlFocusMode(cmd);
|
case 2:
|
return fjrControlFocusMode(cmd);
|
}
|
return false;
|
}
|
|
private Boolean hikControlFocusMode(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
boolean enable = cmd.isEnable();
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
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.intValue(), NET_DVR_GET_FOCUSMODECFG, chanNo, 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.intValue(), NET_DVR_SET_FOCUSMODECFG, chanNo, 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;
|
}
|
|
private Boolean fjrControlFocusMode(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
boolean enable = cmd.isEnable();
|
if (enable) {
|
ThermalCommand autoFocusCmd = ThermalCommand.createAutoFocusCmd(cameraId);
|
nettyClient.sendDelay(cameraId, autoFocusCmd, 300);
|
}
|
return true;
|
}
|
|
//获取聚焦模式
|
@Override
|
public Boolean getFocusMode(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikGetFocusMode(cmd);
|
case 2:
|
throw new ServiceException("设备不支持");
|
}
|
return false;
|
}
|
|
private Boolean hikGetFocusMode(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
|
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.intValue(), NET_DVR_GET_FOCUSMODECFG, chanNo,
|
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=手动
|
}
|
|
//3D定位
|
@Override
|
public Boolean set3DPosition(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikSet3DPosition(cmd);
|
case 2:
|
throw new ServiceException("设备不支持");
|
}
|
return false;
|
}
|
|
private Boolean hikSet3DPosition(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
PointFrame pointFrame = cmd.getPointFrame();
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
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.intValue(), 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;
|
}
|
|
//获取ptz信息
|
@Override
|
public PtzDto getPtz(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
PtzDto ptzDto = CameraPTZCache.get(cameraId);
|
if (ptzDto == null) {
|
ptzDto = new PtzDto(0f, 0f, 0f);
|
CameraPTZCache.put(cameraId, ptzDto);
|
}
|
return ptzDto;
|
}
|
|
//设置ptz信息
|
@Override
|
public Boolean setPtz(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
PtzDto ptzDto = cmd.getPtzDto();
|
int m_lSerialHandle = CameraSerialCache.get(cameraId);
|
float p = ptzDto.getP();
|
int dataInt = Math.round(p * 100);
|
byte[] dataBytes = intTo2BytesBigEndian(dataInt);
|
byte dataH = dataBytes[0]; // DataH(Byte5)
|
byte dataL = dataBytes[1]; // DataL(Byte6)
|
byte CMD_HEAD = (byte) 0xff; // 指令头(Byte1)
|
byte ADDRESS = 0x01; // 设备地址(Byte2)
|
byte COMMAND1 = 0x00; // 命令1(Byte3)
|
byte COMMAND2 = 0x4B; // 命令2(Byte4)
|
// CheckSum = (Address + Command1 + Command2 + DataH + DataL) 的低8位
|
int sum = (ADDRESS & 0xFF) + (COMMAND1 & 0xFF) + (COMMAND2 & 0xFF) + (dataH & 0xFF) + (dataL & 0xFF);
|
byte checkSum = (byte) (sum & 0xFF); // 取低8位作为校验和
|
// 组装指令数组
|
byte[] cmdPBytes = new byte[]{CMD_HEAD, ADDRESS, COMMAND1, COMMAND2, dataH, dataL, checkSum}; // 总长度7字节
|
log.info("[PTZ报文:] cameraId={},p={}, hex={}", cameraId, ptzDto.getP(),bytesToHex(cmdPBytes));
|
Boolean b = SerialSend(m_lSerialHandle, cmdPBytes);
|
try {
|
Thread.sleep(100);
|
} catch (InterruptedException e) {
|
throw new RuntimeException(e);
|
}
|
if (!b) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode);
|
String errorMsg = String.format("设置PTZ(P)失败,错误码:%s,原因:%s", errorCode, errorDesc);
|
log.warn(errorMsg);
|
return false;
|
}
|
|
dataInt = Math.round(ptzDto.getT() * 100);
|
dataInt = 36000 - dataInt;
|
dataBytes = intTo2BytesBigEndian(dataInt);
|
dataH = dataBytes[0];
|
dataL = dataBytes[1];
|
COMMAND2 = 0x4D;
|
// 5. Step3:计算校验和(单行版,规则同水平)
|
sum = (ADDRESS & 0xFF) + (COMMAND1 & 0xFF) + (COMMAND2 & 0xFF) + (dataH & 0xFF) + (dataL & 0xFF);
|
checkSum = (byte) (sum & 0xFF);
|
// 组装指令数组
|
cmdPBytes = new byte[]{CMD_HEAD, ADDRESS, COMMAND1, COMMAND2, dataH, dataL, checkSum}; // 总长度7字节
|
log.info("[PTZ报文:] cameraId={},t={}, hex={}", cameraId, ptzDto.getT(),bytesToHex(cmdPBytes));
|
b = SerialSend(m_lSerialHandle, cmdPBytes);
|
try {
|
Thread.sleep(100);
|
} catch (InterruptedException e) {
|
throw new RuntimeException(e);
|
}
|
if (!b) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode);
|
String errorMsg = String.format("设置PTZ(T)失败,错误码:%s,原因:%s", errorCode, errorDesc);
|
log.warn(errorMsg);
|
return false;
|
}
|
|
int z = ptzDto.getZ().intValue();
|
dataInt = convertFrontZoomToDeviceFocal(z);
|
dataBytes = intTo2BytesBigEndian(dataInt);
|
dataH = dataBytes[0];
|
dataL = dataBytes[1];
|
COMMAND2 = 0x4F;
|
// 5. Step3:计算校验和(单行版,规则同水平)
|
sum = (ADDRESS & 0xFF) + (COMMAND1 & 0xFF) + (COMMAND2 & 0xFF) + (dataH & 0xFF) + (dataL & 0xFF);
|
checkSum = (byte) (sum & 0xFF);
|
// 组装指令数组
|
cmdPBytes = new byte[]{CMD_HEAD, ADDRESS, COMMAND1, COMMAND2, dataH, dataL, checkSum}; // 总长度7字节
|
log.info("[PTZ报文:] cameraId={},z={}, hex={}", cameraId, ptzDto.getZ(),bytesToHex(cmdPBytes));
|
b = SerialSend(m_lSerialHandle, cmdPBytes);
|
if (!b) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
String errorDesc = SdkErrorCodeEnum.getDescByCode(errorCode);
|
String errorMsg = String.format("设置PTZ(Z)失败,错误码:%s,原因:%s", errorCode, errorDesc);
|
log.warn(errorMsg);
|
return false;
|
}
|
return true;
|
}
|
|
/**
|
* 前端1-60的倍率值 转换为 设备可识别的焦距控制值
|
*
|
* @param frontZoom 前端传递的1~60的整数
|
* @return 设备焦距控制值(放大100倍的整数,如3.3mm→330,37mm→3700)
|
*/
|
public int convertFrontZoomToDeviceFocal(int frontZoom) {
|
// 1. 定义固定参数(设备物理参数+映射关系)
|
final float MIN_FOCAL = 3.3f; // 设备最小焦距(mm)
|
final float MAX_FOCAL = 37.0f; // 设备最大焦距(mm)
|
final int FRONT_MIN = 1; // 前端最小值
|
final int FRONT_MAX = 60; // 前端最大值
|
|
// 2. 校验前端输入范围(避免非法值)
|
if (frontZoom < FRONT_MIN || frontZoom > FRONT_MAX) {
|
throw new IllegalArgumentException("前端倍率值需在1~60之间,当前值:" + frontZoom);
|
// 若不想抛异常,也可自动修正:frontZoom = Math.max(FRONT_MIN, Math.min(FRONT_MAX, frontZoom));
|
}
|
|
// 3. 核心:线性映射公式(前端值 → 设备焦距mm)
|
// 公式:目标值 = 最小值 + (最大值-最小值) × (当前前端值-前端最小值)/(前端最大值-前端最小值)
|
float deviceFocal = MIN_FOCAL + (MAX_FOCAL - MIN_FOCAL) * (frontZoom - FRONT_MIN) / (FRONT_MAX - FRONT_MIN);
|
|
// 4. 焦距值放大100倍并取整(设备底层常用整数控制,避免浮点精度问题)
|
return Math.round(deviceFocal * 100);
|
}
|
|
/**
|
* 整数转2字节数组(大端模式:高字节在前,低字节在后)
|
* 示例:9000 → 0x23 0x28(因为9000的十六进制是0x2328)
|
*
|
* @param value 待转换整数(0~36000)
|
* @return 2字节数组([0]=高字节,[1]=低字节)
|
*/
|
private byte[] intTo2BytesBigEndian(int value) {
|
byte[] bytes = new byte[2];
|
bytes[0] = (byte) (value >> 8 & 0xFF); // 高字节(DataH)
|
bytes[1] = (byte) (value & 0xFF); // 低字节(DataL)
|
return bytes;
|
}
|
|
//设置零方位角
|
@Override
|
public Boolean setZeroPtz(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
int m_lSerialHandle = CameraSerialCache.get(cameraId);
|
byte[] cmdPBytes = new byte[]{(byte) 0xff, 0x01, 0x00, 0x6F, 0x00, 0x00, 0x70};
|
boolean bool = SerialSend(m_lSerialHandle, cmdPBytes);
|
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;
|
}
|
|
//获取视场角
|
@Override
|
public PtzParamDTO getGisInfo(CameraCmd cmd) {
|
PtzParamDTO ptzParamDTO = new PtzParamDTO();
|
String cameraId = cmd.getCameraId();
|
PtzDto ptzDto = CameraPTZCache.get(cameraId);
|
if (ptzDto == null) {
|
ptzParamDTO.setP(0f);
|
ptzParamDTO.setT(0f);
|
ptzParamDTO.setZ(0f);
|
}
|
ptzParamDTO.setFHorFieldAngle(1.8f);
|
ptzParamDTO.setFVerFieldAngle(1f);
|
return ptzParamDTO;
|
}
|
|
//获取球机PTZ参数取值范围
|
@Override
|
public PtzScope getPtzScope(CameraCmd cmd) {
|
PtzScope ptzScope = new PtzScope();
|
ptzScope.setPMax(360f);
|
ptzScope.setPMin(0f);
|
ptzScope.setTMax(45f);
|
ptzScope.setTMin(315f);
|
ptzScope.setZMax(60f);
|
ptzScope.setZMin(1f);
|
return ptzScope;
|
}
|
|
//引导目标位置
|
@Override
|
public Boolean guideTargetPosition(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Float baseYaw = cmd.getBaseYaw();
|
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 targetPTZ = GisUtil.getCameraPTZ(cameraPosition, targetPositions, 20, 150);
|
//若为车载光电,需要根据修改P值
|
if ("4".equals(ardCamera.getType())) {
|
targetPTZ.setP((targetPTZ.getP() - baseYaw + 360) % 360);
|
}
|
// pt校验范围
|
cmd.setPtzDto(targetPTZ);
|
validatePtzRange(cmd);
|
return setPtz(cmd);
|
}
|
|
//录像开始
|
@Override
|
public Boolean recordStart(CameraCmd cmd) {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return false;
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikRecordStart(cmd);
|
case 2:
|
throw new ServiceException("设备不支持");
|
}
|
return false;
|
}
|
|
private Boolean hikRecordStart(CameraCmd cmd) {
|
try {
|
String cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
String name = cameraId + "_" + chanNo;
|
// 本地临时录像地址
|
String path = FileUtils.createFile(tempDir + "/record/" + name + ".mp4");
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
|
//强制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.intValue(), 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.intValue(), 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 cameraId = cmd.getCameraId();
|
Integer chanNo = cmd.getChanNo();
|
ArdCamera ardCamera = redisService.getCacheMapValue(CacheConstants.CAMERA_LIST, cameraId);
|
ArdCameraHepu targetNode = ardCamera.getHepuNodes().stream()
|
.filter(node -> node.getSortOrder().equals(chanNo))
|
.findFirst()
|
.orElse(null); // 如果没找到,返回 null
|
if (targetNode == null) return "";
|
cmd.setCameraId(targetNode.getId());
|
switch (chanNo) {
|
case 1:
|
return hikRecordStop(cmd);
|
case 2:
|
throw new ServiceException("设备不支持");
|
}
|
return "";
|
}
|
|
private String hikRecordStop(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");
|
|
Long userId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, cameraId);
|
if (userId == null) {
|
throw new ServiceException("设备离线");
|
}
|
|
// 停止录像
|
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() == null ? "record" : cmd.getType();
|
|
MultipartFile multipartFile = new FileMultipartFile(transcodeFile, objectName, MimeTypeUtils.VIDEO_MP4);
|
R<SysFile> 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);
|
}
|
}
|
}
|
|
//获取通道
|
@Override
|
public List<ArdChannel> getChannels(ArdCamera camera) {
|
List<ArdChannel> channelList = new ArrayList<>();
|
// 获取所有子节点
|
List<ArdCameraHepu> nodes = camera.getHepuNodes();
|
if (nodes == null || nodes.isEmpty()) {
|
log.warn("设备 {} 无子节点,无法获取通道", camera.getName());
|
return channelList;
|
}
|
log.debug("开始聚合子设备通道信息,共 {} 个子节点", nodes.size());
|
// 1. 遍历每一个子节点
|
for (ArdCameraHepu node : nodes) {
|
ArdChannel channel = new ArdChannel();
|
if ("1".equals(node.getNodeType())) {
|
Long nodeLoginId = redisService.getCacheMapValue(CacheConstants.CAMERA_ONLINE, node.getId());
|
if (nodeLoginId == null) {
|
log.warn("子节点 {} 未登录或 LoginId 无效,跳过其通道获取", node.getRemark());
|
continue;
|
}
|
int chanNo = 1; // 子设备内部的物理通道号 (通常从 1 开始)
|
NET_DVR_PICCFG_V40 strPicCfg = new NET_DVR_PICCFG_V40();
|
int structSize = strPicCfg.size();
|
IntByReference pInt = new IntByReference(0);
|
// 复用结构体
|
strPicCfg.dwSize = structSize;
|
strPicCfg.write();
|
// 调用 SDK 获取配置 (使用子节点的 loginId 和 物理通道号)
|
boolean success = hCNetSDK.NET_DVR_GetDVRConfig(
|
nodeLoginId.intValue(),
|
HCNetSDK.NET_DVR_GET_PICCFG_V40,
|
chanNo,
|
strPicCfg.getPointer(),
|
structSize,
|
pInt
|
);
|
|
if (!success) {
|
int errorCode = hCNetSDK.NET_DVR_GetLastError();
|
if (errorCode != 0) {
|
continue;
|
}
|
}
|
|
// 读取结果
|
strPicCfg.read();
|
|
// 解析通道名称
|
String channelName;
|
try {
|
channelName = new String(strPicCfg.sChanName, "GBK").trim();
|
} catch (Exception e) {
|
channelName = "通道" + chanNo;
|
}
|
// 如果名字为空,给个默认名
|
if (channelName.isEmpty() || channelName.contains("\u0000")) {
|
channelName = "通道" + chanNo;
|
}
|
// 构建父设备视角的通道对象
|
channel.setDeviceId(camera.getId()); // 归属父设备 ID
|
channel.setName(channelName); // 通道名称
|
channel.setChanNo(chanNo); // 【关键】使用全局递增的通道号
|
// 获取视频编码
|
CameraCmd cmd = new CameraCmd(node.getId(), chanNo);
|
cmd.setLoginId(nodeLoginId);
|
// 假设 getVideoCompressionCfg 能根据 cmd 里的信息去查
|
VideoCompressionCfg videoCfg = getVideoCompressionCfg(cmd);
|
if (videoCfg != null) {
|
channel.setVideoCode(videoCfg.getVideoEncType());
|
}
|
String rtspUrl = buildRtspUrlForType1(node, chanNo);
|
channel.setLiveAddress(rtspUrl);
|
} else if ("2".equals(node.getNodeType())) {
|
|
channel.setDeviceId(camera.getId()); // 归属父设备 ID
|
channel.setName("热红外"); // 写死通道名称
|
channel.setChanNo(2); // 写死通道号2
|
channel.setVideoCode("h264"); // 写死通道编码格式
|
// 【新增】构建 rtspUrl - 类型2
|
String rtspUrl = buildRtspUrlForType2(node);
|
channel.setLiveAddress(rtspUrl);
|
}
|
channelList.add(channel);
|
}
|
log.debug("聚合完成,父设备 {} 共获取到 {} 个逻辑通道", camera.getName(), channelList.size());
|
return channelList;
|
}
|
// 构建类型1的 RTSP URL
|
private String buildRtspUrlForType1(ArdCameraHepu node, int chanNo) {
|
return String.format("rtsp://%s:%s@%s:%d/h264/ch%d/main/av_stream",
|
node.getUsername(),
|
node.getPassword(),
|
node.getIp(),
|
node.getRtspPort(),
|
chanNo
|
);
|
}
|
|
// 构建类型2的 RTSP URL
|
private String buildRtspUrlForType2(ArdCameraHepu node) {
|
return String.format("rtsp://%s:%d/live/video",
|
node.getIp(),
|
node.getRtspPort()
|
);
|
}
|
private String bytesToHex(byte[] bytes) {
|
StringBuilder sb = new StringBuilder();
|
for (byte b : bytes) {
|
sb.append(String.format("%02X ", b));
|
}
|
return sb.toString().trim();
|
}
|
|
}
|