Table of Contents
6.3 undefined reference to `avcodec_configuration()'
6.4 undefined reference to `avcodec_configuration()'
N年前寫了一篇ffmpeg編譯的筆記,那時候的ffmpeg是3.x,現在ffmpeg已經出到4.3版本,今天就用 Windows 10 的 WSL 編譯 ffmpeg 4.2.1 版本,這次就使用 WSL 進行編譯,速度非常快,只需要1分多鐘。當然這和硬件性能升級也有很大關係。
所需工具:
1、Windows 10
2、適用於 linux 的 Windows 子系統
3、ffmpeg-4.2.1
4、android-ndk-r21d-linux-x86_64 (注意是linux版本,不是windows版本)
5、Android Studio 4.0
6、com.android.tools.build:gradle:4.0.0
7、gradle-6.1.1-all
一、安裝適用於 linux 的 Windows 子系統
參考另一篇文章:《無需虛擬機,簡單幾步即可實現在Windows下搭建Linux開發環境》
安裝完成之後,就可以在 cmd 下進入到 linux shell 了。
二、下載 Android NDK
請自行上網下載NDK,我用的是版本:android-ndk-r21d-linux-x86_64 。
下載完成後,直接解壓即可。
三、準備 ffmpeg 源文件
3.1 下載 ffmpeg
ffmpeg release 列表: https://ffmpeg.org/releases/ ,本文所用版本:ffmpeg-4.2.1
打開 cmd 命令窗口,然後按順序執行以下操作:
# 1. 進入 linux 終端
C:\Users\Administrator>bash
root@BASSY-PC:/mnt/c/Users/Administrator# cd ~
root@BASSY-PC:~#
# 2. 創建目錄-存放ffmpeg源碼
root@BASSY-PC:~# mkdir ~/ffmpeg_sources
# 3. 進入目錄
root@BASSY-PC:~# cd ~/ffmpeg_sources
# 4. 下載ffmpeg源碼
root@BASSY-PC:~/ffmpeg_sources# wget -O ffmpeg-4.2.1.tar.gz https://ffmpeg.org/releases/ffmpeg-4.2.1.tar.gz
--2020-07-02 11:44:16-- https://ffmpeg.org/releases/ffmpeg-4.2.1.tar.gz
Resolving ffmpeg.org (ffmpeg.org)... 79.124.17.100
Connecting to ffmpeg.org (ffmpeg.org)|79.124.17.100|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 13670499 (13M) [application/x-gzip]
Saving to: ‘ffmpeg-4.2.1.tar.gz’
ffmpeg-4.2.1.tar.gz 45%[==============> ] 5.95M 405KB/s eta 22s
# 5. 下載完成後,進行解壓文件
root@BASSY-PC:~/ffmpeg_sources# tar zxvf ffmpeg-4.2.1.tar.gz
root@BASSY-PC:~/ffmpeg_sources# ll
total 13376
drwxrwxrwx 1 root root 4096 Jul 2 11:07 ./
drwx------ 1 root root 4096 Jul 2 11:00 ../
drwx------ 1 1000 1000 4096 Aug 6 2019 ffmpeg-4.2.1/
-rw-rw-rw- 1 root root 13667216 Aug 6 2019 ffmpeg-4.2.1.tar.gz
# 7. 進入源碼目錄
root@BASSY-PC:~/ffmpeg_sources# cd ffmpeg-4.2.1/
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1#
四、編寫編譯腳本
4.1 編寫腳本
在 ~/ffmpeg_sources/ffmpeg-4.2.1/ 目錄下,創建一個 build_android.sh 的文件,並添加可執行權限,如下:
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1# touch build_android.sh
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1# sudo chmod +x build_android.sh
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1# ll build_android.sh
-rwxrwxrwx 1 root root 0 Jul 2 11:23 build_android.sh*
使用 vim 編輯器打開:
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1# vim build_android.sh
按字母 i 進入編輯模式,然後粘貼以下腳本,把NDK修改爲你的地址:
#!/bin/bash
make clean
export NDK=/mnt/e/Developer/android-ndk-r21d-linux-x86_64
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
API=21
function build_android
{
./configure \
--prefix=$PREFIX \
--enable-neon \
--enable-hwaccels \
--enable-gpl \
--enable-postproc \
--enable-static \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--disable-shared \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-avdevice \
--disable-doc \
--disable-symver \
--disable-x86asm \
--cross-prefix=$CROSS_PREFIX \
--target-os=android \
--arch=$ARCH \
--cpu=$CPU \
--cc=$CC \
--cxx=$CXX \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS"
make clean
make -j16
make install
}
ARCH=arm
CPU=armv7-a
PREFIX=$(pwd)/android/$CPU
SYSROOT=$TOOLCHAIN/sysroot
CC=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang
CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang++
CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
OPTIMIZE_CFLAGS=" -mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU"
build_android
ARCH=arm64
CPU=armv8-a
PREFIX=$(pwd)/android/$CPU
SYSROOT=$TOOLCHAIN/sysroot
CC=$TOOLCHAIN/bin/aarch64-linux-android$API-clang
CXX=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++
CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
OPTIMIZE_CFLAGS="-march=$CPU"
build_android
- NDK:使用 適用於 linux 的 windows 子系統 的話,訪問其它盤符的方法是 /mnt/e/ ,其中 e 表示 E 盤,其它類似;
- TOOLTHAIN:我們用的是 NDK-21 ,使用 clang 編譯器,不再使用 gcc 編譯器了。clang 編譯器位於 ndk/toolchains/llvm/prebuilt/linux-x86_64 目錄下;
- ARCH 和 CPU:指定 CPU 架構模式;
- PREFIX :指定編譯輸出目錄;
- SYSROOT:指定NDK的庫和頭文件所在的根目錄,在編譯過程中需要用到。對於是NDK-21,它所在目錄是 toolchains\llvm\prebuilt\linux-x86_64\sysroot\ 。打開該目錄就會發現,裏面放了我們熟知的 jni.h、strings.h、stdlib.h、stdio.h、GLES2、GLES3等頭文件和相關的庫;
- CC:指定 clang 所在目錄;
- CXX:指定 clang++ 所在目錄;
- CROSS_PREFIX:指定交叉編譯的工具的前綴,例如:aarch64-linux-android-ld、aarch64-linux-android-nm、aarch64-linux-android-objcopy等,它們都是有固定前綴的。這是就把這個前綴和路徑拼接在一起即可。需要注意不同CPU構架的前綴是不一樣的。你打開該目錄看一下就知道了;
- OPTIMIZE_CFLAGS:指定一些編譯器的配置;
- API:因爲 NDK-21 最低要求是 API-21 (Android.5.0),所以在引入到Android工程中使用的時候,minSdkVersion 必須與這裏的 API 保持一致,不然會有很多問題。
- 注意 make -j16 的 -j 參數(也可以是 --jobs),用於指定編譯線程,它可以提高編譯速度,一般是CPU核心數的2倍,我的CPU是8核心,所以寫了16。如果不寫的話,編譯時長大概爲10多分鐘,加上 -j16 之後,大約是1分鐘。
上述腳本一般一定要仔細檢查,特別是路徑必須確認是準確的,CPU和ARCH也必須確認是無誤的。這可以減少很多編譯問題。
4.2 開始編譯
此時執行上述腳本,開始編譯。
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1# sudo ./build_android.sh
可能會出現以下錯誤:
./android_config.sh: line 36: --enable-shared: command not found
Makefile:2: ffbuild/config.mak: No such file or directory
Makefile:40: /tools/Makefile: No such file or directory
Makefile:41: /ffbuild/common.mak: No such file or directory
Makefile:91: /libavutil/Makefile: No such file or directory
Makefile:91: /ffbuild/library.mak: No such file or directory
Makefile:93: /fftools/Makefile: No such file or directory
Makefile:94: /doc/Makefile: No such file or directory
Makefile:95: /doc/examples/Makefile: No such file or directo
解決方法:
執行以下命令生成 config.mak 文件
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1# sudo ./configure --disable-x86asm
重新執行腳本,如果環境搭建沒有問題、腳本也沒有問題,就會有以下輸出:
...
...
...
INSTALL libavutil/tx.h
INSTALL libavutil/lzo.h
INSTALL libavutil/avconfig.h
INSTALL libavutil/ffversion.h
INSTALL libavutil/libavutil.pc
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1#
編譯輸出就在 ./android 目錄下,如下所示:
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1# ll android
total 0
drwxr-xr-x 1 root root 4096 Jul 2 15:55 ./
drwx------ 1 1000 1000 4096 Jul 2 16:02 ../
drwxr-xr-x 1 root root 4096 Jul 2 15:53 armv7-a/
drwxr-xr-x 1 root root 4096 Jul 2 15:55 armv8-a/
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1# ll android/armv7-a/
total 0
drwxr-xr-x 1 root root 4096 Jul 2 15:53 ./
drwxr-xr-x 1 root root 4096 Jul 2 15:55 ../
drwxr-xr-x 1 root root 4096 Jul 2 15:53 include/
drwxr-xr-x 1 root root 4096 Jul 2 15:53 lib/
drwxr-xr-x 1 root root 4096 Jul 2 15:53 share/
root@BASSY-PC:~/ffmpeg_sources/ffmpeg-4.2.1#
說明:
- include:是頭文件所在目錄;
- lib :是靜態庫(.a)文件所在目錄;
- share :是 examples 所在目錄;
五、導入 Android 目錄使用
5.1 創建 C++ 工程
使用 Android Studio 嚮導創建 C++ 工程,如下:
5.2 修改 build.gradle
修改 app 模塊下的 build.gradle 文件,如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "cn.bassy.ffmepg01"
minSdkVersion 18
targetSdkVersion 28
versionCode 1
versionName "1.0"
externalNativeBuild {
cmake {
cppFlags "-std=c++11" //加上
abiFilters "armeabi-v7a","arm64-v8a" //加上
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt" //指定CMakeLists.txt文件路徑
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'androidx.appcompat:appcompat:1.1.0'
api 'androidx.constraintlayout:constraintlayout:1.1.3'
}
5.3 將 ffmpeg 編譯結果導入工程
直接把 ./android/ 下的 armv7-a 目錄 和 armv8-a 目錄 拷貝到 Android 工程下的 app/src/main/cpp/ffmpeg 目錄下,並進行重命名,如下:
重命名爲 arm64-v8a 和 armeabi-v7a 有利於我們後續根據不同的編譯配置依賴不同的目錄。
5.4 修改 CMakeLists.txt
將 ffmpeg的頭文件和靜態庫添加到構建中,如下:
cmake_minimum_required(VERSION 3.4.1)
include_directories( ${CMAKE_CURRENT_LIST_DIR}/src/main/cpp/ffmpeg/${CMAKE_ANDROID_ARCH_ABI}/include)
add_library(native-lib SHARED src/main/cpp/native-lib.cpp)
find_library(log-lib log)
target_link_libraries( # Specifies the target library.
native-lib
#增加ffmpeg庫,注意順序(具體看lib/pkgconfig的依賴關係)
${CMAKE_SOURCE_DIR}/src/main/cpp/ffmpeg/${CMAKE_ANDROID_ARCH_ABI}/lib/libavfilter.a
${CMAKE_SOURCE_DIR}/src/main/cpp/ffmpeg/${CMAKE_ANDROID_ARCH_ABI}/lib/libavformat.a
${CMAKE_SOURCE_DIR}/src/main/cpp/ffmpeg/${CMAKE_ANDROID_ARCH_ABI}/lib/libavcodec.a
${CMAKE_SOURCE_DIR}/src/main/cpp/ffmpeg/${CMAKE_ANDROID_ARCH_ABI}/lib/libswresample.a
${CMAKE_SOURCE_DIR}/src/main/cpp/ffmpeg/${CMAKE_ANDROID_ARCH_ABI}/lib/libswscale.a
${CMAKE_SOURCE_DIR}/src/main/cpp/ffmpeg/${CMAKE_ANDROID_ARCH_ABI}/lib/libavutil.a
#增加ffmpeg所需要的依賴庫
-lm -lz
# Links the target library to the log library
# included in the NDK.
${log-lib}
)
這裏使用了系統變量 CMAKE_ANDROID_ARCH_ABI
- 當爲 armv7-a 架構是,它的值是 armeabi-v7a ;
- 當爲 armv8-a 架構是,它的值是 arm64-v8a ;
- 這樣就剛好和目錄對應上了
5.5 編寫代碼
activity_main.xml 佈局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
MainActivity.java 代碼:
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(getFFmpegConfiguration());
}
public native String getFFmpegConfiguration();
}
native-lib.cpp 代碼:
#include <jni.h>
#ifdef __cplusplus
extern "C"
{
#endif
//ffmpeg 的頭文件需要使用 extern "C" {} 包圍
#include <libavcodec/avcodec.h>
JNIEXPORT jstring JNICALL
Java_cn_bassy_ffmepg01_MainActivity_getFFmpegConfiguration(JNIEnv *env, jobject job1) {
return env->NewStringUTF(avcodec_configuration());
}
#ifdef __cplusplus
}
#endif
運行效果:
6、常見錯誤
6.1 mmap64 問題
編譯的時候出現錯誤,具體如下:
libavcodec/v4l2_buffers.c:0: error: undefined reference to 'mmap64'
解決方法:
這是因爲編譯 ffmpeg 的時候 API=21,但是在導入工程中使用的時候,minSdkVersion 與 不是 21。解決方法是把 minSdkVersion 設置爲 build_android.sh 中定義的 API 的值即可。
6.2 armv7和armv8問題
如果編譯 ffmpeg 的是 armeabi-v7a,但是導入工程的時候是 arm64-v8a,就會導致以下錯誤:
Build command failed.
Error while executing process E:\Developer\android-sdk\cmake\3.10.2.4988404\bin\ninja.exe with arguments {-C F:\AndroidStudioWorkSpace\FFMepg01\app\.cxx\cmake\debug\arm64-v8a native-lib}
ninja: Entering directory `F:\AndroidStudioWorkSpace\FFMepg01\app\.cxx\cmake\debug\arm64-v8a'
[1/2] Building CXX object CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o
../../../../src/main/cpp/native-lib.cpp:23:5: warning: 'av_register_all' is deprecated [-Wdeprecated-declarations]
....
^
2 warnings generated.
[2/2] Linking CXX shared library ..\..\..\..\build\intermediates\cmake\debug\obj\arm64-v8a\libnative-lib.so
FAILED: ../../../../build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
cmd.exe /C "cd . && E:\Developer\android-ndk-r21d\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe --target=aarch64-none-linux-android21 --gcc-toolchain=E:/Developer/android-ndk-r21d/toolchains/llvm/prebuilt/windows-x86_64 --sysroot=E:/Developer/android-ndk-r21d/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -std=c++11 -O0 -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libgcc_real.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--fatal-warnings -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libnative-lib.so -o ..\..\..\..\build\intermediates\cmake\debug\obj\arm64-v8a\libnative-lib.so CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o ../../../../src/main/cpp/ffmpeg/lib/libavfilter.a ../../../../src/main/cpp/ffmpeg/lib/libavformat.a ../../../../src/main/cpp/ffmpeg/lib/libavcodec.a ../../../../src/main/cpp/ffmpeg/lib/libswresample.a ../../../../src/main/cpp/ffmpeg/lib/libswscale.a ../../../../src/main/cpp/ffmpeg/lib/libavutil.a -lm -lz -llog -latomic -lm && cd ."
../../../../src/main/cpp/ffmpeg/lib/libavformat.a: error adding symbols: File in wrong format
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
解決方法:
Android工程用的是什麼CPU構架,那麼就對應用什麼CPU架構的 ffmpeg,
例如:
build.gradle 中配置了 armeabi-v7a:
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
abiFilters "armeabi-v7a"
}
}
那麼 build_android.sh 中的配置應該是:
ARCH=arm
CPU=armv7-a
PREFIX=$(pwd)/android/$CPU
SYSROOT=$TOOLCHAIN/sysroot
CC=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang
CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang++
CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
OPTIMIZE_CFLAGS=" -mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU"
build_android
arm64-v8a 同理。
也可以把 v7和v8都導入到工程中,通過 CMakeLists.txt 腳本動態控制。
6.3 undefined reference to `avcodec_configuration()'
因爲 ffmpeg 採用了 c 語言編寫,爲了解決 c 和 c++ 混編問題,需要使用 extern "C" {} 包圍,如果沒有的話,會報以下錯誤:
Build command failed.
Error while executing process E:\Developer\android-sdk\cmake\3.10.2.4988404\bin\ninja.exe with arguments {-C F:\AndroidStudioWorkSpace\FFMepg01\app\.cxx\cmake\debug\arm64-v8a native-lib}
ninja: Entering directory `F:\AndroidStudioWorkSpace\FFMepg01\app\.cxx\cmake\debug\arm64-v8a'
[1/2] Building CXX object CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o
[2/2] Linking CXX shared library ..\..\..\..\build\intermediates\cmake\debug\obj\arm64-v8a\libnative-lib.so
FAILED: ../../../../build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
cmd.exe /C "cd . && E:\Developer\android-ndk-r21d\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe --target=aarch64-none-linux-android21 --gcc-toolchain=E:/Developer/android-ndk-r21d/toolchains/llvm/prebuilt/windows-x86_64 --sysroot=E:/Developer/android-ndk-r21d/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -std=c++11 -O0 -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libgcc_real.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--fatal-warnings -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libnative-lib.so -o ..\..\..\..\build\intermediates\cmake\debug\obj\arm64-v8a\libnative-lib.so CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o ../../../../src/main/cpp/ffmpeg/arm64-v8a/lib/libavfilter.a ../../../../src/main/cpp/ffmpeg/arm64-v8a/lib/libavformat.a ../../../../src/main/cpp/ffmpeg/arm64-v8a/lib/libavcodec.a ../../../../src/main/cpp/ffmpeg/arm64-v8a/lib/libswresample.a ../../../../src/main/cpp/ffmpeg/arm64-v8a/lib/libswscale.a ../../../../src/main/cpp/ffmpeg/arm64-v8a/lib/libavutil.a -lm -lz -llog -latomic -lm && cd ."
CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o: In function `Java_cn_bassy_ffmepg01_MainActivity_getFFmpegConfiguration':
F:\AndroidStudioWorkSpace\FFMepg01\app\.cxx\cmake\debug\arm64-v8a/../../../../src/main/cpp/native-lib.cpp:14: undefined reference to `avcodec_configuration()'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
解決方法:
#ifdef __cplusplus
extern "C"
{
#endif
//ffmpeg 的頭文件需要使用 extern "C" {} 包圍
#include <libavcodec/avcodec.h>
JNIEXPORT jstring JNICALL
Java_cn_bassy_ffmepg01_MainActivity_getFFmpegConfiguration(JNIEnv *env, jobject job1) {
return env->NewStringUTF(avcodec_configuration());
}
#ifdef __cplusplus
}
#endif
6.4 undefined reference to `avcodec_configuration()'
出現這種問題的另一種原因是,靜態庫 .a 有問題,例如用了 x86 架構的靜態庫。
可以通過 linux 的 nm 命令查看是否是 arm 架構:
nm -a android/armv7-a/lib/libavcodec.a | grep "arm"
如果沒有輸出,則表示不是 arm 架構,如果有輸出 arm 相關的符號,那就沒問題
6.5 其它一些錯誤
可能會出現莫名其妙的錯誤,一般是由於 NDK 版本引起的,解決方法,最好是編譯的 ffmpeg 和使用 ffmpeg 的 NDK 的版本保持一致。
.