天天看點

Android-NDK-clang 編譯 FFmpeg

作者:音視訊流媒體技術

前期準備

  1. 下載下傳 Android-NDK
  2. 下載下傳 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 中的目錄基本一樣)

Android-NDK-clang 編譯 FFmpeg

如上圖,主要用的就是這幾個目錄,其中編譯 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++ 檔案。

Android-NDK-clang 編譯 FFmpeg
Android-NDK-clang 編譯 FFmpeg
Android-NDK-clang 編譯 FFmpeg
Android-NDK-clang 編譯 FFmpeg
  • 這裡需要注意的是:21 和 i686
  • 21:表示編譯出的庫支援的最低 Android 版本
  • i686:表示編譯出 x86 庫平台的編譯工具

上面的 NDK 目錄名在各系統上的對應形式:

mac:darwin-x86_64

linux:linux-x86_64

windows:windows-x86_64

注意:下面都以 mac 系統下的 NDK 目錄進行介紹

2.編譯環境,需要用到的庫

Android-NDK-clang 編譯 FFmpeg

庫和頭檔案所在的目錄在 darwin-x86_64 下的 sysroot 目錄,其中頭檔案在 include 目錄,庫在 lib 目錄,了解完這些,就可以開始編譯了。

相關學習資料推薦,點選下方連結免費報名,先碼住不迷路~】

【免費分享】音視訊學習資料包、大廠面試題、技術視訊和學習路線圖,資料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以點選加群免費領取~

Android-NDK-clang 編譯 FFmpeg

二、使用 clang 交叉編譯出 Android 平台可以使用的 libffmpeg.so 庫

進入 FFmpeg 源碼根目錄

Android-NDK-clang 編譯 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           

運作結果如下

Android-NDK-clang 編譯 FFmpeg

如上圖,紅框内的就是我們編譯出的是以檔案,但是這麼多個 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.再次編譯

運作結果如下

Android-NDK-clang 編譯 FFmpeg

至此,我們就完成了 FFmpeg 的編譯工作。

三、腳本使用介紹

筆者把編譯腳本封裝了一下,以适應友善的編譯出 Android 各個平台的 so 庫,腳本連結在文章開頭,下面介紹使用步驟:

  1. 将腳本放在 FFmpeg 源碼根目錄
  2. 以文本方式打開腳本,簡單的修改下面列出的幾個參數
# 建構的最低支援 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           
  1. 打開終端,進入到 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

四、遇到的問題

  1. 運作腳本,顯示沒權限
修改檔案權限,再次運作即可:chmod 777 build_ffmpeg_android.sh
  1. 運作腳本,顯示腳本中存在無法識别的字元不能運作

解決方法一:

Visual Studio Code 代替記事本,重新編輯

解決方法二:

安裝 dos2unix 軟體

mac 下:brew install dos2unix

ubuntu 下:sudo apt install dos2unix

使用:dos2unix build_ffmpeg_android.sh

然後再次運作即可

編譯x86庫的時候報錯,錯誤如下

Android-NDK-clang 編譯 FFmpeg

這個時候隻需要在 ./configure 後加上:--disable-asm 即可,然後重新編譯就沒問題了,因為 x86 平台移除了寄存器,如果不禁用這一項就會報錯,詳細原因在這。

  1. 将多個庫打包時用到的 gcc 的庫在别的目錄也有

這裡容許我吐槽一下,我認為是一個巨坑... 因為我打包的時候一開始用的是别的目錄的 gcc ,部分平台的打包 是正常的,但是 armeabi-v7a 平台的一直打包不成功,試了很久才發現現在用的目錄也有,并且沒問題。如果你 也遇到了同樣的問題,那就換成我介紹的目錄的 gcc 就沒問題了。

五、總結

  1. 在 Android 端的編譯問題很多時候是因為對編譯工具鍊的目錄不熟,找不到對應的庫
  2. 編譯時,如果遇到缺哪個庫,去上文介紹的目錄找到并且在編譯時加進去就可以了
  3. 多動手實踐,你會發現“書上得來終覺淺”這句話的真谛 最後,如果你覺得這篇文章對你有所幫助,那就點個贊呗 ^w^

參考文章

FFmpeg so庫編譯

如何跨平台編譯能執行在 Android 上的檔案

FFmpeg x86 編譯問題

連結器的-rpath介紹

将FFmpeg編譯成一個libffmpeg.so庫

原文 https://juejin.cn/post/6990246430682120223

繼續閱讀