package com.inspect.nvr.service;
|
|
|
|
import com.inspect.nvr.domain.Infrared.Camera;
|
|
import com.inspect.nvr.domain.Infrared.NvrInfo;
|
|
import com.inspect.nvr.domain.Infrared.TemperatureData;
|
|
import com.inspect.nvr.jna.lincseek.IRNetSDK;
|
|
import com.inspect.nvr.jna.lincseek.IRNetSDKCallback;
|
|
import com.inspect.nvr.jna.lincseek.IRNetSDKConst;
|
|
import com.inspect.nvr.jna.lincseek.IRNetSDKStruct;
|
|
import com.inspect.nvr.jna.lincseek.IRNetSDKStruct.IRNETHANDLE;
|
|
import com.sun.jna.Memory;
|
|
import com.sun.jna.Pointer;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import javax.annotation.Resource;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
@Slf4j
|
|
@Service
|
|
public class LincseekCameraService extends CommonCameraService {
|
|
// [新增]全局流水号生成器
|
|
private static final AtomicInteger SERIAL_COUNTER = new AtomicInteger(1);
|
|
// Key: CmdSerial (流水号), Value: CompletableFuture (用于通知调用线程)
|
|
private static final ConcurrentHashMap<String, CompletableFuture<byte[]>> PENDING_REQUESTS = new ConcurrentHashMap<>();
|
|
|
|
@Resource
|
|
private IRNetSDK irNetSDK;
|
|
@Resource
|
|
private LincseekLoginService lincseekLoginService;
|
|
|
|
public static int nextSerial() {
|
|
return SERIAL_COUNTER.updateAndGet(current -> (current + 1) & 0xFFFF);
|
|
}
|
|
|
|
public static Pointer stringToPointer(String str) {
|
|
if (str == null) return null;
|
|
byte[] bytes = str.getBytes(); // 1. 转换为字节数组
|
|
Pointer pointer = new Memory(bytes.length + 1); // 2. 分配内存(+1 用于存放 \0)
|
|
pointer.write(0, bytes, 0, bytes.length); // 3. 写入字节
|
|
pointer.setByte(bytes.length, (byte) 0); // 4. 追加空字节(Null-termination)
|
|
return pointer;
|
|
}
|
|
|
|
/**
|
|
* 受并发控制的朗驰抓图
|
|
*/
|
|
public byte[] capture(NvrInfo nvrInfo, int channel) {
|
|
return withConcurrencyControl(nvrInfo, channel, () -> captureWithRetry(nvrInfo, channel));
|
|
}
|
|
|
|
private byte[] captureWithRetry(NvrInfo nvrInfo, int channel) {
|
|
Path fullPath = getFullPath("lc", nvrInfo.getNvrIp(), channel);
|
|
ensureDirectoryExists(fullPath.getParent());
|
|
int retryCount = 0;
|
|
int maxRetries = DEFAULT_MAX_RETRIES;
|
|
while (retryCount < maxRetries) {
|
|
try {
|
|
byte[] imageBytes = jpegCapSingle(nvrInfo, channel, fullPath);
|
|
log.info("[朗驰]抓图成功:第{}次,文件地址:{}", retryCount + 1, fullPath);
|
|
return imageBytes;
|
|
} catch (Exception e) {
|
|
log.error("[朗驰]抓图异常(第{}次):{}", retryCount + 1, e.getMessage());
|
|
} finally {
|
|
retryCount++;
|
|
}
|
|
}
|
|
// 当所有抓图方式均失败时,记录失败图片
|
|
writeCaptureFailedImage(fullPath);
|
|
log.error("[朗驰]抓图失败,图片地址:{}", fullPath);
|
|
return new byte[0];
|
|
}
|
|
|
|
private byte[] jpegCapSingle(NvrInfo nvrInfo, int channel, Path fullPath) {
|
|
IRNetSDKCallback.JpegDataCallback jpegCb = (hHandle, mCh, pBuffer, size, extraData, userdata) -> {
|
|
String requestKey = userdata.getString(0);
|
|
CompletableFuture<byte[]> future = PENDING_REQUESTS.remove(requestKey);
|
|
if (future != null) {
|
|
log.info("[朗驰]匹配到抓图回调 (requestKey={}, 通道={}, 大小={}B)", requestKey, mCh, size);
|
|
byte[] jpeg = pBuffer.getByteArray(0, size);
|
|
future.complete(jpeg);
|
|
} else {
|
|
// 可能是由于超时已经被移除了,或者是其他类型的抓图
|
|
log.error("[朗驰]未匹配的抓图回调,requestKey={}", requestKey);
|
|
}
|
|
};
|
|
|
|
int mySerialId = nextSerial();
|
|
String requestKey = ":" + mySerialId;
|
|
Pointer userData = stringToPointer(requestKey);
|
|
IRNETHANDLE capHandle = irNetSDK.IRNET_ClientJpegCapStart("video server", nvrInfo.getNvrIp(), nvrInfo.getAccount(), nvrInfo.getPassword(), (short) nvrInfo.getServerPort().intValue(), jpegCb, userData);
|
|
if (!IRNETHANDLE.INVALID_HANDLE_VALUE.equals(capHandle)) {
|
|
CompletableFuture<byte[]> future = new CompletableFuture<>();
|
|
log.info("capHandle: {}, {}", capHandle.toString(), capHandle.hashCode());
|
|
PENDING_REQUESTS.put(requestKey, future);
|
|
final int TIMEOUT_SEC = 10;
|
|
try {
|
|
boolean isCaptured = irNetSDK.IRNET_ClientJpegCapSingle(capHandle, channel, 100);
|
|
if (!isCaptured) {
|
|
throw new RuntimeException("SDK抓图失败");
|
|
}
|
|
byte[] byteArray = future.get(TIMEOUT_SEC, TimeUnit.SECONDS);
|
|
Files.write(fullPath, byteArray);
|
|
return byteArray;
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("[朗驰]抓图异常:", e);
|
|
} finally {
|
|
PENDING_REQUESTS.remove(requestKey);
|
|
irNetSDK.IRNET_ClientJpegCapStop(capHandle);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public TemperatureData getTemp(Camera camera) {
|
|
NvrInfo nvrInfo = new NvrInfo();
|
|
nvrInfo.setNvrIp(camera.getIp());
|
|
nvrInfo.setServerPort(camera.getPort());
|
|
nvrInfo.setAccount(camera.getUserName());
|
|
nvrInfo.setPassword(camera.getPassword());
|
|
|
|
int channel = camera.getChannel();
|
|
return withConcurrencyControl(nvrInfo, channel, () -> tempWithRetry(nvrInfo, channel));
|
|
}
|
|
|
|
/**
|
|
* 获取最高温、最低温、平均温
|
|
*/
|
|
private TemperatureData tempWithRetry(NvrInfo nvrInfo, int channel) {
|
|
IRNETHANDLE msgHandle = irNetSDK.IRNET_ClientMessageOpen("video server", nvrInfo.getNvrIp(), nvrInfo.getAccount(), nvrInfo.getPassword(), (short) nvrInfo.getServerPort().intValue());
|
|
try {
|
|
if (!IRNETHANDLE.INVALID_HANDLE_VALUE.equals(msgHandle)) {
|
|
IRNetSDKStruct.VSNET_TEMP_VALUE_EX temps = new IRNetSDKStruct.VSNET_TEMP_VALUE_EX();
|
|
temps.write();
|
|
int r = irNetSDK.IRNET_ClientMessageOpt(msgHandle, IRNetSDKConst.MessageOpt.MESSAGE_CMD_GET_TEMPVALUE_EX, channel, temps.getPointer(), null, null);
|
|
if (r == 1) {
|
|
temps.read();
|
|
float maxTemp = temps.m_maxtempinfo.m_temp_value;
|
|
int maxX = temps.m_maxtempinfo.m_temp_x;
|
|
int maxY = temps.m_maxtempinfo.m_temp_y;
|
|
float minTemp = temps.m_mintempinfo.m_temp_value;
|
|
int minX = temps.m_mintempinfo.m_temp_x;
|
|
int minY = temps.m_mintempinfo.m_temp_y;
|
|
float avgTemp = temps.m_avgtempinfo;
|
|
log.info("[朗驰]测温结果: 最高={}℃({},{}), 最低={}℃({},{}), 平均={}℃",
|
|
maxTemp, maxX, maxY, minTemp, minX, minY, avgTemp);
|
|
return new TemperatureData(
|
|
String.valueOf(maxTemp),
|
|
String.valueOf(minTemp),
|
|
avgTemp,
|
|
0,
|
|
channel,
|
|
-1
|
|
);
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("[朗驰]测温异常:", e);
|
|
} finally {
|
|
irNetSDK.IRNET_ClientMessageClose(msgHandle);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 跳转预置位
|
|
*/
|
|
public boolean ptzPreset(Camera camera) {
|
|
//预置位跳转
|
|
NvrInfo nvrInfo = new NvrInfo();
|
|
nvrInfo.setNvrIp(camera.getIp());
|
|
nvrInfo.setServerPort(camera.getPort());
|
|
nvrInfo.setAccount(camera.getUserName());
|
|
nvrInfo.setPassword(camera.getPassword());
|
|
nvrInfo.setChannelNumber(camera.getChannel());
|
|
IRNETHANDLE hHandle = lincseekLoginService.login(nvrInfo);
|
|
boolean ret = irNetSDK.IRNET_ClientPTZCtrl(hHandle, IRNetSDKConst.PTZ_GOTOPOINT, camera.getPointNum(), 0, null, 0);
|
|
log.info("[朗驰]跳转预置位{}, 结果: {}", camera.getPointNum(), ret);
|
|
return ret;
|
|
}
|
|
}
|