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

參考內容