在实际项目中,依赖管理是首要考虑的问题。JavaCV提供了两种依赖引入方式:
xml<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.12</version>
<scope>provided</scope>
</dependency>
问题:会导致JAR包增大650MB+,包含所有支持的库,多数情况下不必要。
xml<!-- 视频压缩工具 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.12</version>
</dependency>
<!-- 支持linux系统环境ffmpeg -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>7.1.1-1.5.12</version>
<classifier>linux-x86_64</classifier>
</dependency>
<!-- 支持windows系统环境ffmpeg -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>7.1.1-1.5.12</version>
<classifier>windows-x86_64</classifier>
</dependency>
必须使用eclipse-temurin:17
基础镜像
dockerfileFROM eclipse-temurin:17
注意
OpenJDK会导致运行时错误:Could not initialize class org.bytedeco.ffmpeg.global.avutil
以下是完整的视频压缩工具类实现,包含详细注释:
javapackage com.truth.engine.common.util;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Slf4j
public class VideoCompressionUtil {
/**
* 压缩视频文件
*
* @param videoFile 上传的视频文件
* @param targetWidth 目标宽度(设置为0保持原始比例)
* @param targetBitrate 目标比特率(设置为0自动计算)
* @param quality 压缩质量(1-51, 越低质量越好)
* @return 压缩后的视频字节数组
* @throws IOException 文件处理异常
*/
public static byte[] compressVideo(MultipartFile videoFile,
int targetWidth,
int targetBitrate,
int quality) throws IOException {
// === 参数校验部分 ===
// 检查视频文件是否为空
if (videoFile == null || videoFile.isEmpty()) {
throw new IllegalArgumentException("视频文件不能为空");
}
// 检查质量参数是否在有效范围内(1-51)
if (quality < 1 || quality > 51) {
throw new IllegalArgumentException("质量参数必须在1-51之间");
}
// === 临时文件创建 ===
// 创建临时输入文件(用于存储上传的原始视频)
File tempInputFile = FileUtil.createTempFile("input_", ".mp4", true);
// 创建临时输出文件(用于存储压缩后的视频)
File tempOutFile = FileUtil.createTempFile("input_", ".mp4", true);
try {
// 将上传的视频文件转存到临时输入文件
videoFile.transferTo(tempInputFile);
// === 视频抓取器初始化 ===
// 创建FFmpeg帧抓取器,用于读取视频信息
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(tempInputFile);
grabber.start(); // 启动视频抓取器
// === 计算目标视频参数 ===
// 获取原始视频宽度
int originalWidth = grabber.getImageWidth();
// 获取原始视频高度
int originalHeight = grabber.getImageHeight();
// 计算输出宽度(如果targetWidth为0则保持原始宽度)
int outputWidth = targetWidth > 0 ? targetWidth : originalWidth;
// 计算输出高度(保持原始宽高比)
int outputHeight = targetWidth > 0 ?
(int) (originalHeight * ((double) targetWidth / originalWidth)) : originalHeight;
// === 计算比特率 ===
// 获取原始视频比特率
int originalBitrate = grabber.getVideoBitrate();
// 计算输出比特率(如果targetBitrate为0则使用原始比特率的70%)
int outputBitrate = targetBitrate > 0 ? targetBitrate : (int) (originalBitrate * 0.7);
// === 视频录制器初始化 ===
// 创建FFmpeg帧录制器,指定输出文件和分辨率
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(tempOutFile, outputWidth, outputHeight);
// === 设置视频编码参数 ===
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 使用H.264编码
recorder.setFormat("mp4"); // 输出格式为MP4
recorder.setVideoBitrate(outputBitrate); // 设置视频比特率
recorder.setVideoQuality(quality); // 设置视频质量(1-51)
// 设置FFmpeg高级选项:
recorder.setVideoOption("preset", "fast"); // 编码速度预设(fast平衡速度和质量)
recorder.setVideoOption("tune", "film"); // 优化场景(film针对电影/动画优化)
recorder.setVideoOption("crf", String.valueOf(quality)); // 恒定质量因子
recorder.setFrameRate(grabber.getFrameRate()); // 保持原始帧率
// === 音频处理配置 ===
// 如果原始视频包含音频轨道
if (grabber.getAudioChannels() > 0) {
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); // 使用AAC音频编码
recorder.setAudioChannels(grabber.getAudioChannels()); // 保持原始声道数
recorder.setAudioBitrate(grabber.getAudioBitrate()); // 保持原始音频比特率
recorder.setSampleRate(grabber.getSampleRate()); // 保持原始采样率
}
// === 开始视频处理 ===
recorder.start(); // 启动视频录制器
// === 逐帧处理视频 ===
Frame frame;
while (true) {
// 抓取下一 {
// 抓取下一帧(包含视频和音频帧)
frame = grabber.grabFrame();
// 当没有更多帧时退出循环
if (frame == null) {
break;
}
// 将当前帧写入输出视频
recorder.record(frame);
}
// === 资源释放 ===
// 停止并释放录制器资源(注意顺序:先停止录制器)
recorder.stop(); // 停止录制
grabber.stop(); // 停止抓取
recorder.release(); // 释放录制器资源
grabber.release(); // 释放抓取器资源
recorder.close(); // 关闭录制器
grabber.close(); // 关闭抓取器
// 读取压缩后的视频文件到字节数组
return FileUtil.readBytes(tempOutFile);
} finally {
// === 清理临时文件 ===
// 记录临时文件路径(用于调试)
log.info("tempInputFile:{}", tempInputFile.getAbsolutePath());
log.info("tempOutFile:{}", tempOutFile.getAbsolutePath());
// 删除临时输入文件
FileUtil.del(tempInputFile);
// 删除临时输出文件
FileUtil.del(tempOutFile);
}
}
/**
* 默认参数的视频压缩
*
* @param videoFile 上传的视频文件
* @return 压缩后的视频字节数组
* @throws Exception 处理异常
*/
public static byte[] compressVideo(MultipartFile videoFile) throws Exception {
// 使用默认参数调用压缩方法:
// 目标宽度640px, 比特率自动计算(原始比特率的70%), 质量参数28(一般质量)
return compressVideo(videoFile, 640, 0, 28);
}
}
参数 | 说明 | 推荐值 |
---|---|---|
targetWidth | 目标宽度(像素) | 640/1280/1920 |
targetBitrate | 目标比特率(bps) | 0(自动计算) |
quality | 压缩质量(1-51) | 23-28(平衡质量) |
ultrafast
:最快编码,最低压缩率fast
:平衡速度和质量(推荐)slow
:高质量,慢速编码film
:电影/动画stillimage
:静态图像fastdecode
:快速解码java// 使用线程池处理多个视频
ExecutorService executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(), // 核心线程数
Runtime.getRuntime().availableProcessors(), // 最大线程数
0L, // 空闲线程存活时间
TimeUnit.MILLISECONDS, // 时间单位
new LinkedBlockingQueue<Runnable>() // 任务队列
);
java// 限制帧处理速率
recorder.setFrameRate(25);
// 设置最大比特率
recorder.setVideoBitrate(1000000);
javarecorder.setVideoCodec(avcodec.AV_CODEC_ID_H264_QSV);
javatry {
grabber.start();
} catch (FFmpegFrameGrabber.Exception e) {
log.error("视频处理失败 - 格式不支持或已损坏", e);
throw new IllegalArgumentException("不支持的视频格式");
}
try {
recorder.start();
} catch (FFmpegFrameRecorder.Exception e) {
log.error("视频编码器初始化失败", e);
throw new IllegalStateException("视频编码失败");
}
本文完整呈现了基于JavaCV的视频压缩解决方案,重点包括:
该方案已在生产环境验证,可直接集成到项目中,根据实际需求调整参数即可实现高效的视频压缩功能。