Android mac 交叉编译与ffmpeg编译踩坑记 (v7a 与 v8a and 动态库与静态库)

999 阅读4分钟

Android mac 交叉编译与ffmpeg编译踩坑记

环境:

system: mac

NDK: android-ndk-r17c

Fffmpeg: ffmpeg-4.0.2

Cmake: 3.10.2

Gradle: 4.1.3

tips: 本文记录踩坑过程,具体细节如果感兴趣可以在评论区留言交流讨论!

mac 编译 (动态库(so))

首先来回顾一下,mac原始库是如何编译的

先来创建一个最简单的so文件:

image-20230711093344318

只需要输入:

gcc -fPIC -shared main.c -o libmain.so

image-20230711093507390

这样就可以将c代码编译成so库

mac 编译 (静态库(a))

静态库稍微麻烦一点,需要先生成 .o文件 [ gcc -fPIC -c main.c -o main.o ]

然后通过 .o文件生成 .a文件 [ar -r libmain.a main.o]

image-20230711094503924

这样一来,就很轻易的编译成了静态库

android 交叉编译(动态库 arm-v7a)

这里就有疑问了, 为什么需要交叉编译 ?

因为刚才我们的代码是在mac上编译的 ,android并不认识, 要想android认识,就引出了交叉编译

就好像越南人来china买东西,必须吧越南盾换成人民币一样,必须要认识,才能够使用

在mac上,编译的方式是采用gcc

那么NDK也为我们提供了GCC

image-20230711095455235

那么只能这样:

image-20230711100232342

此时就会出现第一个坑:

找不到头文件

他的头文件都在这里, 只需要再次配置一下就OK

image-20230711101202396

配置完成头文件后,就会有第二个坑:

找不到 asm/types.h

image-20230711101338201

asm/types.h 在这里:

image-20230711101835708

那么这个也需要配置一下:

image-20230711102217655

最终编译, 还是不成功! .

最后还需要告诉gcc,当前编译的是什么android架构,当前编译的是v7a,那么只需要告诉gcc即可

image-20230711102511048

最终编译:

image-20230711102731233

完整命令:

/Users/ndk/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-gcc --sysroot=$NDK/platforms/android-21/arch-arm -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/arm-linux-androideabi -fPIC -shared main.c -o libmain-ndk-32.so

这样一来, 打包成的so库就可以在android v7a的手机上运行了!

但是问题就来了,我不可能每次打包都这么麻烦,输这么长的指令,实在是太丑了, 所以说我将它配置成了全局变量,并写成了shell

配置全局变量:

# .bash_profile

# NDK路径
export NDK="/Users/ndk/android-ndk-r17c"

# NDK的命令操作 配置到 Linux环境变量里面去
export PATH=$NDK:$PATH


# 下面是交叉编译相关
# ================= GCC ==================================
export NDK_GCC_x86="$NDK/toolchains/x86-4.9/prebuilt/darwin-x86_64/bin/i686-linux-android-gcc"

export NDK_GCC_x64="$NDK/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin/x86_64-linux-android-gcc"

export NDK_GCC_arm="$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-gcc"

export NDK_GCC_arm_64="$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-gcc"


# ================================== config 配置架构 ==================================
export NDK_CFIG_x86="--sysroot=$NDK/platforms/android-21/arch-x86 -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/i686-linux-android"

export NDK_CFIG_x64="--sysroot=$NDK/platforms/android-21/arch-x86_64 -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/x86_64-linux-android"

export NDK_CFIG_arm="--sysroot=$NDK/platforms/android-21/arch-arm -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/arm-linux-androideabi"

export NDK_CFIG_arm_64="--sysroot=$NDK/platforms/android-21/arch-arm64 -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/aarch64-linux-android"

记得保存:

source ~/.bash_profile

Shell 命令

image-20230711103651463

重新编译v7a 动态库(so):

image-20230711103822045

android 交叉编译(动态库 arm-v8a)

v8a 的方式也是一样的,只是选择架构的时候选择v8a即可

image-20230711103915648

为了在android端演示, 写一个util方法,里面有加减乘除函数, 通过JNI调用试试

image-20230711112017150

可以直接采用sh命令来直接生成交叉编译后的so,

然后可以通过 file. XXX.so 来查看编译结果!

在android中测试打包后的so库:

创建一个C++工程, 配置CMake

image-20230711112611458

记得在gradle中设置NDK架构:

image-20230711112702104

最后直接来调用即可:

image-20230711112850894

android 交叉编译 (静态库 arm-v7a)

静态库与动态库类似,只需要将linux的编译方式换成NDK的编译方式即可

Linux编译方式:
1. 生成.o文件
gcc -fPIC -c main.c -o main.o 

2. 通过.o文件来构建 静态库
ar -r libmain.a main.o

先来找到NDK对应v7a架构的ar, 因为这里是通过ar来生成.a 文件的

image-20230711130713079

同样为了方便使用,我把它配置成了全局变量,并且写成了shell命令

# .bash_profile

# 下面是交叉编译相关
# ================= GCC ==================================
export NDK_GCC_x86="$NDK/toolchains/x86-4.9/prebuilt/darwin-x86_64/bin/i686-linux-android-gcc"

export NDK_GCC_x64="$NDK/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin/x86_64-linux-android-gcc"

export NDK_GCC_arm="$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-gcc"

export NDK_GCC_arm_64="$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-gcc"


# ================================== config 配置架构 ==================================
export NDK_CFIG_x86="--sysroot=$NDK/platforms/android-21/arch-x86 -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/i686-linux-android"

export NDK_CFIG_x64="--sysroot=$NDK/platforms/android-21/arch-x86_64 -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/x86_64-linux-android"

export NDK_CFIG_arm="--sysroot=$NDK/platforms/android-21/arch-arm -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/arm-linux-androideabi"

export NDK_CFIG_arm_64="--sysroot=$NDK/platforms/android-21/arch-arm64 -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/aarch64-linux-android"


# ================================== ar配置 用来生成静态库 ==================================
export NDK_AR_x86="$NDK/toolchains/x86-4.9/prebuilt/darwin-x86_64/bin/i686-linux-android-ar"

export NDK_AR_x64="$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-ar"

export NDK_AR_arm="$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-ar"

export NDK_AR_arm_64="$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-ar"

同样记得保存:

source ~/.bash_profile	

shell命令:

#!bin/bash

if [  $1 != 'v7a' ] && [ $1 != 'v8a' ]; then
	echo "请选择平台 正确指令: sh $0 v7a或v8a(生成架构) xxx"
	exit 0
fi

echo 您选择编译的平台是: $1

if [ -e $NDK ]; then

	 if [ "v7a" = $1 ]; then
	
		# 交叉编译生成.o文件
		$NDK_GCC_arm $NDK_CFIG_arm -fPIC -c $2.c -o $2.o
		
		echo "生成$2.o中..."
		sleep 1
		
		$NDK_AR_arm rcs -o lib-$2.a $2.o
	 else 
		$NDK_GCC_arm_64 $NDK_CFIG_arm_64 -fPIC -c $2.c -o $2.o

    echo "生成$2.o中..."
    sleep 1

    $NDK_AR_arm_64 rcs -o lib-$2.a $2.o
	 fi
	echo "执行成功"

else
	echo "执行失败"
fi

使用:

只需要简单的:

sh static_build.sh v7a util 即可

image-20230711132834051

android 交叉编译 (静态库 arm-v8a)

Arm-v8a编译静态库与动态库编译方式是一样的; 使用同一个shell命令

image-20230711132718889

在android中使用:

image-20230711133200529

使用效果和动态库类似,只是配置的方式不同

动态库与静态库的区别

  1. 静态库不会显示在apk中,在编译过程中,静态库的太吗都会copy到主库中

image-20230711133500901

相反动态库则全会打包到apk中

image-20230711133745838

ffmpeg编译(armv7a)

ffmpeg如何下载我就不说了, 写了一个脚本,我测试了很多次,只要NDK地址给对 80%都是可以编译成功的. 坑我全踩了 花了2天时间

build32.sh

#!/bin/bash

# NDK_PATH
NDK_ROOT=/Users/ndk/android-ndk-r17c

# NDK中gcc地址 arm-v7a地址
TOOLCHAIN=$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64

FLAGS="-isystem $NDK_ROOT/sysroot/usr/include/arm-linux-androideabi -D__ANDROID_API__=21 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -Wa,--noexecstack -Wformat -Werror=format-security  -O0 -fPIC"
INCLUDES=" -isystem $NDK_ROOT/sources/android/support/include"

# 1.定义编译后,所存放的目录
PREFIX=./android/arm32

# 2.--enable-small 优化大小 非常重要,必须优化才行的哦
# 3.--disable-programs 不编译ffmpeg程序(命令行工具),我们是需要获取静态、动态库
# 4.--disable-avdevice 关闭avdevice模块,此模块在android中无用
# 5.--disable-encoders 关闭所有编码器(播放不需要编码)
# 6.--disable-muxers 关闭所有复用器(封装器),不需要生成mp4这样的文件,所有关闭
# 7.--disable-filters 关闭所有滤镜
# 8.--enable-cross-compile 开启交叉编译(ffmpeg是跨平台的,注意:并不是所有库都有这么happy的选项)
# 9.--cross-prefix 看右边的值就知道是干嘛的,gcc的前缀..
# 10.disable-shared / enable-static 这个不写也可以,默认就是这样的,(代表关闭动态库,开启静态库)
# 11.--sysroot
# 12.--extra-cflags 会传给gcc的参数
# 13.--arch  --target-os 编译版本 v7a=arm; v8a=arm64;

./configure \
--prefix=$PREFIX \
--enable-small \
--disable-programs \
--disable-avdevice \
--disable-encoders \
--disable-muxers \
--disable-filters \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--disable-shared \
--enable-static \
--sysroot=$NDK_ROOT/platforms/android-21/arch-arm \
--extra-cflags="$FLAGS $INCLUDES" \
--extra-cflags="-isysroot $NDK_ROOT/sysroot/" \
--arch=arm \
--target-os=android

make clean

make install

如果你想编译v7a的,直接在这里创建build32.sh 吧NDK路径换成你自己的

image-20230711134705544

最好是手动创建好编译后存放的目录 [android/arm32]

编译大约3-5分钟就OK了

sh build32.sh

切记,编译时候切换到root环境编译

ffmpeg编译(armv8a)

还是同样的套路,写一个shell脚本:

build64.sh

#!/bin/bash

# NDK位置
NDK_ROOT=/Users/ndk/android-ndk-r17c

# GCC
TOOLCHAIN=$NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64

FLAGS="-isystem $NDK_ROOT/sysroot/usr/include/aarch64-linux-android -D__ANDROID_API__=21 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv8-a -mcpu=generic -Wa,--noexecstack -Wformat -Werror=format-security  -O0 -fPIC"
INCLUDES=" -isystem $NDK_ROOT/sources/android/support/include"

# 1.定义编译后,所存放的目录
PREFIX=./android/arm64

# 2.--enable-small 优化大小 非常重要,必须优化才行的哦
# 3.--disable-programs 不编译ffmpeg程序(命令行工具),我们是需要获取静态、动态库
# 4.--disable-avdevice 关闭avdevice模块,此模块在android中无用
# 5.--disable-encoders 关闭所有编码器(播放不需要编码)
# 6.--disable-muxers 关闭所有复用器(封装器),不需要生成mp4这样的文件,所有关闭
# 7.--disable-filters 关闭所有滤镜
# 8.--enable-cross-compile 开启交叉编译(ffmpeg是跨平台的,注意:并不是所有库都有这么happy的选项)
# 9.--cross-prefix 看右边的值就知道是干嘛的,gcc的前缀..
# 10.disable-shared / enable-static 这个不写也可以,默认就是这样的,(代表关闭动态库,开启静态库)
# 11.--sysroot
# 12.--extra-cflags 会传给gcc的参数
# 13.--arch  --target-os 

./configure \
--prefix=$PREFIX \
--enable-small \
--disable-programs \
--disable-avdevice \
--disable-encoders \
--disable-muxers \
--disable-filters \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
--disable-shared \
--enable-static \
--sysroot=$NDK_ROOT/platforms/android-21/arch-arm64 \
--extra-cflags="$FLAGS $INCLUDES" \
--extra-cflags="-isysroot $NDK_ROOT/sysroot/" \
--arch=arm64 \
--target-os=android

make clean

make install

只需要吧NDK地址换成自己的即可:

image-20230711134959290

sh build64.sh

最终编译后,这就是编译结果:

image-20230711135314188

导入到android中使用:

image-20230711135700846

调用看看结果:

image-20230711135810027

我将本篇用到的所有命令已经全局配置放到项目中了,有需要的亲下载源码自取!

image-20230711140131262

完整代码

原创不易,您的点赞就是对我最大的支持!

往期文章: