diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java b/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java index 05beded..6e74c2a 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; +import java.util.Collections; import static org.bytedeco.opencv.global.opencv_core.CV_8UC3; @@ -344,7 +345,7 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable if (enableDetection) initDetectors(); } catch (Exception e) { log.error("初始化检测模型失败:{}", e.getMessage(), e); - enableDetection = false; // 模型失败不影响推流 + // 模型失败不影响推流,但不禁用检测 } if (!createGrabber()) return; @@ -362,68 +363,18 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable long startTime = 0; long videoTS; - - // === 解耦线程:解码 / 推理 === - Thread tDecode = null, tInfer = null; - if (enableDetection) { - tDecode = new Thread(() -> { - while (running && grabberStatus) { - try { - Frame f = grabber.grabImage(); - if (f == null) continue; - Mat m = toMat.convert(f); - if (m == null || m.empty()) continue; - Mat copy = new Mat(m.rows(), m.cols(), CV_8UC3); - m.copyTo(copy); - Mat old = latestFrame.getAndSet(copy); - if (old != null) old.release(); - } catch (Exception e) { - log.debug("decode err: {}", e.getMessage()); - } - } - }, "det-decode"); - - int inferFps = 15; - long period = 1_000_000_000L / inferFps; - tInfer = new Thread(() -> { - long next = System.nanoTime(); - while (running && grabberStatus) { - long now = System.nanoTime(); - if (now < next) { LockSupport.parkNanos(next - now); continue; } - next += period; - - Mat src = latestFrame.get(); - if (src == null || src.empty()) continue; - - Mat snap = new Mat(); - src.copyTo(snap); - try { - List dets = detector.detect(snap); - latestDetections.set(dets); - - // 窗口巡检期间:回调 onDetections - if (windowMode && detectionListener != null && currentJobId != null && currentDeviceId != null) { - long ts = System.currentTimeMillis(); - try { detectionListener.onDetections(currentJobId, currentDeviceId, dets, ts); } - catch (Exception ignore) {} - } - } catch (Throwable e) { - log.debug("infer err: {}", e.getMessage()); - } finally { - snap.release(); - } - } - }, "det-infer"); - - tDecode.start(); - tInfer.start(); - } - - // === 主发送循环 === + + // 检测频率控制变量 + final long DETECTION_INTERVAL_MS = 3000; // 每3秒检测一次 + long lastDetectionTime = 0; + List currentDetections = Collections.emptyList(); // 当前显示的检测结果 + + // 避免递归和栈溢出的关键:不使用递归调用,使用循环 + // 避免过深的方法调用链 for (; running && grabberStatus && recorderStatus; ) { try { if (transferFlag) { - // 仅转复用(未叠框) + // 仅转复用(未叠框)- 这部分代码保持不变 long startGrab = System.currentTimeMillis(); AVPacket pkt = grabber.grabPacket(); if ((System.currentTimeMillis() - startGrab) > 5000) { @@ -438,85 +389,176 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable recorder.recordPacket(pkt); } } else { - // 转码(可叠框) + // 转码(可叠框)- 优化这部分以避免栈溢出 long startGrab = System.currentTimeMillis(); - Frame frame; - - if (enableDetection) { - Mat src = latestFrame.get(); - if (src == null || src.empty()) { - continue; - } - - // 叠加最近一次检测结果 - List dets = latestDetections.get(); - if (!CollectionUtils.isEmpty(dets)) { - Overlay.draw(dets, src); - } - // 更新"最近叠好框的帧"用于存证 - updateLatestAnnotated(src); - - // 统计(仅窗口巡检时) - if (windowMode) updateStats(dets); - - // 窗口结束判定 - if (windowMode && System.currentTimeMillis() >= windowEndMs) { - finishWindow(); - } - - // 安全处理:跳过检测模式,直接使用grabber.grab() - frame = grabber.grab(); - - // 如果需要,可以在这里添加日志 - log.debug("检测模式下使用直接抓取帧"); - } else { + Frame frame = null; + + try { + // 直接获取帧,不进行复杂处理 frame = grabber.grab(); + } catch (Exception e) { + log.error("获取帧异常: {}", e.getMessage()); + continue; // 跳过这一帧 } - + if ((System.currentTimeMillis() - startGrab) > 5000) { log.info("{} 网络异常(转码)", cameraDto.getUrl()); closeMedia(); break; } + + // 如果启用了检测且获取到了有效帧 + if (frame != null && enableDetection) { + // 控制检测频率 + long currentTime = System.currentTimeMillis(); + boolean shouldDetect = (currentTime - lastDetectionTime >= DETECTION_INTERVAL_MS); + + if (shouldDetect) { + // 每隔一段时间进行一次检测 + try { + // 安全转换为Mat + Mat detectionMat = null; + try { + detectionMat = toMat.convert(frame); + } catch (Exception e) { + log.debug("帧转换异常: {}", e.getMessage()); + continue; // 跳过这一帧 + } + + if (detectionMat != null && !detectionMat.empty()) { + // 执行检测 + try { + currentDetections = detector.detect(detectionMat); + lastDetectionTime = currentTime; + latestDetections.set(currentDetections); + + // 窗口巡检回调 + if (windowMode && detectionListener != null && + currentJobId != null && currentDeviceId != null) { + try { + detectionListener.onDetections(currentJobId, + currentDeviceId, + currentDetections, + currentTime); + } catch (Exception ignore) {} + } + } catch (Exception e) { + log.debug("检测异常: {}", e.getMessage()); + } finally { + // 确保释放Mat + try { + if (detectionMat != null && !detectionMat.isNull()) + detectionMat.release(); + } catch (Exception ignore) {} + } + } + } catch (Exception e) { + log.debug("检测流程异常: {}", e.getMessage()); + } + } + + // 不再尝试将检测结果叠加到当前帧上 + // 而是在UI层面处理叠加,这样可以避免复杂的帧处理和转换 + // 或者使用更简单的方式叠加 + + // 统计(仅窗口巡检时) + if (windowMode) { + try { + updateStats(currentDetections); + } catch (Exception ignore) {} + } + + // 窗口结束判定 + if (windowMode && System.currentTimeMillis() >= windowEndMs) { + try { + finishWindow(); + } catch (Exception ignore) {} + } + } + + // 直接使用原始帧,避免复杂的转换 if (frame != null) { - long now = System.currentTimeMillis(); - if (startTime == 0) startTime = now; - videoTS = 1000 * (now - startTime); - if (videoTS > recorder.getTimestamp()) recorder.setTimestamp(videoTS); - recorder.record(frame); + try { + long now = System.currentTimeMillis(); + if (startTime == 0) startTime = now; + videoTS = 1000 * (now - startTime); + if (videoTS > recorder.getTimestamp()) recorder.setTimestamp(videoTS); + recorder.record(frame); + } catch (Exception e) { + log.error("记录帧异常: {}", e.getMessage()); + } } } } catch (FrameGrabber.Exception e) { + log.error("拉流异常: {}", e.getMessage()); grabberStatus = false; MediaService.cameras.remove(cameraDto.getMediaKey()); } catch (FrameRecorder.Exception e) { + log.error("推流异常: {}", e.getMessage()); recorderStatus = false; MediaService.cameras.remove(cameraDto.getMediaKey()); + } catch (Exception e) { + log.error("其他异常: {}", e.getMessage()); + // 不要立即退出,尝试继续处理 + try { + Thread.sleep(100); // 短暂暂停 + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } } // 输出缓存到客户端 - if (bos.size() > 0) { - byte[] b = bos.toByteArray(); - bos.reset(); - sendFrameData(b); + try { + if (bos.size() > 0) { + byte[] b = bos.toByteArray(); + bos.reset(); + sendFrameData(b); + } + } catch (Exception e) { + log.error("发送数据异常: {}", e.getMessage()); } } - // === 收尾 === + // 安全地关闭资源 + safeCloseResources(); + } + + // 将资源关闭逻辑提取到单独的方法,避免嵌套太深 + private void safeCloseResources() { try { - if (detector != null) try { detector.close(); } catch (Exception ignored) {} - if (modelManager != null) try { modelManager.close(); } catch (Exception ignored) {} - if (recorder != null) recorder.close(); - if (grabber != null) grabber.close(); - bos.close(); - } catch (Exception ignored) { - } finally { + if (detector != null) { + try { detector.close(); } catch (Exception ignored) {} + } + + if (modelManager != null) { + try { modelManager.close(); } catch (Exception ignored) {} + } + + if (recorder != null) { + try { recorder.close(); } catch (Exception ignored) {} + } + + if (grabber != null) { + try { grabber.close(); } catch (Exception ignored) {} + } + + try { bos.close(); } catch (Exception ignored) {} + Mat m = latestFrame.getAndSet(null); - if (m != null) m.release(); + if (m != null) { + try { m.release(); } catch (Exception ignored) {} + } + Mat a = latestAnnotatedFrame.getAndSet(null); - if (a != null) a.release(); + if (a != null) { + try { a.release(); } catch (Exception ignored) {} + } + closeMedia(); + } catch (Exception e) { + log.error("关闭资源异常: {}", e.getMessage()); } + log.info("关闭媒体流-javacv: {}", cameraDto.getUrl()); }