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的專案根目錄,然後輸入以下指令開始編譯:

./build_arm.sh

編譯過程可能會需要sudo權限,因此可以使用以下指令來避免編譯時因為要求sudo密碼而暫停的情形:

sudo ./build_arm.sh

編譯需要數分鐘到數十分鐘,請耐心等候。

編譯完成後,在FFmpeg的專案根目錄可以找到「android」的目錄,「android」目錄內應該要有「arm」和「arm_neon」兩個目錄,在這兩個目錄中的「bin」目錄可以找到「libffmpeg.so」,這就是我們要的執行檔案了。

也許您會覺得奇怪,為什麼執行檔案要命名成「libffmpeg.so」?.so不是函式庫嗎?這是因為我們要將ffmpeg的執行檔放置於Android專案的Lib資料夾下,讓Android在安裝App時自動區分不同ARM指令集的執行檔,所以偽裝成.so檔案。

如果您沒有成功編譯出.so檔案,可以下載筆者編譯好的。

偽libffmpeg.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裝置轉換影片格式,倒不如用更大台、更快速的個人電腦來完成。