Java 调用 FFMPEG 命令时用 url 作为输入源,Linux 下出现 “no such file or directory” 问题的解决
Windows 下执行 ffmpeg 命令,
D:/tools/ffmpeg/bin>ffmpeg.exe -i "某视频文件下载URL" -f flv D:/1.flv
可以进行成功调用。
Linux 下执行 ffmpeg 命令,
/usr/local/ffmpeg/bin/ffmpeg -i "某视频文件下载URL" -f flv /usr/userfile/ffmpeg/tempfile/1.flv
FFmpeg 会报错:
No such file or directory:"某视频文件下载URL"。
stackoverflow 上有人遇到了类似的问题:
FFMPEG “no such file or directory” on Android
I am trying to use the ffmpeg binary and call it via a native linux command in android. Most of the commands work fine but the problem is that when i need to pass an http url as an input to the -i option i get "No such file or directory" for the url. The url however is existing and running the SAME command on a mac does the job as expected.
问题迎刃而解。
当前线程会等待子进程 process 执行结束,然后继续往下执行。
值得注意的一点是,ffmpeg 进程在执行时,会产生大量输出信息,如果我们没有及时将流输出的话,存放这些信息的缓存会很快填满,之后该进程等待我们将这些信息输出,然而我们也在等待该进程执行结束(process.waitFor();很明显 process 不会结束因为它也在等待我们),于是一个很经典的死锁案例就此产生。
这种情况表现为我们的子进程阻塞住了,而我们启动该子进程的线程由于一直没有拿到 waitFor() 的返回也就此止步于那条语句。
所以我们需要不断地从该子进程中的 input stream 中读出数据以确保它不会阻塞。
When Runtime.exec() won't
Navigate yourself around pitfalls related to the Runtime.exec() method
D:/tools/ffmpeg/bin>ffmpeg.exe -i "某视频文件下载URL" -f flv D:/1.flv
可以成功直接将下载链接输入源转为 1.flv。
String raw2flvCmd = "D:/tools/ffmpeg/bin/ffmpeg.exe -i \"某视频文件下载URL\" -f flv 1.flv";
Runtime.getRuntime().exec(raw2flvCmd);
可以进行成功调用。
Linux 下执行 ffmpeg 命令,
/usr/local/ffmpeg/bin/ffmpeg -i "某视频文件下载URL" -f flv /usr/userfile/ffmpeg/tempfile/1.flv
也可以成功直接将下载链接输入源转为 1.flv。
String raw2flvCmd = "/usr/local/ffmpeg/bin/ffmpeg -i \"某视频文件下载URL\" -f flv /usr/userfile/ffmpeg/tempfile/1.flv";
Runtime.getRuntime().exec(raw2flvCmd);
FFmpeg 会报错:
No such file or directory:"某视频文件下载URL"。
stackoverflow 上有人遇到了类似的问题:
FFMPEG “no such file or directory” on Android
I am trying to use the ffmpeg binary and call it via a native linux command in android. Most of the commands work fine but the problem is that when i need to pass an http url as an input to the -i option i get "No such file or directory" for the url. The url however is existing and running the SAME command on a mac does the job as expected.
但最终没人给出正确的解决方案。
为什么 terminal 执行正常的同一条命令行语句,Java 调用就挂了呢?看来 Java 并没有将程序员的意图良好地转达给底层。
笔者经过多次测试,终于找到解决办法。既然 terminal 可以成功执行,启动 shell,然后自定义命令行作为参数传递给 shell 解释器。shell 知道如何将程序员的意图转达给底层。使用 sh -c,将自定义 CMD 行作为其参数,最后使用 java.lang.Runtimeexec(String[] cmdarray):
String raw2flvCmd = "/usr/local/ffmpeg/bin/ffmpeg -i \"某视频文件下载URL\" -f flv /usr/userfile/ffmpeg/tempfile/1.flv";
Runtime.getRuntime().exec(new String[]{"sh","-c",raw2flvCmd});
问题迎刃而解。
Runtime.getRuntime().exec(raw2flvCmd);会开启一个子进程,如果当前线程想等待该子进程执行完毕之后再继续往下执行,可以调用 java.lang.Process 的 waitFor() 方法:
Process process = null;
try {
String raw2flvCmd = "/usr/local/ffmpeg/bin/ffmpeg -i \"某视频文件下载URL\" -f flv /usr/userfile/ffmpeg/tempfile/1.flv";
process = Runtime.getRuntime().exec(new String[]{"sh","-c",raw2flvCmd});
process.waitFor();
} catch (Exception e) {
//do some thing
}
当前线程会等待子进程 process 执行结束,然后继续往下执行。
值得注意的一点是,ffmpeg 进程在执行时,会产生大量输出信息,如果我们没有及时将流输出的话,存放这些信息的缓存会很快填满,之后该进程等待我们将这些信息输出,然而我们也在等待该进程执行结束(process.waitFor();很明显 process 不会结束因为它也在等待我们),于是一个很经典的死锁案例就此产生。
这种情况表现为我们的子进程阻塞住了,而我们启动该子进程的线程由于一直没有拿到 waitFor() 的返回也就此止步于那条语句。
所以我们需要不断地从该子进程中的 input stream 中读出数据以确保它不会阻塞。
When Runtime.exec() won't
Navigate yourself around pitfalls related to the Runtime.exec() method
这篇文章对此进行了深入分析,并给出了推荐解决方案。我们依据该文将我们 Linux 下的 Java 调用 FFmpeg 最终完善为:
Process process = null;
try {
String raw2flvCmd = "/usr/local/ffmpeg/bin/ffmpeg -i \"某视频文件下载URL\" -f flv /usr/userfile/ffmpeg/tempfile/1.flv";
process = Runtime.getRuntime().exec(new String[]{"sh","-c",raw2flvCmd});
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR");
errorGobbler.start();// kick off stderr
StreamGobbler outGobbler = new StreamGobbler(process.getInputStream(), "STDOUT");
outGobbler.start();// kick off stdout
process.waitFor();
} catch (Exception e) {
//do some thing
}
其中 StreamGobbler 源码为:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
public class StreamGobbler extends Thread {
InputStream is;
String type;
OutputStream os;
public StreamGobbler(InputStream is, String type) {
this(is, type, null);
}
public StreamGobbler(InputStream is, String type, OutputStream redirect) {
this.is = is;
this.type = type;
this.os = redirect;
}
@Override
public void run() {
try {
PrintWriter pw = null;
if (os != null)
pw = new PrintWriter(os);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
if (pw != null)
pw.println(line);
System.out.println(type + ">" + line);
}
if (pw != null)
pw.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
最后补充一点,关闭所有的 io 输入/输出以及错误流、调用 Process 的 destroy() 方法关闭子进程。不然程序可能会出现"java.io.IOException: error=24, Too many open files"。