什么是交叉編譯?
引自百度百科的定義:
交叉編譯
是在一個平臺上生成另一個平臺上的可執(zhí)行代碼册招。同一個體系結(jié)構(gòu)可以運行不同的操作系統(tǒng)岔激;同樣,同一個操作系統(tǒng)也可以在不同的體系結(jié)構(gòu)上運行是掰,就是說使用其他平臺來編譯Android或其他系統(tǒng)的庫虑鼎,于是,交叉編譯出現(xiàn)键痛。
舉例來說炫彩,我們常說的x86 Linux平臺實際上是Intel x86體系結(jié)構(gòu)和Linux for x86操作系統(tǒng)的統(tǒng)稱;而x86 WinNT平臺實際上是Intel x86體系結(jié)構(gòu)和Windows NT for x86操作系統(tǒng)的簡稱絮短。
如何實現(xiàn)交叉編譯江兢?
1、編譯環(huán)境
我們知道PC上的環(huán)境和手機上的運行環(huán)境是絕然不同的戚丸,很顯然交叉編譯非常重要的划址,要配置好編譯過程中使用到的相關(guān)的環(huán)境,而這個環(huán)境其實就是目標(biāo)機器(比如Android手機)運行時的環(huán)境限府,比如在Android上開發(fā)C/C++夺颤,那么他就需要NDK環(huán)境去編譯,這樣在Android上才可以使用胁勺。
2世澜、編譯工具鏈
1、對于C/C++的編譯署穗,通常有兩個工具 GCC 和 CLANG 寥裂。GCC 可能大家都有聽說過,這是一個老牌的編譯工具案疲,不僅可以編譯C/C++封恰,也可以編譯Java,Object-C褐啡,Go等語言诺舔。
2、CLANG 則是一個效率更高的C/C++編譯工具,并且兼容GCC低飒,Google在很早以前就開始建議使用clang進行編譯许昨,并且在 ndk 17 以后,把 GCC 移除了褥赊,全面推行使用 CLANG ,感興趣可以去工具鏈了解一下糕档。
3、 Clang是一個C++編寫拌喉、基于LLVM速那、發(fā)布于LLVM BSD許可證下的C/C++、Objective-C/Objective-C++編譯器司光。它與GNU C語言規(guī)范幾乎完全兼容(當(dāng)然琅坡,也有部分不兼容的內(nèi)容悉患,包括編譯命令選項也會有點差異)残家,并在此基礎(chǔ)上增加了額外的語法特性,比如C函數(shù)重載(通過attribute((overloadable))來修飾函數(shù))售躁,其目標(biāo)(之一)就是超越GCC坞淮。
4、LLVM是構(gòu)架編譯(compiler)的框架系統(tǒng)陪捷,以C++編寫而成回窘,用于優(yōu)化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)市袖、運行時間(run-time)以及空閑時間(idle-time)啡直,對開發(fā)者保持開放,并兼容已有腳本苍碟。
-----------------以上引用百度百科
編譯原理
編譯就是將高級語言編寫的程序轉(zhuǎn)換為二進制目標(biāo)程序過程酒觅,在開始之前先來大概了解編譯原理,至少知道每一步做了什么微峰。
預(yù)處理
完成宏替換舷丹、文件引入,以及去除空行蜓肆、注釋等颜凯,為下一步做準(zhǔn)備。
-
也就是對各種預(yù)處理命令仗扬,包括頭文件的包含症概、宏定義、條件編譯的選擇等早芭。
#include<stdio.h> //test hello int main(){ printf("hello world!\n"); return 0; }
預(yù)處理命令:clang -E test.c -o test.i
主要作用就是處理關(guān)于 #的指令:
- 刪除
#define
彼城,展開所有宏定義。例#define hello "helloworld"
。 - 處理條件預(yù)編譯
#if, #ifdef, #if, #elif,#endif
精肃。 - 處理
#include
預(yù)編譯指令秤涩,將包含的.h
文件插入對應(yīng)位置。這可是遞歸進行的司抱,文件內(nèi)可能包含其他.h
文件筐眷。 - 刪除所有注釋
/**/,//
习柠。 - 添加行號和文件標(biāo)識符匀谣。用于顯示調(diào)試信息:錯誤或警告的位置。
- 保留
#pragma
編譯器指令资溃。(1)設(shè)定編譯器狀態(tài)武翎,(2)指示編譯器完成一些特定的動作
編譯
主要作用:1.掃描(詞法分析),2.語法分析溶锭,3.語義分析宝恶,4.源代碼優(yōu)化(中間語言生成),5.代碼生成趴捅,目標(biāo)代碼優(yōu)化垫毙。
編譯命令:clang -S test.i -o test.s
匯編
主要作用:匯編器是將匯編代碼轉(zhuǎn)變成可以執(zhí)行的指令,生成 目標(biāo)文件拱绑。
匯編命令: clang -c test.s -o test.o
鏈接
主要作用:將多個目標(biāo)文件以及所需的庫文件鏈接成生成可執(zhí)行目標(biāo)文件的過程综芥。
鏈接命令: clang test.o -o test
靜態(tài)庫(.a)和動態(tài)庫(.so)
靜態(tài)庫實際就是一些目標(biāo)文件(一般是.0結(jié)尾,即我們匯編產(chǎn)生的文件)的集合猎拨,靜態(tài)庫一般以.a結(jié)尾膀藐,就是經(jīng)過打包產(chǎn)生.a文件,只用于生成可執(zhí)行文件階段红省。
也就是說當(dāng)我們需要生成可執(zhí)行文件時额各,需要將這個已經(jīng)打包好的靜態(tài)庫,經(jīng)過鏈接器將匯編產(chǎn)生的.o文件的集合即靜態(tài)庫中的代碼直接復(fù)制到可執(zhí)行文件类腮。
那么在ndk的工具鏈中對應(yīng)了
aarch64-linux-android-ar
臊泰,ndk中就是這個可執(zhí)行文件將.o
打包成靜態(tài)庫:
linux下大打包成靜態(tài)庫命令:ar rcs libtest.a test.o
使用ndk的打包的命令:aarch64-linux-android-ar rcs libtest.a test.o.
aarch64-linux-android-as
這是匯編器動態(tài)庫在鏈接階段沒有復(fù)制到程序中,而是在程序運行時有系統(tǒng)動態(tài)加載到內(nèi)存中共程序使用蚜枢。
系統(tǒng)只需載入一次動態(tài)庫缸逃,不同的程序可以得到內(nèi)存中相同的動態(tài)庫的副本。
配置ubuntu系統(tǒng)編譯環(huán)境
首先我使用的是騰訊服務(wù)器使用XShell+Xftp操作服務(wù)器厂抽,為什么使用服務(wù)器編譯ffmpeg需频,我個人沒有mac電腦,而window系統(tǒng)編譯需要配置的環(huán)境太復(fù)雜筷凤,剛好碰到騰訊搞活動索性買了一年昭殉,當(dāng)然使用服務(wù)器的好處有很多苞七。
首先你需要先下載NDK。我這里使用的r17c.
1挪丢、 NDK 版本 r17c蹂风,ffmpeg 版本4.2.3
2、 在ubuntu系統(tǒng)中我們使用命令下載ndk:
wget https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip?hl=zh_cn
解壓 : tar xvf ffmpeg-4.2.3.tar
3乾蓬、下載ffmpeg:
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
4惠啄、配置環(huán)境變量
vim ~/.bashrc
PATH="$PATH:/usr/local/ffmepg/bin"
export JAVA_HOME=/home/ubuntu/jdk1.8.0_92
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
export NDKROOT=/home/ubuntu/android-ndk-r17c
export PATH=$PATH:$NDKROOT
source ~/.bashrc
實際上NDK的環(huán)境可以不用配置,因為我們在編譯腳本中已經(jīng)聲明了NDK的路徑任内,所以無需配置撵渡,當(dāng)然你可以直接引用這個環(huán)境變量。
編寫build for Android的Shell 腳本
在FFMpeg根目錄新建sh文件死嗦,命名為:build_android.sh趋距,復(fù)制腳本到build_android.sh, 內(nèi)容如下:
#!/bin/bash
#構(gòu)建腳本,沒有輸入?yún)?shù)越除,那么默認是64位,如:32 或 64 分別構(gòu)建32位 或 64位
archbit=$1
if [ $archbit -eq 32 ];then
echo "Start build for 32bit for ABI"
#32bit
#ABI是Application Binary Interface的縮寫节腐。
CPU='arm'
ABI='armeabi-v7a'
#架構(gòu) 32位
ARCH='arm'
ANDROID='androideabi'
NATIVE_CPU='armeabi-v7a'
else
#64bit
echo "Start build for 64bit for ABI"
#架構(gòu) 64位
CPU='aarch64'
ABI='arm64-v8a'
ARCH='arm64'
ANDROID='android'
NATIVE_CPU='arm64-v8a'
fi
echo "Biuld param={CPU = $CPU,ABP=$ABI,ARCH=$ARCH,ANDROID=$ANDOIRD,NATIVE_CPU=$NATIVE_CPU}"
#ndk 主目錄,如果已經(jīng)配置了環(huán)境變量廊敌,可以直接引用環(huán)境變量即可
NDKROOT=/home/ubuntu/android-ndk-r17c
#TOOLCHAIN 變量指向ndk中的交叉編譯gcc所在目錄
TOOLCHAIN=$NDKROOT/toolchains/$CPU-linux-$ANDROID-4.9/prebuilt/linux-x86_64
ISYSROOT=$NDKROOT/sysroot
ASM=$ISYSROOT/usr/include/$CPU-linux-$ANDROID
ADDI_CFLAGS=" -marm"
PREFIX=./android/$NATIVE_CPU
echo "NDKROOT=$NDKROOT,TOOLCHAIN=$TOOLCHAIN"
if [ $archbit -eq 32 ];then
FLAGS=" -I$ASM -isysroot $ISYSROOT -D__ANDROID_API__=21 -U_FILE_OFFSET_BITS -Os -fPIC -DANDROID -Wno-deprecated -mfloat-abi=softfp -marm"
else
FLAGS=" -I$ASM -isysroot $ISYSROOT -D__ANDROID_API__=21 -U_FILE_OFFSET_BITS -Os -fPIC -DANDROID -Wno-deprecated"
fi
echo -e "\033[32m build start \033[0m"
./configure \
--prefix=$PREFIX \
--enable-small \
--disable-programs \
--disable-avdevice \
--disable-encoders \
--disable-muxers \
--disable-filters \
--disable-doc \
--enable-cross-compile \
--cc=$TOOLCHAIN/bin/$CPU-linux-$ANDROID-gcc \
--enable-neon \
--cross-prefix=$TOOLCHAIN/bin/$CPU-linux-$ANDROID- \
--enable-static \
--disable-shared \
--sysroot=$NDKROOT/platforms/android-21/arch-$ARCH \
--extra-cflags="$FLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
--arch=$CPU \
--target-os=android
#執(zhí)行清理
sudo make clean
#執(zhí)行腳本生成的 makefile
sudo make -j8
sudo make install
echo -e "\033[32m build successful\033[0m"
最后在中端輸入:
sudo ./build_android.sh 32
如果想構(gòu)建64位就把32改為64铜跑,執(zhí)行腳本门怪。
編譯參數(shù)詳解
-
ndk 主目錄
NDKROOT=/home/ubuntu/android-ndk-r17c
實際上我已經(jīng)配置好了NDK環(huán)境變量骡澈,可以直接在編譯腳本$NDKROOT使用。
-
TOOLCHAIN 變量指向ndk中的交叉編譯gcc所在目錄掷空,這些我們在編譯腳本中通過條件邏輯需要編譯那種類型的cup架構(gòu)肋殴,而在工具鏈中32位和64位的工具鏈目錄是不一樣的,詳情參照編譯腳本當(dāng)中的配置坦弟。
TOOLCHAIN=$NDKROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/
-
-extra-cflags 會傳值給gcc的參數(shù)护锤, FLAGS和INCLUDES變量
可以在 AS NDK工程中的.cxx\cmake\debug\armeabi-v7a\build.ninja拷貝,需要注意的修改地址, 替換ndk主目錄酿傍,刪除-IE:/AndroidStudioprojects/NDKExampl/app/src/main/cpp/inc和- LE:/AndroidStudioprojects/NDKExampl/app/src/main/cpp/../jniLibs/armeabi-v7a烙懦,需要注意的是如果你加入不被支持的選項,會出現(xiàn)C compiler test failed的錯誤赤炒,具體哪些不被識別氯析,請報錯之后查看../ffbuild/config.log文件,比如在編譯arm64-v8a時.那就把不識別的刪掉就可以:../prebuilt/linux-x86_64/bin/arm-linux-androideabi-clang is unable to create an executable file.C compiler test failed.
--disabble-static --enable-shared 分別用于禁止輸出靜態(tài)庫,以及輸出動態(tài)庫莺褒;
./confiure 腳本掩缓,用于生成makefile
--prefix : 安裝目錄,編譯后文件的輸出目錄,用于配置輸出的so庫的存放路徑。
--enable--small 優(yōu)化大小
--disable-programs 不編譯ffmpeg程序(命令行工具)遵岩,我們是需要獲得靜態(tài)(動態(tài))庫
--disable-avdevice 關(guān)閉avdevice模塊你辣,此模塊在android中無用
--disable-encoders 關(guān)閉所有編碼器(播放器不需要編碼)
--disable-muxers 關(guān)閉所有復(fù)用器(封裝器),不需要生成mp4這樣的文件,所以關(guān)閉
--disable-filters 關(guān)閉視頻濾鏡(抖音那種開啟)
--enable-cross-compile 開啟交叉編譯(ffmpeg比較 跨平臺 并不是所有庫都有這么happy的選項)
--cross-prefix 這個選項直譯為 交叉編譯前綴舍哄,指的是交叉編譯工具的前綴宴凉,他這個是找到ndk 中的工具鏈中的可執(zhí)行的編譯工具來進行編譯的。
設(shè)置編譯器表悬,不然會報錯跪解,系統(tǒng)默認會使用arm-linux-androideabi-clang,但是此編譯器在NDK不存在签孔,導(dǎo)致編譯失敗叉讥。--cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \ 設(shè)置編譯器,不然會報錯饥追,系統(tǒng)默認會使用arm-linux-androideabi-clang图仓,但是此編譯器在NDK不存在,導(dǎo)致編譯失敗但绕,這個和上一點有關(guān)聯(lián)的
--sysroot 用于配置交叉編譯環(huán)境的 根路徑 救崔,編譯的時候會默認從這個路徑下去尋找 usr/include usr/lib 這兩個路徑,進而找到相關(guān)的頭文件和庫文件捏顺。
--arch 設(shè)置編譯so庫的架構(gòu)六孵,當(dāng)前設(shè)置為arm,可以根據(jù)實際需求修改
--target-os=android 設(shè)-target-os=android:在舊版本的 FFmpeg 中幅骄,對Android平臺的支持并不是很完善劫窒,并沒有 android 這個target,所以在一些比較老的文章中都會提到拆座,編譯Android平臺的so庫主巍,需要對 configure 做以下修改,否則會按照 linux 標(biāo)準(zhǔn)的方式輸出so庫挪凑,其命名方式和Android的so不一樣孕索,Android是無法加載的。
--arch --cpu 用于配置輸出的so庫是什么架構(gòu)的
需要注意的是:
-
--cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \ 這條指定我整了幾天躏碳,這個參數(shù)就是指定編譯器搞旭,不然會報錯,系統(tǒng)默認會使用arm-linux-androideabi-clang菇绵,但是此編譯器在NDK不存在肄渗,導(dǎo)致編譯失敗:
../prebuilt/linux-x86_64/bin/arm-linux-androideabi-clang is unable to create an executable file.C compiler test failed.
就是ndk中文件的權(quán)限一定是要有可執(zhí)行文件的權(quán)限脸甘。
--extra-cflags里不要加不被支持的選項恳啥,否則會出現(xiàn)C compiler test failed的錯誤,具體哪些不被識別丹诀,請報錯之后查看../ffbuild/config.log文件,比如在編譯arm64-v8a時.那就把不識別的刪掉就可以了:
aarch64-linux-android-gcc: error: unrecognized command line option '-mfloat-abi=softfp'
編譯完成最后會在我們制定的prefix目錄中生成編譯的庫:
使用CLANG編譯FFmpeg
下載Android NDK
Android 的 NDK 已經(jīng)迭代了很多版本钝的,在 r17c 以后翁垂,Google正式移除 GCC ,不再支持 GCC 硝桩,新版本的 NDK 都是使用 CLANG 進行編譯沿猜。
這里就使用目前最新的 NDK r21 版本來編譯。
最主要的就是這兩個路徑:
編譯工具鏈目錄:
toolchains/llvm/prebuilt/linux-x86_64/bin
交叉編譯環(huán)境目錄:
toolchains/llvm/prebuilt/linux-x86_64/sysroot
在NDK r21這兩個路徑和NDKr17是不同的碗脊。
我們選擇 CPU 架構(gòu) armv7a啼肩,Android版本 21,當(dāng)然你可以選擇最低支持版本比如19也是可以的衙伶。
這個兩個是交叉編譯的工具鏈一個是編譯C的另一個是編譯C++的:
armv7a-linux-androideabi21-clang
armv7a-linux-androideabi21-clang++
下面就是編譯的Shell腳本:
#!/bin/bash
#構(gòu)建日志輸出文件
LOG_FILE=./android/config.log
#ndk 主目錄祈坠,如果已經(jīng)配置了環(huán)境變量,可以直接引用環(huán)境變量即可
NDKROOT=/home/ubuntu/android-ndk-r21
#TOOLCHAIN 變量指向ndk中的交叉編譯gcc所在目錄
TOOLCHAIN=$NDKROOT/toolchains/llvm/prebuilt/linux-x86_64
ISYSROOT=$NDKROOT/sysroot
ASM=$ISYSROOT/usr/include/arm-linux-androideabi
ADDI_CFLAGS=" -marm"
PREFIX=./android2/armeabi-v7a
echo "NDKROOT=$NDKROOT,TOOLCHAIN=$TOOLCHAIN"
FLAGS=" -I$ASM -isysroot $ISYSROOT -D__ANDROID_API__=21 - U_FILE_OFFSET_BITS -Os -fPIC -DANDROID -Wno-deprecated -mfloat-abi=softfp -marm"
echo -e "\033[32m build start \033[0m"
./configure \
--prefix=$PREFIX \
--enable-small \
--disable-programs \
--disable-avdevice \
--disable-encoders \
--disable-muxers \
--disable-filters \
--disable-doc \
--enable-cross-compile \
--cc=$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--enable-neon \
--enable-static \
--disable-shared \
--sysroot="$TOOLCHAIN/sysroot" \
--extra-cflags="$FLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
--arch=arm \
--target-os=android
#執(zhí)行清理
sudo make clean
#執(zhí)行腳本生成的 makefile
sudo make -j8
sudo make install
echo -e "\033[32m build successful\033[0m"
需要注意ndk-r21 已經(jīng)把sysroot移動到 $TOOLCHAIN/sysroot
矢劲,而r17c的 sysroot還在 $NDKROOT/platforms/android-21/arch-arm
注意ndk-r21中 --cc也是要指定的赦拘,因為他的cross-prefix命名規(guī)則不一樣了:
比如:--cc=$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang和cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi-
明顯不一樣了。
ndk r21版本-----------------------默認的 cc ar nm 路徑前綴是一樣的芬沉,但是在r21之后NDK的ar/nm 和 cc的前綴是不一樣了躺同,如下:
- 工具鏈:/home/ubuntu/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin
1、構(gòu)建armeabi64-v8a:
aarch64-linux-android-ar
aarch64-linux-android-nm
aarch64-linux-android21-clang
2丸逸、構(gòu)建armeabi-v7a:
arm-linux-androideabi-ar
arm-linux-androideabi-nm
armv7a-linux-androideabi21-clang
這個問題怎么解決蹋艺,網(wǎng)上有人說修改ffmpeg的configure文件增加corss-prefix-clang命令,是可以解決這個問題黄刚,但是我們有更簡單的捎谨?
這個時候就需要:
指定C的編譯工具--cc=$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang
指定交叉編譯工具前綴而這個前綴 會匹配到ar、nm等編譯工具--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi-
那么我們看看ffmpeg中configure的腳本:
ar_default="ar"
cc_default="gcc"
cxx_default="g++"
host_cc_default="gcc"
doxygen_default="doxygen"
//如果是android 那么就是clang
set_default target_os
if test "$target_os" = android; then
cc_default="clang"
fi
//cross_prefix默認是[] 意思是"",所以一般我們都需要指定cross_prefix
ar_default="${cross_prefix}${ar_default}"
cc_default="${cross_prefix}${cc_default}"
nm_default="${cross_prefix}${nm_default}"
看到了嗎隘击?ar/nm 和 cc的前綴是不一樣的侍芝,前者是 arm-linux-androideabi- , 后者是 armv7a-linux-androideabi16-埋同。
所以我們直接在命令中直接修改覆蓋了默認的值:
--cc=$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang
覆蓋默認值就可以了,那么ar和nm我們可以使用默認值棵红。
最后我建議大家都換成Clang編譯凶赁,Clang會對編譯的代碼做優(yōu)化,感興趣的可以連接Clang編譯器逆甜。
然后要記住每次官方發(fā)布新版NDK時自行使用最新的版本重新編譯
參考鏈接
在Android項目中調(diào)用FFmpeg命令
在Android項目中調(diào)用FFmpeg命令
FFmpegAndroid
EpMedia