Java是 write once,run anywhre,但 C 不一样,各平台均有差异,无法只写一次,而且各个平台的编译都不一样。比如android的ndk工具链,不同平台的库都是不一样的
本文主要讲解下 ffmpeg 在 win 平台下的编译以及集成
1、交叉编译
交叉编译:交叉编译就是程序的编译环境和实际运行环境不一致,即在一个平台上生成另一个平台上的可执行代码。
为什么要交叉编译,其实之前原因已经说过了,因为不同平台的差异,指令集都不一样,比如win上面是intel的指令集,但android手机上几乎百分百都是arm的指令集,所以直接拿win上编译出来的库给android用,肯定无法使用的,所以需要交叉编译。
交叉编译主要是借助android 的ndk工具包
下面大致列举了一下经常会用到的组件。
- ARM 交叉编译器
- 构建工具
- Java 原生接口头文件
- C 库
- Math 库
- 最小的 C++ 库
- ZLib 压缩库
- POSIX 线程
- Android 日志库
- Android 原生应用 API
- OpenGL ES 库
- OpenSL ES 库
下面来看一下 Android 所提供的 NDK 跟目录下的结构。
- ndk-build: 该 Shell 脚本是 Android NDK 构建系统的起始点,一般在项目中仅仅执行这一个命令就可以编译出对应的动态链接库了。
- ndk-gdb: 该 Shell 脚本允许用 GUN 调试器调试 Native 代码,并且可以配置到 AS 中,可以做到像调试 Java 代码一样调试 Native 代码。
- ndk-stack: 该 Shell 脚本可以帮组分析 Native 代码崩溃时的堆栈信息。
- build: 该目录包含 NDK 构建系统的所有模块。
- platforms: 该目录包含支持不同 Android 目标版本的头文件和库文件, NDK 构建系统会根据具体的配置来引用指定平台下的头文件和库文件。
- toolchains: 该目录包含目前 NDK 所支持的不同平台下的交叉编译器 - ARM 、X86、MIPS ,目前比较常用的是 ARM 。构建系统会根据具体的配置选择不同的交叉编译器。
toolchains里一般会提供这么一些工具:
- CC:编译器,对C源文件进行编译处理,生成汇编文件。
- AS:将汇编文件生成目标文件(汇编文件使用的是指令助记符,AS将它翻译成机器码)。
- AR:打包器,用于库操作,可以通过该工具从一个库中删除或者增加目标代码模块。
- LD:链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者是可执行文件。
- GDB:调试工具,可以对运行过程中的程序进行代码调试工作。
- STRIP:以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码。
- NM:查看静态库文件中的符号表。
- Objdump:查看静态库或者动态库的方法签名。
不过不同版本的ndk,里边的工具不一样,部分新的ndk里可能就没有ar 、strip 之类的,可能在新的ndk里这些工具命名不一样或者是放在其它地方了,比如本人发现的21.1.6352462(win)中包含 strip 和 ar,但 24.0.8215888 版本中没有相关库,而且这几个版本中都没有 nm 库,在编译 ffmpeg时一定会提示找不到nm,幸好 nm不是必须的,不慌,如果遇到找不到相关工具,说明路径设置的有问题,或者根本就是当前版本的ndk中没有此类工具或者已经改名,需要去找找资料看看新版本的工具叫啥或者干脆下载旧版本ndk
2、FFmpeg编译
一名优秀的c++开发,必须得对c++编译有一定了解。前文已经介绍了交叉编译,那现在就来学习如何编译 ffmpeg 吧
在ffmpeg官网下载源码:
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
根据自己需要,切换自己想要的版本。
ffmpeg的编译其实已经非常简单了,因为牛逼的ffmpeg开发者提供了一个脚本,叫
configure,其实我们写的编译脚本就是在指定编译工具的位置,然后调用 configure 脚本编译
本人是在win11上编译 ffmpeg,需要下载msys2工具并配置相关环境,必须以管理员运行msys2之后才能来配置环境,否则就会报异常
pacman -S make yasm diffutils pkg-config #在msys2上安装必要软件
然后在ffmpeg文件夹内建脚本文件,并把如下内容贴上:
#!/bin/sh
NDK_PATH=/c/workspace/android_sdk/ndk/21.1.6352462
BUILD_PLATFORM=windows-x86_64
API=21
ANDROID_ARMV5_CFLAGS="-march=armv5te"
ANDROID_ARMV7_CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon"
ANDROID_ARMV8_CFLAGS="-march=armv8-a"
ANDROID_X86_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
ANDROID_X86_64_CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel"
# params($1:arch,$2:arch_abi,$3:compiler,$4:cross_prefix,$5:cflags)
build_bin() {
echo "-------------------star build $2-------------------------"
ARCH=$1 # arm arm64 x86 x86_64
# CPU
ANDROID_ARCH_ABI=$2 # armeabi armeabi-v7a x86 mips
COMPILER=$3
PREFIX=$(pwd)/dist/${ANDROID_ARCH_ABI}/
TOOLCHAIN=${NDK_PATH}/toolchains/llvm/prebuilt/${BUILD_PLATFORM}
CC=${TOOLCHAIN}/bin/${COMPILER}-clang
CXX=${TOOLCHAIN}/bin/${COMPILER}-clang++
SYSROOT=${TOOLCHAIN}/sysroot
CROSS_PREFIX=${TOOLCHAIN}/bin/$4-
CFLAGS=$5
echo "pwd==$(pwd)"
echo "ARCH==${ARCH}"
echo "PREFIX==${PREFIX}"
echo "SYSROOT=${SYSROOT}"
echo "CFLAGS=${CFLAGS}"
echo "CC==${CC}"
echo "CROSS_PREFIX=${CROSS_PREFIX}"
sh ./configure \
--prefix=${PREFIX} \
--enable-neon \
--enable-hwaccels \
--enable-gpl \
--disable-postproc \
--disable-debug \
--enable-small \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--disable-static \
--enable-shared \
--disable-doc \
--enable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-avdevice \
--disable-doc \
--disable-symver \
--target-os=android \
--arch=${ARCH} \
--cc=$CC \
--sysroot=$SYSROOT \
--enable-cross-compile \
--cross-prefix=${CROSS_PREFIX} \
--extra-cflags="-Os -fPIC -DANDROID -Wfatal-errors -Wno-deprecated $CFLAGS" \
--extra-cxxflags="-D__thumb__ -fexceptions -frtti" \
--extra-ldflags="-L${SYSROOT}/usr/lib" \
make clean
make -j8
make install
echo "-------------------$2 build end-------------------------"
}
# build armeabi
# build_bin arm armeabi arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV5_CFLAGS"
#build armeabi-v7a
#build_bin arm armeabi-v7a armv7a-linux-androideabi${API} arm-linux-androideabi "$ANDROID_ARMV7_CFLAGS"
#build arm64-v8a
build_bin arm64 arm64-v8a aarch64-linux-android${API} aarch64-linux-android "$ANDROID_ARMV8_CFLAGS"
#build x86
# build_bin x86 x86 i686-linux-android${API} i686-linux-android "$ANDROID_X86_CFLAGS"
#build x86_64
# build_bin x86_64 x86_64 x86_64-linux-android${API} x86_64-linux-android "$ANDROID_X86_64_CFLAGS"
相关解释:
- CC:指定c编译器路径
- CROSS_PREFIX:指定交叉编译工具文件路径的统一前缀。各个工具的最终文件路径为:cross-prefix + 工具名,比如上面脚本的prefix为TOOLCHAIN/bin/arm-linux-androideabi-,那么ar工具的路径即为TOOLCHAIN/bin/arm-linux-androideabi-ar
- target-os:指定目标平台,因为 ffmpeg 可以在各平台上运行的,各平台上一些配置不太一样,所以需要指定的
另外编译脚本里边还有大量的 enable disable ,这些都是 configure 脚本里的编译选项,比如说 --enable-shared 意思就是编译动态库,所以上面的脚本最终会生成 so 文件,而不会生成 a 文件。
这些编译选项都可以使用 configure --help,可以查询到,大家可以试试
不管是这些 enable 编译选项,还是像 CC 一类的选项,都是在配置 configure 脚本,通过文本方式打开 configure 文件,可以看到:
--cc=CC use C compiler CC [$cc_default]
--target-os=OS compiler targets OS [$target_os]
--enable-shared build shared libraries [no]
运行编译脚本之后,如果编译成功了就会看到相关so库了,so库在lib文件夹中
3、FFmpeg集成
首先看cmakelist怎么写:
# 设置最小使用版本
cmake_minimum_required(VERSION 3.18.1)
project("demo")
include_directories(include)
# 添加本地so库 native-lib:这个是声明引用so库的名称 SHARED:表示共享so库文件
# 构建so库的源文件
add_library(
demo
SHARED
native-lib.cpp
)
set(SO_DIR ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
# 使用系统ndk 提供的库,如 log库
# log-lib 这个指定的是在NDK库中每个类型的库会存放一个特定的位置,而log库存放
# 在log-lib中
# log 指定使用log库
find_library(
log-lib
log
)
message("c_CMAKE_SOURCE_DIR:" ${CMAKE_SOURCE_DIR} )
# 加载avcodec-57库
add_library( avcodec
SHARED
IMPORTED)
set_target_properties( avcodec
PROPERTIES IMPORTED_LOCATION
${SO_DIR}/libavcodec.so)
add_library( avutil
SHARED
IMPORTED)
set_target_properties( avutil
PROPERTIES IMPORTED_LOCATION
${SO_DIR}/libavutil.so)
add_library( swresample
SHARED
IMPORTED)
set_target_properties( swresample
PROPERTIES IMPORTED_LOCATION
${SO_DIR}/libswresample.so)
add_library( avfilter
SHARED
IMPORTED)
set_target_properties( avfilter
PROPERTIES IMPORTED_LOCATION
${SO_DIR}/libavfilter.so)
add_library( avformat
SHARED
IMPORTED)
set_target_properties( avformat
PROPERTIES IMPORTED_LOCATION
${SO_DIR}/libavformat.so)
add_library( swscale
SHARED
IMPORTED)
set_target_properties( swscale
PROPERTIES IMPORTED_LOCATION
${SO_DIR}/libswscale.so)
#----------------------end-----------------------
# 如果你本地的库(native-lib)想要调用log库的方法,
# 那么就需要配置这个属性,意思是把NDK库关联到本地库。
# 第一个参数表示本地的库 native-lib 要调用到log库的方法,即要被关联的库名称,log-lib 要关联的库名称
target_link_libraries(
demo
#ffmpeg------start----------
avcodec
avutil
swresample
avfilter
avformat
swscale
#ffmpeg------end------------
${log-lib}
)
其实这些写法都非常简单,如果出错肯定是没写对,注意下相关细节即可。
所有代码均已上传到本人github中