【音频处理】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();
        }
    }
}
相关推荐
AntsData24 分钟前
用 12K+ 条亚马逊评论信号,定位机械键盘下一版该改什么
java·大数据·计算机外设
code2cat32 分钟前
【随笔】连续批处理:长短请求怎样在迭代之间补位
java·人工智能·后端·大语言模型·vllm
最强小杰34 分钟前
DeepSeek V4 Flash + Codex CLI 接入教程:原生 Responses API 适配,从配置到跑通收藏这篇就够了(2026)
java·前端·python·ai
Flynn Liang1 小时前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
灿飞1 小时前
Kotlin与Java虚拟机六:基础与类型系统:一切皆对象,但对象有代价
java·kotlin
计算机毕设定制辅导-无忧学长1 小时前
《基于微信小程序健康习惯激励系统设计与实现》
java·spring boot·mysql·微信小程序·uniapp·健康习惯激励系统
听我哔哔2 小时前
口播视频唇形总对不上,Seedance 2.5在zeekeAI里同步
人工智能·音视频
雪芽蓝域zzs2 小时前
第 1 章:IDEA 创建 SpringBoot3.5.16 Maven 项目
java·maven·intellij-idea
伯奥克客服2 小时前
2026年视频矩阵厂家技术评估:从EDID到控制协议开放度的六维筛选法
线性代数·矩阵·音视频
golang学习记2 小时前
IDEA 开始换芯,发布全新AI版终端
java·人工智能·intellij-idea