1、FFmpeg是什么
FFmpeg(https://www.ffmpeg.org)是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它用来干吗呢?视频采集、视频格式转化、视频截图、视频添加水印、视频切片(m3u8、ts)、视频录制、视频推流、更改音视频参数(编码方式、分辨率、码率、比特率等)功能,等等...
下载下来解压完了呢是这个样子:

bin中文件夹有个 ffmpeg.exe,点开,是的,一闪而逝并没有什么用,因为ffmpeg靠命令行来调用:

如上图命令读取 《小星星》.mp4 文件的文件信息,下面的一大片输出就是文件的信息输出了。
2、Java调用FFmpeg
现在我们需要把目标视频在第10秒处截图,怎么做呢,先看看用命令行是这样:

C:workspaceprojectgreejoypicManagerweb oolsffmpeginffmpeg.exe -i C:UsersDulkDesktopukulele 1《小星星》.mp4 -f image2 -ss 10 -t 0.001 -s 320*240 C:UsersDulkDesktopukulele 1littleStar.jpg1
1
C:workspaceprojectgreejoypicManagerweb oolsffmpeginffmpeg.exe -i C:UsersDulkDesktopukulele 1《小星星》.mp4 -f image2 -ss 10 -t 0.001 -s 320*240 C:UsersDulkDesktopukulele 1littleStar.jpg其中:
- C:workspaceprojectgreejoypicManagerweb oolsffmpeginffmpeg.exe 指 ffmpeg.exe 的路径
- C:UsersDulkDesktopukulele 1《小星星》.mp4 指源视频路径
- C:UsersDulkDesktopukulele 1littleStar.jpg 指截图的输出路径
- -i 表示输入文件的命令
- -f 表示输出格式,image2表示输出为图片
- -ss 表示指定的起始位置,这里设置为10,即10s处
- -t 表示设置录制/转码时长,既然截图就0.001就足够了
- -s 表示size,设置帧大小

(更多的 ffmpeg命令行参数详解)
现在我们看看用Java调用FFmpeg来实现相同的目的,要用到 ProcessBuilder 这个类,该类用于创建操作系统进程,执行本地命令或脚本等工作,其属性command是一个字符串列表(用以输入命令),然后通过start()方法启动执行,但需要注意的是,该方法调用会启动新的进程执行操作,所以并不是和原Java代码同步执行的。
public class FFmpegTest {
public static void main(String[] args) {
String ffmpegExePath = "C:\workspace\project\greejoy\picManager\web\tools\ffmpeg\bin\ffmpeg.exe";
String inputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\《小星星》.mp4";
String outputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\littleStarJava.jpg";
List<String> command = new ArrayList<String>();
command.add(ffmpegExePath);
command.add("-i");
command.add(inputFilePath);
command.add("-f");
command.add("image2");
command.add("-ss");
command.add("10");
command.add("-t");
command.add("0.001");
command.add("-s");
command.add("320*240");
command.add(outputFilePath);
ProcessBuilder builder = new ProcessBuilder();
builder.command(command);
//正常信息和错误信息合并输出
builder.redirectErrorStream(true);
try {
//开始执行命令
Process process = builder.start();
//如果你想获取到执行完后的信息,那么下面的代码也是需要的
StringBuffer sbf = new StringBuffer();
String line = null;
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = br.readLine()) != null) {
sbf.append(line);
sbf.append(" ");
}
String resultInfo = sbf.toString();
System.out.println(resultInfo);
} catch (IOException e) {
e.printStackTrace();
}
}
}47
1
public class FFmpegTest {2
3
public static void main(String[] args) {4
5
String ffmpegExePath = "C:\workspace\project\greejoy\picManager\web\tools\ffmpeg\bin\ffmpeg.exe";6
String inputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\《小星星》.mp4";7
String outputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\littleStarJava.jpg";8
9
List<String> command = new ArrayList<String>();10
command.add(ffmpegExePath);11
command.add("-i");12
command.add(inputFilePath);13
command.add("-f");14
command.add("image2");15
command.add("-ss");16
command.add("10");17
command.add("-t");18
command.add("0.001");19
command.add("-s");20
command.add("320*240");21
command.add(outputFilePath);22
23
ProcessBuilder builder = new ProcessBuilder();24
builder.command(command);25
//正常信息和错误信息合并输出26
builder.redirectErrorStream(true);27
try {28
//开始执行命令29
Process process = builder.start();30
31
//如果你想获取到执行完后的信息,那么下面的代码也是需要的32
StringBuffer sbf = new StringBuffer();33
String line = null;34
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));35
while ((line = br.readLine()) != null) {36
sbf.append(line);37
sbf.append(" ");38
}39
String resultInfo = sbf.toString();40
System.out.println(resultInfo);41
42
} catch (IOException e) {43
e.printStackTrace();44
}45
}46
47
}可以看到,拼接命令实际上就是把参数和值按序传入一个List,然后将该List传给 ProcessBuilder 类,然后执行 start() 方法即可。可以看到控制台输出的信息如下:


需要注意的是:
- 操作执行信息的读取是分为正常信息和错误信息,分别需要使用 getInputStream() 和 getErrorStream() 读取,上例使用了 builder.redirectErrorStream(true); 将两者合并输出
- 如果要使用 Process.waitFor() 则需要小心阻塞问题,此处不展开(FFmpeg在JAVA中的使用以及Process.waitFor()引发的阻塞问题)
3、Builder设计模式的应用
如果你每次使用都要如上例中add()一大堆参数,我估计也是头疼:
List<String> command = new ArrayList<String>();
command.add(ffmpegExePath);
command.add("-i");
command.add(inputFilePath);
command.add("-f");
command.add("image2");
command.add("-ss");
command.add("10");
command.add("-t");
command.add("0.001");
command.add("-s");
command.add("320*240");
command.add(outputFilePath);13
1
List<String> command = new ArrayList<String>();2
command.add(ffmpegExePath);3
command.add("-i");4
command.add(inputFilePath);5
command.add("-f");6
command.add("image2");7
command.add("-ss");8
command.add("10");9
command.add("-t");10
command.add("0.001");11
command.add("-s");12
command.add("320*240");13
command.add(outputFilePath);后来一想,这简直就是绝佳的使用builder模式的示例,先把命令封装成一个类:
/**
* FFmpeg命令的封装类
*/
public class FFmpegCommand {
private List<String> command;
public FFmpegCommand(List<String> command) {
this.command = command == null ? new ArrayList<String>() : command;
}
public List<String> getCommand() {
return command;
}
public void setCommand(List<String> command) {
this.command = command;
}
/**
* 开始执行命令
*
* @param callback 回调
* @return 命令的信息输出
* @throws FFmpegCommandException
*/
public String start(FFmpegCallback callback) throws FFmpegCommandException {
BufferedReader br = null;
StringBuffer sbf = new StringBuffer();
String resultInfo = null;
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(command);
//正常信息和错误信息合并输出
builder.redirectErrorStream(true);
//开启执行子线程
Process process = builder.start();
String line = null;
br = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = br.readLine()) != null) {
sbf.append(line);
sbf.append(" ");
}
resultInfo = sbf.toString();
//等待命令子线程执行完成
int exitValue = process.waitFor();
//完成后执行回调
if (exitValue == 0 && callback != null) {
callback.complete(resultInfo);
}
//销毁子线程
process.destroy();
} catch (IOException e) {
e.printStackTrace();
throw new FFmpegCommandException(e.getMessage());
} catch (InterruptedException e) {
e.printStackTrace();
throw new FFmpegCommandException("线程阻塞异常:" + e.getMessage());
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultInfo;
}
}75
1
/**2
* FFmpeg命令的封装类3
*/4
public class FFmpegCommand {5
6
private List<String> command;7
8
public FFmpegCommand(List<String> command) {9
this.command = command == null ? new ArrayList<String>() : command;10
}11
12
public List<String> getCommand() {13
return command;14
}15
16
public void setCommand(List<String> command) {17
this.command = command;18
}19
20
/**21
* 开始执行命令22
*23
* @param callback 回调24
* @return 命令的信息输出25
* @throws FFmpegCommandException26
*/27
public String start(FFmpegCallback callback) throws FFmpegCommandException {28
BufferedReader br = null;29
StringBuffer sbf = new StringBuffer();30
String resultInfo = null;31
try {32
ProcessBuilder builder = new ProcessBuilder();33
builder.command(command);34
//正常信息和错误信息合并输出35
builder.redirectErrorStream(true);36
//开启执行子线程37
Process process = builder.start();38
39
String line = null;40
br = new BufferedReader(new InputStreamReader(process.getInputStream()));41
while ((line = br.readLine()) != null) {42
sbf.append(line);43
sbf.append(" ");44
}45
resultInfo = sbf.toString();46
47
//等待命令子线程执行完成48
int exitValue = process.waitFor();49
//完成后执行回调50
if (exitValue == 0 && callback != null) {51
callback.complete(resultInfo);52
}53
//销毁子线程54
process.destroy();55
56
} catch (IOException e) {57
e.printStackTrace();58
throw new FFmpegCommandException(e.getMessage());59
} catch (InterruptedException e) {60
e.printStackTrace();61
throw new FFmpegCommandException("线程阻塞异常:" + e.getMessage());62
} finally {63
try {64
if (br != null) {65
br.close();66
}67
} catch (IOException e) {68
e.printStackTrace();69
}70
}71
72
return resultInfo;73
}74
75
}自然,要有builder类:
public class FFmpegCommandBuilder {
List<String> command = new ArrayList<String>();
public FFmpegCommandBuilder(String exePath) {
if (exePath == null) {
throw new FFmpegCommandRuntimeException("ffmpeg.exe 路径不得为空");
}
//添加命令的exe执行文件位置
command.add(exePath);
}
/**
* 添加输入文件的路径
*
* @param inputFilePath
*/
public FFmpegCommandBuilder input(String inputFilePath) {
if (inputFilePath != null) {
command.add("-i");
command.add(inputFilePath);
}
return this;
}
/**
* 添加输出文件的路径
*
* @param outputFilePath
*/
public FFmpegCommandBuilder output(String outputFilePath) {
if (outputFilePath != null) {
command.add(outputFilePath);
}
return this;
}
/**
* 覆盖输出文件
*/
public FFmpegCommandBuilder override() {
command.add("-y");
return this;
}
/**
* 强制输出格式
*
* @param format 输出格式
*/
public FFmpegCommandBuilder format(FFmpegCommandFormatEnum format) {
if (format != null) {
command.add("-f");
command.add(format.getValue());
}
return this;
}
/**
* 设置录制/转码的时长
*
* @param duration 形如 0.001 表示0.001秒,hh:mm:ss[.xxx]格式的记录时间也支持
*/
public FFmpegCommandBuilder duration(String duration) {
if (duration != null) {
command.add("-t");
command.add(duration);
}
return this;
}
/**
* 搜索到指定的起始时间
*
* @param position 形如 17 表示17秒,[-]hh:mm:ss[.xxx]的格式也支持
*/
public FFmpegCommandBuilder position(String position) {
if (position != null) {
command.add("-ss");
command.add(position);
}
return this;
}
/**
* 设置帧大小
*
* @param size 形如 xxx*xxx
* @return
*/
public FFmpegCommandBuilder size(String size) {
if (size != null) {
command.add("-s");
command.add(size);
}
return this;
}
/**
* 创建FFmpegCommand命令封装类
*
* @return FFmpegCommand
*/
public FFmpegCommand build() {
return new FFmpegCommand(command);
}
}108
1
public class FFmpegCommandBuilder {2
3
List<String> command = new ArrayList<String>();4
5
public FFmpegCommandBuilder(String exePath) {6
if (exePath == null) {7
throw new FFmpegCommandRuntimeException("ffmpeg.exe 路径不得为空");8
}9
//添加命令的exe执行文件位置10
command.add(exePath);11
}12
13
/**14
* 添加输入文件的路径15
*16
* @param inputFilePath17
*/18
public FFmpegCommandBuilder input(String inputFilePath) {19
if (inputFilePath != null) {20
command.add("-i");21
command.add(inputFilePath);22
}23
return this;24
}25
26
/**27
* 添加输出文件的路径28
*29
* @param outputFilePath30
*/31
public FFmpegCommandBuilder output(String outputFilePath) {32
if (outputFilePath != null) {33
command.add(outputFilePath);34
}35
return this;36
}37
38
/**39
* 覆盖输出文件40
*/41
public FFmpegCommandBuilder override() {42
command.add("-y");43
return this;44
}45
46
/**47
* 强制输出格式48
*49
* @param format 输出格式50
*/51
public FFmpegCommandBuilder format(FFmpegCommandFormatEnum format) {52
if (format != null) {53
command.add("-f");54
command.add(format.getValue());55
}56
return this;57
}58
59
/**60
* 设置录制/转码的时长61
*62
* @param duration 形如 0.001 表示0.001秒,hh:mm:ss[.xxx]格式的记录时间也支持63
*/64
public FFmpegCommandBuilder duration(String duration) {65
if (duration != null) {66
command.add("-t");67
command.add(duration);68
}69
return this;70
}71
72
/**73
* 搜索到指定的起始时间74
*75
* @param position 形如 17 表示17秒,[-]hh:mm:ss[.xxx]的格式也支持76
*/77
public FFmpegCommandBuilder position(String position) {78
if (position != null) {79
command.add("-ss");80
command.add(position);81
}82
return this;83
}84
85
/**86
* 设置帧大小87
*88
* @param size 形如 xxx*xxx89
* @return90
*/91
public FFmpegCommandBuilder size(String size) {92
if (size != null) {93
command.add("-s");94
command.add(size);95
}96
return this;97
}98
99
/**100
* 创建FFmpegCommand命令封装类101
*102
* @return FFmpegCommand103
*/104
public FFmpegCommand build() {105
return new FFmpegCommand(command);106
}107
108
}那么使用builder模式,还是刚才的目的,我们的代码就变成了:
public class FFmpegBuilderTest {
public static void main(String[] args) {
String ffmpegExePath = "C:\workspace\project\greejoy\picManager\web\tools\ffmpeg\bin\ffmpeg.exe";
String inputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\《小星星》.mp4";
String outputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\littleStarJavaBuilder.jpg";
FFmpegCommandBuilder builder = new FFmpegCommandBuilder(ffmpegExePath);
builder.input(inputFilePath).format(FFmpegCommandFormatEnum.IMAGE)
.position("10").duration("0.001").size("320*240").output(outputFilePath);
FFmpegCommand command = builder.build();
try {
String result = command.start(null);
System.out.println(result);
} catch (FFmpegCommandException e) {
e.printStackTrace();
}
}
}x
1
public class FFmpegBuilderTest {2
3
public static void main(String[] args) {4
5
String ffmpegExePath = "C:\workspace\project\greejoy\picManager\web\tools\ffmpeg\bin\ffmpeg.exe";6
String inputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\《小星星》.mp4";7
String outputFilePath = "C:\Users\Dulk\Desktop\ukulele\01\littleStarJavaBuilder.jpg";8
9
FFmpegCommandBuilder builder = new FFmpegCommandBuilder(ffmpegExePath);10
builder.input(inputFilePath).format(FFmpegCommandFormatEnum.IMAGE)11
.position("10").duration("0.001").size("320*240").output(outputFilePath);12
FFmpegCommand command = builder.build();13
try {14
String result = command.start(null);15
System.out.println(result);16
} catch (FFmpegCommandException e) {17
e.printStackTrace();18
}19
}20
21
}
