【音频处理】java流式调用ffmpeg命令

今天发现一个ffmpeg的用法,用子进程直接从标准输入写入输入,就可以从标准流式输出获取转码结果。

这样的好处是不用去写ffmpeg的代码,只需要写对ffmpeg的命令、在输入输出的地方加缓存就能进行流式转码了,方便快捷。

但是也有坏处,在开始的时候会引入几百ms的延时,到某个时间集体输出,后面的时间就正常了。

java 复制代码
package ffmpegPro;
import java.io.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main {
    public static void main(String[] argv) {
        ExecutorService executor = Executors.newFixedThreadPool(8);
        Future<?> f1 = executor.submit(()->{
            progress("D:\\data\\audio\\a_out.wav","D:\\data\\audio\\a_output.pcm", executor);
        });
        Future<?> f2 = executor.submit(()->{
            progress("D:\\data\\audio\\b_out.wav","D:\\data\\audio\\b_output.pcm", executor);
        });

        try {
            f1.get();
            f2.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        executor.shutdown();

    }

    public static void progress(String inputPath, String outputPath, ExecutorService executor) {
        try {
            // 1. 定义 FFmpeg 命令(示例:H264 → VP9,实时转码)
            String[] ffmpegCmd = {
                    "ffmpeg",
                    "-loglevel", "error",
                    "-hide_banner", "-nostats", //关闭日志
                    "-f", "wav",      // 输入格式
                    "-i", "pipe:0",    // 从标准输入读取
                    "-f", "s16le",      // 输出格式
                    "-acodec", "pcm_s16le",
                    "-ar", "8000",       // 16kHz
                    "-ac", "1",           // 单声道
                    "pipe:1"           // 输出到标准输出
            };

            // 2. 启动 FFmpeg 进程
            ProcessBuilder pb = new ProcessBuilder(ffmpegCmd);
            Process process = pb.start();

            // 3. 获取输入/输出流
            OutputStream ffmpegStdin = process.getOutputStream(); // FFmpeg 的 stdin
            InputStream ffmpegStdout = process.getInputStream(); // FFmpeg 的 stdout
            InputStream ffmpegStderr = process.getErrorStream();  // FFmpeg 的 stderr(日志)

            // 4. 异步读取转码后的数据(防止阻塞)


            // 线程1:读取 FFmpeg 的输出(转码后的数据)
            executor.submit(() -> {
                byte[] buffer = new byte[8192];
                int bytesRead;
                try {
                    FileOutputStream pcmFile = new FileOutputStream(outputPath);
                    while ((bytesRead = ffmpegStdout.read(buffer)) != -1) {
                        // 处理转码后的数据(示例:写入文件或推送到网络)
                        System.out.println("收到转码数据,长度: " + bytesRead);
                        pcmFile.write(buffer, 0, bytesRead);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            // 线程2:打印 FFmpeg 的错误日志(调试用)
            executor.submit(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(ffmpegStderr))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        System.err.println("[FFmpeg] " + line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            // 5. 模拟向 FFmpeg 发送原始数据(示例:从文件读取)
            try (InputStream rawVideoStream = new FileInputStream(inputPath)) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = rawVideoStream.read(buffer)) != -1) {
                    ffmpegStdin.write(buffer, 0, bytesRead);
                    System.out.println("已发送原始数据,长度: " + bytesRead);
                }
                ffmpegStdin.close(); // 关闭输入流,通知 FFmpeg 结束
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 6. 等待 FFmpeg 结束
            try {
                int exitCode = process.waitFor();
                System.out.println("FFmpeg 进程结束,退出码: " + exitCode);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
相关推荐
桦说编程3 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
lifallen3 小时前
Java Stream sort算子实现:SortedOps
java·开发语言
IT毕设实战小研3 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
没有bug.的程序员3 小时前
JVM 总览与运行原理:深入Java虚拟机的核心引擎
java·jvm·python·虚拟机
甄超锋4 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Zyy~4 小时前
《设计模式》装饰模式
java·设计模式
A尘埃4 小时前
企业级Java项目和大模型结合场景(智能客服系统:电商、金融、政务、企业)
java·金融·政务·智能客服系统
青云交5 小时前
Java 大视界 -- 基于 Java 的大数据可视化在城市交通拥堵治理与出行效率提升中的应用(398)
java·大数据·flink·大数据可视化·拥堵预测·城市交通治理·实时热力图
CHEN5_025 小时前
【Java基础面试题】Java基础概念
java·开发语言