Java 程序调用 FFmpeg 教程

在 Java 应用中集成视频处理功能时,FFmpeg 是一个强大而灵活的选择。虽然 Java 本身没有内置音视频编解码能力,但我们可以通过调用系统命令的方式,借助 FFmpeg 实现转码、压缩、裁剪等操作。

核心原理 :Java 通过 ProcessBuilder 启动外部进程(如 ffmpeg.exe),并传递命令行参数来执行具体任务。这本质上是一种"命令行调用",但通过 Java 封装后,可以无缝集成到应用程序中。


一、为什么使用 ProcessBuilder

ProcessBuilder 是 Java 标准库(java.lang 包)提供的用于创建和管理操作系统进程的工具类。相比旧的 Runtime.exec(),它具有以下优势:

  • 更清晰的 API 设计
  • 支持设置工作目录、环境变量
  • 更安全地处理输入/输出流
  • 避免常见的死锁问题(需正确读取 stdout/stderr)

二、示例:用 Java 压缩视频

下面是一个完整的示例,展示如何使用 Java 调用 FFmpeg 对视频进行压缩:

java 复制代码
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class FfmpegCompressor {

    /**
     * 使用 FFmpeg 压缩视频
     *
     * @param inputPath     输入视频路径(相对或绝对)
     * @param outputPath    输出视频路径
     * @param videoBitrate  视频目标码率(如 "3M" 表示 3 Mbps)
     * @throws IOException          启动进程或读取流失败
     * @throws InterruptedException 进程被中断
     */
    public static void compressVideo(String inputPath, String outputPath, String videoBitrate)
            throws IOException, InterruptedException {

        // 务必指定 ffmpeg 可执行文件的完整路径(不是安装目录!)
        String ffmpegPath = "D:\\Develop\\ffmpeg-8.0.1-essentials_build\\bin\\ffmpeg.exe";

        ProcessBuilder pb = new ProcessBuilder(
                ffmpegPath,
                "-i", inputPath,
                "-b:v", videoBitrate,
                "-bufsize", videoBitrate,
                "-c:a", "aac",   // 音频编码为 AAC
                "-y",            // 覆盖输出文件(不提示)
                outputPath
        );

        // 设置工作目录(建议设为视频所在目录,避免路径问题)
        pb.directory(new File("C:\\Users\\x\\Videos\\原画"));

        System.out.println("正在启动 FFmpeg...");
        Process process = pb.start();

        // 异步读取 stdout 和 stderr,防止缓冲区阻塞导致死锁
        Thread outThread = new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                reader.lines().forEach(line -> System.out.println("[FFmpeg OUT] " + line));
            } catch (IOException e) {
                System.err.println("读取 FFmpeg 标准输出失败: " + e.getMessage());
            }
        });

        Thread errThread = new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getErrorStream()))) {
                reader.lines().forEach(line -> System.err.println("[FFmpeg LOG] " + line));
            } catch (IOException e) {
                System.err.println("读取 FFmpeg 错误流失败: " + e.getMessage());
            }
        });

        outThread.start();
        errThread.start();

        // 等待进程结束
        int exitCode = process.waitFor();
        outThread.join();
        errThread.join();

        if (exitCode == 0) {
            System.out.println("视频压缩成功: " + outputPath);
        } else {
            System.err.println("FFmpeg 执行失败,退出码: " + exitCode);
            throw new RuntimeException("视频压缩失败,请检查输入路径或 FFmpeg 配置");
        }
    }

    public static void main(String[] args) {
        try {
            compressVideo("1.mp4", "1-compressed.mp4", "3M");
        } catch (Exception e) {
            System.err.println("程序异常: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

三、关键注意事项

1. ffmpegPath 必须指向 ffmpeg.exe 文件

  • 错误:D:\Develop\ffmpeg-8.0.1-essentials_build
  • 正确:D:\Develop\ffmpeg-8.0.1-essentials_build\bin\ffmpeg.exe

2. 工作目录(Working Directory)建议设置

  • 若输入/输出路径为相对路径,pb.directory(...) 决定了这些路径的基准。
  • 推荐设为视频所在目录,避免路径解析错误。

3. FFmpeg 的日志输出在 stderr

⚠️ 注意:[FFmpeg ERR] 并不表示"错误"!

FFmpeg 遵循 Unix 哲学:

  • stdout :仅用于真正的数据输出(如 -f image2pipe 输出图像流)
  • stderr:输出所有日志、进度、警告、统计信息(包括成功信息)

因此,你看到的 [FFmpeg ERR]是正常运行日志。


四、进阶建议

方向 建议
路径配置 ffmpegPath 提取为配置项(如 properties 文件或环境变量),避免硬编码
异常处理 捕获更细粒度的异常,记录日志(如使用 SLF4J)
跨平台支持 检测操作系统,自动选择 ffmpegffmpeg.exe
资源清理 确保 BufferedReader 正确关闭(当前代码已用 try-with-resources)
异步执行 若在 Web 应用中使用,建议将压缩任务提交到线程池,避免阻塞主线程

五、总结

通过 ProcessBuilder 调用 FFmpeg 是 Java 中实现音视频处理的常用方案。虽然它依赖外部程序,但只要注意路径、流处理和错误判断,就能稳定高效地完成任务。

📌 记住:FFmpeg 强大,但"黑盒"调用需谨慎。建议先在命令行测试命令,再集成到 Java 代码中。

相关推荐
用户8356290780519 小时前
Python 实现 PDF 文件加密与解密方法
后端·python
用户8356290780519 小时前
使用 Python 冻结与拆分 Excel 窗格教程
后端·python
karry_k9 小时前
MyBatis批量insert-select踩坑:useGeneratedKeys=true 可能让PostgreSQL返回大量插入结果
java·后端
karry_k9 小时前
PostgreSQL 在 MyBatis 中执行正常 SQL 失效:一次 DELETE USING 踩坑记录
java·后端
SamDeepThinking13 小时前
从源码到代码:MyBatis-Flex 与 MyBatis-Plus 的逐项对比
java·后端·程序员
她的男孩15 小时前
Spring Boot 接 Flowable 工作流:用 3 个注解搭一个请假审批流程
java·后端·架构
你好潘先生17 小时前
别再记命令了,用 yeero do 说句人话就能跑脚本,而且不烧 token
服务器·python·命令行
Agent_大师17 小时前
WebSocket 行情重连成功,K线缺口不会自动消失
python
荣码17 小时前
LLM结构化输出:让AI返回JSON而不是废话,我踩了4个坑
java·python