【音频处理】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();
        }
    }
}
相关推荐
非凡ghost15 分钟前
FxSound:提升音频体验,让音乐更动听
前端·学习·音视频·生活·软件需求
Ai工具分享28 分钟前
视频画质差怎么办?AI优化视频清晰度技术原理与实战应用
人工智能·音视频
我没想到原来他们都是一堆坏人43 分钟前
(未完待续...)如何编写一个用于构建python web项目镜像的dockerfile文件
java·前端·python
沙二原住民1 小时前
提升数据库性能的秘密武器:深入解析慢查询、连接池与Druid监控
java·数据库·oracle
Jerry&Grj1 小时前
SpringBoot埋点功能技术实现方案深度解析:架构设计、性能优化与扩展性实践
java·微服务·性能优化·springboot·架构设计·埋点技术
没有bug.的程序员1 小时前
Redis Stream:轻量级消息队列深度解析
java·数据库·chrome·redis·消息队列
用户8160791833332 小时前
告别“魔法”:包你解决 Gradle 的下载慢问题
java
当归10242 小时前
SQL Server死锁排查实战指南
java·服务器·网络
echoyu.2 小时前
消息队列-初识kafka
java·分布式·后端·spring cloud·中间件·架构·kafka
little_xianzhong3 小时前
关于对逾期提醒的定时任务~改进完善
java·数据库·spring boot·spring·mybatis