Java实现视频转码案例

wen java案例 2

本文目录导读:

Java实现视频转码案例

  1. 视频转码工具类
  2. 异步转码服务
  3. 使用示例
  4. 批量转码处理器
  5. 进度监控实现
  6. 使用前准备
  7. 注意事项

我来为您提供一个完整的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("进度监控结束");
    }
}

使用前准备

  1. 安装FFmpeg

    • Windows: 下载FFmpeg并添加到系统PATH
    • Linux: sudo apt-get install ffmpeg
    • macOS: brew install ffmpeg
  2. 添加FFmpeg路径配置 如果FFmpeg不在系统PATH中,需要在代码中指定完整路径:

    VideoTranscoder transcoder = new VideoTranscoder("C:\\ffmpeg\\bin\\ffmpeg.exe");

注意事项

  1. 性能考虑:视频转码是CPU密集型操作,建议使用线程池控制并发
  2. 内存管理:大视频文件可能需要较大的内存,注意JVM参数设置
  3. 错误处理:确保处理FFmpeg返回的错误码和错误输出
  4. 文件清理:转码完成后及时清理临时文件
  5. 安全:如果处理用户上传的文件,注意路径验证和格式检查

这个案例提供了完整的视频转码解决方案,包括基础转码、HLS流转换、音频提取、异步处理和进度监控等功能。

抱歉,评论功能暂时关闭!