Java视频处理案例

wen java案例 2

本文目录导读:

Java视频处理案例

  1. 使用JavaCV进行视频处理
  2. 注意事项

我将为您提供一个完整的Java视频处理案例,包含视频转码、截帧、拼接等常见功能。

使用JavaCV进行视频处理

项目依赖配置

<dependencies>
    <!-- JavaCV核心库 -->
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv</artifactId>
        <version>1.5.9</version>
    </dependency>
    <!-- FFmpeg平台库 -->
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv-platform</artifactId>
        <version>1.5.9</version>
    </dependency>
    <!-- OpenCV平台库(用于图像处理) -->
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>opencv-platform</artifactId>
        <version>4.7.0-1.5.9</version>
    </dependency>
    <!-- 日志 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
    </dependency>
</dependencies>

视频处理工具类

package com.example.video;
import org.bytedeco.javacv.*;
import org.bytedeco.javacv.Frame;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.global.opencv_imgcodecs;
import org.bytedeco.opencv.global.opencv_imgproc;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class VideoProcessor {
    private static final int VIDEO_WIDTH = 1920;
    private static final int VIDEO_HEIGHT = 1080;
    private static final int FRAME_RATE = 30;
    /**
     * 视频格式转换
     * @param inputPath 输入文件路径
     * @param outputPath 输出文件路径
     * @param format 目标格式(mp4/avi/mov等)
     */
    public static void convertVideo(String inputPath, String outputPath, String format) {
        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputPath);
             FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, 0, 0, 0)) {
            // 启动视频抓取器
            grabber.start();
            // 设置录制参数
            recorder.setFormat(format);
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setVideoQuality(0); // 0-无损, 1-最高质量
            recorder.setFrameRate(grabber.getFrameRate());
            // 如果输入有音频
            if (grabber.getAudioChannels() > 0) {
                recorder.setAudioChannels(grabber.getAudioChannels());
                recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
                recorder.setAudioQuality(0);
            }
            recorder.start();
            Frame frame;
            while ((frame = grabber.grab()) != null) {
                if (frame.image != null) {
                    recorder.record(frame);
                } else if (frame.samples != null) {
                    recorder.record(frame);
                }
            }
            System.out.println("视频转换完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 提取视频帧(截图)
     * @param videoPath 视频文件路径
     * @param outputDir 输出目录
     * @param interval 截图间隔(秒)
     */
    public static List<String> extractFrames(String videoPath, String outputDir, int interval) {
        List<String> framePaths = new ArrayList<>();
        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoPath)) {
            grabber.start();
            File dir = new File(outputDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            double frameRate = grabber.getFrameRate();
            int frameInterval = (int) (frameRate * interval);
            int frameCount = 0;
            int totalFrames = (int) grabber.getLengthInFrames();
            System.out.println("总帧数: " + totalFrames);
            Frame frame;
            int counter = 0;
            while ((frame = grabber.grab()) != null) {
                frameCount++;
                if (frameCount % frameInterval == 0) {
                    counter++;
                    String outputPath = String.format("%s/frame_%04d.jpg", outputDir, counter);
                    // 转换为BufferedImage并保存
                    Java2DFrameConverter converter = new Java2DFrameConverter();
                    BufferedImage image = converter.convert(frame);
                    if (image != null) {
                        File outputFile = new File(outputPath);
                        ImageIO.write(image, "jpg", outputFile);
                        framePaths.add(outputPath);
                        System.out.println("已提取帧: " + outputPath);
                    }
                }
            }
            grabber.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return framePaths;
    }
    /**
     * 截取视频片段
     * @param inputPath 输入路径
     * @param outputPath 输出路径
     * @param startTime 开始时间(秒)
     * @param duration 持续时间(秒)
     */
    public static void cutVideo(String inputPath, String outputPath, double startTime, double duration) {
        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputPath);
             FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, 0, 0, 0)) {
            grabber.start();
            recorder.setFormat("mp4");
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            // 设置时间戳偏移
            grabber.setTimestamp((long) (startTime * 1000000)); // 微秒
            recorder.start();
            long endTimestamp = (long) ((startTime + duration) * 1000000);
            Frame frame;
            while ((frame = grabber.grab()) != null && grabber.getTimestamp() <= endTimestamp) {
                if (frame.image != null || frame.samples != null) {
                    recorder.record(frame);
                }
            }
            System.out.println("视频截取完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 视频拼接
     * @param inputPaths 输入视频路径数组
     * @param outputPath 输出路径
     */
    public static void mergeVideos(String[] inputPaths, String outputPath) {
        try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, VIDEO_WIDTH, VIDEO_HEIGHT)) {
            recorder.setFormat("mp4");
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setFrameRate(FRAME_RATE);
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            recorder.setAudioChannels(2);
            recorder.start();
            for (String inputPath : inputPaths) {
                try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputPath)) {
                    grabber.start();
                    System.out.println("正在拼接: " + inputPath);
                    Frame frame;
                    while ((frame = grabber.grab()) != null) {
                        if (frame.image != null || frame.samples != null) {
                            recorder.record(frame);
                        }
                    }
                    grabber.stop();
                } catch (Exception e) {
                    System.err.println("处理文件失败: " + inputPath);
                    e.printStackTrace();
                }
            }
            recorder.stop();
            System.out.println("视频拼接完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 添加水印
     * @param videoPath 视频文件路径
     * @param watermarkPath 水印图片路径
     * @param outputPath 输出路径
     * @param x 水印X坐标
     * @param y 水印Y坐标
     */
    public static void addWatermark(String videoPath, String watermarkPath, String outputPath, int x, int y) {
        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoPath);
             FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, 0, 0, 0)) {
            // 读取水印图片
            Mat watermark = opencv_imgcodecs.imread(watermarkPath);
            if (watermark.empty()) {
                throw new RuntimeException("无法加载水印图片");
            }
            grabber.start();
            recorder.setFormat("mp4");
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            recorder.start();
            Frame frame;
            while ((frame = grabber.grab()) != null) {
                if (frame.image != null) {
                    // 转换Frame为Mat进行处理
                    Frame toMat = frame.clone();
                    Mat videoMat = new Mat(frame.imageHeight, frame.imageWidth, 
                                          org.bytedeco.opencv.opencv_core.CV_8UC3,
                                          frame.image[0]);
                    // 添加水印
                    addOverlay(videoMat, watermark, x, y);
                    // 转换回Frame
                    Frame processedFrame = new Frame();
                    processedFrame.image = new org.bytedeco.javacpp.Pointer[]{videoMat.data()};
                    processedFrame.imageWidth = videoMat.cols();
                    processedFrame.imageHeight = videoMat.rows();
                    processedFrame.imageStride = new int[]{videoMat.step()};
                    processedFrame.imageDepth = org.bytedeco.javacv.Frame.DEPTH_UBYTE;
                    processedFrame.imageChannels = 3;
                    recorder.record(processedFrame);
                } else if (frame.samples != null) {
                    recorder.record(frame);
                }
            }
            System.out.println("水印添加完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 在视频帧上叠加水印
     */
    private static void addOverlay(Mat video, Mat watermark, int x, int y) {
        // 调整水印大小
        Mat resizedWatermark = new Mat();
        int width = watermark.cols() / 4;
        int height = watermark.rows() / 4;
        if (width > 0 && height > 0) {
            opencv_imgproc.resize(watermark, resizedWatermark, new Size(width, height));
        } else {
            resizedWatermark = watermark;
        }
        // 确保坐标在视频范围内
        x = Math.min(x, video.cols() - resizedWatermark.cols());
        y = Math.min(y, video.rows() - resizedWatermark.rows());
        // 定义感兴趣区域
        Rect roi = new Rect(x, y, resizedWatermark.cols(), resizedWatermark.rows());
        Mat videoROI = new Mat(video, roi);
        resizedWatermark.copyTo(videoROI);
    }
    /**
     * 获取视频信息
     * @param videoPath 视频路径
     */
    public static void getVideoInfo(String videoPath) {
        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoPath)) {
            grabber.start();
            System.out.println("=== 视频信息 ===");
            System.out.println("视频路径: " + videoPath);
            System.out.println("视频宽度: " + grabber.getImageWidth() + " 像素");
            System.out.println("视频高度: " + grabber.getImageHeight() + " 像素");
            System.out.println("帧率: " + grabber.getFrameRate() + " fps");
            System.out.println("总帧数: " + grabber.getLengthInFrames());
            System.out.println("视频时长: " + (grabber.getLengthInTime() / 1000000.0) + " 秒");
            System.out.println("音频采样率: " + grabber.getSampleRate() + " Hz");
            System.out.println("音频通道数: " + grabber.getAudioChannels());
            System.out.println("视频编码: " + grabber.getVideoCodec());
            System.out.println("音频编码: " + grabber.getAudioCodec());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

视频处理示例类

package com.example.video;
import java.util.List;
public class VideoProcessingExample {
    public static void main(String[] args) {
        // 测试视频路径
        String inputVideo = "input/sample.mp4";
        String outputVideo = "output/processed.mp4";
        String outputDir = "output/frames";
        // 1. 获取视频信息
        System.out.println("=== 获取视频信息 ===");
        VideoProcessor.getVideoInfo(inputVideo);
        // 2. 视频格式转换
        System.out.println("\n=== 视频格式转换 ===");
        VideoProcessor.convertVideo(inputVideo, "output/converted.avi", "avi");
        // 3. 提取视频帧(每2秒提取一帧)
        System.out.println("\n=== 提取视频帧 ===");
        List<String> framePaths = VideoProcessor.extractFrames(inputVideo, outputDir, 2);
        System.out.println("提取了 " + framePaths.size() + " 帧图片");
        // 4. 截取视频片段(从第10秒开始,截取5秒)
        System.out.println("\n=== 截取视频片段 ===");
        VideoProcessor.cutVideo(inputVideo, "output/cut.mp4", 10, 5);
        // 5. 视频拼接
        System.out.println("\n=== 视频拼接 ===");
        String[] videosToMerge = {
            "output/cut.mp4",
            "output/cut.mp4"
        };
        VideoProcessor.mergeVideos(videosToMerge, "output/merged.mp4");
        // 6. 添加水印
        System.out.println("\n=== 添加水印 ===");
        VideoProcessor.addWatermark(inputVideo, "input/watermark.png", 
                                 "output/watermarked.mp4", 50, 50);
        System.out.println("\n所有视频处理完成!");
    }
}

批量处理视频类

package com.example.video;
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.ArrayList;
import java.util.List;
public class BatchVideoProcessor {
    private static final int MAX_THREADS = 4;
    /**
     * 并行批量处理视频
     */
    public static void batchProcess(String inputDir, String outputDir) {
        File inputDirectory = new File(inputDir);
        File[] videoFiles = inputDirectory.listFiles((dir, name) -> 
            name.endsWith(".mp4") || name.endsWith(".avi") || name.endsWith(".mov"));
        if (videoFiles == null || videoFiles.length == 0) {
            System.out.println("没有找到视频文件");
            return;
        }
        // 创建输出目录
        File outDir = new File(outputDir);
        if (!outDir.exists()) {
            outDir.mkdirs();
        }
        ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS);
        List<Future<?>> futures = new ArrayList<>();
        for (File videoFile : videoFiles) {
            String outputPath = outputDir + "/" + videoFile.getName().replace(".", "_processed.");
            Future<?> future = executor.submit(() -> {
                System.out.println("处理文件: " + videoFile.getName());
                VideoProcessor.convertVideo(videoFile.getAbsolutePath(), outputPath, "mp4");
            });
            futures.add(future);
        }
        // 等待所有任务完成
        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (Exception e) {
                System.err.println("处理视频时发生错误: " + e.getMessage());
            }
        }
        executor.shutdown();
        System.out.println("批量处理完成!");
    }
}

配置文件示例

# application.yml
video:
  processor:
    input-dir: ./input
    output-dir: ./output
    frame-directory: ./output/frames
    watermark-image: ./input/watermark.png
    frame-interval-seconds: 2
    cut-start-time: 10
    cut-duration: 5
    thread-pool-size: 4

详细使用示例

package com.example.video;
import java.io.File;
public class CompleteExample {
    public static void main(String[] args) {
        // 完整视频处理流程示例
        // 1. 准备输入输出目录
        File inputFile = new File("input/sample_video.mp4");
        File outputDir = new File("output/complete_example");
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }
        // 2. 检查输入文件
        if (!inputFile.exists()) {
            System.err.println("输入文件不存在!");
            return;
        }
        try {
            // 步骤1: 获取视频基本信息
            System.out.println("步骤1: 获取视频信息");
            VideoProcessor.getVideoInfo(inputFile.getAbsolutePath());
            // 步骤2: 提取关键帧生成缩略图
            System.out.println("\n步骤2: 生成缩略图");
            List<String> thumbnails = VideoProcessor.extractFrames(
                inputFile.getAbsolutePath(),
                "output/complete_example/thumbnails",
                5 // 每5秒提取一帧
            );
            // 步骤3: 压缩视频
            System.out.println("\n步骤3: 压缩视频");
            VideoProcessor.convertVideo(
                inputFile.getAbsolutePath(),
                "output/complete_example/compressed.mp4",
                "mp4"
            );
            // 步骤4: 添加水印
            System.out.println("\n步骤4: 添加水印");
            if (new File("input/watermark.png").exists()) {
                VideoProcessor.addWatermark(
                    inputFile.getAbsolutePath(),
                    "input/watermark.png",
                    "output/complete_example/watermarked.mp4",
                    100, 100
                );
            } else {
                System.out.println("水印文件不存在,跳过此步骤");
            }
            // 步骤5: 创建视频剪辑
            System.out.println("\n步骤5: 创建剪辑");
            VideoProcessor.cutVideo(
                inputFile.getAbsolutePath(),
                "output/complete_example/highlight.mp4",
                30, // 从30秒开始
                10  // 持续10秒
            );
            System.out.println("\n=== 完整视频处理流程完成 ===");
            System.out.println("输出目录: " + outputDir.getAbsolutePath());
        } catch (Exception e) {
            System.err.println("视频处理失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

性能优化建议

package com.example.video;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
public class VideoProcessingOptimizer {
    /**
     * 高性能视频处理配置
     */
    public static FFmpegFrameRecorder getOptimizedRecorder(String outputPath) {
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, 1920, 1080);
        // 编码器设置
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        recorder.setVideoBitrate(8000000); // 8 Mbps
        // 编码质量设置
        recorder.setVideoQuality(0); // 无损(0-51,0最好)
        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        // 帧率控制
        recorder.setFrameRate(30);
        // 预设(优化编码速度)
        recorder.setVideoOption("preset", "medium");
        // 音频设置
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
        recorder.setAudioBitrate(192000);
        recorder.setSampleRate(44100);
        recorder.setAudioChannels(2);
        // 多线程优化
        recorder.setVideoOption("threads", "4");
        return recorder;
    }
}

注意事项

  1. 依赖大小:JavaCV的FFmpeg库较大,首次运行需要下载平台相关库
  2. 内存管理:处理大型视频时注意内存使用,及时释放Frame对象
  3. 性能优化:使用并行处理提高大批量视频处理效率
  4. 错误处理:生产环境需要完善的异常处理和日志记录
  5. 跨平台:JavaCV支持多平台,但需要包含对应平台的库依赖

这个案例涵盖了视频处理的主要功能,你可以根据具体需求进行扩展和修改。

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