最近在学习android NDK开发相关内容,借ffmpeg练练手。ffmpeg是做音视频方面功能的基础,后面会随着个人的学习更新一系列ffmpeg博客,防止自己遗忘。
这个系列博客主要目的是基于ffmpeg通过NDK开发的方式完成一个基本的视频播放器。
本篇博客主要实现了 ffmpeg编译 以及 引入 android 项目成功调用 静态库方法显示版本号。
编译ffmpeg时使用Linux或者Mac都可以;如果是Mac需要安装gcc,使用gcc -v输出版本号即为成功。
ffmpeg(4.0.2)
ffmpeg全版本下载地址:
http://www.ffmpeg.org/releases/
NDK(android-ndk-r17c)
NDK历史版本下载地址:
https://developer.android.google.cn/ndk/downloads/older_releases
ffmpeg下载完成后解压缩;进入ffmpeg解压后到目录
cd ./ffmpeg-4.0.2
ffmpeg编译时有很多命令参数,查看命令参数命令:
./configure --help
为了方便直接在 ffmpeg-4.0.2 目录里新建shell脚本
vim ./ffmpeg-4.0.2/build_ffmpeg_4.0.2.sh
输入以下内容;
注意:每一行脚本都有对应的注释,需要仔细查看,直接复制可能会编译失败,NDK工具包的相关路径要改成自己的。
NDK_SRC 和 NDK_GCC 的目录可能不同!!!
NDK_GCC 的 arm-linux-androideabi-4.9 目录根据不同的平台路径不同 本次实现仅实现了armeabi-v7a平台!!!
#!/bin/bash
# NDK工具包目录
NDK_SRC=/Users/tttell/tools/android-ndk-r17c
# 此变量执行ndk中的交叉编译gcc所在目录
NDK_GCC=$NDK_SRC/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
# 传递给gcc的参数 [从Android Project的 externalNativeBuild/xxx/build.ninja 中复制到参数 可以减少警告信息]
FLAGS="-isystem $NDK_SRC/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"
# 导入必需的头文件 [NDK 工具包的目录]
INCLUDES=" -isystem $NDK_SRC/sources/android/support/include"
# 编译后输出的目录 [根据自己情况自定义目录]
PREFIX=./bulid_ffmpeg_lib
./configure \
--prefix=$PREFIX \
--enable-static \
--enable-small \
--enable-cross-compile \
--disable-programs \
--disable-avdevice \
--disable-encoders \
--disable-muxers \
--disable-filters \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--extra-cflags="$FLAGS $INCLUDES" \
--extra-cflags="-isysroot $NDK_SRC/sysroot/" \
--sysroot=$NDK_SRC/platforms/android-21/arch-arm \
--arch=arm \
--target-os=android
make clean
make install
# 参数注释
# --enable-static # 编译静态库
# --enable-small # 体积优化
# --enable-cross-compile # 开启交叉编译
# --disable-programs # 不编译ffmpeg命令行工具 [如果需要可以编译]
# --disable-avdevice # 不编译avdevice模块 [--help 提示中表示该模块不支持Android平台]
# --disable-encoders # 不编译编码器 [本系列仅实现播放功能 所以不需要编码]
# --disable-muxers # 不编译封装器 [本系列仅实现播放功能 所以不需要封装]
# --disable-filters # 不编译所有滤镜
# --cross-prefix # 指定使用NDK工具包中的gcc相关编译器
# --extra-cflags # gcc的参数
# --sysroot # 平台标准库 [具体根据android项目的平台、最低支持版本而定 本次实现仅编译了armeabi-v7a 最低支持sdk21 ]
# --arch # 平台
# --target-os # 平台
开始执行脚本
sh ./ffmpeg-4.0.2/build_ffmpeg_4.0.2.sh
脚本运行时,要关注日志输出,如果出现error日志就及时停止吧 肯定会失败的 不用等到之行结束;编译过程可能会出现各种错误,具体错误可能跟环境也有关系,根据提示信息去搜索对应解决办法修改即可。
如果编译失败,输出目录中应该是空的,需要查看下终端的输出日志排查错误;
编译成功则会在你指定的输出目录下出现以下文件:
include 目录是待会需要导入到Android Project中的头文件;
lib 目录中就是成功编译出的静态库文件;
share 目录中的examples是一些例子 都是c语言的例子;
Android Studio 版本 我这应该是最新的
新建一个支持Native的项目
然后一直下一步就可以了。
打开项目后需要在SDKManager中下载NDK和Cmake:
NDK 不用全部下载,我是为了调试不同版本全部下载了 下载21左右的就可以。
Cmake 下载的是最新版本的。
接着修改 app 目录的 build.gradle 文件:
android {
...
defaultConfig {
externalNativeBuild {
cmake {
abiFilters "armeabi-v7a"
}
}
ndk {
abiFilters("armeabi-v7a")
}
}
# 为了开发方便不写findviewbyId 我开启了 databinding
dataBinding {
enabled = true
}
...
}
下载完成后同步一下项目,运行下测试下 如果页面成功显示了 Hello from C++ 即为成功;
将编译好的 ffmpeg 头文件目录 include 复制到 android project 的 cpp 目录下:
接着开始导入静态库,在 cpp 目录下新建 libs 目录;因为上一步骤中编译的是 armeabi-v7a 的 ffmpeg 所以在libs下新建 armeabi-v7a 目录(实际开发中会打多个平台的库文件,如:x86_64需要新建x86_64的目录)。将静态库文件复制到 armeabi-v7a 目录中:
导入工作完成后 开始编写 CMakeLists.txt :
cmake_minimum_required(VERSION 3.18.1)
project("studyffmepg")
# 导入 include目录的 库文件
# CMAKE_SOURCE_DIR 是常量 代表CMake文件所在目录
include_directories(${CMAKE_SOURCE_DIR}/include)
# 批量导入源文件 定义一个 SOURCE 变量
# 这样写在cpp目录下新建c++文件时不用再一个个写导入了
file(GLOB SOURCE *.cpp *.c)
# 添加动态库
add_library(
studyffmepg # 生成库的名字
SHARED # SHARED 动态库 STATIC 静态库
${SOURCE} # 源文件 上面定义的变量 表示全部打包进去
)
# 在NDK工具包中寻找 log 库
find_library(
log-lib
log
)
# 导入FFmpeg的库文件
# CMAKE_CXX_FLAGS 类似于 windows 环境变量中的 path;设置目录后会去目录下自动寻找库文件
# -L 代表追加目录
# CMAKE_ANDROID_ARCH_ABI 表示运行的平台(如:armeabi-v7a) 因为在 app:build.gradle中指定了平台所以这里可以读取到运行的平台
# 注意我这里的 -L 后的路径 /libs 对应上一步骤新建的目录 如果你的目录不同切记修改 否则会找不到依赖库
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI}")
target_link_libraries(
studyffmepg
${log-lib}
# ffmpeg的静态库有依赖关系 使用 -Wl,--start-group -Wl,--end-group 方式写 可以忽略顺序 否则容易出错
-Wl,--start-group
libavcodec.a libavfilter.a libavformat.a libavutil.a libswresample.a libswscale.a
-Wl,--end-group
)
完成之后同步一下。
修改 native-lib.cpp 文件,输出一下 ffmpeg 的版本号
#include
#include
using namespace std;
extern "C" {
#include
}
extern "C" JNIEXPORT jstring JNICALL
Java_top_sunhy_studyffmepg_MainActivity_stringFromJNI(
JNIEnv *env,
jobject jobj
) {
//std::string hello = "Hello from C++";
string s = "集成成功!FFmpeg Version:";
s.append(av_version_info());
return env->NewStringUTF(s.c_str());
}