Java 中通过 Runtime.exec() 或 ProcessBuilder 执行 FFmpeg 命令时,若未及时读取子进程的标准输出(stdout)和标准错误(stderr)流,会导致缓冲区满而进程永久挂起——这是最常见的“假死”原因。
java
中通过 `runtime.exec()` 或 `processbuilder` 执行 ffmpeg 命令时,若未及时读取子进程的标准输出(stdout)和标准错误(stderr)流,会导致缓冲区满而进程永久挂起——这是最常见的“假死”原因。
在 Java 应用中集成 FFmpeg(如音视频合成、剪辑、字幕嵌入等)非常常见,但许多开发者会遇到一个典型问题:
命令在 Windows 命令行中能秒级完成,而在 Java 程序中却“卡住不动”,输出文件仅创建空壳、无法打开,且主线程无限等待
。根本原因并非 FFmpeg 本身或路径/参数错误,而是 Java 对子进程 I/O 流的默认处理机制缺失。
FFmpeg 在运行过程中会持续向 stdout 输出进度信息(如 frame= 123 fps= 45.6 q=23.0...),并向 stderr 输出警告、错误或调试日志。当这些流未被主动消费时,其内部缓冲区(通常为 4KB–8KB)很快填满,后续写入操作将被操作系统阻塞——此时 FFmpeg 暂停执行,而 Java 端调用 waitFor() 也在等待进程结束,形成
双向死锁
。
✅ 正确做法是:
始终显式处理 InputStream(stdout)和 ErrorStream(stderr)
。推荐使用 ProcessBuilder 替代已过时的 Runtime.exec(),因其 API 更清晰、支持流重定向与异常诊断。
以下是一个健壮、生产就绪的 FFmpeg 执行封装示例:
立即学习
“
Java免费学习笔记(深入)
”;
Eclipse导入Android或其他的JAVA项目的正确方法 WORD版
本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
下载
?
关键注意事项:
❌
切勿省略 getErrorStream() 的读取
:即使你认为 FFmpeg “没报错”,它仍可能输出大量调试信息到 stderr;
⏱️
务必设置超时
(如 waitFor(timeout, unit)),避免因 FFmpeg 异常卡死导致整个应用不可用;
?
确保 ffmpeg.exe 路径正确且可执行
:使用绝对路径,或确认 PATH 环境变量已配置(可通过 pb.environment().put("PATH", ...) 显式设置);
?
注意文件路径转义
:Windows 下反斜杠需双写("C:\Users\Test.mp4")或改用正斜杠("C:/Users/Test.mp4"),Java 字符串中 是转义符;
?
复杂场景建议用 ProcessBuilder + redirectOutput/redirectError 到文件
:便于事后审计日志,例如 pb.redirectOutput(new File("ffmpeg.log"))。
最后,将你原代码中的 runFFMPEG 方法重构为调用上述 execute() 即可彻底解决冻结问题:
遵循以上实践,你的 Java + FFmpeg 集成将稳定、可观测、易维护——不再“神秘冻结”,只留高效音视频处理。
import java.io.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class FFmpegRunner {
/**
* 安全执行 FFmpeg 命令,自动消费 stdout/stderr 防止阻塞
* @param cmd FFmpeg 命令数组(如 {"ffmpeg.exe", "-i", "in.mp4", "-i", "audio.mp3", ...})
* @param timeoutSec 超时秒数(建议设置,避免无限等待)
* @return 进程退出码(0 表示成功)
*/
public static int execute(String[] cmd, int timeoutSec) throws IOException, InterruptedException {
Objects.requireNonNull(cmd);
System.out.println("▶ Executing: " + Arrays.toString(cmd));
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(false); // 不合并流,分别处理
Process process = pb.start();
// 启动后台线程实时读取 stderr(关键!)
Thread errThread = new Thread(() -> {
try (InputStream err = process.getErrorStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(err))) {
String line;
while ((line = reader.readLine()) != null) {
System.err.println("[FFMPEG-ERR] " + line); // 或写入日志文件
}
} catch (IOException ignored) {}
}, "FFmpeg-stderr-reader");
errThread.start();
// 同步读取 stdout(也可异步,此处简化)
try (InputStream out = process.getInputStream();
BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(out))) {
String line;
while ((line = stdoutReader.readLine()) != null) {
System.out.println("[FFMPEG-OUT] " + line);
}
}
// 等待完成,带超时保护
boolean finished = process.waitFor(timeoutSec, TimeUnit.SECONDS);
if (!finished) {
System.err.println("⚠ FFmpeg timed out after " + timeoutSec + "s, destroying...");
process.destroyForcibly();
throw new RuntimeException("FFmpeg execution timeout");
}
int exitCode = process.exitValue();
System.out.println("✅ FFmpeg exited with code: " + exitCode);
// 确保 stderr 线程结束
try { errThread.join(1000); } catch (InterruptedException ignored) {}
return exitCode;
}
}protected void runFFMPEG(Multimap args, String outputFile, String fileFormat)
throws IOException, InterruptedException {
String[] cmd = setCommand(args, outputFile, fileFormat);
int exitCode = execute(cmd, 300); // 5分钟超时
if (exitCode != 0) {
throw new RuntimeException("FFmpeg failed with exit code: " + exitCode);
}
} 