Java 呼叫 FFMPEG 命令時用 url 作為輸入源,Linux 下出現 “no such file or directory” 問題的解決
阿新 • • 發佈:2019-01-09
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"。