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<String> 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();
|
}
|
}
|