海康工业相机优化关闭socket视频重连异常
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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()) {
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user