這兩天一直在處理flv視頻環境的搭建工作,包括伺服器的安裝和java中的應用。安裝ffmpeg加mencoder倒沒有什麼大問題,不過還是有一個小問題弄得我鬱悶了下,就是在安裝amrwb和amrnb的時候出錯,錯誤如下:
/usr/bin/wget -N http://www.3gpp.org/ftp/Specs/archive/26_series/26.104/26104-610.zip
--20:41:59-- http://www.3gpp.org/ftp/Specs/archive/26_series/26.104/26104-610.zip
正在解析主機 www.3gpp.org... 212.234.161.21
Connecting to www.3gpp.org|212.234.161.21|:80... 已串連。
已發出 HTTP 要求,正在等待回應... 403 Forbidden
20:42:00 錯誤 403:Forbidden。
make: *** [26104-610.zip] 錯誤 1
後來仔細看了和問了下公司的網路系統管理員,才知道我這台測試機器沒有開外網,剛開始也想到了是這個問題,可ping的時候卻可以ping通,所以就放棄了那個想法,誰知道還真是這問題,結果就好說咯,開通網路後安裝就非常順利了~~~這是題外話了,呵
ffmpeg的環境搭建起來後,在本地進行手動轉換沒什麼問題,通過程式調用(不擷取程式反饋結果)也沒什麼問題。可後來進一步深入的時候,我在提交視頻的轉換請求的時候,必然要進行資料庫的相關操作,比如在轉換前更新該視頻的狀態為正在更新狀態,在轉換完畢後填如該視頻的flv檔案路徑和圖片路徑資訊,還有flv檔案的大小和時間長度等等資料。這就需要線上程中控制ffmpeg進程的狀態了,這裡我們就需要用到Process這個類了,典型的我們寫代碼如下:
……
Process process = Runtime.getRuntime.exec(cmd); // 執行調用ffmpeg命令
InputStream is = process.getInputStream(); // 擷取ffmpeg進程的輸出資料流
BufferedReader br = new Buffered(new InputStreamReader(is)); // 緩衝讀入
StringBuilder buf = new StringBuilder(); // 儲存ffmpeg的輸出結果流
String line = null;
while((line = br.readLine()) != null) buf.append(line); // 迴圈等待ffmpeg進程結束
System.out.println("ffmpeg輸出內容為:" + buf);
……
本來一般都是這樣來調用程式並擷取進程的輸出資料流的,但是我在windows上執行這樣的調用的時候卻總是在while那裡被堵塞了,結果造成ffmpeg程式在執行了一會後不再執行,這裡從官方的參考文檔中我們可以看到這是由於緩衝區的問題,由於java進程沒有清空ffmpeg程式寫到緩衝區的內容,結果導致ffmpeg程式一直在等待。在網上也尋找了很多這樣的問題,不過說的都是使用單獨的線程來進行控制,我也嘗試過很多網是所說的方法,可一直沒起什麼作用。下面就是我的解決方案了,注意到上述代碼中的紅色部分了嗎?這裡就是關鍵,我把它改成如下結果就可以正常運行了。
InputStream is = process.getErrorStream(); // 擷取ffmpeg進程的輸出資料流
注意到沒?我把它改成擷取錯誤流這樣進程就不會被堵塞了,而我之前一直想的是同樣的命令我手動調用的時候可以完成,而java調用卻總是完成不了,一直認為是getInputStream的緩衝區沒有被清空,不過問題確實是緩衝區的內容沒有被清空,但不是getInputStream的,而是getErrorStream的緩衝區,這樣問題就得到解決了。所以我們在遇到java調用外部程式而導致線程阻塞的時候,可以考慮使用兩個線程來同時清空process擷取的兩個輸入資料流,如下這段程式:
……
Process process = Runtime.getRuntime.exec(command); // 調用外部程式
final InputStream is1 = process.getInputStream();
new Thread(new Runnable() {
public void run() {
BufferedReader br = new Buffered(new InputStreamReader(is));
while(br.readLine() != null) ;
}
}.start(); // 啟動單獨的線程來清空process.getInputStream()的緩衝區
InputStream is2 = process.getErrorStream();
BufferedReader br2 = new Buffered(new InputStreamReader(is2));
StringBuilder buf = new StringBuilder(); // 儲存輸出結果流
String line = null;
while((line = br.readLine()) != null) buf.append(line); // 迴圈等待ffmpeg進程結束
System.out.println("輸出結果為:" + buf);
……
通過這樣我們使用一個線程來讀取process.getInputStream()的輸出資料流,使用另外一個線程來擷取process.getErrorStream()的輸出資料流,這樣我們就可以保證緩衝區得到及時的清空而不擔心線程被阻塞了。當然根據需要你也可以保留process.getInputStream()流中的內容,這個就看調用的程式的處理了。我在windows下調用FFmpeg程式進行視頻轉換的時候就是通過這樣來解決線程被堵塞的問題的,呵呵~
所以我覺得像Process.getInputStream和process.getErrorStream兩個方法並不是真的就像名稱表示的那樣,一個是擷取被調用程式的正常輸出,一個是擷取被調用程式的錯誤流。有時候錯誤流(即ErrorStream)也是程式的正常輸出,因為畢竟java在調用windows和linux下的都是一套這樣的程式,所以我們在寫程式的時候應該開啟思路,我們自己通常認為理所當然的並不一定是正確的呢!