前期準備
- 下載下傳 Android-NDK
-
下載下傳 FFmpeg 源碼 注意:筆者用的是 NDK-21 和 ffmpeg-4.4 進行編譯,如果版本不同可能會有所不同。
測試:mac 與 ubuntu 下的NDK20 - NDK22 和 ffmpeg 4.0 - ffmpeg 4.4,均可使用。
本文你可以了解到
- NDK20 - NDK22 提供的交叉編譯工具鍊主要目錄
- 使用 clang 交叉編譯出 Android 平台可以使用的 libffmpeg.so 庫
- 部分編譯細節
一、NDK 提供的交叉編譯工具鍊主要目錄與檔案
從 NDK20 - NDK22 編譯工具鍊目錄結構基本沒變,這裡以 NDK21 作為示範( NDK 在 windows、linux、mac 中的目錄基本一樣)
如上圖,主要用的就是這幾個目錄,其中編譯 FFmpeg 需要用到的 gcc 庫就在 aarch64、arm、x86_64、x86 這幾個檔案夾中,這裡先介紹一下這幾個名字在 Android 中的不同平台庫的聯系。
aarch64:帶這個字首的目錄都是與 arm64-v8a 庫相關
arm:帶這個字首的目錄都是與 armeabi-v7a 庫相關
x86_64:帶這個字首的目錄都是與 x86_64 庫相關
x86:帶這個字首的目錄都是與 x86 庫相關
1.clang 編譯工具
進入目錄 llvm->prebuilt->darwin-x86_64->bin 裡面都是與交叉編譯相關的檔案,我們以 clang 進行編譯,是以主要關注的是以 clang、clang++ 結尾的檔案,clang 用于編譯 c 檔案、clang++ 用于編譯 c++ 檔案。
- 這裡需要注意的是:21 和 i686
- 21:表示編譯出的庫支援的最低 Android 版本
- i686:表示編譯出 x86 庫平台的編譯工具
上面的 NDK 目錄名在各系統上的對應形式:
mac:darwin-x86_64
linux:linux-x86_64
windows:windows-x86_64
注意:下面都以 mac 系統下的 NDK 目錄進行介紹
2.編譯環境,需要用到的庫
庫和頭檔案所在的目錄在 darwin-x86_64 下的 sysroot 目錄,其中頭檔案在 include 目錄,庫在 lib 目錄,了解完這些,就可以開始編譯了。
相關學習資料推薦,點選下方連結免費報名,先碼住不迷路~】
【免費分享】音視訊學習資料包、大廠面試題、技術視訊和學習路線圖,資料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以點選加群免費領取~
二、使用 clang 交叉編譯出 Android 平台可以使用的 libffmpeg.so 庫
進入 FFmpeg 源碼根目錄
1.建立編譯腳本:build_ffmpeg_android.sh
腳本的主要内容如下:
#!/bin/sh
# NDK 所在的路徑
NDK=/Users/mac/Library/Android/sdk/ndk/21.4.7075529
# 需要編譯出的平台,這裡是 arm64-v8a
ARCH=aarch64
# 支援的最低 Android API
API=21
# 編譯後輸出目錄,在 ffmpeg 源碼目錄下的 /android/arm64-v8a
OUTPUT=$(pwd)/android/arm64-v8a
# NDK 交叉編譯工具鍊所在路徑
TOOLCHAIN=/Users/mac/Library/Android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/darwin-x86_64
build() {
./configure \
--target-os=android \
--prefix=$OUTPUT \
--arch=$ARCH \
--sysroot=$TOOLCHAIN/sysroot \
--disable-static \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-debug \
--disable-doc \
--disable-avdevice \
--enable-shared \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
--cc=$TOOLCHAIN/bin/aarch64-linux-android$API-clang \
--cxx=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++ \
--extra-cflags="-fpic"
make clean all
make -j12
make install
}
build
這個shell腳本,大體上其實還是很容易懂的,比如
--disabble-static 禁止輸出靜态庫
--enable-shared 輸出動态庫
--arch 用于配置輸出的so庫是什麼架構的
--prefix 用于配置輸出的so庫的存放路徑
enable-cross-compile 開啟多平台編譯,也就是可以編譯多個平台的庫
更多的選項可以檢視官網的介紹,這裡不再多說。
接下來重點來講一下幾個選項:
- target-os --target-os=android:在舊版本的 FFmpeg 中,對Android平台的支援并不是很完善,并沒有 android 這個target,是以在一些比較老的文章中都會提到,編譯Android平台的so庫,需要對 configure 做修改,否則會按照 linux 标準的方式輸出so庫,其命名方式和Android的so不一樣,Android是無法加載的,是以編譯時,FFmpeg 源碼版本最好選和筆者的一緻。
問題一:Linux 下輸出的 so 庫,Android 下無法加載
-
sysroot --sysroot=$TOOLCHAIN/sysroot: 用于配置交叉編譯環境的 根路徑 ,編譯的時候會預設從這個路徑下去尋找 usr/includeusr/lib 這兩個路徑,進而找到相關的頭檔案和庫檔案。
NDK20-NDK22 系統的頭檔案和庫檔案就是在 $SYSYROOT/usr/include 和 $SYSYROOT/usr/lib 中。
extra-cflags 給編譯器指定一些編譯标志,例如:
設定頭檔案路徑:格式 -I頭檔案路徑
設定編譯出的二進制檔案為位置無關碼檔案:格式 -fpic
至于為什麼需要編譯出位置無關碼檔案,就是因為 打包 出的 so 庫就是由多個為位置無關碼的二進制檔案組成的。
extra-ldflags 給連結器指定一些連結标志,例如:
設定需要連結的庫的路徑:格式 -L庫檔案路徑
輸出庫并設定名字:格式 -o 庫名
設定需要連結的庫:格式 -l庫名
這裡需要注意:
假設庫名為:a
-o 庫名 需要帶 lib 字首,與 .so/.a 字尾的部分,如 -o liba.so
-l庫名 是不帶 lib 字首,與 .so/.a 字尾的部分,如 -la
關于編譯與連結标志的問題,想了解詳情可以檢視這裡。
- cross-prefix 配置交叉編譯的編譯工具的字首,就是上面介紹的交叉編譯相關的檔案所在的目錄内的檔案名的字首,如:編譯 arm64-v8a 平台的就是 aarch64-linux-android-,而編譯 armeabi-v7a 平台的就是 arm-linux-androideabi-,具體是什麼,到 交叉編譯工具鍊目錄下的 bin 目錄檢視即可。
- cc
- cxx 這兩項就是配置上面說的使用 Android 自帶的 clang 工具的具體路徑。
2.開始編譯
通過終端進入到 FFmpeg 源碼根目錄,并運作剛剛寫好的編譯腳本,
sh build_ffmpeg_android.sh
運作結果如下
如上圖,紅框内的就是我們編譯出的是以檔案,但是這麼多個 so 檔案,用起來也麻煩,是以我們要把它們打包成一個 so 檔案。
3.将多個庫打包成一個庫
- 修改編譯腳本,修改後如下
#!/bin/sh
# ...省略了不變的部分
SYSROOT_L=$TOOLCHAIN/sysroot/usr/lib/aarch64-linux-android
GCC_L=$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/lib/gcc/aarch64-linux-android/4.9.x
build() {
# ...省略了不變的部分
--disable-shared \
--enable-static \
--extra-cflags="-fpic -I$OUTPUT/include" \
--extra-ldflags="-lc -ldl -lm -lz -llog -lgcc -L$OUTPUT/lib"
# ...省略了不變的部分
}
package_library() {
$TOOLCHAIN/bin/aarch64-linux-android-ld -L$OUTPUT/lib -L$GCC_L \
-rpath-link=$SYSROOT_L/$API -L$SYSROOT_L/$API -soname libffmpeg.so \
-shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o $OUTPUT/libffmpeg.so \
-lavcodec -lpostproc -lavfilter -lswresample -lavformat -lavutil -lswscale -lgcc \
-lc -ldl -lm -lz -llog \
--dynamic-linker=/system/bin/linker
# 設定動态連結器,不同平台的不同,android 使用的是/system/bin/linker
}
build
package_library
上面列出的隻是片段代碼,隻列出了做出了改變的部分,大緻流程就是:
clang 編譯出靜态庫 -> 使用 Android 自帶的連結器将編譯出的靜态庫打包成一個動态庫
接下來重點來講一下幾個選項:
- -rpath-link
When using ELF or SunOS, one shared library may require another. This happens when
an `"ld -shared"` link includes a shared library as one of the input files.
When the linker encounters such a dependency when doing a non-shared, non-relocatable
link, it will automatically try to locate the required shared library and include it in
the link, if it is not included explicitly. In such a case, the **-rpath-link** option
specifies the first set of directories to search. The **-rpath-link** option may
specify a sequence of directory names either by specifying a list of names separated by
colons, or by appearing multiple times.
上面是官方給出的介紹,這裡我鬥膽用一句話概括一下,可能不太準确,但是能了解就行了:
這是傳遞給連結器的一個标志,當我們使用的庫有依賴關系時,打包就需要按照依賴關系進行,否則會報錯,用了這标志,我們隻需要設定庫的目錄,不需要管依賴關系,連結時連結器幫我們處理。
- -soname 這個選項的解釋為:給庫添加一個别名,也就是可以通過别名引用庫,為什麼要起别名?因為我們一開始是先打出了多個靜态庫,靜态庫已經有它自己的名字了,如果不對它們統一的做别名映射,你會發現你加載庫的時候一直報錯,說找不到你指定的庫檔案,不信你可以嘗試一下哦^^。
- --dynamic-linker 這個選項用于設定動态連結器,還記得上面提出的問題一嗎,為了解決這個問題,這裡設定成 Android 使用的連結器,就是/system/bin/linker,關于這點,可以看看這裡
4.再次編譯
運作結果如下
至此,我們就完成了 FFmpeg 的編譯工作。
三、腳本使用介紹
筆者把編譯腳本封裝了一下,以适應友善的編譯出 Android 各個平台的 so 庫,腳本連結在文章開頭,下面介紹使用步驟:
- 将腳本放在 FFmpeg 源碼根目錄
- 以文本方式打開腳本,簡單的修改下面列出的幾個參數
# 建構的最低支援 API 等級
API=21
# 在什麼系統上建構,mac:darwin,linux:linux,windows:windows
OS_TYPE=darwin
# 自己本機 NDK 所在目錄
NDK=/Users/mac/Library/Android/sdk/ndk/21.4.7075529
# 目标檔案輸出目錄,預設是目前目錄下的 android 目錄
OUTPUT=$(pwd)/android/$ABI
- 打開終端,進入到 FFmpeg 源碼目錄,執行腳本:sh build_ffmpeg_android.sh 1
執行規則
sh build_ffmpeg_android.sh 後可以附帶 1、2、3、4 這四項,下面說明這四項的意義
1:建構出 arm64-v8a 架構的庫檔案
2:建構出 armeabi-v7a 架構的庫檔案
3:建構出 x86_64 架構的庫檔案
4:建構出 x86 架構的庫檔案
如果想要建構多個平台的,可以附帶多項,中間通過空格分隔開即可,如建構全平台:
sh build_ffmpeg_android.sh 1 2 3 4
四、遇到的問題
- 運作腳本,顯示沒權限
修改檔案權限,再次運作即可:chmod 777 build_ffmpeg_android.sh
- 運作腳本,顯示腳本中存在無法識别的字元不能運作
解決方法一:
Visual Studio Code 代替記事本,重新編輯
解決方法二:
安裝 dos2unix 軟體
mac 下:brew install dos2unix
ubuntu 下:sudo apt install dos2unix
使用:dos2unix build_ffmpeg_android.sh
然後再次運作即可
編譯x86庫的時候報錯,錯誤如下
這個時候隻需要在 ./configure 後加上:--disable-asm 即可,然後重新編譯就沒問題了,因為 x86 平台移除了寄存器,如果不禁用這一項就會報錯,詳細原因在這。
- 将多個庫打包時用到的 gcc 的庫在别的目錄也有
這裡容許我吐槽一下,我認為是一個巨坑... 因為我打包的時候一開始用的是别的目錄的 gcc ,部分平台的打包 是正常的,但是 armeabi-v7a 平台的一直打包不成功,試了很久才發現現在用的目錄也有,并且沒問題。如果你 也遇到了同樣的問題,那就換成我介紹的目錄的 gcc 就沒問題了。
五、總結
- 在 Android 端的編譯問題很多時候是因為對編譯工具鍊的目錄不熟,找不到對應的庫
- 編譯時,如果遇到缺哪個庫,去上文介紹的目錄找到并且在編譯時加進去就可以了
- 多動手實踐,你會發現“書上得來終覺淺”這句話的真谛 最後,如果你覺得這篇文章對你有所幫助,那就點個贊呗 ^w^
參考文章
FFmpeg so庫編譯
如何跨平台編譯能執行在 Android 上的檔案
FFmpeg x86 編譯問題
連結器的-rpath介紹
将FFmpeg編譯成一個libffmpeg.so庫
原文 https://juejin.cn/post/6990246430682120223