Android之FFmpeg 5.0起航

語言: CN / TW / HK

theme: github highlight: androidstudio


本文介紹FFmpeg交叉編譯中的一些概念以及編譯的詳細過程,幫助你跳過一些交叉編譯的坑。

  • 系統:Ubuntu 22.04
  • FFmpeg:5.0.1、5.1.2
  • NDK:r21,r22,r23,r24,r25

NDK直到r18才正式移除GCC(原因檢視此issue),雖然此前很長一段時間內籌劃將Clang/LLVM作為GCC的替代者。接下來補充GCC和Clang/LLVM一些知識。

GCC 和 Clang/LLVM

GCCClang/LLVM編譯器聯絡甚密,它們實現編譯器的理念基本遵循最經典的三段式架構(編譯器具體實的理論基礎可以檢視編譯原理或者龍書)。

  • 編譯器: Compiler,一種將高階語言編寫的原始碼翻譯成低階語言的軟體,注意: 這裡的低階語言有很多型別,可以是組合語言也可以是機器語言,一些編譯器只負責將高階語言翻譯成組合語言而不是直接翻譯成機器語言,至於翻譯成機器語言的工作交給彙編器處理
  • 三段式設計: 將原始碼編譯生成目標語言的過程劃分成前端中端(也稱優化器)以及後端三個階段:

1)前端(front end):負責詞法、語法和語義分析,隨後將原始碼轉換為中間程式碼。

2)中端(middle end或optimizer ):對中間程式碼進行優化。

3)後端(back end):負責將中端產物轉化為針對各自平臺的機器碼(可以是彙編也可以是目的碼)。

GCC

1987年3月22日理查德·馬修·斯托曼釋出GCC第一個beta測試版(當時被稱為GNU C Compiler,也就是GNU C編譯器)。1999年4月,經過長時間的談判EGCS最終成為GCC(此時改名為GNU Compiler Collection,GNU 編譯器集合)的官方版本,並於當年7月31號釋出統一後的第一個GCC版本2.95,之後由GCC指導委員會的指導下不斷迭代更新。GCC中編譯器也遵循三段式設計(如下圖中所示,詳見GCC內部課程),有前端中端以及後端

Clang/LLVM

介紹Clang之前不得不說一下LLVM(原來被稱為Low Level Virtual Machine,其實它與傳統虛擬機器一點關係也沒有,準確說它是LLVM Project的全名),它源於2000年美國UIUC大學的一個研究專案,目的是提供一種現代的、基於 SSA 的編譯策略,並能支援任意程式語言的靜態和動態編譯(參考內容),最初由該大學的 Vikram AdveChris Lattner開發(有趣的是Chris Lattner主頁中稱自己是LLVM的原作者,理論還是實踐這裡就不清楚了)。現在它已經發展成為一個由多個子專案組成的巨型專案,原來的定義已經不適合,更為確切的描述是LLVM Project是模組化和可重用的編譯器和工具鏈技術的集合。下圖描述的是LLVM三段式設計實現,源於Chris Lattner在The Architecture of Open Source Applications一書中的介紹。

Clang(a C language family frontend for LLVM,LLVM中C家族語言的前端)誕生背景也是很有意思,2005年Chris Lattner進入蘋果公司,並致力於LLVM在蘋果開發體系中應用,自此蘋果公司開始在多個商業產品中廣泛使用LLVM,包括使用修改版的GCC(llvm-gcc) 作為Objective-C、C、C++等語言的編譯器前端,然並卵,受限於GCC社群的支援以及許可協議的限制(GCC 4.2.1是最後一個GPL v2許可證下的版本),蘋果公司選擇開發Clang,替代llvm-gcc作為LLVM的一個前端,並於2007年7月11開源。

小結

GCC是編譯器的集合;LLVM是編譯器+工具鏈的集合;Clang僅是用於替代當初llvm-gcc補足LLVM中C家族語言編譯器缺失的前端。如果僅從程式碼庫出發考慮,GCC就是烤全羊,LLVM就是全羊宴,Clang就是全羊宴中的一道菜,你要說誰更好吃,那就見仁見智了。

交叉編譯

交叉編譯實質上是在本機上為其它平臺生成庫或者可執行代程式,在這個過程中涉及使用工具鏈(toolchain,一組專用工具,一般包括編譯器、彙編器以及連結器,可以檢視此處),它們一般一個接一個的呼叫,上一個的輸出即是下一個的輸入。

NDK去GNU化

NDK存在兩套工具鏈如下:

  1. gcc/g++和GNU Binutils
  2. clang/clang++和LLVM tools(LLVM Binutils)

早期工具鏈1能夠投入實際生產環境,工具鏈2還處於預研階段,中期逐步將工具鏈2中的工具替換工具鏈1中的工具,後期工具鏈2完全投入生產環境,拋棄工具鏈1。

  • NDK r19之前工具鏈1位於$NDK/toolchains/資料夾下字尾為-4.9的子資料夾下,工具鏈2位於$NDK/toolchains/llvm/prebuilt/<host-tag>/bin/資料夾下。
  • NDK r18時,移除GCC,雖然工具鏈中還有名為gcc/g++的指令碼,但是已被強制指向clang/clang++。
  • NDK r19進一步將工具鏈中的GNU Binutils移動到$NDK/toolchains/llvm/prebuilt/<host-tag>下,自此去GNU化拉開帷幕。
  • NDK r22時,使用LLD替換ld。
  • NDK r23時GNU Binutils中除了GNU彙編器(一般被稱為gas)之外都被移除。
  • NDK r24時移除gas(替代者不是llvm-as),自此完成去GNU化,這也意味著自NDK r24開始NDK中只有一套工具鏈,即工具鏈2。

關於NDK中Binutils大致變化可以檢視BulildSystemMaintainers#binutils,更多細節請檢視NDK各個版本對應的Changelog

工具鏈變化:

由於NDK去GUN化,需要工具鏈2中的工具相容工具鏈1中的功能,所以一些工具並不直接對應於LLVM中的工具,如下表中所示:

| GNU | LLVM | 介紹 | | --------- | -------------------------------- | ------------------------ | | gcc | clang | C編譯器 | | g++ | clang++ | c++編譯器 | | as | clang(llvm-as處理的是LLVM IR,不是彙編原始碼) | 彙編器,將彙編程式碼生成目標檔案 | | ld | lld、clang(也能用於連結) | 連結器,將所有的目標檔案以及依賴的庫檔案進行連結 | | addr2line | llvm-addr2line | 查詢對應位置的原始碼資訊,一般用於除錯 | | ar | llvm-ar | 用於建立、修改和從檔案中提取的實用程式 | | nm | llvm-nm | 列出object檔案中的符號。 | | ranlib | llvm-ranlib | 生成存檔內容的索引 | | strip | llvm-strip | 裁剪符號表 |

需要注意表中是clang不是Clang,clang是 一個基於Clang和LLVM的C家族語言編譯器,它涉及預處理、解析、優化、程式碼生成、彙編和連結,你可以通過傳遞引數控制它在執行完整連結之前的任何階段停止;你也可以將clang理解成驅動器 Clang Driver,由它控制諸如編譯器、彙編器和連結器的執行,完成編譯過程。關於clang的命令引數可以檢視對應版本的文件或者直接在終端使用--help檢視,clang同時也相容gcc中的一些命令引數,編輯指令碼時可以仔細閱讀。

連結器GNU ld的替代者是lld或者clang,為什麼不是llvm-ld呢?其實llvm-ld也是存在的,只不過在LLVM 3.2中被移除,它的部分功能被其它程式取代。

llvm-ld and llvm-stub have been removed, llvm-ld functionality can be partially replaced by llvm-link | opt | {llc | as, llc -filetype=obj} | ld, or fully replaced by Clang.

lld是LLVM Linker的縮寫,llvm-link是LLVM bitcode linker的縮寫,這樣就很容易區分lld和llvm-link,二者都是連結器,前者GNU連結器的直接替代,接受與GNU相同的命令列引數和連結指令碼;後者用於處理多個LLVM bitcode檔案,並將它們連結在一起形成一個LLVM bitcode檔案。上述內容可以LLVM Download Page頁面中查各個版本的release notes 或者 docs,如下圖所示。關於llvm tools的一些命令描述文件可以從docs中檢視,例如Clang的命令列引數描述就可以從docs的index介面中的如下內容中找到:

  • Clang Compiler User’s Manual(Clang編譯器的使用者手冊)
  • Clang command line argument reference(Clang命令列引數引用)
  • Clang “man” pages

FFmpeg交叉編譯

一般步驟如下:

  1. 建立並啟動Ubuntu虛擬機器
  2. 搭建NDK環境
  3. 安裝必要軟體
  4. 下載並解壓FFmpeg原始碼包
  5. 將指令碼放置於FFmpeg解壓包的根路徑
  6. 執行指令碼,等待編譯結束

步驟1可以參考《VMware虛擬機器安裝Ubuntu20.04詳細圖文教程》,不再贅述;接下來對2-6步驟中一些關鍵部分進行一一介紹。

NDK環境搭建

下載NDK並將它解壓至usr/local/路徑下,在終端中進行如下操作,進行環境變數配置(可以參考超詳乾貨!Linux 環境變數配置全攻略,當然你還可以在/etc/profile.d/目錄下新增指令碼檔案)。

  • 執行 sudo vim ~/.bashrc,如果未安裝vim軟體,使用sudo apt install vim命令進行安裝。
  • 鍵盤輸入i進入編輯模式,並在檔案末尾依次新增export NDK=/usr/local/android-ndk-r23bexport PATH=$PATH:$NDK,如下圖所示:

  • 鍵盤敲擊Esc鍵,退出編輯模式後,鍵盤輸入:wq儲存並退出vim軟體。
  • 繼續執行source ~/.bashrc更新配置或者開啟新的終端,保證NDK的環境變數配置生效
  • 輸入ndk-build -v驗證是NDK的環境變數配置是否成功,成功結果如下圖所示:

安裝必要軟體

雖然一般情況下僅需安裝NDK+pkg-config+make就可以進行交叉編譯,但是FFmpeg對外提供的編譯配置中有一些特殊優化,比如enable-asm時,可能需要安裝額外的軟體包。

  • pkg-config:提供庫的一些資訊,終端輸入sudo apt install pkg-config進行安裝
  • make:用於解釋Makefile檔案中的規則,終端輸入sudo apt install make進行安裝

安裝yasm/nasm條件

FFmpeg中x86/x86_64架構對應的彙編程式碼採用了Intel語法風格的x86組合語言(x86組合語言有兩種語法風格(Intel和AT&T ) ),開啟彙編優化時需要支援Intel語法風格的彙編器。由於gas或者llvm-as只支援AT&T語法風格,因此安裝yasm/nasm。

yasm:雖然在Github上的倉庫時有更新,但最新版本停留在了2014年8月10號。終端中輸入sudo apt install yasm

nasm:一直在維護更新,可以檢視changelog。終端輸入sudo apt install nasm

下載並解壓FFmpeg原始碼包

原始碼包可以https://github.com/FFmpeg/FFmpeg 倉庫中選擇對應版本下載,當然也可使用📎FFmpeg-n5.0.1.zip。~~原先即使使用最新版本的 open-vm-tools-desktop (2:11.3.5-1ubuntu4,終端中輸入dpkg -l | grep open-vm可檢視版本)也無法從windows直接拷貝檔案到虛擬機器。(已被修復,僅做記錄)~~

編寫指令碼

Tip:這部分內容涉及Shell指令碼的語法規則非常簡單,花費一點時間就能掌握,閱讀Shell教程

FFmpeg遵循與大多數Unix軟體一樣的構建方式,用到如下三條命令:

  1. ./configure+引數,向configure指令碼傳遞引數並執行,並生成相應的Makefile檔案或與編譯相關檔案(配置可能與其它專案中的引數不一致,使用./configure --help檢視更多引數,當然也可以使用./configure --help > configure_help.txt將幫助文件的內容輸入到txt檔案中檢視)。
  2. make,使用命令make編譯,make命令會自動查詢上一步生成的Makefile檔案並根據Makefile中的內容執行編譯。
  3. make install,將編譯生成的庫檔案、配置檔案、標頭檔案等等拷貝到指定的安裝目錄。

除了上述指令外還有可能用到make cleanmake distcleanmake uninstall等命令配合使用,具體介紹可以檢視make、make clean、make install、make uninstall、make dist、make distcheck和make distclean

配置./configure+引數

shell指令碼中的字串可以用單引號,也可以用雙引號,也可以不用引號

下面是一個基本的架子:

接下來對上面指令碼中使用到的FFmpeg Toolchanain option和自定義變數逐行介紹:

Tip:為了方便閱讀,將變數的定義和賦值的內容放到下面介紹

補充上述變數的定義與值

CONFIGURATION:封裝一些向configure指令碼傳遞的引數。

CFLAGSLDFLAGS:向編譯器或連結器傳遞引數,輔助編譯。

其它變數:用於確定檔案位置,如下:

```bash NDK="/usr/local/android-ndk-r25" HOST="linux-x86_64" SYSROOT="$NDK/toolchains/llvm/prebuilt/$HOST/sysroot" LLVM_TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/$HOST/bin" PKG_CONFIG="/usr/bin/pkg-config"

省略了一些程式碼,具體檢視指令碼

PREFIX="$(pwd)/android/$ARCH" CC=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang CXX=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang++ AS=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang LD=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang AR=$LLVM_TOOLCHAIN/llvm-ar NM=$LLVM_TOOLCHAIN/llvm-nm STRIP=$LLVM_TOOLCHAIN/llvm-strip RUNLIB=$LLVM_TOOLCHAIN/llvm-ranlib ```

接下來重點介紹CONFIGURATIONCFLAGS以及LDFLAGS的值:

$CONFIGURATION

FFmpeg configure可用以配置的選項有很多,如圖📎configure_help.svg,網上有很多這方面的解釋,可以自行搜尋,這裡不做一一介紹。

注意:在字首為--disable---enable-的選項中,disable表示關閉 ,enable 代表開啟。對於變數COMPONENT_LIST和變數CMDLINE_SELECT中的選項來說,可以自由選擇設定--disable---enable-作為字首(詳見下方Configure指令碼分析)。

```bash

位於4105行,不同版本可能有變化。

關於for關鍵字,可以在終端中輸入help for檢視文件,這裡有一些特殊,沒有指定[in WORDS],

因此按照文件中說法,就是預設使用了in "$@",

相當於遍歷一個由命令列的所有引數分別填充的字串陣列,["opt0","opt1","opt2"...].

for opt do # ${opt#=}表示將opt字串中=右邊的字串取出,詳見參考內容《shell變數操作${}詳細用法》 optval="${opt#=}" case "$opt" in # 省略部分分支 #匹配以--enable-或者--disable-開頭的選項,當然有一些已經提前處理 --enable-?|--disable-?) #eval命令會將引數組合成一個字串,並將結果作為shell的輸入,然後執行。 #1. echo "--enable-xxx" | sed 's/--/action=/;s/-/ option=/;s/-//g' #2.執行上述命令,列印action=enable option=xxx eval $(echo "$opt" | sed 's/--/action=/;s/-/ option=/;s/-//g') # is_in函式相當於ArrayList#contains方法,在後面解析 if is_in $option $COMPONENT_LIST; then # 對於COMPONENT_LIST變數中的引數來說,如果設定disable,那麼重新將action設定為unset test $action = disable && action=unset # 假設$option=bsfs, # bsfs去掉s,並轉換成大寫;最終執行unset $BSF_LIST或者enable $BSF_LIST eval $action $$(toupper ${option%s})_LIST elif is_in $option $CMDLINE_SELECT; then # 對於CMDLINE_SELECT變數中的引數可以自由設定disable還是enable $action $option else die_unknown $opt fi ;; # 省略部分判斷 esac done ```

```bash

假設 is_in $option $COMPONENT_LIST

is_in(){ # $1的值就是$option value=$1 # 保證後面$獲取的引數中移除$1(即$option) shift # 此處遍歷$,如果$option在$COMPONENT_LIST中,那麼返回0,shell中0表示true. for var in $*; do [ $var = $value ] && return 0 done return 1 } ```

這裡介紹一些常用的。

Configuration options

| --disable-static | 不構建靜態庫 | | ----------------- | ------- | | --enable-shared | 編譯生成動態庫 | | --enable-small | 降低庫體積 |

Program options

| --disable-programs | 不構建命令列程式(指ffmpeg、ffplay以及ffprobe) | | ------------------- | ----------------------------------------------------------- | | --disable-ffmpeg | 不構建ffmpeg程式(轉換器) | | --disable-ffplay | 不構建ffplay程式(播放器) | | --disable-ffprobe | 不構建ffprobe程式(檢視多媒體檔案的資訊) |

Documentation options

| --disable-doc | 不產生文件 | | -------------------- | ---------------- | | --disable-htmlpages | 不產生 HTML型別的文件 | | --disable-manpages | 不產生 manpage型別的文件 | | --disable-podpages | 不產生 POD 型別的文件 | | --disable-txtpages | 不產生 text 型別的文件 |

Component options

  • --disable-avdevice:不構建libavdevice庫,libavdevice is a library containing input and output devices for grabbing from and rendering to many common multimedia input/output software frameworks, including Video4Linux, Video4Linux2, VfW, and ALSA.
  • --disable-avcodec:不構建libavcodec庫,libavcodec is a library containing decoders and encoders for audio/video codecs.
  • --disable-avformat:不構建libavformat庫,libavformat is a library containing demuxers and muxers for multimedia container formats.
  • --disable-swresample:不構建libswresample庫,libswresample is a library performing highly optimized audio resampling, rematrixing and sample format conversion operations.
  • --disable-swscale:不構建libswcale庫,libswscale is a library performing highly optimized image scaling and color space/pixel format conversion operations.

Toolchain options

| --enable-cross-compile | 開啟交叉編譯 | | ----------------------- | -------- | | --enable-pic | 生成位置無關程式碼 |

Optimization options

  • --disable-asm:禁止所有彙編優化

注:指令碼中在7281行中有enabled asm || { arch=c; disable $ARCH_LIST $ARCH_EXT_LIST; }這樣的程式碼,意為著當檢測到asm關閉時,會關閉ARCH_LISTARCH_EXT_LIST中的選項。順便提一下,當關閉asm時,neon也被關閉,asm預設被開啟的,具體可以檢視指令碼。

```

這裡已被調整,只做參考,具體詳見對於版本中的configure指令碼

ARCH_LIST=" aarch64 alpha arm avr32 avr32_ap avr32_uc bfin ia64 loongarch loongarch32 loongarch64 m68k mips mips64 parisc ppc ppc64 riscv s390 sh4 sparc sparc64 tilegx tilepro tomi x86 x86_32 x86_64" ARCH_EXT_LIST=" $ARCH_EXT_LIST_ARM $ARCH_EXT_LIST_PPC $ARCH_EXT_LIST_X86 $ARCH_EXT_LIST_MIPS $ARCH_EXT_LIST_LOONGSON"

ARCH_EXT_LIST_ARM=" armv5te armv6 armv6t2 armv8 neon vfp vfpv3 setend"

ARCH_EXT_LIST_X86=" $ARCH_EXT_LIST_X86_SIMD cpunop i686 " ARCH_EXT_LIST_X86_SIMD="aesni amd3dnow amd3dnowext avx avx2 avx512 fma3 fma4 mmx mmxext sse sse2 sse3 sse4 sse42 ssse3 xop" ```

  • --disable-neon:禁止NEON優化

Demystifying ARM Floating Point Compiler Options一文中對NEON、float-abi、fpu有詳細解釋,可以解決很多疑惑。

一般情況下對於ARM架構推薦開啟NEON優化,原因:1.絕大多數ARM 安卓裝置支援NEON,特別是基於ARMv8的裝置都支援NEON。2.NDK r23是最後一個支援non-Neon的版本,原因是極少數非常舊的裝置不支援Neon。3.效能優化很可觀。本文中對於x86架構關閉NEON優化的原因:x86架構不相容NEON指令。如果你需要在基於x86架構的安卓裝置(比如Window11上的android子系統)支援NEON指令,可以參考From ARM NEON to Intel SSE-the automatic porting solution, tips and tricks。一些拓展連結error:'neon_vector_type'attribute is not suppoted for this target #420 Neon for x86 not enabled in Clang #260

Developer options

  • --disable-debug:禁止除錯符號,指令碼中預設開啟。
$CFLAGS

這裡介紹clang/clang++常見引數。

控制程式碼優化程度,-O0, -O1, -O2, -O3, -Ofast, -Os, -Oz, -Og, -O, -O4

-O0, -O1, -O2, -O3, -Ofast, -Os, -Oz, -Og, -O, -O4

Specify which optimization level to use:

-O0 Means “no optimization”: this level compiles the fastest and generates the most debuggable code.

-O1 Somewhere between -O0 and -O2.

-O2 Moderate level of optimization which enables most optimizations.

-O3 Like -O2, except that it enables optimizations that take longer to perform or that may generate larger code (in an attempt to make the program run faster).

-Ofast Enables all the optimizations from -O3 along with other aggressive optimizations that may violate strict compliance with language standards.

-Os Like -O2 with extra optimizations to reduce code size.

-Oz Like -Os (and thus -O2), but reduces code size further.

-Og Like -O1. In future versions, this option might disable different optimizations in order to improve debuggability.

-O Equivalent to -O1.

-O4 and higher

Currently equivalent to -O3

-D \=\ :Define \ to \ (or 1 if \ omitted)

相當於C語言中的#define macro=value

-Wl,\ :Pass the comma separated arguments in to the linker

向linker傳遞引數

-I \

:Add directory to the end of the list of include search paths

尋找標頭檔案時會在dir路徑中尋找

-L \

: Add directory to library search path

$LDFLAGS

-L \

:Add a directory to the library search path

-l \ :Root name of library to use

--nostdlib: Only search directories specified on the command line

修改config.h中的內容

利用sed指令修改config.h中的值,config.h檔案會在configure指令碼執行完成後自動生成,裡面記錄一些配置資訊,比如是否開啟了asm、是否開啟neon等等。因此也可以通過config.h檔案確定配置是否與預期相符。

編譯&安裝

編譯

提供指令碼:📎build_shared_asm.sh📎build_static_asm.sh

你可以使用上述提供的指令碼進行編譯(你需要根據需求調整引數的編譯出最合適的庫),注:已經測試NDKr21及以上版本,你只需調整NDK版本或者路徑。

將指令碼放入FFmpeg原始碼解壓包的根目錄下,在終端中執行指令碼,等待結束,如圖:

編譯結果如下:

問題記錄

編譯過程中出現

GNU assembler not found, install/update gas-preprocessor

原來指令碼中彙編器使用的GAS,由於未開啟彙編優化,所以一直沒有發現指令碼中的錯誤。當使用NDK中的GAS編譯Arm架構對應的彙編程式碼時,出現此問題。

解決方法:

根據提示安裝gas-preprocessor(沒什麼用,畢竟gas-preprocessor是Perl指令碼,用於實現一個 Apple's as沒有的gas preprocessor的子集):

  • 下載gas-preprocessor.pl檔案。
  • 使用 sudo cp gas-preprocessor.pl /usr/local/bin/將其拷貝至usr/local/bin路徑下,並通過sudo chmod 777 /usr/local/bin/gas-preprocessor.pl 賦予許可權。

~~方法1~~:clang內建彙編器,用於減少生成.s檔案以及調用匯編器的時間。向clang傳遞-fno-integrated-as引數,禁用內建彙編器,預設使用/usr/bin/as替代。經實踐,方法1不適合當前版本,會報如下錯誤(NDK issues),NDK r23中直到r23c中被修復,雖然之前版本依舊能使用,但是NDK r24中此引數已被移除。

test_ld cc test_cc BEGIN /tmp/ffconf.MNVahc6Q/test.c 1 int main(void){ return 0; } END /tmp/ffconf.MNVahc6Q/test.c /usr/local/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang --sysroot=/usr/local/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/sysroot -I/usr/local/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/arm-linux-androideabi -Os -fPIC -fno-integrated-as -ferror-limit=0 -DVK_ENABLE_BETA_EXTENSIONS=0 -Wno-implicit-int-float-conversion -Wno-sometimes-uninitialized -Wno-unused-function -Wno-deprecated-declarations -Wno-tautological-constant-out-of-range-compare -Wno-inline-asm -march=armv7-a -c -o /tmp/ffconf.MNVahc6Q/test.o /tmp/ffconf.MNVahc6Q/test.c /usr/bin/as: unrecognized option '-EL' clang-12: error: assembler command failed with exit code 1 (use -v to see invocation) C compiler test failed.

方法2:直接由clang指定彙編器,即--as=$cc

x86架構開啟asm,編譯時報錯,inline assembly requires more registers than available

libavcodec/x86/cabac.h:200:9: error: inline assembly requires more registers than available BRANCHLESS_GET_CABAC("%0", "%q0", "(%4)", "%1", "%w1", ^ libavcodec/x86/cabac.h:145:9: note: expanded from macro 'BRANCHLESS_GET_CABAC' "movzbl "statep" , "ret" \n\t"\ ^ CC libavcodec/hcom.o CC libavcodec/hevc_cabac.o libavcodec/x86/cabac.h:200:9: error: inline assembly requires more registers than available libavcodec/x86/cabac.h:145:9: note: expanded from macro 'BRANCHLESS_GET_CABAC' "movzbl "statep" , "ret" \n\t"\ ^ CC libavcodec/hevc_data.o CC libavcodec/hevc_filter.o In file included from libavcodec/h264_cabac.c:43: libavcodec/x86/h264_cabac.c:66:9: error: inline assembly requires more registers than available "3: \n\t" ^ CC libavcodec/hevc_mp4toannexb_bsf.o CC libavcodec/hevc_mvs.o CC libavcodec/hevc_parse.o libavcodec/hevc_cabac.c:37:21: warning: variable 'num_bins_in_se' is not needed and will not be emitted [-Wunneeded-internal-declaration] static const int8_t num_bins_in_se[] = { ^ CC libavcodec/hevc_parser.o CC libavcodec/hevc_ps.o CC libavcodec/hevc_refs.o CC libavcodec/hevc_sei.o In file included from libavcodec/hevc_cabac.c:27: In file included from libavcodec/cabac_functions.h:49: libavcodec/x86/cabac.h:200:9: error: inline assembly requires more registers than available BRANCHLESS_GET_CABAC("%0", "%q0", "(%4)", "%1", "%w1", ^ libavcodec/x86/cabac.h:145:9: note: expanded from macro 'BRANCHLESS_GET_CABAC' "movzbl "statep" , "ret" \n\t"\ ^

經過搜尋和實踐ffmepg trac 779x86 inline assembly code which compiled with r16b does not compile with r17android 4.2.2 x86,解決Android NDK編譯FFmpeg 4.2.2的x86 cpu版時的問題的內容,向CFLAG中傳遞-DHAVE_EBP_AVAILABLE=0方法仍會出錯,報如下錯誤:

./config.h:356:9: warning: 'HAVE_EBP_AVAILABLE' macro redefined [-Wmacro-redefined] warning: 'HAVE_EBP_AVAILABLE' macro redefined [-Wmacro-redefined]

將libavutils/x86/asm.h中#define HAVE_7REGS (ARCH_X86_64 || (HAVE_EBX_AVAILABLE && HAVE_EBP_AVAILABLE))修改為#define HAVE_7REGS (ARCH_X86_64)的方式可以編譯通過。

執行過程中出現

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "xxx" referenced by

一般無法定位符號的原因是沒有連結對應的庫檔案。下面是三個架構不同報錯:

W/System.err: java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__extenddftf2" referenced by "/data/app/~~5iB4UFPumZmTrJq8DUYFmg==/com.floatWind.snap-2vw43nTh6DG8YL6K3Td2aA==/lib/x86_64/libavutil.so"... W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1077) W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:998) W/System.err: at java.lang.System.loadLibrary(System.java:1656)

W/System.err: java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__ashldi3" referenced by "/data/app/com.floatWind.snap-s8RJlycCvRQZYgyulGFC3Q==/lib/x86/libavformat.so"... W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1016) W/System.err: at java.lang.System.loadLibrary(System.java:1657) W/System.err: at com.ffmpeg.VideoSnap.<clinit>(VideoSnap.java:16) W/System.err: at com.floatWind.snap.MainActivity$2.run(MainActivity.java:70)

W/System.err: java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__floatunsitf" referenced by "/data/app/com.floatWind.snap-K2FtbiNL0c7eCXN_xanOMg==/lib/arm64/libavutil.so"... W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1071) W/System.err: at java.lang.Runtime.loadLibrary0(Runtime.java:1007) W/System.err: at java.lang.System.loadLibrary(System.java:1668) W/System.err: at com.ffmpeg.VideoSnap.<clinit>(VideoSnap.java:16) W/System.err: at com.floatWind.snap.MainActivity$2.run(MainActivity.java:70)

extenddftf2、ashldi3、floatunsitf這幾個符號皆是用於long double資料型別的浮點運算,它們位於compiler-rt project中builtins中(原本libgcc也有對應實現,後被移除),它位於$NDK/toolchains/llvm/prebuilt/$HOST/lib64/clang/*/lib/linux之下。你也可在Building with 128-bit floating-point (long double) support for AArch64 state, with Arm Compiler 6文章瞭解更多。

原因:使用-nostdlib導致無法連結到compiler-rt

解決方法: ~~1.將對應庫(clang_rt.builtins-XXX-android)的標頭檔案位置和庫檔案所在位置分別告知編譯器和連結器(NDK r21中此方案不生效)。~~

2.移除-nostdlib標誌

持續跟蹤該問題

undefined reference to 'inflateInit_'

編譯ffmpeg靜態產物時連結了libz,本地編寫CMakeList.txt未連結libz,導致出現未定義的引用

fatal error: 'vulkan_beta.h' file not found

方法1:配置configure指令碼時禁用vulkan:--disable-vulkan

方法2:配置extra-cflags時新增 -DVK_ENABLE_BETA_EXTENSIONS=0,防止引用vulkan_beta.h標頭檔案

ld: error: relocation XXX cannot be used against symbol XXX; recompile with -fPIC

arm64-v8a

雖然可以通過CFlAG中新增-mcmodel=large避免(閱讀12),但是還有其它錯誤發生,僅作記錄

C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_32'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_32'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_64'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_64'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_128'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_128'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_256'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_256'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_512'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_512'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_1024'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_1024'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_2048'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_2048'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_4096'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_4096'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_8192'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_8192'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'ff_cos_16384'; recompile with -fPIC C/C++: ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_cos_16384'; recompile with -fPIC C/C++: ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)

x86_64

C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_h264_cabac_tables'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_ac3_bap_bits'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_ac3_bap_bits'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pd_1'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pd_1'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)

x86

C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_ac3_bap_bits'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_ac3_bap_bits'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pd_1'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pd_1'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against local symbol; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_4'; recompile with -fPIC C/C++: ld: error: relocation R_386_32 cannot be used against symbol 'ff_pw_5'; recompile with -fPIC C/C++: ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)

經過長時間嘗試各種方案,最終在Unable to compile ffmpeg extension with NDK 23 + CMake-3.22看到解決方案(不是很完美,無法解決x86中的全部報錯,但已經夠用),如下圖所示,當然你也可以在CMakeLists.txt檔案中向CFLAGS中新增-Wl,-Bsymbolic,即向連結器傳遞-Bsymbolic,強制採用本地的全域性變數定義。

雖然問題解決了,突然想起FFmpeg官網中Platform Specific InformationAdvanced linking configuration中提及(/(ㄒoㄒ)/~~)

If you compiled FFmpeg libraries statically and you want to use them to build your own shared library, you may need to force PIC support (with --enable-pic during FFmpeg configure) and add the following option to your project LDFLAGS:-Wl,-Bsymbolic.

If your target platform requires position independent binaries, you should pass the correct linking flag (e.g. -pie) to --extra-ldexeflags.

dlopen failed: "xxx.so" has text relocations

System.err: java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/com.floatWind.snap-lMxvRHX5I84nYETkUOhOow==/lib/x86/libavformat.so" has text relocations

通過閱讀Android linker changes for NDK developers中Text Relocations一些情況,可以通過Gentoo Textrels guide中的介紹處理文字重定位的問題,下面是一些針對上述具體問題的處理步驟:

  1. 文章中用到scanelf命令來自pax-utils 軟體包,因此在終端輸入sudo apt install pax-utils

  2. 終端中輸入scanelf -qT libavformat.so尋找出錯程式碼,-q表示只輸出一些可能有問題的提示,-T表示文字重定位的位置,scanelf命令其它具體引數的含義可以檢視這裡

  3. 上面的還沒研究透徹,待續。
  4. 經過查詢,向連結器傳遞了"-z notext" 導致連線時允許text relocations,忽略了ld: error: can't create dynamic relocation R_386_32 against local symbol in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output錯誤。
  5. libavutil/x86/float_dsp.asm、libavutil/x86/tx_float.asm檔案出了問題,需要重新編寫。

dlopen failed: cannot locate symbol "ff_tx_codelet_list_float_x86"(x86 & x86_64 )

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "ff_tx_codelet_list_float_x86" referenced by "/data/app/~~K5KAUCDK4rV3L6O1Qu8t7w==/com.floatWind.snap-_Cs7dWX9r8E_ELZ7aiTP-g==/lib/x86_64/libavutil.so"...

dlopen failed: cannot locate symbol "__aarch64_ldadd8_acq_rel"

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__aarch64_ldadd8_acq_rel" referenced by "/data/app/com.floatWind.snap--aifSTof5wmQ0hbczZDovw==/lib/arm64/libavformat.so"...

在CFLAGS中新增-mno-outline-atomics(Don't generate local calls to out-of-line atomic operations,關於Out-of-line Atomics可以檢視What is new in LLVM12 for Arm),參考Building for Android with OpenSSL

參考內容