ffmpeg实现视频流抽帧
# ffmpeg 实现视频流抽帧
# 抽取实时视频帧
如果你的实时视频是通过 RTSP、UDP 或其他协议获取的,可以直接调用 FFmpeg 命令来抽取帧。
# ffmpeg 命令
# 示例 1
ffmpeg -i rtsp://your_rtsp_stream_url -vf fps=1 -update 1 output.jpg
1
说明:
- -i rtsp://your_rtsp_stream_url:指定输入的实时视频流 URL。
- -vf fps=1:使用视频滤镜,每秒抽取 1 帧(可根据需要调整帧率,例如
fps=1/5
表示每 5 秒抽取一帧)。 - -update 1 output.jpg:参数
-update 1
表示不断用最新的帧更新同一个输出文件(output.jpg),适合用于监控场景;如果需要保存多张图片,则可以使用类似output_%04d.jpg
的命名格式保存为连续文件。
# 示例 2
ffmpeg -i "rtmp://ns8.indexforce.com/home/mystream" -vf fps=1 "frame_%03d.jpg"
1
说明:
-i "rtsp://your_rtsp_stream_url"
:指定 RTSP 视频流的 URL。-vf fps=1
:设置帧率,每秒抽取一帧。您可以根据需要调整此值,例如fps=1/5
表示每 5 秒抽取一帧。"frame_%03d.jpg"
:指定输出的图像文件名,%03d
表示编号,生成的文件名将依次为frame_001.jpg
、frame_002.jpg
等。
# 示例 3
ffmpeg -i "rtmp://ns8.indexforce.com/home/mystream" -ss 1 -frames:v 1 "C:\Users\26913\Videos\ffmpeg-img\frame.jpg"
1
说明:
- 从指定的 RTSP 流中读取数据,并只输出一帧图像,保存到
C:\Users\26913\Videos\ffmpeg-img
目录下,文件名为frame.jpg
。 -ss 1
:表示设置时间偏移量为 1 秒。也就是说,从视频的第 1 秒处开始处理。对于抽帧来说,FFmpeg 会在视频的 1 秒处截取当前帧。- 不加这个参数的话,会获取视频开头的第一帧。
- 若视频流开头是黑屏或加载帧,可能会影响抓取效果。
- 效率较低,因为 FFmpeg 需要解析部分流的关键帧来决定输出。
# 代码示例
# 引入依赖
<!-- 集成javacv -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.11</version>
</dependency>
<!-- 集成ffmpeg:https://mvnrepository.com/artifact/org.bytedeco/ffmpeg -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>7.1-1.5.11</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 1、javacv 写法
public class LiveStreamFrameExtractor {
private static final Logger log = LoggerFactory.getLogger(LiveStreamFrameExtractor.class);
/**
* javacv实时抓取视频帧
*/
public static void main(String[] args) {
String streamUrl = VideoConstant.RTSP_URL_2;
String outputDirPath = VideoConstant.FRAME_FILE_PREFIX;
File outputDir = new File(outputDirPath);
if (!outputDir.exists()) {
outputDir.mkdirs();
}
// 使用 try-with-resources 自动管理资源
try (
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(streamUrl);
Java2DFrameConverter converter = new Java2DFrameConverter()
) {
// 启动抓取器,开始实时视频读取
grabber.start();
int frameCount = 0;
Frame frame;
// 无限循环抓取实时视频帧
while ((frame = grabber.grabImage()) != null) {
// 可根据需要添加实时显示、处理等操作
BufferedImage image = converter.convert(frame);
if (image != null) {
// 构造图片输出路径,例如 live_frame_0001.jpg
String outputFileName = outputDirPath + File.separator + String.format("live_frame_%04d.jpg", frameCount);
File outputFile = new File(outputFileName);
ImageIO.write(image, "jpg", outputFile);
System.out.println("保存帧:" + frameCount + " 到文件:" + outputFileName);
}
frameCount++;
// 根据抓取帧率或其他需求设置适当延时,避免过快抽帧
TimeUnit.MILLISECONDS.sleep(50);
if (frameCount > 5) {
break;
}
}
System.out.println("抓取结束,共抓取 " + frameCount + " 帧");
// 停止抓取器
grabber.stop();
} catch (Exception e) {
log.error("抓取视频帧出错:", e);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 2、ffmpeg 命令写法
@Slf4j
public class FfmpegProcess {
static String rtspUrl = VideoConstant.RTSP_URL_1;
static String streamOutputFile = VideoConstant.FRAME_FILE_PREFIX + File.separator + DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss") + ".jpg";
public static void main(String[] args) throws Exception {
String command = getStreamFrameExtractionCommand(rtspUrl, streamOutputFile);
System.out.println("执行命令: " + command);
// 创建操作系统进程
ProcessBuilder builder = new ProcessBuilder();
// 执行命令
executeCommand(builder, command, "windows");
// 合并标准输出和标准错误输出流
builder.redirectErrorStream(true);
Process process = builder.start();
// 异步读取输出流,避免阻塞
CompletableFuture<Void> future = readOutputAsync(process.getInputStream());
int exitCode = process.waitFor();
if (exitCode == 0) {
System.out.println("执行成功");
} else {
System.err.println("执行过程中出现错误,退出代码:" + exitCode);
}
}
/**
* 获取实时视频流抽帧的命令(只抓取一张图片)
*
* @param url 视频流 url,例如 rtsp 流地址
* @param outputFile 输出文件路径
* @return 实时视频流抽帧的命令
*/
private static String getStreamFrameExtractionCommand(String url, String outputFile) {
return String.format("%s -y -i %s -ss 1 -frames:v 1 %s", VideoConstant.FFMPEG_PATH, url, outputFile);
}
/**
* 异步读取 ffmpeg 输出流
*/
private static CompletableFuture<Void> readOutputAsync(InputStream inputStream) {
return CompletableFuture.runAsync(() -> {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
log.error("读取 ffmpeg 输出流异常", e);
}
});
}
/**
* 执行命令
*
* @param builder 进程构建器
* @param command 命令
* @param osType 操作系统类型
*/
private static void executeCommand(ProcessBuilder builder, String command, String osType) {
switch (osType) {
case "windows":
builder.command("cmd", "/c", command);
break;
case "Linux":
case "macOS":
builder.command("bash", "-c", command);
break;
default:
throw new RuntimeException("不支持的操作系统类型");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 学习参考
上次更新: 2025/2/8 18:02:22