本文目录导读:

我将为您提供一个完整的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支持多平台,但需要包含对应平台的库依赖
这个案例涵盖了视频处理的主要功能,你可以根据具体需求进行扩展和修改。
版权声明: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直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播