*要实现使用 Java 调用 FFmpeg 截取视频每一帧并保存图片,我们可以通过 Java 的 ProcessBuilder 来执行 FFmpeg 命令。下面是一个实现该功能的 Java 程序:
点击查看代码
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
public class VideoFrameExtractor {
// FFmpeg可执行文件路径
private String ffmpegPath;
public VideoFrameExtractor(String ffmpegPath) {
this.ffmpegPath = ffmpegPath;
// 验证FFmpeg路径是否有效
if (!new File(ffmpegPath).exists()) {
throw new IllegalArgumentException("FFmpeg executable not found at: " + ffmpegPath);
}
}
/**
* 从视频中提取每一帧并保存为图片
* @param videoPath 视频文件路径
* @param outputDir 输出图片目录
* @param format 输出图片格式 (例如: png, jpg)
* @throws IOException 如果执行过程中发生错误
*/
public void extractFrames(String videoPath, String outputDir, String format) throws IOException {
// 检查视频文件是否存在
File videoFile = new File(videoPath);
if (!videoFile.exists()) {
throw new IllegalArgumentException("Video file not found: " + videoPath);
}
// 创建输出目录
File outputDirectory = new File(outputDir);
if (!outputDirectory.exists()) {
outputDirectory.mkdirs();
}
// 构建FFmpeg命令
// -i: 输入文件
// -vf fps=1: 每秒提取1帧(修改此值可调整提取频率)
// %04d: 4位数字序号,从0001开始
String outputPattern = outputDir + File.separator + "frame_%04d." + format;
ProcessBuilder processBuilder = new ProcessBuilder(
ffmpegPath,
"-i", videoPath,
"-vf", "fps=1", // 每秒提取1帧,要提取每一帧可改为"fps=60"(根据视频帧率调整)
"-q:v", "2", // 图片质量,1-31,越小质量越高
outputPattern
);
// 重定向错误流到输入流,便于捕获错误信息
processBuilder.redirectErrorStream(true);
// 启动进程
Process process = processBuilder.start();
// 读取进程输出
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line); // 打印FFmpeg输出信息
}
}
// 等待进程完成
try {
int exitCode = process.waitFor();
if (exitCode == 0) {
System.out.println("Frames extracted successfully to: " + outputDir);
} else {
throw new IOException("FFmpeg process exited with error code: " + exitCode);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Process interrupted", e);
}
}
public static void main(String[] args) {
// 示例用法
try {
// 设置FFmpeg路径(根据实际情况修改)
String ffmpegPath = "C:/ffmpeg/bin/ffmpeg.exe"; // Windows示例
// String ffmpegPath = "/usr/local/bin/ffmpeg"; // Linux/Mac示例
VideoFrameExtractor extractor = new VideoFrameExtractor(ffmpegPath);
// 视频文件路径
String videoPath = "input_video.mp4";
// 输出目录
String outputDir = "extracted_frames";
// 提取帧并保存为PNG格式
extractor.extractFrames(videoPath, outputDir, "png");
} catch (Exception e) {
e.printStackTrace();
}
}
}
说明:
1.首先需要安装 FFmpeg 并获取其可执行文件路径,程序需要通过此路径调用 FFmpeg
2.代码中的关键参数说明:
- fps=1:表示每秒提取 1 帧,如果需要提取每一帧,可以根据视频实际帧率调整(如 60fps 视频使用fps=60)
- -q:v 2:控制输出图片质量,值越小质量越高(范围 1-31)
- 输出文件名格式frame_%04d.png会生成 frame_0001.png, frame_0002.png 等文件
3.使用时需要根据操作系统修改ffmpegPath变量: - Windows 系统通常为C:/ffmpeg/bin/ffmpeg.exe
- Linux/Mac 系统通常为/usr/local/bin/ffmpeg
4.运行程序前需要确保: - FFmpeg 已正确安装并能在指定路径找到
- 输入视频文件存在且路径正确
- 程序有写入输出目录的权限
注意:提取视频的每一帧可能会生成大量图片文件,尤其是对于长视频,会占用较多磁盘空间,请确保有足够的存储空间。
单元测试
下面为 VideoFrameExtractor 类编写一个单元测试,使用 JUnit 5 框架。这个测试会验证程序的基本功能,包括错误处理和正常提取帧的情况。
点击查看代码
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class VideoFrameExtractorTest {
// 临时目录,测试结束后会自动清理
@TempDir
Path tempDir;
private VideoFrameExtractor extractor;
private String validFfmpegPath;
private String invalidFfmpegPath;
private Path testVideoPath;
private Path outputDir;
@BeforeEach
void setUp() throws IOException {
// 确定有效的FFmpeg路径(根据实际环境修改)
validFfmpegPath = findFfmpegPath();
invalidFfmpegPath = tempDir.resolve("invalid_ffmpeg.exe").toString();
// 创建测试输出目录
outputDir = tempDir.resolve("test_output");
Files.createDirectory(outputDir);
// 创建一个小型测试视频文件(这里使用一个真实存在的测试视频)
// 注意:实际测试时需要提供一个真实的短视频文件路径
testVideoPath = Path.of("src/test/resources/test_video.mp4");
}
@AfterEach
void tearDown() {
// 清理测试生成的文件(临时目录会自动清理)
}
/**
* 尝试自动查找系统中的FFmpeg路径
*/
private String findFfmpegPath() {
// Windows系统
String windowsPath = "C:/ffmpeg/bin/ffmpeg.exe";
if (new File(windowsPath).exists()) {
return windowsPath;
}
// Linux/Mac系统
String[] unixPaths = {
"/usr/local/bin/ffmpeg",
"/usr/bin/ffmpeg",
"/opt/homebrew/bin/ffmpeg"
};
for (String path : unixPaths) {
if (new File(path).exists()) {
return path;
}
}
// 如果找不到,测试时会跳过需要FFmpeg的测试
return null;
}
/**
* 测试无效的FFmpeg路径是否会抛出异常
*/
@Test
void whenInvalidFfmpegPath_thenThrowException() {
assertThrows(IllegalArgumentException.class,
() -> new VideoFrameExtractor(invalidFfmpegPath));
}
/**
* 测试无效的视频文件路径是否会抛出异常
*/
@Test
void whenInvalidVideoPath_thenThrowException() {
if (validFfmpegPath == null) {
System.out.println("FFmpeg not found, skipping test");
return;
}
extractor = new VideoFrameExtractor(validFfmpegPath);
String invalidVideoPath = tempDir.resolve("nonexistent_video.mp4").toString();
assertThrows(IllegalArgumentException.class,
() -> extractor.extractFrames(invalidVideoPath, outputDir.toString(), "png"));
}
/**
* 测试正常提取视频帧的功能
*/
@Test
void whenValidInputs_thenExtractFramesSuccessfully() throws IOException {
if (validFfmpegPath == null) {
System.out.println("FFmpeg not found, skipping test");
return;
}
// 检查测试视频是否存在
if (!Files.exists(testVideoPath)) {
System.out.println("Test video not found at " + testVideoPath + ", skipping test");
return;
}
extractor = new VideoFrameExtractor(validFfmpegPath);
// 执行帧提取
extractor.extractFrames(testVideoPath.toString(), outputDir.toString(), "png");
// 验证输出目录中是否生成了图片文件
List<Path> imageFiles = Files.list(outputDir)
.filter(p -> p.getFileName().toString().matches("frame_\\d{4}\\.png"))
.toList();
assertTrue(imageFiles.size() > 0, "No frames were extracted");
// 验证生成的图片文件是否有效(非空)
for (Path imageFile : imageFiles) {
assertTrue(Files.size(imageFile) > 0, "Extracted frame is empty: " + imageFile);
}
}
/**
* 测试不同的图片格式是否能正常工作
*/
@Test
void whenUsingDifferentImageFormats_thenExtractFramesSuccessfully() throws IOException {
if (validFfmpegPath == null || !Files.exists(testVideoPath)) {
System.out.println("FFmpeg or test video not found, skipping test");
return;
}
extractor = new VideoFrameExtractor(validFfmpegPath);
String jpgOutputDir = outputDir.resolve("jpg").toString();
// 测试JPG格式
extractor.extractFrames(testVideoPath.toString(), jpgOutputDir, "jpg");
List<Path> jpgFiles = Files.list(Path.of(jpgOutputDir))
.filter(p -> p.getFileName().toString().matches("frame_\\d{4}\\.jpg"))
.toList();
assertTrue(jpgFiles.size() > 0, "No JPG frames were extracted");
}
}
说明:
1.这个测试需要 JUnit 5 框架支持,确保项目中已添加相关依赖
2测试前需要:
- 确保系统中安装了 FFmpeg
- 准备一个测试视频文件(建议使用短时长视频),并放在src/test/resources/test_video.mp4路径下
- 根据实际环境调整findFfmpegPath()方法中的路径
3.测试内容包括: - 验证无效 FFmpeg 路径的错误处理
- 验证无效视频文件的错误处理
- 验证正常情况下能否成功提取帧
- 验证不同图片格式(PNG、JPG)的支持情况
4.测试使用了 JUnit 5 的临时目录功能,测试生成的文件会在测试结束后自动清理,不会污染系统
5.如果找不到 FFmpeg 或测试视频,相关测试会自动跳过而不是失败
另外,可以根据实际需求调整测试用例,例如添加对提取帧率的验证、大文件处理的测试等。