Java H265视频抽帧提取照片
1. Windows下可调用ffmpeg.exe实现,亲测可行
- 可以直接执行命令行
# 把目录下视频——E:\\data\\test.h265抽帧每一帧为图片,并命名为1.jpg,2.jpg,3.jpg...存储在E:\\data\\test\\images\\文件夹
ffmpeg.exe -y -i E:\\data\\test.h265 -ss 00:00:00 -f image2 -vsync 2 E:\\data\\test\\images\\%d.jpg
前提需要目录E:\data\test\images\存在;
- 可以Java ProcessBuilder调用如上命令行实现;
2. linux下调用FFmpegFrameGrabber
- 调用跨平台的ffmpeg
- python代码打包成linux平台可调用的二进制文件
- 推荐优雅的方案:FFmpegFrameGrabber
3. 源码
包括
- ffmpeg.exe windows支持,linux需要自己配置ffmpeg
- FFmpegFrameGrabber,跨平台,支持windows,支持linux
俩个的实现源码如下:
package com.video.extract.test;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.javacv.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Arrays;
/*************************************
*Class Name: VideoUtils
*Description: <视频抽帧类>
*@author: Seminar
*@create: 2021/1/28
*@since 1.0.0
*************************************/
@Slf4j
public class VideoUtils {
/**
* 抽帧程序
* <p>
* ffmpeg.exe -y -i data\test.H265 -ss 00:00:00 -f image2 -vsync 2 data\test\image\%d.jpg
*
* @param videoPath 原始视频文件路径
* @param imgPath 抽帧图片存储路径
* @throws IOException
*/
public static void ffmpegFrameExtract(String videoPath, String imgPath) throws IOException {
if (StringUtils.isEmpty(videoPath)) {
return;
}
File imgPathFolder = new File(imgPath);
if (!imgPathFolder.exists()) {
imgPathFolder.mkdirs();
} else {
delDir(imgPathFolder);
imgPathFolder.mkdirs();
}
String path = System.getProperty("user.dir") + File.separator + "frameExtract" + File.separator + "ffmpeg.exe";
ProcessBuilder processBuilder = new ProcessBuilder(path, "-y",
"-i", videoPath,
"-ss", "00:00:00",
"-f", "image2",
"-vsync", "2",
imgPath + "%d.jpg");
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
StringBuilder processOutput = new StringBuilder();
try (BufferedReader processOutputReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));) {
String readLine;
while ((readLine = processOutputReader.readLine()) != null) {
processOutput.append(readLine + System.lineSeparator());
}
process.waitFor();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (process != null) {
process.destroy();
}
}
// 只返回抽帧的最后3行总览信息
String[] str = processOutput.toString().trim().split("\n");
String[] res = new String[3];
System.arraycopy(str, str.length - 3, res, 0, 3);
log.info("ffmpegFrameExtract res: {}", String.join("\n", Arrays.asList(res)));
}
/**
* 视频抽帧
*
* @param videoPath 原始视频文件路径
* @param imagePath 抽帧图片存储路径
* @throws FrameGrabber.Exception
* @throws FileNotFoundException
*/
public static void ffmpegFrameGrabber(String videoPath, String imagePath) throws FrameGrabber.Exception, FileNotFoundException {
// FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoPath);// 此处可为视频路径,也可为二进制视频流
FFmpegFrameGrabber ff = new FFmpegFrameGrabber(new FileInputStream(videoPath));// 此处可为视频路径,也可为二进制视频流
// ff.setAudioCodecName("h265");
ff.start();
int ffLength = ff.getLengthInFrames();
double ffFrameRate = ff.getFrameRate();
long videoLength = ff.getLengthInTime();
log.info("\n");
log.info("ffmpegFrameGrabber: 帧数:{}, 帧率:{},视频长度:", ffLength, ffFrameRate, videoLength);
Frame f = ff.grabImage();
int k = 0;
while (f != null) {
Java2DFrameConverter converter = new Java2DFrameConverter();
String imageMat = "jpg";
String fileName = imagePath + k + "." + imageMat;
BufferedImage bi = converter.getBufferedImage(f);
File output = new File(fileName);
try {
ImageIO.write(bi, imageMat, output);
} catch (IOException e) {
log.error("writeJpgError: {}", e.getMessage());
e.printStackTrace();
}
k++;
f = ff.grabImage();
}
ff.stop();
log.info("jpgSize: {}", k);
}
/**
* 递归删除多层目录
*
* @param file
*/
public static void delDir(File file) {
// 文件直接删除
if (file.isFile()) {
file.delete();
} else {
// 目录,需要递归删除
for (File sonfile : file.listFiles()) {
delDir(sonfile);
}
}
file.delete();
}
public static void main(String[] args) throws IOException {
String videoPath = "D:\\project\\frame-extract\\doc\\test.h265";
String imagePath = videoPath.substring(0, videoPath.lastIndexOf(".")).concat(File.separator).concat("images").concat(File.separator);
// ffmpeg.exe抽帧
ffmpegFrameExtract(videoPath, imagePath);
// FFmpegFrameGrabber 抽帧
ffmpegFrameGrabber(videoPath, imagePath);
}
}
4. 效果图
5. pom依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bytedeco/javacv -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.1-1.4.4</version>
</dependency>