本文目录导读:

我来为您提供一个完整的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流转换、音频提取、异步处理和进度监控等功能。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播