FFmpeg全名是Fast Forward MPEG(Moving Picture Experts Group),為開源的影音處理軟體,支援多種音訊和視訊的格式,用來處理音訊和視訊的串流,像是轉換檔案格式、剪輯與串接影音,可使用多種外部函式庫來擴展內建函式庫不足的問題。由於FFmpeg是C語言的專案,因此可以使用Android NDK來編譯成函式庫(Library)或是靜態的執行檔(Executable File),在Android裝置上使用,如此以來便能實作影音處理的App。
要編譯成函式庫還是靜態的執行檔?
如果您的C語言程式設計功力不錯,可以將FFmpeg和其他FFmpeg支援的外部編解碼器編譯成函式庫,使用jni實作呼叫函式庫的Java/C語言的轉換介面,直接處理檔案串流。這樣做雖然會十分複雜,但是程式流程會比較好控制。
如果您不太會寫C語言,可以跟筆者一樣將FFmpeg編譯成可以直接在Android上用Command Line執行的檔案,再用Android SDK撰寫呼叫這個檔案的程式。這樣做雖然會簡單很多,但程式流程也變得相當難處理,因為將會有多個行程(Process)。
編譯FFmpeg For Android
編譯環境
編譯FFmpeg,最好是在Linux作業系統或是Unix-like作業系統上進行,且必須要有對應平台的Android NDK,還要有FFmpeg的原始碼專案。
作業系統
推薦使用適合開發軟體的Linux Mint來編譯。
Android NDK
參考以下網址取得Android NDK:
FFmpeg 原始碼專案
FFmpeg的官網可以下載到FFmpeg的原始碼專案。
本篇文章的編譯環境
作業系統:Linux Mint 17 MATE x86_64
Android NDK:Android NDK R10 x86_64
FFmpeg:FFmpeg 2.3.1
開始編譯
由於使用Android NDK來編譯FFmpeg For Android需要進行許多設定,因此筆者把這些設定都整理成一個bash script檔案,檔名為「build_arm.sh」。
build_arm.sh下載:
請將下載下來的檔案解壓縮後,將「build_arm.sh」放置於FFmpeg的專案根目錄中。
接著用文字編輯軟體開啟「build_arm.sh」,修改NDK路徑、SYSROOT路徑、TOOLCHAIN路徑,使其符合您的環境。如果您的CPU的執行緒(Thread)數量並非是四個,請修改「make -j4」的數字「4」,將其改為您CPU的執行緒數量,提升編譯效率。
修改好後,將終端機的工作目錄移到FFmpeg的專案根目錄,然後輸入以下指令開始編譯:
編譯過程可能會需要sudo權限,因此可以使用以下指令來避免編譯時因為要求sudo密碼而暫停的情形:
編譯需要數分鐘到數十分鐘,請耐心等候。
編譯完成後,在FFmpeg的專案根目錄可以找到「android」的目錄,「android」目錄內應該要有「arm」和「arm_neon」兩個目錄,在這兩個目錄中的「bin」目錄可以找到「libffmpeg.so」,這就是我們要的執行檔案了。
也許您會覺得奇怪,為什麼執行檔案要命名成「libffmpeg.so」?.so不是函式庫嗎?這是因為我們要將ffmpeg的執行檔放置於Android專案的Lib資料夾下,讓Android在安裝App時自動區分不同ARM指令集的執行檔,所以偽裝成.so檔案。
如果您沒有成功編譯出.so檔案,可以下載筆者編譯好的。
在Android SDK中使用FFmpeg
「arm」和「arm_neon」兩個目錄中存放的「libffmpeg.so」分別是通用型執行檔以及經過neon優化使用armv7-a指令集的執行檔。將這兩個目錄下的「libffmpeg.so」分別放置到Android專案根目錄底下的「libs/armeabi」和「libs/armeabi-v7a」中,如果目錄不存在,就自行建立吧!
接著撰寫專門用來執行「libffmpeg.so」與取得執行結果的類別,程式大概可以如以下這樣寫:
File fileBinDir = new File(activity.getFilesDir().getParentFile(), "lib"); //FFmpeg執行檔的所在目錄
File fileBin = new File(fileBinDir, "libffmpeg.so"); //FFmpeg執行檔的檔案路徑
ProcessBuilder pb = new ProcessBuilder(cmds); //cmds為Command Line的指令,例如某行指令為:「libffmpeg.so -h」cmds的List內容就是["libffmpeg.so","-h"]
pb.directory(fileBinDir); //將工作目錄移到FFmpeg執行檔的所在目錄,不過還是建議在cmds中以絕對路徑來表示libffmpeg.so
pb.redirectErrorStream(true); //將FFmpeg的標準輸出和錯誤輸出合併成同一個inputStream
Process process = pb.start(); //執行行程
如果程式只有寫到這樣,那麼將無法得知Process的執行狀況和結果到底是怎麼樣,因此可以寫個執行緒,專門用來讀取FFmpeg的output,也就是Process的InputStream(執行檔的輸出成為行程的輸入)。程式如下:
class StreamGobbler extends Thread {
InputStream is;
StreamGobbler(InputStream is) {
this.is = is;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
Log.i("FFmpeg", line);
Log.i("FFmpeg", "Finish");
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
在FFmpeg的Process建立後,使用StreamGobbler來監看Process的執行情況。
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream());
outputGobbler.start();
當ProcessBuilder使用start方法之後,會產生新的Process出來並非同步的執行,因此如果要取得Process傳回的結果數值的話,必須使用Process的waitFor方法,並等待Process執行結束後才有傳回結果。因此這個方法應該要在主執行緒之外的執行緒中執行會比較好。以下程式可以得到Process執行結束後回傳的數值:
int exitVal = process.waitFor();
if (exitVal != 0) { // 失敗
//在這裡通知主執行緒Process執行失敗
} else { // 成功
//在這裡通知主執行緒Process執行成功
}
當然,您也可以直接使用MagicCommand,透過命令呼叫執行檔:
Android SDK操作FFmpeg的程式大致上就是這樣,可以直接使用CLI指令來操作FFmpeg,還算是蠻容易使用的。只是FFmpeg強大的轉檔功能在Android裝置上可能會因裝置硬體設備的關係而效能非常低落,如果要製作影片格式轉換格式這種App的話,還請三思啊!雖然您做起來不一定困難,但是使用者會用得十分痛苦,與其用小小的、慢吞吞的Android裝置轉換影片格式,倒不如用更大台、更快速的個人電腦來完成。