package com.ard.work.sdk.zlxd.record; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j @Component public class RtspToMP4 { private Process process; private Thread logThread; private volatile boolean running = false; /** * 开始录制 * * @param streamUrl rtsp地址 * @param filePath 输出路径 * @param maxFileSizeBytes 最大文件大小(可传null) * @param maxDurationSec 最大录制秒(可传null) */ public synchronized void start(String streamUrl, String filePath, Long maxFileSizeBytes, Integer maxDurationSec) { if (running) { log.warn("录制已在运行中"); return; } try { List command = new ArrayList<>(); command.add("ffmpeg"); command.add("-rtsp_transport"); command.add("tcp"); command.add("-y"); command.add("-i"); command.add(streamUrl); // 视频直接copy(CPU最低) command.add("-c:v"); command.add("copy"); // 禁止音频(避免pcm_mulaw问题) command.add("-an"); // 最大文件大小限制 if (maxFileSizeBytes != null) { command.add("-fs"); command.add(String.valueOf(maxFileSizeBytes)); } // 最大时长限制 if (maxDurationSec != null) { command.add("-t"); command.add(String.valueOf(maxDurationSec)); } // MP4优化(避免损坏) command.add("-movflags"); command.add("faststart"); command.add(filePath); log.info("启动FFmpeg:{}", command); ProcessBuilder pb = new ProcessBuilder(command); pb.redirectErrorStream(true); process = pb.start(); running = true; startLogThread(process.getInputStream()); // 等待进程结束(异步) new Thread(() -> { try { int exitCode = process.waitFor(); log.info("FFmpeg退出,code={}", exitCode); } catch (Exception e) { log.error("等待进程异常", e); } finally { running = false; process = null; } }).start(); } catch (Exception e) { running = false; log.error("启动录制失败", e); } } /** * 停止录制(优雅停止) */ public synchronized void stop() { if (!running || process == null) { log.warn("录制未运行"); return; } log.info("开始停止录制..."); try { // 发送q优雅退出 OutputStream os = process.getOutputStream(); os.write('q'); os.flush(); os.close(); // 等待3秒 if (!process.waitFor(3, TimeUnit.SECONDS)) { log.warn("FFmpeg未退出,强制关闭"); process.destroy(); if (!process.waitFor(1, TimeUnit.SECONDS)) { process.destroyForcibly(); } } } catch (Exception e) { log.error("停止录制异常", e); process.destroyForcibly(); } finally { running = false; process = null; } // 停止日志线程 if (logThread != null && logThread.isAlive()) { logThread.interrupt(); } log.info("录制已停止"); } /** * 是否运行 */ public boolean isRunning() { return running; } /** * 启动日志线程 */ private void startLogThread(InputStream inputStream) { logThread = new Thread(() -> { try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { String line; while ((line = br.readLine()) != null && running) { log.debug("FFmpeg: {}", line); } } catch (Exception e) { if (running) { log.error("日志线程异常", e); } } }); logThread.setDaemon(true); logThread.start(); } }