海康工业相机优化关闭socket视频重连异常

This commit is contained in:
zc
2026-03-11 17:39:05 +08:00
parent c80dce6419
commit 7d48c78c9b
2 changed files with 120 additions and 13 deletions

View File

@@ -74,7 +74,7 @@
<version>2.9.2</version> <version>2.9.2</version>
</dependency> </dependency>
<!-- pom.xml --> <!-- WebSocket API -->
<dependency> <dependency>
<groupId>jakarta.websocket</groupId> <groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId> <artifactId>jakarta.websocket-api</artifactId>

View File

@@ -31,6 +31,10 @@ public class CameraService {
private long lastFrameTime = 0; private long lastFrameTime = 0;
private final int targetFps = 15; // 目标帧率 private final int targetFps = 15; // 目标帧率
private final long frameInterval = 1000 / targetFps; // 帧间隔(毫秒) 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..."); log.info("Initializing SDK...");
nRet = MvCameraControl.MV_CC_Initialize(); nRet = MvCameraControl.MV_CC_Initialize();
if (MV_OK != nRet) { 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; return false;
} }
@@ -92,7 +96,7 @@ public class CameraService {
log.info("Opening device..."); log.info("Opening device...");
nRet = MvCameraControl.MV_CC_OpenDevice(hCamera); nRet = MvCameraControl.MV_CC_OpenDevice(hCamera);
if (MV_OK != nRet) { 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); MvCameraControl.MV_CC_DestroyHandle(hCamera);
hCamera = null; hCamera = null;
MvCameraControl.MV_CC_Finalize(); MvCameraControl.MV_CC_Finalize();
@@ -103,7 +107,7 @@ public class CameraService {
log.info("Turning off trigger mode..."); log.info("Turning off trigger mode...");
nRet = MvCameraControl.MV_CC_SetEnumValueByString(hCamera, "TriggerMode", "Off"); nRet = MvCameraControl.MV_CC_SetEnumValueByString(hCamera, "TriggerMode", "Off");
if (MV_OK != nRet) { if (MV_OK != nRet) {
log.error("SetTriggerMode failed, errcode: [0x{0}]", Integer.toHexString(nRet)); log.error("SetTriggerMode failed, errcode: {}", Integer.toHexString(nRet));
closeCamera(); closeCamera();
return false; return false;
} }
@@ -112,7 +116,7 @@ public class CameraService {
log.info("Setting Bayer convert quality..."); log.info("Setting Bayer convert quality...");
nRet = MvCameraControl.MV_CC_SetBayerCvtQuality(hCamera, 1); nRet = MvCameraControl.MV_CC_SetBayerCvtQuality(hCamera, 1);
if (MV_OK != nRet) { 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(); closeCamera();
return false; return false;
} }
@@ -148,11 +152,12 @@ public class CameraService {
@Override @Override
public int OnImageCallBack(byte[] bytes, MV_FRAME_OUT_INFO mv_frame_out_info) { public int OnImageCallBack(byte[] bytes, MV_FRAME_OUT_INFO mv_frame_out_info) {
processImage(bytes, mv_frame_out_info); processImage(bytes, mv_frame_out_info);
// processBlackWhiteImage(bytes, mv_frame_out_info);
return 0; return 0;
} }
}); });
if (MV_OK != nRet) { 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; return false;
} }
@@ -160,7 +165,7 @@ public class CameraService {
log.info("Starting grabbing..."); log.info("Starting grabbing...");
nRet = MvCameraControl.MV_CC_StartGrabbing(hCamera); nRet = MvCameraControl.MV_CC_StartGrabbing(hCamera);
if (MV_OK != nRet) { if (MV_OK != nRet) {
log.error("StartGrabbing failed, errcode: [0x{0}]", Integer.toHexString(nRet)); log.error("StartGrabbing failed, errcode: {}", Integer.toHexString(nRet));
return false; return false;
} }
@@ -186,9 +191,26 @@ public class CameraService {
log.info("Stopping grabbing..."); log.info("Stopping grabbing...");
int nRet = MvCameraControl.MV_CC_StopGrabbing(hCamera); int nRet = MvCameraControl.MV_CC_StopGrabbing(hCamera);
if (MV_OK != nRet) { 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; isRunning = false;
log.info("Stream stopped"); log.info("Stream stopped");
} catch (Exception e) { } 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会话 * 添加WebSocket会话
* @param session WebSocket会话 * @param session WebSocket会话
@@ -332,15 +421,20 @@ public class CameraService {
webSocketSessions.put(session.getId(), session); webSocketSessions.put(session.getId(), session);
log.info("WebSocket session added: {}", session.getId()); log.info("WebSocket session added: {}", session.getId());
// 如果相机未初始化,初始化相机 // 确保相机已初始化
if (!initializeCamera()) { if (!initializeCamera()) {
log.error("Failed to initialize camera for new WebSocket session"); log.error("Failed to initialize camera for new WebSocket session");
return; return;
} }
// 如果视频流未开始,开始视频流 // 确保视频流已开始
if (!isRunning) { 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()); webSocketSessions.remove(session.getId());
log.info("WebSocket session removed: {}", session.getId()); log.info("WebSocket session removed: {}", session.getId());
// 如果没有活跃的WebSocket会话停止视频流 // 如果没有活跃的WebSocket会话记录移除时间
if (webSocketSessions.isEmpty()) {
lastSessionRemoveTime = System.currentTimeMillis();
// 启动一个线程,延迟停止视频流,以便页面刷新时能够快速恢复
new Thread(() -> {
try {
Thread.sleep(cameraResourceRetentionTime);
// 再次检查是否仍然没有活跃会话
if (webSocketSessions.isEmpty()) { if (webSocketSessions.isEmpty()) {
stopStream(); stopStream();
log.info("No active sessions after retention period, stopped stream");
}
} catch (InterruptedException e) {
log.error("Interrupted while waiting to stop stream", e);
}
}).start();
} }
} }
} }