【音频处理】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();
        }
    }
}
相关推荐
SimonKing11 分钟前
SpringBoot 集成 SSE 实现服务端推送或可代替Websocket
java·后端·程序员
不正经的码狗13 分钟前
JAVA 、Eclipse 安装及配置 JAVA 项目(无需配置环境)
java·eclipse
xiaoqiMikko22 分钟前
你升的是 jackson-databind,中招的是 jackson-core:一个超长 JSON key 就能吃掉 200MB 堆
java·安全
不正经的码狗26 分钟前
Eclipse汉化(快速,推荐)
java·eclipse
极客互动API28 分钟前
极客互动-企业微信基于外部API接口实现AI客服自动接管外部联系人消息收发
java·微信·企业微信·ai编程·rpa
Sam_Deep_Thinking29 分钟前
关于java final关键字的可见性
java·后端·面试·程序员
tryxr31 分钟前
矩阵的几种基础变换
java·数据结构·算法·矩阵
写后端的胖头鱼32 分钟前
如何区分IO/CPU 密集型,线程池核心参数怎么设置
java·线程池·多线程·io密集·cpu密集
爱吃提升44 分钟前
文生视频核心
人工智能·音视频
JAVA面经实录9171 小时前
IDEA 开发知识
java·intellij-idea·idea