本文目录导读:

我将为您提供一个完整的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;
}
}
注意事项
- 依赖大小:JavaCV的FFmpeg库较大,首次运行需要下载平台相关库
- 内存管理:处理大型视频时注意内存使用,及时释放Frame对象
- 性能优化:使用并行处理提高大批量视频处理效率
- 错误处理:生产环境需要完善的异常处理和日志记录
- 跨平台:JavaCV支持多平台,但需要包含对应平台的库依赖
这个案例涵盖了视频处理的主要功能,你可以根据具体需求进行扩展和修改。