今天发现一个ffmpeg的用法,用子进程直接从标准输入写入输入,就可以从标准流式输出获取转码结果。
这样的好处是不用去写ffmpeg的代码,只需要写对ffmpeg的命令、在输入输出的地方加缓存就能进行流式转码了,方便快捷。
但是也有坏处,在开始的时候会引入几百ms的延时,到某个时间集体输出,后面的时间就正常了。
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();
}
}
}