一、ffmpeg的编译
1、准备工作
安装虚拟机Centos
下载ndk:https://dl.google.com/android/repository/android-ndk-r13b-linux-x86_64.zip
下载ffmpeg-3.2.1.tar.gz:https://ffmpeg.org/releases/ffmpeg-3.2.1.tar.gz
坑:3.4.x报错:./libavutil/timer.h:38:31: fatal error: linux/perf_event.h: No such file or directory
根据 http://www.ffmpeg-archive.org/ffmpeg-3-4-1-with-android-td4682624.html 这个反馈,改了之后,果然没有报错,但是限制了最低版本为Android-21,这不是我想要的,好像还有一种办法,说是注释掉include ‘<‘linux/perf_event.h’>’
下载JDK
https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
下载dk-8u201-linux-x64.tar.gz:https://download.oracle.com/otn-pub/java/jdk/8u201-b09/42970487e3af4f5aa5bca3f542482c60/jdk-8u201-linux-x64.tar.gz?AuthParam=1554439431_16b24965e20f66725c8647fbf4af8d41
2、安装配置
解压配置Java环境
JAVA_PATH=/home/jdk1.8.0_201
if [ -d "$JAVA_PATH/jre" -a -d "$JAVA_PATH/bin" ]; then
export JAVA_HOME=$JAVA_PATH
export ANDROID_JAVA_HOME=$JAVA_HOME
export JRE_HOME=$JAVA_PATH/jre
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
if [ -z "$CLASSPATH" ]; then
export CLASSPATH=.:$JAVA_HOME/bin/lib:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
else
export CLASSPATH=.:$JAVA_HOME/bin/lib:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:$CLASS_PATH
fi
fi
解压配置NDK环境
#!/bin/sh
export ANDROID_NDK=/home/android-ndk-r13b
export NDK_HOME=$ANDROID_NDK
export ANDROID_NDK_HOME=$ANDROID_NDK
export PATH=$ANDROID_NDK:$PATH
3、遇到的问题
nasm/yasm not found or too old. Use --disable-x86asm for a crippled build
解决:安装yasm编译器。安装方法如下:
在http://www.tortall.net/projects/yasm/releases
下面找到适合自己平台的yasm版本。然后进行安装。举例如下:
1)下载:wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
2)解压:tar zxvf yasm-1.3.0.tar.gz
3)切换路径: cd yasm-1.3.0
4)执行配置: ./configure
5)
编译:make
6)安装:
make install
4、修改ffmpeg的configure文件
原因是生成的.so文件是版本号后缀,例如:xxx.so.5.1.4,Android不能识别。
找到
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
替换为
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
5、编写编译脚本
最好再Linux环境编写
vim build_android.sh
#!/bin/bash
make clean
export NDK=/home/android-ndk-r13b
export SYSROOT=$NDK/platforms/android-9/arch-arm/
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
export CPU=arm
export PREFIX=$(pwd)/android/$CPU
export ADDI_CFLAGS="-marm"
export TEMPDIR=/home/ffmpeg-3.2.1/temp
./configure --target-os=linux \
--prefix=$PREFIX --arch=arm \
--disable-doc \
--enable-shared \
--disable-static \
--disable-yasm \
--disable-symver \
--enable-gpl \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_CFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
设置权限:chmod +x build_android.sh
执行build_android.sh脚本开始编译
./build_android.sh
之后会在当前目录下出现android目录,里面就有需要的.so文件了。
5、Android 设备的CPU类型(通常称为”ABIs”)
- armeabiv-v7a: 第7代及以上的 ARM 处理器。2011年15月以后的生产的大部分Android设备都使用它.
- arm64-v8a: 第8代、64位ARM处理器,很少设备,三星 Galaxy S6是其中之一。
- armeabi: 第5代、第6代的ARM处理器,早期的手机用的比较多。
- x86: 平板、模拟器用得比较多。
- x86_64: 64位的平板。
二、Android studio 在项目中配置ffmpeg
1、结构
include 放头文件,其它的一看就直到了
main目录下的CMakeLists.txt 配置:
cmake_minimum_required(VERSION 3.4.1)
#${CMAKE_SOURCE_DIR} 表示CMakeLists.txt文件所在的目录
#${CMAKE_ANDROID_ARCH_ABI} 表示不同架构的CPU 名字
#${PROJECT_SOURCE_DIR}/src/main/cpp
#定义.so输出路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/jniLibs/${ANDROID_ABI})
#定义常量cpp目录
set(CPP_FILE_DIR ${CMAKE_SOURCE_DIR}/cpp)
#定义常量
set(JNILIBS_PATH ${CMAKE_SOURCE_DIR}/jniLibs/${ANDROID_ABI})
#添加子目录,将自动找到指定目录中的CMakeLists.txt
ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/cpp)
cpp目录下的CMakeLists.txt 配置:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
#${CMAKE_SOURCE_DIR} 表示CMakeLists.txt文件所在的目录
#${CMAKE_ANDROID_ARCH_ABI} 表示不同架构的CPU 名字
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${CPP_FILE_DIR}/native-lib.cpp
)
add_library(
avcodec
SHARED
IMPORTED
)
set_target_properties(
avcodec
PROPERTIES IMPORTED_LOCATION
${JNILIBS_PATH}/libavcodec.so
)
add_library(
avfilter
SHARED
IMPORTED
)
set_target_properties(
avfilter
PROPERTIES IMPORTED_LOCATION
${JNILIBS_PATH}/libavfilter.so
)
add_library(
avformat
SHARED
IMPORTED
)
set_target_properties(
avformat
PROPERTIES IMPORTED_LOCATION
${JNILIBS_PATH}/libavformat.so
)
add_library(
avutil
SHARED
IMPORTED
)
set_target_properties(
avutil
PROPERTIES IMPORTED_LOCATION
${JNILIBS_PATH}/libavutil.so
)
add_library(
swresample
SHARED
IMPORTED
)
set_target_properties(
swresample
PROPERTIES IMPORTED_LOCATION
${JNILIBS_PATH}/libswscale.so
)
include_directories(
native-lib
PRIVATE
${CPP_FILE_DIR}/include)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
target_link_libraries( # Specifies the target library.
native-lib
avcodec
avfilter
avformat
avutil
swresample
# Links the target library to the log library
# included in the NDK.
${log-lib})
上面两个配置文件名字虽然相同,但是作用不用,第一个CMakeLists.txt是主要的,在它的配置中能自动搜索cpp目录中的CMakeLists.txt文件,还可以自定义常量定义路径。
注意:第一个CMakeLists.txt需要在build.gradle中配置,其实就是给指定路径,如下:
externalNativeBuild {
cmake {
path "src/main/CMakeLists.txt"
}
}
可能会遇到报错:
Paths that match a first-pick pattern will be selected into the APK. If more than one path matches the first-pick, only the first found will be selected.
解决:
packagingOptions {
pickFirst 'lib/arm64-v8a/libnative-lib.so'
pickFirst 'lib/armeabi/libnative-lib.so'
pickFirst 'lib/armeabi-v7a/libnative-lib.so'
pickFirst 'lib/x86/libnative-lib.so'
pickFirst 'lib/x86_64/libnative-lib.so'
}
配置好的build_gradle文件的内容
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.lht.hijni"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
ndk{
abiFilters "armeabi-v7a","arm64-v8a","x86","x86_64"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/CMakeLists.txt"
}
}
packagingOptions {
pickFirst 'lib/arm64-v8a/libnative-lib.so'
pickFirst 'lib/armeabi/libnative-lib.so'
pickFirst 'lib/armeabi-v7a/libnative-lib.so'
pickFirst 'lib/x86/libnative-lib.so'
pickFirst 'lib/x86_64/libnative-lib.so'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
参考资料:
1、add_library
该指令的主要作用就是将指定的源文件生成链接文件(.so文件),然后添加到工程中去。该指令常用的语法如下:
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[source1] [source2] [...])
其中<name>表示库文件的名字,该库文件会根据命令里列出的源文件来创建。而STATIC、SHARED和MODULE的作用是指定生成的库文件的类型。STATIC库是目标文件的归档文件,在链接其它目标的时候使用。SHARED库会被动态链接(动态链接库),在运行时会被加载。MODULE库是一种不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数。默认状态下,库文件将会在于源文件目录树的构建目录树的位置被创建,该命令也会在这里被调用。
而语法中的source1 source2分别表示各个源文件。
例子:
2. link_directories
该指令的作用主要是指定要链接的库文件的路径,该指令有时候不一定需要。因为find_package和find_library指令可以得到库文件的绝对路径。不过你自己写的动态库文件放在自己新建的目录下时,可以用该指令指定该目录的路径以便工程能够找到。
例子如下:
link_directories(
lib
)
3. target_link_libraries
该指令的作用为将目标文件与库文件进行链接。该指令的语法如下:
target_link_libraries(<target> [item1] [item2] [...]
[[debug|optimized|general] <item>] ...)
上述指令中的<target>是指通过add_executable()和add_library()指令生成已经创建的目标文件。而[item]表示库文件没有后缀的名字。默认情况下,库依赖项是传递的。当这个目标链接到另一个目标时,链接到这个目标的库也会出现在另一个目标的连接线上。这个传递的接口存储在interface_link_libraries的目标属性中,可以通过设置该属性直接重写传递接口。
4、find_library用于查找包含的库文件
搜索指定的预建库并将路径存储为变量。因为cmake在搜索路径中包含系统库默认情况下,只需指定公共NDK库的名称您要添加。cmake验证库之前是否存在正在完成其生成。
find_library( # 设置路径变量的名称
log-lib
# 指定要CMAKE定位的NDK库的名称。
log)
5、SET_TARGET_PROPERTIES
1、按照一般的习惯,静态库名字跟动态库名字应该是一致的,只是扩展名不同;
即:静态库名为 libhello.a; 动态库名为libhello.so ;
希望 "hello_static" 在输出时,不是"hello_static",而是以"hello"的名字显示,故设置如下:
SET_TARGET_PROPERTIES (hello_static PROPERTIES OUTPUT_NAME "hello")
2、# cmake在构建一个新的target时,会尝试清理掉其他使用这个名字的库,
# 因此,在构建libhello.a时,就会清理掉libhello.so.
# 为了回避这个问题,比如再次使用SET_TARGET_PROPERTIES定义 CLEAN_DIRECT_OUTPUT属性。
SET_TARGET_PROPERTIES (hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES (hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
3、# 按照规则,动态库是应该包含一个版本号的,
# VERSION指代动态库版本,SOVERSION指代API版本。
SET_TARGET_PROPERTIES (hello PROPERTIES VERSION 1.2 SOVERSION 1)
6、添加头文件目录INCLUDE_DIRECTORIES
语法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
它相当于g++选项中的-I参数的作用,也相当于环境变量中增加路径到CPLUS_INCLUDE_PATH变量的作用。
include_directories(../../../thirdparty/comm/include)
二、测试环境是否可用
public class MainActivity extends Activity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
testLog();
}
public native String stringFromJNI();
public native void testLog();
}
#include <jni.h>
#include <string>
#include "android/log.h"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"========info========",__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROE,"========error========",__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARING,"========waring========",__VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
// Java_包名(点换成下划线)_类名_方法名( )
Java_com_lht_hijni_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_lht_hijni_MainActivity_testLog(JNIEnv *env, jobject instance) {
LOGI("LOG %s","ceshi");
}
三、