最近有一个需求需要调用linux上的ffmpeg将视频转换为MP4,切片,然后保存,在了解了ffmpeg后,下一步就是如何用java调用linux命令行了
一、概述
java要执行系统命令的话,其实是支持的,不需要添加其他的jar包或者依赖,就可以,java8API https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html#Process
主要涉及的类就是公共抽象类Process,使用ProcessBuilder.start(),Runtime.exec方法来得到一个process对象,用来控制进程,他可以获取命令的执行结果,可以获得打印的日志以及杀死进程
注意:创建进程的方法可能不适用于某些本机平台上的特殊进程,例如本机窗口进程,守护进程,Microsoft Windows上的Win16 / DOS进程或Shell脚本
二、使用
方式一:ProcessBuilder
目录
一、概述
二、使用
方式一:ProcessBuilder
2.1流程
2.2. waitfor 问题描述分析
2.3代码
方式二、Runtime.exec
2.1流程
创建processBuilder对象,设置需要执行的command,设置合并输出流(按需合并),调用start()开始执行,并获取process对象,获取输出流并处理,调用process.waitFor();获取执行结果,根据结果返回
2.2. waitfor 问题描述分析
- 主进程中调用pb.start会创建一个子进程,用于执行shell /exe 脚本。子进程创建后会和主进程分别独立运行。
- 因为主进程需要等待脚本执行完成,然后对脚本返回值或输出进行处理,所以这里主进程调用Process.waitfor等待子进程完成。
- 子进程执行过程就是不断的打印信息。主进程中可以通过Process.getInputStream和Process.getErrorStream获取并处理。
- 这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。
- 这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。
参考博客:
2.3代码
private static ExecutorService executor;
static {
//创建线程池
executor = Executors.newFixedThreadPool(10);
}
public String processMp4(String path){
StringBuilder commend = new StringBuilder();
commend.append("ffmpeg ")
.append(" -i ")
.append(path)
.append(" -movflags faststart ") //将视频的moov移动到最前方
.append(" -c:v libx264 ")
.append(" -c:a copy ")
.append(" -n ")//如果文件已存在,则直接退出
.append(newPath);
Process process = null;
try{
ProcessBuilder pb = new ProcessBuilder();
//因为process执行命令并不是像窗口中执行shell一样,所以需要添加参数,用于执行脚本
pb.command(new String[]{"sh","-c",commend.toString()});
//processBuilder支持将inputStream与ErrorStream合并为一个Stream,即所有的输出信息都合并到inputStream中,这样做可以减少一个线程
pb.redirectErrorStream(true);
process = pb.start();
//由于process机制原因会导致死锁,所以需要在waitfor方法之前,创建线程用于处理inputstream中缓冲区的数据,这也是为什么要合并inputstream和errorstream的原因,在这里可以少创建一个线程
readInputStream(process.getInputStream());
//返回0则表示输出正常
int resultCode = process.waitFor();
System.out.println("resultCode:"+resultCode);
}catch (Exception e){
e.printStackTrace();
}finally {
try{
process.getErrorStream().close();
process.getInputStream().close();
process.getOutputStream().close();
}
catch(Exception ee){}
}
return newPath;
}
//创建线程处理输出流
private void readInputStream(InputStream in){
executor.execute(new Runnable() {
@Override
public void run() {
InputStreamReader reader = null;
try {
reader = new InputStreamReader(in);
LineNumberReader line = new LineNumberReader(reader);
String str = null;
while((str=line.readLine())!=null){
System.out.println(str);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (reader!= null ){
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
方式二、Runtime.exec
参考博客:
public String processMp42(String path){
File file = new File(path);
if (!file.isFile()){
System.out.println(path+"不是文件");
return "不是文件";
}
String fileName = this.getFileName(path);
String path1 = path.substring(0, path.lastIndexOf("/")+1);
String newPath = path1+fileName+".mp4";
StringBuilder commend = new StringBuilder();
commend.append("ffmpeg ")
.append(" -i ")
.append(path)
.append(" -movflags faststart ") //将视频的moov移动到最前方
.append(" -c:v libx264 ")
.append(" -c:a copy ")
.append(" -n ")//如果文件已存在,则直接退出
.append(newPath);
try{
//和processBuilder一样,某些命令不可以直接以string的形式作为参数执行,需要以String[]的形式,当前命令经过测试,可以直接执行
Process process=Runtime.getRuntime().exec(commend.toString());
//处理输出流
readInputStream(process.getInputStream());
//返回0则表示输出正常
int resultCode = process.waitFor();
System.out.println("执行返回码:"+resultCode);
System.out.println("exitValue:"+process.exitValue());
System.out.println("执行完毕");
}catch (Exception e){
e.printStackTrace();
}
return newPath;
}
如有理解不对的地方,劳烦指正