修复工作

This commit is contained in:
2025-10-02 14:32:42 +08:00
parent 2a689d6e5c
commit 4bbbff266d

View File

@@ -86,18 +86,50 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
public MediaTransferFlvByFFmpeg(CameraDto cameraDto) {
// 优先使用系统 FFmpeg包含 libx264如果不存在则使用 JavaCV 的
String ffmpegPath = System.getProperty(MediaConstant.ffmpegPathKey);
// 尝试使用系统 FFmpeg
if (new File("/usr/bin/ffmpeg").exists()) {
command.add("/usr/bin/ffmpeg");
log.info("使用系统 FFmpeg: /usr/bin/ffmpeg");
String osName = System.getProperty("os.name").toLowerCase();
// 根据操作系统选择 FFmpeg
if (osName.contains("win")) {
// Windows 系统:尝试从 PATH 环境变量中查找 ffmpeg
String systemFFmpeg = findFFmpegInPath();
if (systemFFmpeg != null) {
command.add(systemFFmpeg);
log.info("✅ 使用系统 FFmpeg (Windows): {}", systemFFmpeg);
} else {
command.add(ffmpegPath);
log.warn("⚠️ 未找到系统 FFmpeg使用 JavaCV FFmpeg: {}(注意:可能不支持 libx264", ffmpegPath);
}
} else {
command.add(ffmpegPath);
log.info("使用 JavaCV FFmpeg: {}", ffmpegPath);
// Linux/Unix 系统
if (new File("/usr/bin/ffmpeg").exists()) {
command.add("/usr/bin/ffmpeg");
log.info("✅ 使用系统 FFmpeg (Linux): /usr/bin/ffmpeg");
} else {
command.add(ffmpegPath);
log.warn("⚠️ 未找到系统 FFmpeg使用 JavaCV FFmpeg: {}(注意:可能不支持 libx264", ffmpegPath);
}
}
this.cameraDto = cameraDto;
this.enableLog = true; // 临时启用日志,用于调试
buildCommand();
}
/**
* 在 PATH 环境变量中查找 ffmpeg.exe
*/
private String findFFmpegInPath() {
String pathEnv = System.getenv("PATH");
if (pathEnv == null) return null;
String[] paths = pathEnv.split(File.pathSeparator);
for (String path : paths) {
File ffmpegFile = new File(path, "ffmpeg.exe");
if (ffmpegFile.exists() && ffmpegFile.canExecute()) {
return ffmpegFile.getAbsolutePath();
}
}
return null;
}
public MediaTransferFlvByFFmpeg(final String executable, CameraDto cameraDto) {
command.add(executable);
@@ -139,28 +171,40 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
* 使用 libx264 编码器(系统 FFmpeg 包含)
*/
private void buildCommand() {
// 检查是否使用系统 FFmpeg包含 libx264
boolean useSystemFFmpeg = command.get(0).contains("ffmpeg.exe") || command.get(0).equals("/usr/bin/ffmpeg");
// 添加日志级别参数(临时调试用)
this.addArgument("-loglevel").addArgument("info")
.addArgument("-rtsp_transport").addArgument("tcp")
.addArgument("-i").addArgument(cameraDto.getUrl())
.addArgument("-max_delay").addArgument("1")
.addArgument("-g").addArgument("25")
.addArgument("-r").addArgument("25")
// 使用 libx264 编码H.264FLV 播放器标准支持)
.addArgument("-c:v").addArgument("libx264")
.addArgument("-preset").addArgument("ultrafast")
.addArgument("-tune").addArgument("zerolatency")
.addArgument("-profile:v").addArgument("baseline") // baseline 兼容性最好
.addArgument("-level").addArgument("3.0")
.addArgument("-b:v").addArgument("1000k") // 视频比特率
.addArgument("-maxrate").addArgument("1000k")
.addArgument("-bufsize").addArgument("2000k")
// 音频编码
.addArgument("-c:a").addArgument("aac")
.addArgument("-strict").addArgument("experimental")
.addArgument("-b:a").addArgument("64k")
.addArgument("-ar").addArgument("44100") // 音频采样率
.addArgument("-f").addArgument("flv");
.addArgument("-max_delay").addArgument("1");
if (useSystemFFmpeg) {
// 系统 FFmpeg使用 libx264 重新编码
log.info("🎬 使用 libx264 编码器(重新编码)");
this.addArgument("-g").addArgument("25")
.addArgument("-r").addArgument("25")
.addArgument("-c:v").addArgument("libx264")
.addArgument("-preset").addArgument("ultrafast")
.addArgument("-tune").addArgument("zerolatency")
.addArgument("-profile:v").addArgument("baseline")
.addArgument("-level").addArgument("3.0")
.addArgument("-b:v").addArgument("1000k")
.addArgument("-maxrate").addArgument("1000k")
.addArgument("-bufsize").addArgument("2000k")
.addArgument("-c:a").addArgument("aac")
.addArgument("-strict").addArgument("experimental")
.addArgument("-b:a").addArgument("64k")
.addArgument("-ar").addArgument("44100");
} else {
// JavaCV FFmpeg使用 copy不重新编码只重新封装
log.info("📦 使用 copy 编码器(不重新编码,只重新封装)");
this.addArgument("-c:v").addArgument("copy")
.addArgument("-c:a").addArgument("copy");
}
this.addArgument("-f").addArgument("flv");
}
// private void buildCommand() {
@@ -201,16 +245,26 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
log.info("输出地址: {}", output);
log.info("=================================");
try {
process = new ProcessBuilder(command).start();
ProcessBuilder pb = new ProcessBuilder(command);
// 重要:合并错误流到输出流,便于日志收集
pb.redirectErrorStream(false);
process = pb.start();
running = true;
log.info("✅ FFmpeg 进程已启动,等待数据...");
log.info("✅ FFmpeg 进程已启动 PID={},等待数据...", process.pid());
// 先启动输出数据线程监听TCP连接
outputData();
// 再启动日志监听线程
listenNetTimeout();
dealStream(process);
outputData();
// 最后启动客户端监听
listenClient();
log.info("✅ FFmpeg 所有线程已启动,等待客户端连接...");
} catch (IOException e) {
log.error("❌ FFmpeg 启动失败: {}", e.getMessage());
e.printStackTrace();
log.error("❌ FFmpeg 启动失败: {}", e.getMessage(), e);
running = false;
MediaService.cameras.remove(cameraDto.getMediaKey());
}
return this;
}
@@ -223,20 +277,32 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
public void run() {
Socket client = null;
try {
log.info("🎬 开始监听 TCP Socket等待 FFmpeg 连接...");
log.info("🎬 开始监听 TCP Socket: {} 端口: {},等待 FFmpeg 连接...",
tcpServer.getInetAddress(), tcpServer.getLocalPort());
client = tcpServer.accept();
log.info("✅ FFmpeg 已连接到 TCP Socket");
log.info("✅ FFmpeg 已连接到 TCP Socket,远程地址: {}", client.getRemoteSocketAddress());
DataInputStream input = new DataInputStream(client.getInputStream());
byte[] buffer = new byte[4096]; // 增加缓冲区到4KB
int len = 0;
boolean headerSent = false;
int totalBytes = 0;
log.info("📡 开始读取 FLV 数据流...");
while (running) {
len = input.read(buffer);
if (len == -1) {
log.warn("⚠️ FFmpeg 连接关闭读取到EOF");
break;
}
totalBytes += len;
if (totalBytes % 102400 == 0) { // 每100KB输出一次
log.debug("📊 已接收 {} KB 数据", totalBytes / 1024);
}
// 第一次读取的是FLV header13字节
if (header == null && len >= 13) {
@@ -254,12 +320,14 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
headerSent = true;
// 如果有剩余数据(包含header+数据),一起发送
if (len > 0) {
byte[] data = new byte[len];
System.arraycopy(buffer, 0, data, 0, len);
log.info("📤 发送第一批数据: {} bytes客户端数量: HTTP={}, WS={}",
len, httpClients.size(), wsClients.size());
// 如果有剩余数据header之后的数据),发送剩余部分
// 注意header 已经在 addClient() 中发送给客户端了,这里只发送 header 之后的数据
if (len > 13) {
int remainingLen = len - 13;
byte[] data = new byte[remainingLen];
System.arraycopy(buffer, 13, data, 0, remainingLen);
log.info("📤 发送第一批数据不含header: {} bytes客户端数量: HTTP={}, WS={}",
remainingLen, httpClients.size(), wsClients.size());
sendFrameData(data);
}
continue;
@@ -414,30 +482,45 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
public void run() {
BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = null;
boolean streamStarted = false;
try {
while (running) {
line = err.readLine();
currentTimeMillis = System.currentTimeMillis();
if (line == null) {
log.warn("⚠️ FFmpeg 错误流已关闭");
break;
}
if (enableLog) {
// 检测关键信息
if (line.contains("Stream #0") || line.contains("Output #0")) {
streamStarted = true;
log.info("[FFmpeg] " + line);
}
// 即使不启用日志,也输出关键错误信息
if (!enableLog && (line.contains("error") || line.contains("failed") ||
line.contains("Connection") || line.contains("timeout"))) {
} else if (line.contains("frame=") && line.contains("fps=")) {
// 这是输出进度信息,定期输出
if (enableLog) {
log.debug("[FFmpeg Progress] " + line.trim());
}
} else if (line.toLowerCase().contains("error") ||
line.toLowerCase().contains("failed") ||
line.toLowerCase().contains("invalid") ||
line.toLowerCase().contains("connection") ||
line.toLowerCase().contains("timeout") ||
line.toLowerCase().contains("could not")) {
// 错误信息始终输出
log.error("[FFmpeg Error] " + line);
} else if (enableLog) {
log.info("[FFmpeg] " + line);
}
}
} catch (IOException e) {
e.printStackTrace();
log.error("❌ 读取 FFmpeg 错误流异常: {}", e.getMessage());
} finally {
try {
running = false;
err.close();
} catch (IOException e) {
e.printStackTrace();
log.error("关闭错误流异常: {}", e.getMessage());
}
}
}
@@ -579,12 +662,20 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
* @param ctype enum,ClientType
*/
public void addClient(ChannelHandlerContext ctx, ClientType ctype) {
log.info("🔗 新客户端连接: 类型={}, Channel={}", ctype, ctx.channel().remoteAddress());
log.info("🔗 新客户端连接: 类型={}, Channel={}, FFmpeg运行状态={}",
ctype, ctx.channel().remoteAddress(), running);
// 检查FFmpeg进程状态
if (process != null && !process.isAlive()) {
log.error("❌ FFmpeg 进程已死亡,无法添加客户端");
return;
}
int timeout = 0;
while (true) {
try {
if (header != null) {
log.info("✅ FLV header 已就绪,准备发送给客户端");
log.info("✅ FLV header 已就绪,准备发送给客户端 (总共 {} bytes)", header.length);
try {
if (ctx.channel().isWritable()) {
// 发送帧前先发送header
@@ -596,10 +687,14 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
public void operationComplete(Future<? super Void> future) throws FrameGrabber.Exception {
if (future.isSuccess()) {
httpClients.put(ctx.channel().id().toString(), ctx);
log.info("✅ HTTP 客户端已添加,当前客户端数: {}", httpClients.size());
} else {
log.error("❌ 发送 FLV header 失败: {}", future.cause());
}
}
});
} else if (ClientType.WEBSOCKET == ctype) {
log.info("📤 发送 FLV header 到 WebSocket 客户端: {} bytes", header.length);
ChannelFuture future = ctx
.writeAndFlush(new BinaryWebSocketFrame(Unpooled.copiedBuffer(header)));
future.addListener(new GenericFutureListener<Future<? super Void>>() {
@@ -607,35 +702,46 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
public void operationComplete(Future<? super Void> future) throws FrameGrabber.Exception {
if (future.isSuccess()) {
wsClients.put(ctx.channel().id().toString(), ctx);
log.info("✅ WebSocket 客户端已添加,当前客户端数: {}", wsClients.size());
} else {
log.error("❌ 发送 FLV header 失败: {}", future.cause());
}
}
});
}
} else {
log.warn("⚠️ 客户端通道不可写");
}
} catch (java.lang.Exception e) {
e.printStackTrace();
log.error("❌ 添加客户端异常: {}", e.getMessage(), e);
}
break;
}
// 等待推拉流启动
if (timeout == 0) {
log.info("⏳ 等待 FLV header...");
log.info("⏳ 等待 FLV header...FFmpeg 进程状态: {}",
process != null && process.isAlive() ? "运行中" : "未启动/已停止");
}
Thread.sleep(50);
// 启动录制器失败
timeout += 50;
if (timeout % 5000 == 0) {
log.warn("⚠️ 等待 FLV header 超时: {} ms可能 FFmpeg 拉流失败", timeout);
log.warn("⚠️ 等待 FLV header 超时: {} msFFmpeg 可能拉流失败或未连接到TCP Socket", timeout);
if (process != null && !process.isAlive()) {
log.error("❌ FFmpeg 进程已死亡!退出码: {}", process.exitValue());
break;
}
}
if (timeout > 30000) {
log.error("❌ 等待 FLV header 超时 30 秒,放弃连接");
log.error(" 可能原因: 1) FFmpeg 拉流失败 2) RTSP 地址不可达 3) 网络问题");
break;
}
} catch (java.lang.Exception e) {
e.printStackTrace();
log.error("❌ 等待 header 异常: {}", e.getMessage(), e);
break;
}
}
}