本文目录导读:

我来为您提供一个完整的Java视频转码案例,使用FFmpeg命令行工具实现。
视频转码工具类
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 视频转码工具类
* 基于FFmpeg命令行实现
*/
public class VideoTranscoder {
// FFmpeg可执行文件路径
private String ffmpegPath;
public VideoTranscoder(String ffmpegPath) {
this.ffmpegPath = ffmpegPath;
}
/**
* 执行FFmpeg命令
* @param command 命令参数列表
* @return 执行结果
*/
public CommandResult executeCommand(List<String> command)
throws IOException, InterruptedException {
List<String> fullCommand = new ArrayList<>();
fullCommand.add(ffmpegPath);
fullCommand.addAll(command);
ProcessBuilder processBuilder = new ProcessBuilder(fullCommand);
processBuilder.redirectErrorStream(false);
Process process = processBuilder.start();
// 读取输出流
BufferedReader stdoutReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
BufferedReader stderrReader = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
// 读取标准输出
StringBuilder stdout = new StringBuilder();
String line;
while ((line = stdoutReader.readLine()) != null) {
stdout.append(line).append("\n");
}
// 读取错误输出(FFmpeg的进度信息在stderr中)
StringBuilder stderr = new StringBuilder();
while ((line = stderrReader.readLine()) != null) {
stderr.append(line).append("\n");
}
// 等待进程完成
boolean finished = process.waitFor(5, TimeUnit.MINUTES);
if (!finished) {
process.destroyForcibly();
throw new IOException("FFmpeg进程执行超时");
}
int exitCode = process.exitValue();
// 关闭资源
stdoutReader.close();
stderrReader.close();
process.getInputStream().close();
process.getErrorStream().close();
process.getOutputStream().close();
return new CommandResult(exitCode, stdout.toString(), stderr.toString());
}
/**
* 简单视频转码
* @param inputPath 输入文件路径
* @param outputPath 输出文件路径
* @param width 输出宽度(可为null保持原样)
* @param height 输出高度(可为null保持原样)
* @param bitRate 视频码率(如 "1M")
*/
public CommandResult transcodeVideo(String inputPath, String outputPath,
Integer width, Integer height,
String bitRate)
throws IOException, InterruptedException {
List<String> command = new ArrayList<>();
// 覆盖输出文件
command.add("-y");
// 输入文件
command.add("-i");
command.add(inputPath);
// 视频编码相关设置
if (width != null && height != null) {
command.add("-vf");
command.add(String.format("scale=%d:%d", width, height));
}
// 设置编码参数
command.add("-c:v"); // 视频编码器
command.add("libx264");
command.add("-preset");
command.add("medium"); // 编码速度和质量平衡
if (bitRate != null) {
command.add("-b:v");
command.add(bitRate);
}
// 音频编码
command.add("-c:a");
command.add("aac");
command.add("-b:a");
command.add("128k");
// 输出格式参数
command.add("-movflags");
command.add("+faststart"); // 优化Web播放
command.add("-pix_fmt");
command.add("yuv420p"); // 兼容格式
// 输出文件
command.add(outputPath);
return executeCommand(command);
}
/**
* 转码为HLS流
* @param inputPath 输入文件路径
* @param outputDir 输出目录
* @param segmentTime 分段时间(秒)
*/
public CommandResult transcodeToHLS(String inputPath, String outputDir,
int segmentTime)
throws IOException, InterruptedException {
List<String> command = new ArrayList<>();
command.add("-y");
command.add("-i");
command.add(inputPath);
// HLS流配置
command.add("-codec:v");
command.add("libx264");
command.add("-codec:a");
command.add("aac");
command.add("-hls_time");
command.add(String.valueOf(segmentTime));
command.add("-hls_list_size");
command.add("0"); // 0表示生成所有分段
command.add("-hls_segment_filename");
command.add(outputDir + "/segment_%03d.ts");
// 输出M3U8文件
command.add(outputDir + "/playlist.m3u8");
return executeCommand(command);
}
/**
* 提取视频中的音频
* @param inputPath 输入视频文件路径
* @param outputPath 输出音频文件路径
*/
public CommandResult extractAudio(String inputPath, String outputPath)
throws IOException, InterruptedException {
List<String> command = new ArrayList<>();
command.add("-y");
command.add("-i");
command.add(inputPath);
command.add("-vn"); // 移除视频流
command.add("-c:a");
command.add("libmp3lame"); // MP3编码器
command.add("-q:a");
command.add("2"); // 音质设置
command.add(outputPath);
return executeCommand(command);
}
/**
* 获取视频信息
* @param inputPath 视频文件路径
*/
public String getVideoInfo(String inputPath)
throws IOException, InterruptedException {
List<String> command = new ArrayList<>();
command.add("-i");
command.add(inputPath);
// 不输出文件,只获取信息
command.add("-f");
command.add("null");
command.add("-");
// FFmpeg信息在stderr中
return executeCommand(command).getStderr();
}
/**
* 命令执行结果内部类
*/
public static class CommandResult {
private final int exitCode;
private final String stdout;
private final String stderr;
public CommandResult(int exitCode, String stdout, String stderr) {
this.exitCode = exitCode;
this.stdout = stdout;
this.stderr = stderr;
}
public int getExitCode() {
return exitCode;
}
public String getStdout() {
return stdout;
}
public String getStderr() {
return stderr;
}
public boolean isSuccess() {
return exitCode == 0;
}
}
}
异步转码服务
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 异步视频转码服务
*/
public class AsyncVideoTranscoder {
private final VideoTranscoder transcoder;
private final ExecutorService executor;
public AsyncVideoTranscoder(String ffmpegPath) {
this.transcoder = new VideoTranscoder(ffmpegPath);
// 创建固定大小的线程池
this.executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
}
/**
* 异步转码
* @param inputPath 输入文件
* @param outputPath 输出文件
* @return 返回包含转码结果的Future
*/
public CompletableFuture<VideoTranscoder.CommandResult>
asyncTranscode(String inputPath, String outputPath) {
return CompletableFuture.supplyAsync(() -> {
try {
return transcoder.transcodeVideo(
inputPath, outputPath,
1280, 720, "2M"
);
} catch (Exception e) {
throw new RuntimeException("转码失败", e);
}
}, executor);
}
/**
* 关闭线程池
*/
public void shutdown() {
executor.shutdown();
}
}
使用示例
/**
* 视频转码使用示例
*/
public class VideoTranscoderExample {
public static void main(String[] args) {
String ffmpegPath = "ffmpeg"; // 确保ffmpeg在系统PATH中
VideoTranscoder transcoder = new VideoTranscoder(ffmpegPath);
try {
// 示例1:简单转码
System.out.println("开始转码...");
// 从MP4转码为WebM格式,调整分辨率为1280x720
VideoTranscoder.CommandResult result = transcoder.transcodeVideo(
"input.mp4",
"output_720p.mp4",
1280, 720,
"2M"
);
if (result.isSuccess()) {
System.out.println("转码成功!");
System.out.println("输出信息:\n" + result.getStdout());
} else {
System.err.println("转码失败,退出码: " + result.getExitCode());
System.err.println("错误信息:\n" + result.getStderr());
}
// 示例2:转码为HLS流
System.out.println("\n开始转码为HLS流...");
VideoTranscoder.CommandResult hlsResult = transcoder.transcodeToHLS(
"input.mp4", // 输入
"hls_output", // 输出目录
10 // 分段时间(秒)
);
if (hlsResult.isSuccess()) {
System.out.println("HLS转码成功!");
}
// 示例3:提取音频
System.out.println("\n开始提取音频...");
VideoTranscoder.CommandResult audioResult = transcoder.extractAudio(
"input.mp4",
"audio_only.mp3"
);
if (audioResult.isSuccess()) {
System.out.println("音频提取成功!");
}
// 示例4:获取视频信息
System.out.println("\n视频信息:");
String videoInfo = transcoder.getVideoInfo("input.mp4");
System.out.println(videoInfo);
} catch (Exception e) {
e.printStackTrace();
}
}
}
批量转码处理器
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* 批量视频转码处理器
*/
public class BatchVideoTranscoder {
private final AsyncVideoTranscoder asyncTranscoder;
private final List<TranscodeTask> tasks;
public BatchVideoTranscoder(String ffmpegPath) {
this.asyncTranscoder = new AsyncVideoTranscoder(ffmpegPath);
this.tasks = new ArrayList<>();
}
/**
* 添加转码任务
*/
public void addTask(String inputPath, String outputPath) {
tasks.add(new TranscodeTask(inputPath, outputPath));
}
/**
* 执行所有转码任务
*/
public void executeAll() throws ExecutionException, InterruptedException {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (TranscodeTask task : tasks) {
CompletableFuture<Void> future = asyncTranscoder
.asyncTranscode(task.inputPath, task.outputPath)
.thenAccept(result -> {
if (result.isSuccess()) {
System.out.println("转码成功: " + task.outputPath);
} else {
System.err.println("转码失败: " + task.outputPath);
}
});
futures.add(future);
}
// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join();
// 关闭线程池
asyncTranscoder.shutdown();
}
/**
* 转码任务内部类
*/
private static class TranscodeTask {
String inputPath;
String outputPath;
TranscodeTask(String inputPath, String outputPath) {
this.inputPath = inputPath;
this.outputPath = outputPath;
}
}
}
进度监控实现
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* FFmpeg转码进度监控
*/
public class FFmpegProgressMonitor {
private static final Pattern DURATION_PATTERN =
Pattern.compile("Duration: (\\d{2}):(\\d{2}):(\\d{2}\\.\\d{2})");
private static final Pattern TIME_PATTERN =
Pattern.compile("time=(\\d{2}):(\\d{2}):(\\d{2}\\.\\d{2})");
/**
* 监控转码进度
* @param process FFmpeg进程
*/
public void monitorProgress(Process process)
throws IOException, InterruptedException {
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
String line;
String duration = null;
System.out.println("开始监控转码进度...");
while ((line = reader.readLine()) != null) {
// 获取总时长
if (duration == null) {
Matcher durationMatcher = DURATION_PATTERN.matcher(line);
if (durationMatcher.find()) {
String hours = durationMatcher.group(1);
String minutes = durationMatcher.group(2);
String seconds = durationMatcher.group(3);
duration = hours + ":" + minutes + ":" + seconds;
System.out.println("视频总时长: " + duration);
}
}
// 获取当前进度
Matcher timeMatcher = TIME_PATTERN.matcher(line);
if (timeMatcher.find() && duration != null) {
String currentTime = timeMatcher.group(0).replace("time=", "");
System.out.println("当前进度: " + currentTime + " / 总时长: " + duration);
}
}
System.out.println("进度监控结束");
}
}
使用前准备
-
安装FFmpeg
- Windows: 下载FFmpeg并添加到系统PATH
- Linux:
sudo apt-get install ffmpeg - macOS:
brew install ffmpeg
-
添加FFmpeg路径配置 如果FFmpeg不在系统PATH中,需要在代码中指定完整路径:
VideoTranscoder transcoder = new VideoTranscoder("C:\\ffmpeg\\bin\\ffmpeg.exe");
注意事项
- 性能考虑:视频转码是CPU密集型操作,建议使用线程池控制并发
- 内存管理:大视频文件可能需要较大的内存,注意JVM参数设置
- 错误处理:确保处理FFmpeg返回的错误码和错误输出
- 文件清理:转码完成后及时清理临时文件
- 安全:如果处理用户上传的文件,注意路径验证和格式检查
这个案例提供了完整的视频转码解决方案,包括基础转码、HLS流转换、音频提取、异步处理和进度监控等功能。