package com.ard.work.sdk.hp.service; import com.ard.work.api.domian.ArdCamera; import com.ard.work.api.domian.PtzDto; import com.ard.work.sdk.hik.lib.HCNetSDK; import com.ard.work.sdk.hp.cache.CameraPTZCache; import com.sun.jna.Pointer; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; /** * 实时解析Pelco-D完整帧版 */ @Slf4j public class HpSerialDataCallBack implements HCNetSDK.FSerialDataCallBack_V40, AutoCloseable { // 协议常量 private static final byte PELCO_D_HEAD = (byte) 0xFF; private static final int PELCO_D_FRAME_LEN = 7; private static final int CACHE_MAX_SIZE = 1024; private final ArdCamera camera; private final AtomicBoolean isRunning = new AtomicBoolean(true); private final byte[] frameCache = new byte[CACHE_MAX_SIZE]; private int cacheLen = 0; // 数据队列 private final BlockingQueue queue = new LinkedBlockingQueue<>(); // 镜头参数 private static final float MIN_FOCAL = 3.3f; private static final float MAX_FOCAL = 37f; private static final float COEFFICIENT = 1.63f; private static final int FRONT_MIN = 1; private static final int FRONT_MAX = 60; public HpSerialDataCallBack(ArdCamera camera) { this.camera = camera; startConsumer(); } /** * SDK回调:接收碎片化数据 → 放入队列 */ @Override public void invoke(int lSerialHandle, int lChannel, Pointer pRecvDataBuffer, int dwBufSize, Pointer pUser) { if (!isRunning.get() || dwBufSize <= 0 || pRecvDataBuffer == null) return; byte[] recvData = new byte[dwBufSize]; pRecvDataBuffer.read(0, recvData, 0, dwBufSize); queue.offer(recvData); } /** * 单线程消费队列 */ private void startConsumer() { Thread t = new Thread(() -> { while (isRunning.get()) { try { byte[] data = queue.take(); synchronized (frameCache) { if (cacheLen + data.length > CACHE_MAX_SIZE) cacheLen = 0; System.arraycopy(data, 0, frameCache, cacheLen, data.length); cacheLen += data.length; parseCompleteFrames(); } } catch (InterruptedException ignored) { } } }, "Serial-Consumer-" + camera.getIp()); t.setDaemon(true); t.start(); } /** * 解析缓存中的完整帧 */ private void parseCompleteFrames() { while (cacheLen >= PELCO_D_FRAME_LEN) { int headIndex = findHeadIndex(frameCache, cacheLen); if (headIndex == -1) { cacheLen = 0; break; } if (cacheLen - headIndex < PELCO_D_FRAME_LEN) { byte[] remain = Arrays.copyOfRange(frameCache, headIndex, cacheLen); System.arraycopy(remain, 0, frameCache, 0, remain.length); cacheLen = remain.length; break; } byte[] completeFrame = Arrays.copyOfRange(frameCache, headIndex, headIndex + PELCO_D_FRAME_LEN); int remainLen = cacheLen - (headIndex + PELCO_D_FRAME_LEN); if (remainLen > 0) { System.arraycopy(frameCache, headIndex + PELCO_D_FRAME_LEN, frameCache, 0, remainLen); } cacheLen = remainLen; parsePelcoDFrame(completeFrame); } } private int findHeadIndex(byte[] cache, int len) { for (int i = 0; i < len; i++) { if (cache[i] == PELCO_D_HEAD) return i; } return -1; } private void parsePelcoDFrame(byte[] frame) { try { if (frame.length != PELCO_D_FRAME_LEN || frame[0] != PELCO_D_HEAD) return; int address = frame[1] & 0xFF; if (address != 1) return; int command2 = frame[3] & 0xFF; int data1 = frame[4] & 0xFF; int data2 = frame[5] & 0xFF; int completeData = (data1 << 8) | data2; PtzDto ptzDto = CameraPTZCache.get(camera.getId()); if (ptzDto == null) ptzDto = new PtzDto(); switch (command2) { case 0x59: ptzDto.setP(completeData / 100f); break; case 0x5B: float pitch = 360 - completeData / 100f; if (Math.abs(pitch - 360f) < 1e-6) pitch = 0f; ptzDto.setT(Math.round(pitch * 100f) / 100f); break; case 0x5D: float zoomValue = completeData / 100f * COEFFICIENT; ptzDto.setZ((float) convertZoomToFrontValue(zoomValue)); break; default: break; } CameraPTZCache.put(camera.getId(), ptzDto); //printStatusLog(); } catch (Exception e) { log.error("[{}] 解析Pelco-D帧异常 | 帧:{}", camera.getIp(), bytesToHex(frame), e); } } private void printStatusLog() { PtzDto ptzDto = CameraPTZCache.get(camera.getId()); if (ptzDto != null) { log.info("【{}】实时状态- Azimuth = {}° Pitch = {}° Zoom={}", camera.getIp(), ptzDto.getP(), ptzDto.getT(), ptzDto.getZ()); } } private int convertZoomToFrontValue(float zoomValue) { float minZoom = MIN_FOCAL * COEFFICIENT; float maxZoom = MAX_FOCAL * COEFFICIENT; zoomValue = Math.max(minZoom, Math.min(maxZoom, zoomValue)); float frontFloat = FRONT_MIN + (FRONT_MAX - FRONT_MIN) * (zoomValue - minZoom) / (maxZoom - minZoom); return Math.max(FRONT_MIN, Math.min(FRONT_MAX, Math.round(frontFloat))); } private String bytesToHex(byte[] bytes) { if (bytes == null || bytes.length == 0) return ""; StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02X ", b & 0xFF)); return sb.toString().trim(); } public void stop() { isRunning.set(false); queue.clear(); log.info("[{}] 串口回调已关闭", camera.getIp()); } @Override public void close() { stop(); } }