該篇文章主要介紹 Android 端利用 NDK 工具庫來對 C/C++ 進行交叉編譯,並經過 makefile 和 cmake 來構建 Android 項目。java
瞭解 c/c++ 編譯器的基本使用,可以在後續移植第三方框架進行交叉編譯時,清楚的瞭解應該傳遞什麼參數。linux
1. clangandroid
clang 是一個C、C++、Object-C
的輕量級編譯器。基於LLVM
( LLVM是以C++編寫而成的構架編譯器的框架系統,能夠說是一個用於開發編譯器相關的庫),對比 gcc,它具備編譯速度更快、編譯產出更小等優勢,可是某些軟件在使用 clang 編譯時候由於源碼中內容的問題會出現錯誤。c++
2. gccgit
GNU C 編譯器。本來只能處理 C 語言,可是它很快擴展,變得可處理 C++。( GNU目標是建立一套徹底自由的操做系統)。github
3. g++shell
GNU c++ 編譯器,後綴爲 .c 的源文件,gcc 把它看成是 C 程序,而 g++ 看成是 C++ 程序;後綴爲 .cpp 的,二者都會認爲是 c++ 程序,g++ 會自動連接 c++ 標準庫 stl ,gcc 不會,gcc 不會定義 __cplusplus 宏,而 g++ 會。vim
一個 C/C++ 文件要通過預處理(preprocessing)、編譯(compilation)、彙編(assembly)、和連接(linking)才能變成可執行文件。bash
咱們先在 linux 系統上建立一個 test.c 文件,編寫一個最簡單的 c 程序,代碼以下:架構
#include<stdio.h>
int main(){
printf(" 執行成功 ! \n");
return 19921001;
}
複製代碼
預處理階段
預處理階段主要處理 include 和 define 等。它把 #include 包含進來的 .h 文件插入到 #include 所在的位置,把源程序中使用到的用 #define 定義的宏用實際的字符串代替。
咱們能夠經過如下命令來對 c/c++ 文件預處理,命令以下:
gcc -E test.c -o test.i //-E 的做用是讓 gcc 在預處理結束後中止編譯
複製代碼
能夠看到輸入該命令以後就會生成一個 test.i 文件。
編譯階段
在這個階段中,gcc 首先要檢查代碼的規範性、是否有語法錯誤等,以肯定代碼的實際要作的工做。
咱們能夠經過以下命令來處理 test.i 文件,編譯成彙編文件,命令以下:
gcc -S test.i -o test.s//-S 的做用是編譯結束生成彙編文件。
複製代碼
彙編階段
彙編階段把 .S 文件翻譯成二進制機器指令文件 .o ,這個階段接收.c ,.i ,.s 的文件都沒有問題。
下面咱們經過如下命令生成二進制機器指令文件 .o 文件:
gcc -c test.s -o test.o
複製代碼
連接階段
連接階段,連接的是函數庫。能夠經過如下命令實現:
gcc -C test.o -o test
./test
複製代碼
gcc test.c -o test
複製代碼
到這裏咱們成功的在 linux 平臺生成了可執行文件,試想一下咱們能夠將這個可執行文件拷貝到安卓手機上執行嗎?咱們也不猜測了,實際測試下就行,咱們把 test 可執行文件 push 到手機 /data/local/tmp 裏面, 以下所示:
能夠看到 test 在手機 /data/local/tmp 的路徑下是有可讀可寫可執行的權限,可是最後執行不成功,這是爲何呢? 其實 主要緣由是兩個平臺的 CPU 指令集不同,根本就沒法識別指令。那麼怎麼解決這個問題呢? 下面就要用到今天一個比較重要的知識點了, 利用 Android NDK 工具包來對 C/C++ 代碼進行交叉編譯 。
簡單地來講,交叉編譯就是程序的編譯環境和實際運行環境不一致,即在一個平臺上生成另外一個平臺上的可執行代碼。
在音視頻開發中瞭解交叉編譯是頗有必要的,由於不管在哪種移動平臺下開發,第三方庫都是須要進行交叉編譯的。下面咱們就以以前的例子來說解如何在 linux 環境下交叉編譯出移動平臺上的可執行代碼。
Android 原生開發包 (NDK) 可用於 Android 平臺上的 C++ 開發,NDK 不只僅是一個單一功能的工具,仍是一個包含了 API 、交叉編譯器、調試器、構建工具等得綜合工具集。
下面大體列舉了一下常常會用到的組件。
下面來看一下 Android 所提供的 NDK 跟目錄下的結構。
下面咱們就來爲交叉編譯的環境變量配置
ndk 在 Linux 上的環境變量配置:
//1. vim /etc/profile
#NDK環境變量
export NDK_HOME=/root/android/ndk/android-ndk-r17c
export PATH=$PATH:$NDK_HOME
//2. 保存
source /etc/profile
//3. 測試
ndk-build -v
複製代碼
若是出現以下字樣,就證實配置成功了。
交叉編譯在 Linux 上的環境變量配置(作一個參考,採坑以後的環境配置):
export NDK_GCC_x86="/root/android/ndk/android-ndk-r17c/toolchains/x86-4.9/prebuilt/linux-x86_64/bin/i686-linux-android-gcc"
export NDK_GCC_x64="/root/android/ndk/android-ndk-r17c/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-gcc"
export NDK_GCC_arm="/root/android/ndk/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc"
export NDK_GCC_arm_64="/root/android/ndk/android-ndk-r17c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-gcc"
export NDK_CFIG_x86="--sysroot=/root/android/ndk/android-ndk-r17c/platforms/android-21/arch-x86 -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include/i686-linux-android"
export NDK_CFIG_x64="--sysroot=/root/android/ndk/android-ndk-r17c/platforms/android-21/arch-x86_64 -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include/x86_64-linux-android"
export NDK_CFIG_arm="--sysroot=/root/android/ndk/android-ndk-r17c/platforms/android-21/arch-arm -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include/arm-linux-androideabi"
export NDK_CFIG_arm_64="--isysroot=/root/android/ndk/android-ndk-r17c/platforms/android-21/arch-arm64 -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include -isystem -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include/aarch64-linux-android"
export NDK_AR_x86="/root/android/ndk/android-ndk-r17c/toolchains/x86-4.9/prebuilt/linux-x86_64/bin/i686-linux-android-ar"
export NDK_AR_x64="/root/android/ndk/android-ndk-r17c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar"
export NDK_AR_arm="/root/android/ndk/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar"
export NDK_AR_arm_64="/root/android/ndk/android-ndk-r17c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar"
複製代碼
你能夠根據本身的 ndk 路徑對應個人環境變量來進行配置。下面咱們就用 ndk gcc 來對 test.c 進行交叉編譯,步驟以下:
首先找到 /root/android/ndk/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc
執行以下命令:
/root/android/ndk/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc -o test test.c
複製代碼
這種錯誤是說在咱們編得時候編譯器找不到咱們引入的 stdio.h 頭文件,那怎麼告訴編譯器 stdio.h 頭文件在哪裏呢? 下面知識點說明怎麼指定這些報錯的頭文件
指定頭文件代碼
/root/android/ndk/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc --sysroot=/root/android/ndk/android-ndk-r17c/platforms/android-21/arch-arm -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include -pie -o test test.c
複製代碼
上面出現了幾個命令符號,不瞭解了能夠看一下以下解釋:
--sysroot=?: 使用 ?做爲這一次編譯的頭文件與庫文件的查找目錄,查找下面的 usr/include 目錄。
-isystem ?(主要中間有一個英文空格) : 使用頭文件查找目錄,覆蓋 --sysroot, 查找 ?/usr/include 目錄下面的頭文件。
-isystem ?(主要中間有一個英文空格): ** 指定頭文件的查找路徑。
-I?: 頭文件的查找目錄,I 是大寫。
這樣編譯以後仍是會報一個 asm/types.h 文件找不到,咱們還要繼續修改一下路徑,以下
/root/android/ndk/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc --sysroot=/root/android/ndk/android-ndk-r17c/platforms/android-21/arch-arm -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include/arm-linux-androideabi -pie -o test test.c
複製代碼
這樣就能編譯成一個 Android 平臺可執行的文件了,這樣看起來路徑太多不易閱讀,你們能夠參考我提供的全局變量配置來進行設置,最後一行命令解決,以下:
$NDK_GCC_arm $NDK_CFIG_arm -pie -o test test.c
複製代碼
能夠看到,咱們使用 Android NDK 編譯出來的可執行文件已經在 Linux 平臺下不可執行了。下面咱們將 test 文件導入到 手機 /data/local/tmp 目錄。
將 NDK 交叉編譯出來的 test 可執行文件,導入 Android 手機中並執行 test 文件。
根據上面的錄屏,咱們知道已經成功的在 Android 設備下執行了 NDK 交叉編譯後的 test 文件了。
下面咱們利用 NDK 工具交叉編譯 test.c 輸出靜態動態庫。
將 test.c 使用 NDK GCC 編譯爲 .o 文件 ,命令以下:
$NDK_GCC_arm $NDK_CFIG_arm -fpic -c test.c -o test.o
複製代碼
若是出現以下文件,證實已經成功了。
使用 NDK arm-linux-androideabi-ar 工具將 test.o 文件生成 test.a 靜態庫,命令以下:
$NDK_AR_arm r test.a test.o
複製代碼
以後咱們把 test.a 文件導入到 AS 中,來對 .a 的使用。
在編譯動態庫的時候咱們須要指定 -fPIC -shared 額外參數給編譯器,完整命令以下:
$NDK_GCC_arm $NDK_CFIG_arm -fpic -shared test.c -o libTest.so
複製代碼
在平時工做中咱們常常把一些經常使用的函數或者功能封裝爲一個個庫供給別人使用,java開發咱們能夠封裝爲 ja r包提供給別人用,安卓平臺後來能夠打包成 aar 包,一樣的,C/C++ 中咱們封裝的功能或者函數能夠經過靜態庫或者動態庫的方式提供給別人使用。
Linux 平臺靜態庫以 .a 結尾,而動態庫以 .so 結尾。
那靜態庫與動態庫有什麼區別呢?
1. 靜態庫
與靜態庫鏈接時,靜態庫中全部被使用的函數的機器碼在編譯的時候都被拷貝到最終的可執行文件中,而且會被添加到和它鏈接的每一個程序中:
優勢:運行起來會快一些,不用查找其他文件的函數庫了。
缺點:致使最終生成的可執行代碼量相對變多,運行時, 都會被加載到內存中. 又多消耗了內存空間。
2. 動態庫
與動態庫鏈接的可執行文件只包含須要的函數的引用表,而不是全部的函數代碼,只有在程序執行時, 那些須要的函數代碼才被拷貝到內存中。
優勢:生成可執行文件比較小, 節省磁盤空間,一份動態庫駐留在內存中被多個程序使用,也同時節約了內存。
缺點:因爲運行時要去連接庫會花費必定的時間,執行速度相對會慢一些。
靜態庫是時間換空間,動態庫是空間換時間,兩者均有好壞。
若是咱們要修改函數庫,使用動態庫的程序只須要將動態庫從新編譯就能夠了,而使用靜態庫的程序則須要將靜態庫從新編譯好後,將程序再從新編譯一遍。
上一小節咱們經過 NDK 交叉編譯了 test.c 爲動態靜態庫,那麼該小節咱們就基於 makefile 和 cmake 來構建一個 C/C++ 的 Android 程序, 並使用 test .a /libTest.so
Android.mk 是在 Android 平臺上構建一個 C 或者 C ++ 語言編寫的程序系統的 Makefile 文件,不一樣的是, Android 提供了一些列內置變量來提供更加方便的構建語法規則。Application.mk 文件其實是對應用程序自己進行描述的文件,它描述了應用程序要針對哪些 CPU 架構打包動態 so 包、要構建的是 release 包仍是 debug 包以及一些編譯和連接參數等。
1. Android.mk
LOCAL_PATH :=$(call my-dir)
返回當前文件在系統中路徑,Android.mk 文件開始時必須定義該變量。
include $(CLEAR_VARS), 代表清楚上一次構建過程的全部全局變量,由於在一個 Makefile 編譯腳本中,會使用大量的全局變量,使用這行腳本代表須要清除掉全部的全局變量。
LOCAL_SRC_FILES, 要編譯的 C 或者 CPP 的文件,注意這裏不須要列舉頭文件,構建系統會自動幫組開發者依賴這些文件。
LOCAL_LDLIBS:= -L$定編譯過程所依賴的提供的動態靜態庫,變量表明的是下面的目錄(SYSROOT)/usr/lib -Ilog -IOpenSLES -IGLESv2 -IEGL -Iz,定編譯過程所依賴的 NDK 提供的動態靜態庫, SYSROOT 變量表明的是 NDK_ROOT 下面的目錄 $NDK 提供的動態與靜態庫,SYSROOT 變量表明的是 NDK_ROOT 下面目錄 $NDK_ROOT/platforms/android-21/arch-arm, 而在這個目錄的 usr/lib/ 目錄下有不少對應的 so 的動態庫以及 .a 的靜態庫。
LOCAL_CFLAGS , 編譯 C 或者 CPP 的編譯標誌,在實際編譯的時候會發送給編譯器。好比經常使用的實例是加上 -DAUTO_TEST , 而後在代碼中就能夠利用條件判斷 #ifdef AUTO_TEST 來作一些與自動化測試相關的事情。
LOCAL_LDFLAGS, 連接標誌的可選列表,當對目標文件進行連接以生成輸出文件的時候,將這些標誌帶給連接器。該指令與 LOCAL_LDLIBS 有些相似,通常狀況下,該選項會用於指定第三方編譯的靜態庫,LOCAL_LDLIBS 常常用於指定系統的庫(好比 log、OpenGLES、EGL 等)。
LOCAL_MODULE, 該模塊的編譯的目標名,用於區分各個模塊,名字必須是惟一併不包含空格的,若是編譯目標是 so 庫,那麼該 so 庫的名稱就是 lib 項目名 .so。
include $(BUILD_SHARED_LIBRARY) ,其實相似的 include 還有不少,都是構建系統提供的內置變量,該變量的意義是構建動態庫,其餘的內置變量還包括以下幾種。
2. Application.mk
效果:
Makefile 的方式咱們只作一個瞭解,由於之後咱們構建 C/C++ 的 Android 項目都是用 cmake 方式來構建,因此咱們重點掌握 cmake 就行。
以前作 NDK 開發或者老的項目都是基於 Android.mk、Application.mk 來構建項目的,但從 AS 2.2 以後便開始採用 CMake 的方式來構建 C/C++ 項目,採用 CMake 相比與以前的 Android.mk、Application.mk 方便簡單了許多。下面咱們簡單的來介紹下 cmake 基礎語法吧。
#1. 指定 cmake 的最小版本
cmake_minimum_required(VERSION 3.4.1)
#2. 設置項目名稱
project(demo)
#3. 設置編譯類型
add_executable(demo test.cpp) # 生成可執行文件
add_library(common STATIC test.cpp) # 生成靜態庫
add_library(common SHARED test.cpp) # 生成動態庫或共享庫
#4. 明確指定包含哪些源文件
add_library(demo test.cpp test1.cpp test2.cpp)
#5. 自定義搜索規則並加載文件
file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp")
add_library(demo ${SRC_LIST}) //加載當前目錄下全部的 cpp 文件
## 或者
file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
## 或者
aux_source_directory(. SRC_LIST)//搜索當前目錄下的全部.cpp文件
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
#6. 查找指定庫文件
find_library(
log-lib //爲 log 定義一個變量名稱
log ) //ndk 下的 log 庫
#7. 設置包含的目錄
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
)
#8. 設置連接庫搜索目錄
link_directories(
${CMAKE_CURRENT_SOURCE_DIR}/libs
)
#9. 設置 target 須要連接的庫
target_link_libraries( # 目標庫
demo
# 目標庫須要連接的庫
# log-lib 是上面 find_library 指定的變量名
${log-lib} )
#10. 指定連接動態庫或者靜態庫
target_link_libraries(demo libtest.a) # 連接libtest.a
target_link_libraries(demo libtest.so) # 連接libtest.so
#11. 根據全路徑連接動態靜態庫
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libtest.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libtest.so)
#12. 指定連接多個庫
target_link_libraries(demo
${CMAKE_CURRENT_SOURCE_DIR}/libs/libtest.a
test.a
boost_thread
pthread)
複製代碼
預約義變量 | 說明 |
---|---|
PROJECT_SOURCE_DIR | 工程的根目錄 |
PROJECT_BINARY_DIR | 運行 cmake 命令的目錄,一般是 ${PROJECT_SOURCE_DIR}/build |
PROJECT_NAME | 返回經過 project 命令定義的項目名稱 |
CMAKE_CURRENT_SOURCE_DIR | 當前處理的 CMakeLists.txt 所在的路徑 |
CMAKE_CURRENT_BINARY_DIR | target 編譯目錄 |
CMAKE_CURRENT_LIST_DIR | CMakeLists.txt 的完整路徑 |
CMAKE_CURRENT_LIST_LINE | 當前所在的行 |
CMAKE_MODULE_PATH | 定義本身的 cmake 模塊所在的路徑,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),而後能夠用INCLUDE命令來調用本身的模塊 |
EXECUTABLE_OUTPUT_PATH | 從新定義目標二進制可執行文件的存放位置 |
LIBRARY_OUTPUT_PATH | 從新定義目標連接庫文件的存放位置 |
以靜態庫構建項目
定義 native 接口
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testCmake();
}
/** * 測試 cmake 構建程序 */
public native static void testCmake();
}
複製代碼
編寫 cpp
// extern int main(); 這樣寫有坑,由於 main 方法是屬於 c 的,而當前是 CPP
extern "C" { //必須這樣定義
int main();
}
extern "C" JNIEXPORT void JNICALL
Java_com_devyk_cmake_1application_MainActivity_testCmake(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
__android_log_print(ANDROID_LOG_DEBUG, "DevYK", "main--->:%d", main());
}
複製代碼
編寫 CmakeLists.txt 文件
cmake_minimum_required(VERSION 3.4.1)
# 打印日誌
message("當前CMake的路徑是:${CMAKE_SOURCE_DIR}")
message("當前 CMAKE_ANDROID_ARCH_ABI 的路徑是:${CMAKE_ANDROID_ARCH_ABI}")
# 批量引入源文件
file(GLOB allCpp *.cpp)
# 加入cpp源文件
add_library(
native-lib
SHARED
# native-lib.cpp 替換 ${allCpp} 批量導入文件
${allCpp}
)
# 導入靜態庫
add_library(test_a STATIC IMPORTED)
# 開始真正的導入
set_target_properties(test_a PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libtest.a)
# 只能找系統的
find_library(
log-lib
log)
message("當前的log路徑在哪裏啊 >>>>>>>>>>>>>>>>> ${log-lib}")
#開始連接指定的庫
target_link_libraries(
native-lib
${log-lib}
test_a
)
複製代碼
app/build.gradle cmake 配置
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
// cppFlags "" // 默認包含四大平臺
abiFilters 'armeabi-v7a'//編譯armeabi-v7a平臺
}
}
ndk {
//過濾,只使用這個版本的庫,不然默認的但是4個平臺
abiFilters 'armeabi-v7a'
}
}
...
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt" //指定 CMakeLists 路徑
}
}
}
複製代碼
測試結果
以動態庫構建項目
代碼加載 so 庫到手機中
static {
System.loadLibrary("Test");
System.loadLibrary("native-lib");
}
複製代碼
so 庫導入在 main/jniLibs 下
CmakeLists.txt 配置
cmake_minimum_required(VERSION 3.4.1)
# 打印日誌
message("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>>>")
message("當前CMake的路徑是:${CMAKE_SOURCE_DIR}")
message("當前 CMAKE_ANDROID_ARCH_ABI 的路徑是:${CMAKE_ANDROID_ARCH_ABI}")
# 批量引入源文件
file(GLOB allCpp *.cpp)
# 加入cpp源文件
add_library(
native-lib
SHARED
# native-lib.cpp
${allCpp}
)
# 導入靜態庫
#add_library(test_a STATIC IMPORTED)
# 開始真正的導入
#set_target_properties(test_a PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libtest.a)
# 導入動態庫
add_library(test_so SHARED IMPORTED)
# 早起的cmake ANDROID_ABI == 當前CPU平臺
set_target_properties(test_so PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libTest.so)
# 只能找系統的
find_library(
log-lib
log)
message("當前的log路徑在哪裏啊 >>>>>>>>>>>>>>>>> ${log-lib}")
# CMAKE_SOURCE_DIR == D:\NDK\CoursewareCreate\ndk_12\project\ndk12_cmake\app\src\main\cpp\CMakeLists.txt
target_link_libraries(
native-lib
${log-lib}
test_so
)
複製代碼
測試
到這裏,mk 和 cmake 入門基礎知識就講完了,想要所有掌握還須要本身多動手實踐一翻。
該篇文章主要講解了如何利用 NDK 對 C 程序進行交叉編譯,以及交叉編譯後的動態靜態庫在 Android 項目中的使用,還有 makefile 和 cmake 在 Android 的使用,本篇文章也是比較基礎的,對於後續使用或者編譯 FFmpeg 打下基礎。