diff --git a/wms-webapi/pom.xml b/wms-webapi/pom.xml index ae5d3b9..a877065 100644 --- a/wms-webapi/pom.xml +++ b/wms-webapi/pom.xml @@ -74,7 +74,7 @@ 2.9.2 - + jakarta.websocket jakarta.websocket-api diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/hkMVS/CameraService.java b/wms-webapi/src/main/java/top/wms/admin/controller/hkMVS/CameraService.java index ec08707..59ecdbb 100644 --- a/wms-webapi/src/main/java/top/wms/admin/controller/hkMVS/CameraService.java +++ b/wms-webapi/src/main/java/top/wms/admin/controller/hkMVS/CameraService.java @@ -31,6 +31,10 @@ public class CameraService { private long lastFrameTime = 0; private final int targetFps = 15; // 目标帧率 private final long frameInterval = 1000 / targetFps; // 帧间隔(毫秒) + // 最后一次会话移除时间,用于判断是否需要保留相机资源 + private long lastSessionRemoveTime = 0; + // 相机资源保留时间(毫秒) + private final long cameraResourceRetentionTime = 5000; // 5秒 /** * 初始化相机 @@ -49,7 +53,7 @@ public class CameraService { log.info("Initializing SDK..."); nRet = MvCameraControl.MV_CC_Initialize(); if (MV_OK != nRet) { - log.error("Initialize SDK fail! nRet [0x{0}]", Integer.toHexString(nRet)); + log.error("Initialize SDK fail! nRet {}", Integer.toHexString(nRet)); return false; } @@ -92,7 +96,7 @@ public class CameraService { log.info("Opening device..."); nRet = MvCameraControl.MV_CC_OpenDevice(hCamera); if (MV_OK != nRet) { - log.error("Connect to camera failed, errcode: [0x{0}]", Integer.toHexString(nRet)); + log.error("Connect to camera failed, errcode: {}", Integer.toHexString(nRet)); MvCameraControl.MV_CC_DestroyHandle(hCamera); hCamera = null; MvCameraControl.MV_CC_Finalize(); @@ -103,7 +107,7 @@ public class CameraService { log.info("Turning off trigger mode..."); nRet = MvCameraControl.MV_CC_SetEnumValueByString(hCamera, "TriggerMode", "Off"); if (MV_OK != nRet) { - log.error("SetTriggerMode failed, errcode: [0x{0}]", Integer.toHexString(nRet)); + log.error("SetTriggerMode failed, errcode: {}", Integer.toHexString(nRet)); closeCamera(); return false; } @@ -112,7 +116,7 @@ public class CameraService { log.info("Setting Bayer convert quality..."); nRet = MvCameraControl.MV_CC_SetBayerCvtQuality(hCamera, 1); if (MV_OK != nRet) { - log.error("Set Bayer convert quality fail! nRet [0x{0}]", Integer.toHexString(nRet)); + log.error("Set Bayer convert quality fail! nRet {}", Integer.toHexString(nRet)); closeCamera(); return false; } @@ -148,11 +152,12 @@ public class CameraService { @Override public int OnImageCallBack(byte[] bytes, MV_FRAME_OUT_INFO mv_frame_out_info) { processImage(bytes, mv_frame_out_info); +// processBlackWhiteImage(bytes, mv_frame_out_info); return 0; } }); if (MV_OK != nRet) { - log.error("Register image callback failed, errcode: [0x{0}]", Integer.toHexString(nRet)); + log.error("Register image callback failed, errcode: {}", Integer.toHexString(nRet)); return false; } @@ -160,7 +165,7 @@ public class CameraService { log.info("Starting grabbing..."); nRet = MvCameraControl.MV_CC_StartGrabbing(hCamera); if (MV_OK != nRet) { - log.error("StartGrabbing failed, errcode: [0x{0}]", Integer.toHexString(nRet)); + log.error("StartGrabbing failed, errcode: {}", Integer.toHexString(nRet)); return false; } @@ -186,9 +191,26 @@ public class CameraService { log.info("Stopping grabbing..."); int nRet = MvCameraControl.MV_CC_StopGrabbing(hCamera); if (MV_OK != nRet) { - log.error("StopGrabbing failed, errcode: [0x{0}]", Integer.toHexString(nRet)); + log.error("StopGrabbing failed, errcode: {}", Integer.toHexString(nRet)); } + // close device + nRet = MvCameraControl.MV_CC_CloseDevice(hCamera); + if (MV_OK != nRet) { + + log.error("CloseDevice failed, errcode: {}", Integer.toHexString(nRet)); + } + + if (null != hCamera) { + // Destroy handle + nRet = MvCameraControl.MV_CC_DestroyHandle(hCamera); + if (MV_OK != nRet) { + log.error("DestroyHandle failed, errcode: {}", Integer.toHexString(nRet)); + } + hCamera = null; + } + MvCameraControl.MV_CC_Finalize(); + isRunning = false; log.info("Stream stopped"); } catch (Exception e) { @@ -323,6 +345,73 @@ public class CameraService { } } + + /** + * 处理黑白相机图像数据 + * @param bytes 原始图像数据 + * @param frameInfo 帧信息 + */ + private void processBlackWhiteImage(byte[] bytes, MV_FRAME_OUT_INFO frameInfo) { + if (bytes == null || frameInfo == null || webSocketSessions.isEmpty()) { + return; + } + + try { + // 帧率控制 + long currentTime = System.currentTimeMillis(); + if (currentTime - lastFrameTime < frameInterval) { + return; // 跳过当前帧,控制帧率 + } + lastFrameTime = currentTime; + + // 对于黑白相机(MV-CE050-30GM),数据是单通道灰度格式 + // 将原始灰度数据转换为BufferedImage + BufferedImage image = new BufferedImage(frameInfo.width, frameInfo.height, BufferedImage.TYPE_BYTE_GRAY); + image.getRaster().setDataElements(0, 0, frameInfo.width, frameInfo.height, bytes); + + // 压缩图像为JPEG格式 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, "jpeg", baos); + byte[] compressedData = baos.toByteArray(); + baos.close(); + + log.debug("Original size: {} bytes, Compressed size: {} bytes, Compression ratio: {:.2f}%", + bytes.length, compressedData.length, (1 - (double)compressedData.length / bytes.length) * 100); + + // 创建包含图像尺寸信息和压缩数据的消息 + // 消息格式: [width(4 bytes)][height(4 bytes)][image data] + byte[] message = new byte[8 + compressedData.length]; + + // 写入宽度和高度(使用小端序) + message[0] = (byte) (frameInfo.width & 0xFF); + message[1] = (byte) ((frameInfo.width >> 8) & 0xFF); + message[2] = (byte) ((frameInfo.width >> 16) & 0xFF); + message[3] = (byte) ((frameInfo.width >> 24) & 0xFF); + + message[4] = (byte) (frameInfo.height & 0xFF); + message[5] = (byte) ((frameInfo.height >> 8) & 0xFF); + message[6] = (byte) ((frameInfo.height >> 16) & 0xFF); + message[7] = (byte) ((frameInfo.height >> 24) & 0xFF); + + // 写入压缩后的图像数据 + System.arraycopy(compressedData, 0, message, 8, compressedData.length); + + // 发送图像数据到所有连接的WebSocket客户端 + for (WebSocketSession session : webSocketSessions.values()) { + if (session.isOpen()) { + try { + session.sendMessage(new BinaryMessage(message)); + } catch (Exception e) { + log.error("Send message to WebSocket failed", e); + } + } + } + } catch (Exception e) { + log.error("Process image failed", e); + } + } + + /** * 添加WebSocket会话 * @param session WebSocket会话 @@ -332,15 +421,20 @@ public class CameraService { webSocketSessions.put(session.getId(), session); log.info("WebSocket session added: {}", session.getId()); - // 如果相机未初始化,初始化相机 + // 确保相机已初始化 if (!initializeCamera()) { log.error("Failed to initialize camera for new WebSocket session"); return; } - // 如果视频流未开始,开始视频流 + // 确保视频流已开始 if (!isRunning) { - startStream(); + boolean started = startStream(); + if (!started) { + log.error("Failed to start stream for new WebSocket session"); + } + } else { + log.info("Stream already running, no need to start again"); } } } @@ -354,9 +448,22 @@ public class CameraService { webSocketSessions.remove(session.getId()); log.info("WebSocket session removed: {}", session.getId()); - // 如果没有活跃的WebSocket会话,停止视频流 + // 如果没有活跃的WebSocket会话,记录移除时间 if (webSocketSessions.isEmpty()) { - stopStream(); + lastSessionRemoveTime = System.currentTimeMillis(); + // 启动一个线程,延迟停止视频流,以便页面刷新时能够快速恢复 + new Thread(() -> { + try { + Thread.sleep(cameraResourceRetentionTime); + // 再次检查是否仍然没有活跃会话 + if (webSocketSessions.isEmpty()) { + stopStream(); + log.info("No active sessions after retention period, stopped stream"); + } + } catch (InterruptedException e) { + log.error("Interrupted while waiting to stop stream", e); + } + }).start(); } } }