用到的maven依靠:
lombok依靠就不贴出来了
- <dependency>
- <groupId>org.bytedeco</groupId>
- <artifactId>ffmpeg-platform</artifactId>
- <version>4.3.2-1.5.5</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.12.0</version>
- </dependency>
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- <version>5.5.2</version>
- </dependency>
复制代码
工具类:
- import cn.hutool.core.io.IoUtil;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.bytedeco.ffmpeg.avcodec.AVCodec;
- import org.bytedeco.ffmpeg.avcodec.AVCodecContext;
- import org.bytedeco.ffmpeg.avformat.AVFormatContext;
- import org.bytedeco.ffmpeg.global.avcodec;
- import org.bytedeco.ffmpeg.global.avformat;
- import org.bytedeco.ffmpeg.global.avutil;
- import org.bytedeco.javacpp.Loader;
- import org.bytedeco.javacpp.PointerPointer;
- import java.io.*;
- import java.util.concurrent.TimeUnit;
- @Slf4j
- public class VideoUtils {
- static class LazyFfmpeg {
- private static final String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
- }
- public static String ffmpeg() {
- return LazyFfmpeg.ffmpeg;
- }
- /**
- * 压缩视频
- * @param inputFilePath 压缩前视频地址
- * @param outputFilePath 压缩后视频地址
- */
- public static void compressVideo(String inputFilePath, String outputFilePath) {
- if (StringUtils.isAnyBlank(inputFilePath, outputFilePath)) {
- throw new RuntimeException("输入视频路径或输出视频文件路径不能为空");
- }
- if (StringUtils.equals(inputFilePath, outputFilePath)) {
- throw new RuntimeException("outputFilePath不能和inputFilePath相同");
- }
- validIsFile(new File(inputFilePath));
- ProcessBuilder processBuilder = new ProcessBuilder(
- ffmpeg(),
- "-y", // 自动覆盖输出文件
- "-i", inputFilePath, // 输入文件路径
- "-crf","30",
- "-c:v","h264",
- "-preset", "slow", // 使用较慢的预设来提高压缩效率
- // "-b:v", "1000", // 设置视频比特率为 1000 kbps
- // "-vf", String.format("scale=%s:%s", 1920, 1080),
- // "-c:a", "copy", // 保持音频编码不变
- "-c:a", "aac", // 使用 AAC 音频编码
- "-b:a", "2k", // 设置音频比特率为 128 kbps
- outputFilePath // 输出文件路径
- );
- StringBuilder stringBuilder = new StringBuilder();
- int exitCode;
- try {
- Process process = processBuilder.start();
- // 捕获错误输出
- processErrorMsg(process, stringBuilder);
- // 等待 FFmpeg 进程完成
- exitCode = process.waitFor();
- } catch (Throwable e) {
- throw new RuntimeException(e);
- }
- if (exitCode != 0) {
- throw new RuntimeException(stringBuilder.toString());
- }
- }
- /**
- * 提取图片
- *
- * @param videoPath 视频路径
- * @param second 提取指定时间图片
- * @param timeout 等待的最长时间
- * @param unit 参数的时间 timeout 单位
- * @return 图片
- */
- public static byte[] ffmpegExtractImage(String videoPath, Number second, long timeout, TimeUnit unit) {
- if (timeout <= 0) {
- throw new IllegalArgumentException("timeout不能小于等于0");
- }
- if (second == null) {
- second = 0;
- }
- if (unit == null) {
- unit = TimeUnit.MINUTES;
- }
- File videoFile = new File(videoPath);
- validIsFile(videoFile);
- ProcessBuilder extractBuilder = new ProcessBuilder(
- ffmpeg(),
- "-ss", second.toString(),
- "-i", videoPath,
- "-f", "image2pipe",
- "-vframes", "1",
- // "-vcodec", "png",//如果觉得照片不清晰,就启用此选项,但是照片会变大
- "-"
- );
- try {
- Process process = extractBuilder.start();
- try (InputStream inputStream = process.getInputStream()) {
- byte[] bytes = IoUtil.readBytes(inputStream);
- boolean result = process.waitFor(timeout, unit);
- if (!result) {
- throw new RuntimeException("子进程退出之前已超过等待时间");
- }
- return bytes;
- }
- } catch (IOException | InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- /**
- * 获取视频分辨率
- *
- * @param videoFilePath 视频路径
- */
- public static int[] getVideoResolution(String videoFilePath) {
- validIsFile(new File(videoFilePath));
- AVFormatContext formatContext = avformat.avformat_alloc_context();
- AVCodecContext codecContext = avcodec.avcodec_alloc_context3(null);
- // 打开视频文件
- if (avformat.avformat_open_input(formatContext, videoFilePath, null, null) != 0) {
- throw new RuntimeException("无法打开视频文件");
- }
- // 获取视频流信息
- if (avformat.avformat_find_stream_info(formatContext, (PointerPointer) null) < 0) {
- throw new RuntimeException("无法获取视频流信息");
- }
- // 查找视频流
- int videoStreamIndex = -1;
- for (int i = 0; i < formatContext.nb_streams(); i++) {
- if (formatContext.streams(i).codecpar().codec_type() == avutil.AVMEDIA_TYPE_VIDEO) {
- videoStreamIndex = i;
- break;
- }
- }
- if (videoStreamIndex == -1) {
- throw new RuntimeException("视频流未找到");
- }
- // 获取视频解码器上下文
- avcodec.avcodec_parameters_to_context(codecContext, formatContext.streams(videoStreamIndex).codecpar());
- // 查找解码器
- AVCodec codec = avcodec.avcodec_find_decoder(codecContext.codec_id());
- if (codec == null) {
- throw new RuntimeException("无法找到解码器");
- }
- // 打开解码器
- if (avcodec.avcodec_open2(codecContext, codec, (PointerPointer) null) < 0) {
- throw new RuntimeException("无法打开解码器");
- }
- // 获取视频分辨率
- int width = codecContext.width();
- int height = codecContext.height();
- // 清理资源
- codecContext.close();
- return new int[]{width, height};
- }
- private static void processErrorMsg(Process process, StringBuilder stringBuilder) {
- new Thread(() -> {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
- String line;
- while ((line = reader.readLine()) != null) {
- stringBuilder.append(line);
- }
- } catch (IOException e) {
- log.error("打印命令行错误日志出现异常 errMsg:{}", e.getMessage(), e);
- }
- }).start();
- }
- public static void validIsFile(File file) {
- validExists(file);
- if (!file.isFile()) {
- throw new IllegalArgumentException("不是文件");
- }
- }
- public static void validExists(File file) {
- if (!file.exists()) {
- throw new IllegalArgumentException("videoPath不存在");
- }
- }
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |