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> 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 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 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; } }