大家都知道在eclipse上进行ndk开发光是编译C代码就很蛋疼,还好android studio的出现改变了现状,然而,在android studio 3.0以前进行ndk开发,也是各种配置总的来说用着很不爽,如今的android studio作了优化升级,不仅仅配置简化了,还直接引入cmake等功能,终于解放了双手。
一、NDK基础介绍
1、环境配置:
- 导入ndk等工具:Tools->SDK Manager->SDK Tools:选中LLDB、CMake、NDK
- 设置NDK安装路径:File->Project Structure->Android NDK location 选中默认路径
- 检查NDK路径:local.properties里面的ndk.dir在sdk路径里面,默认ndk-bundle
2、创建NDK项目:
- 创建native工程:
创建一个native c++工程:
有些时候可能会是下面这样的,这个时候只需要选中include c++ support就行了
- native工程目录:原来android studio已经帮我们把所有的配置都设置好了,build.gradle里面的配置如下,这其实不需要我们更改。而cpp文件夹也已经帮我们创建了,里面除了有cpp文件之外,还有一个includes和CMakeLists.txt
3、CMakeLists.txt
该文件其实是一个cmake的脚步。除去注释部分,其实核心的内容并不是很多
# 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)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
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).
src/main/cpp/native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
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 )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
- cmake_minimum_required(VERSION 3.4.1):用来设置在编译本地库时我们需要的最小的cmake版本,AndroidStudio自动生成,我们几乎不需要自己管。
- add_library:用来设置编译生成的本地库的名字为
native-lib
,SHARED
表示编译生成的是动态链接库
(这个概念前面已经提到过了),src/main/cpp/native-lib.cpp
表示参与编译的文件的路径,这里面可以写多个文件的路径。如下将两个cpp文件都编译到动态链接库dpc-lib中,注意在java调用native方法之前需要加载的动态库跟这里的名字保持一致 static { System.loadLibrary("dpc-lib");}
- find_library:是用来添加一些我们在编译我们的本地库的时候需要依赖的一些库,由于cmake已经知道系统库的路径,所以我们这里只是指定使用
log
库,然后给log
库起别名为log-lib
便于我们后面引用,此处的log
库是我们后面调试时需要用来打log日志的库,是NDK为我们提供的。
target_link_libraries:是为了关联我们自己的库和一些第三方库或者系统库,这里把我们把自己的库native-lib
库和log
库关联起来。
4、includes
这是一个包含很多头文件的库,这些头文件定义了很多c++系统函数或者android 框架层下的一些函数,这样更加方便了我们的ndk开发
例如在android/log.h文件中提供了一些在c++里面打印日志的函数,在c++中我们可以试着利用这些方法来向logcat打印日子:
#include <jni.h>
#include <string>
//引入log.h头文件
#include <android/log.h>
extern "C" JNIEXPORT jint JNICALL
Java_shen_com_myapplication_MainActivity_printfLog(JNIEnv* env,jobject thiz)
{
//参数1是LOG等级,参数二是TAG,参数三表示后面跟字符串
__android_log_print(ANDROID_LOG_INFO, "SHEN", "%s", "Hello, World");
return 0;
}
例如在jni.h文件中提供了jni相关的定义和接口:
#ifndef JNI_H_
#define JNI_H_
#include <stdarg.h>
#include <stdint.h>
/* Primitive types that match up with Java equivalents. */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
/* "cardinal indices and sizes" */
typedef jint jsize;
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
struct JNIInvokeInterface;
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; //C++中的JNIEnv是一个结构体类型_JNIEnv 里面定义了各种各样的方法
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv; //C中的JNIEnv是一个JNINativeInterface指针
typedef const struct JNIInvokeInterface* JavaVM;
#endif
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
jmethodID (*FromReflectedMethod)(JNIEnv*, jobject);
jfieldID (*FromReflectedField)(JNIEnv*, jobject);
//***各种方法***
}
//_JNIEnv代理了JNINativeInterface 所以JNIEnv在C和C++本质一样,使用方式有所不同
struct _JNIEnv {
const struct JNINativeInterface* functions;
jint GetVersion()
{ return functions->GetVersion(this); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
//***各种方法***
}
例如includes里面还提供了linux里面的一些操作,如线程和信号量的使用,示例如下:
#include <android/log.h>
#include <sys/time.h>
#include <pthread.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"SHEN",__VA_ARGS__)
//线程入口函数
void *threadTest(void *junk)
{
int count =0 ;
while(true){
LOGE("thread1-runing");
sleep(2);
count++;
if(count>=10){
//pthread_exit((void *)&status);
break;
}
}
}
//信号量句柄函数
void signalHandler(int signo)
{
if(signo == SIGALRM){
LOGE("signalHandler-signo=%d",signo);
}
}
//测试信号量
void TestSignal()
{
signal(SIGALRM, signalHandler);
struct itimerval new_value, old_value;
new_value.it_value.tv_sec = 0;
new_value.it_value.tv_usec = 1;
new_value.it_interval.tv_sec = 0;
new_value.it_interval.tv_usec = 180099;
setitimer(ITIMER_REAL, &new_value, &old_value);
for(;;);
}
//测试线程创建
void TestPthread(){
pthread_t t_a;
pthread_create(&t_a, nullptr,threadTest, nullptr);/*创建进程t_a*/
}
6、生成native的头文件
package test.shen.com.shentest2;
import android.util.Log;
public class JniTest {
//静态加载c/c++的native库,注意这里的名称必须跟CMakeLists.txt的add_library配置的名称一样
static {
System.loadLibrary("ShenTestLib");
}
public JniTest(){ }
public native int add(int i,int j);
public native String link(String i,String j);
public native int[] fore(int[] src);
public native void cToJava();
}
在项目路径/app/src/main/java路径下执行javah命令生成头文件,如下:
#切换到src/main/java路径下
E:\workspace\ShenTest2\app\src\main\java>cd E:\workspace\ShenTest2\app\src\main\java
#执行javah 全类名 命令
E:\workspace\ShenTest2\app\src\main\java>javah test.shen.com.shentest2.JniTest
错误: 编码GBK的不可映射字符
错误: 编码GBK的不可映射字符
#查看是否生成了头文件
E:\workspace\ShenTest2\app\src\main\java>dir
驱动器 E 中的卷是 新加卷
卷的序列号是 7C84-9FE1
E:\workspace\ShenTest2\app\src\main\java 的目录
2019/07/21 周日 18:36 <DIR> .
2019/07/21 周日 18:36 <DIR> ..
2019/07/12 周五 21:45 <DIR> test
2019/07/21 周日 18:36 1,173 test_shen_com_shentest2_JniTest.h
1 个文件 1,173 字节
3 个目录 14,369,390,592 可用字节
test_shen_com_shentest2_JniTest.h内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class test_shen_com_shentest2_JniTest */
#ifndef _Included_test_shen_com_shentest2_JniTest
#define _Included_test_shen_com_shentest2_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: test_shen_com_shentest2_JniTest
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_test_shen_com_shentest2_JniTest_add
(JNIEnv *, jobject, jint, jint);
/*
* Class: test_shen_com_shentest2_JniTest
* Method: link
* Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_test_shen_com_shentest2_JniTest_link
(JNIEnv *, jobject, jstring, jstring);
/*
* Class: test_shen_com_shentest2_JniTest
* Method: fore
* Signature: ([I)[I
*/
JNIEXPORT jintArray JNICALL Java_test_shen_com_shentest2_JniTest_fore
(JNIEnv *, jobject, jintArray);
/*
* Class: test_shen_com_shentest2_JniTest
* Method: cToJava
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_test_shen_com_shentest2_JniTest_cToJava
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
7、Jni的基本使用
jni的使用方式跟普通的c/c++的使用方式稍有不同,主要用来进行java与c/c++之间的数据转换,函数定义格式:
JNIEXPORT 函数返回值 JNICALL 函数名(包名+方法名)
#include "JniTest.h"
#include <string>
#include <android/log.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"SHEN",__VA_ARGS__)
using std::string;
#ifdef __cplusplus
char* _JString2CStr(JNIEnv* env, jstring jstr) {
char* rtn = nullptr;
jclass cstr = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("UTF-8");
jmethodID mid = env->GetMethodID(cstr,"getBytes","(Ljava/lang/String;)[B");
jbyteArray bytes = (jbyteArray)env->CallObjectMethod(jstr,mid,strencode);
jsize size = env->GetArrayLength(bytes);
jbyte* bytej = env->GetByteArrayElements(bytes,JNI_FALSE);
if(size > 0) {
rtn = (char*)malloc(size+1); //"\0"
memset(rtn,'\0',size);
memcpy(rtn, bytej, size);
}
env->ReleaseByteArrayElements(bytes,bytej,0);
return rtn;
}
#else
char* _JString2CStr(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env, barr);
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
if(alen > 0) {
rtn = (char*)malloc(alen+1); //"\0"
memcpy(rtn, ba, alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env, barr, ba,0);
return rtn;
}
#endif
//实现字符串的拼接
JNIEXPORT jstring JNICALL Java_test_shen_com_shentest2_JniTest_link
(JNIEnv *env, jobject thiz, jstring s1, jstring s2){
char* c1 = _JString2CStr(env,s1);
char* c2 = _JString2CStr(env,s2);
char* cs = strcat(c1,c2);
return env->NewStringUTF(cs);
}
//实现遍历数组
JNIEXPORT jintArray JNICALL Java_test_shen_com_shentest2_JniTest_fore
(JNIEnv *env, jobject thiz, jintArray array){
jsize size = env->GetArrayLength(array); //通过env来获取数组的大小
jint* array0 = env->GetIntArrayElements(array,JNI_FALSE);//通过env来得到数组的首地址(指针)
for(int i=0;i<size;i++) array0[i] = array0[i] + i;
env->ReleaseIntArrayElements(array,array0,0); //jni的数组在不使用的时候通过env进行释放
return array;
}
//回调Java有参数有返回值的方法
JNIEXPORT void JNICALL Java_test_shen_com_shentest2_JniTest_cToJava
(JNIEnv *env, jobject thiz){
jclass clasz = env->GetObjectClass(thiz);
//得到需要调用的方法
jmethodID methodID2 = env->GetMethodID(clasz,"printfJavaLog","(Ljava/lang/String;Ljava/lang/String;)Z");
//第一种调用方式 将参数组装成jvalue指针
jvalue * values = new jvalue[2];
values[0].l = env->NewStringUTF("SHEN");
values[1].l = env->NewStringUTF("dingpc");
jboolean reslut = env->CallBooleanMethodA(thiz,methodID2,values);
//第二种调用方式 直接传递参数
reslut = env->CallBooleanMethod(thiz,methodID2,
env->NewStringUTF("SHEN"),env->NewStringUTF("dingpc"));
//错误调用方式 这里会导致程序崩溃,因此参数不匹配,被调用的java方法的参数是String,对应jni的参数就应该是jstring,jstring继承于jobject,这里"SHEN"类型是char*,因此会抛出jni的异常
//reslut = env->CallBooleanMethod(thiz,methodID2,"SHEN","dingpc"); //该方式参数不对,这里参数应该是object,SHEN并不是一个object
if(reslut)
LOGE("reslut = true"); //打印logo
else
LOGE("reslut = faluse");
}
8、生成方法签名
jni回调java的方法,需要首先获取一个java变量,thiz是java调用者本身,如果要获取其他java的类需要通过env的FindClass方法,除此之外还需要通过方法签名来获取对应方法的id,方法签名可以使用javap来生成,步骤如下:
- Build -> Rebuild Project 进行重新编译生成对应的class文件
- Terminal切换到classes路径
- 找到对应的class文件执行javap -s xxx.class
#切换到class文件对应目录
E:\workspace\ShenTest2\app\src\main\java>cd E:\workspace\ShenTest2\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\test\shen\com\shentest2
#执行javap -s 被执行的class文件
E:\workspace\ShenTest2\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\test\shen\com\shentest2>javap -s JniTest.class
Compiled from "JniTest.java"
public class test.shen.com.shentest2.JniTest {
public test.shen.com.shentest2.JniTest();
descriptor: ()V
public native int add(int, int);
descriptor: (II)I
public native java.lang.String link(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
public native int[] fore(int[]);
descriptor: ([I)[I
public native void cToJava();
descriptor: ()V
public boolean printfJavaLog(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)Z
public void printfJavaLog(java.lang.String);
descriptor: (Ljava/lang/String;)V
public void printfJavaLog();
descriptor: ()V
public void printfJavaValue(int);
descriptor: (I)V
static {};
descriptor: ()V
}
二、NDK高级用法
上面介绍了NDK的基本用法, 但是在很多时候我们需要去封装多个动态库或者静态库(或者需要链接其他预构建库),把第三方so或者a文件编译到我们jni中好让我们编写的jni代码随意调用。下面就以ffmpeg来进行实践。
1、准备预构建库
创建一个ndk基本工程(可以参考第一章),但是在最新版的android studio(3.4.2)中创建出来的工程有所不一样,其CMakeLists.txt文件的路径被放在了cpp目录下(以前版本改文件与build.gradle同路径),这个先不纠结,我们先看看工程目录。
- 在cpp下创建了第三方库的目录ffmpeg
- 将编译好的ffmpeg的include目录拷贝进来作为native-lib.cpp链接的头文件路径
- 在ffmpeg中创建src来存放不同平台架构的动态库文件,当然名字可以任意取,关键是后面指定路径的时候要正确
- 暂时只创建了armeabi-v7a架构,为了兼容所有平台,你可以自己定义所有架构的目录,这里的路径名称最好不要随便取
2、配置CMakeLists.txt
ffmpeg的头文件路径
${CMAKE_SOURCE_DIR}/ffmpeg/include/
ffmpeg的动态库路径:
${CMAKE_SOURCE_DIR}/ffmpeg/src/${ANDROID_ABI}/xxx.so
其中ANDROID_ABI就是芯片架构,所以上面的src里面的路径建议按照ABI来命名。CMAKE_SOURCE_DIR表示CMakeLists.txt文件的当前路径,在最新版的studio里面该文件被放在cpp目录下,这也是为什么我把ffmpeg的根目录放在cpp下面,就是为了能够用改环境变量。网上很多资料将ffmpeg的动态库放在app/libs或者app/src/main/jniLibs,当然都可以,但是唯一要注意的就是CMakeLists.txt的配置要正确,否则就会出现各种ninja Error:.,needed by ...,missing and no known rule to make it错误。配置如下:
cmake_minimum_required(VERSION 3.4.1)
#设置添加动态库native-lib
add_library( # Sets the name of the library.
native-lib
SHARED
native-lib.cpp)
#设置添加我们需要调用的第一个预构建库
add_library(
ffmpeg_avcodec #为第一个so文件指定一个名字
SHARED #对应的文件是动态库(可以为静态库)
IMPORTED #标志库文件已经存在只需要导入
)
set_target_properties( #配合add_library使用,为其指定库文件路径
ffmpeg_avcodec #上面的名字
PROPERTIES IMPORTED_LOCATION #标志下面的参数为改库的绝对路径
${CMAKE_SOURCE_DIR}/ffmpeg/src/${ANDROID_ABI}/libavcodec-57.so
)
#设置添加我们需要调用的第二个预构建库 这个不能批量添加,有N个so就需要N条命令
add_library(
ffmpeg_avdevice
SHARED
IMPORTED
)
set_target_properties(
ffmpeg_avdevice
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/ffmpeg/src/${ANDROID_ABI}/libavdevice-57.so
)
#指定预构建库头文件的路径${CMAKE_SOURCE_DIR}表示cmake文件的当前路径,一定要看好这个路径
include_directories(
${CMAKE_SOURCE_DIR}/ffmpeg/include/
)
#以前的不用改
find_library( # Sets the name of the path variable.
log-lib
log)
#链接所需要的所有库,注意参数是上面add_library里面指定的名字而不是so的路径
target_link_libraries( # Specifies the target library.
native-lib
ffmpeg_avcodec
ffmpeg_avdevice
# Links the target library to the log library
# included in the NDK.
${log-lib})
关于最新版本的cmake用法,可以参考官方文档https://developer.android.google.cn/studio/projects/add-native-code?hl=zh-cn
3、配置build.gradle
这个其实没有多少改动,值得注意的是CMakeLists.txt文件路径,有些版本可能在app目录下:
有些版本的CMakeLists.txt在cpp目录下,这里的配置为:
默认情况下,Gradle 会针对 NDK 支持的 ABI 将您的原生库构建到单独的 .so
文件中,并将其全部打包到您的 APK 中。如果您希望 Gradle 仅构建和打包原生库的特定 ABI 配置,您可以在模块级 build.gradle
文件中使用 ndk.abiFilters
标志指定这些配置如下,我这里因为只准备了armeabi-v7a的动态库,所以需要过滤一下,否则就会出现找不到x86平台下的动态库。
除此之外,还需要为Gradle指定so文件路径,让gradle能够正确打包进去,这里的so的路径为src/main/cpp/ffmpeg/src进行如下配置:
android {
compileSdkVersion 29
buildToolsVersion "29.0.1"
defaultConfig {
applicationId "com.shen.ndktest"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
abiFilters "armeabi-v7a"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
sourceSets.main {
jniLibs.srcDirs = ['src/main/cpp/ffmpeg/src']
jni.srcDirs = []
}
}
4、在jni中调用ffmpeg的函数
在native-lib.cpp文件里面,编写代码调用avcodec_register_all函数,编译在手机运行OK
//native-lib.cpp
#include <jni.h>
#include <string>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h> //包含ffmpeg头文件
#ifdef __cplusplus
}
#endif
extern "C" JNIEXPORT jstring JNICALL
Java_com_shen_ndktest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
avcodec_register_all(); //调用ffmpeg注册函数
return env->NewStringUTF(hello.c_str());
}
5、所踩的坑
1)、ninja: error:.., needed by ..., missing and no known rule to make it
如上图,改问题一般原因是error: 'ffmpeg/src/armeabi-v7a/libavdevice-57.so',文件没有找到,但是该文件已经在项目工程中了,所以应该从CMakeLists.txt配置的路径着手排查,检查so文件的路径是否配置正确,我这里因为没有加上CMAKE_SOURCE_DIR进行定位绝对路径,所以报出没有找到so文件。在后面的ANDROID_ABI路径不匹配的时候也会报出相同问题。
2)、ninja: error:.., needed by ..., missing and no known rule to make it
如上图,这里也是报出ninja:error...needed by ....missing and no known rule to make it,但是这里的cmakelists.txt的配置并没有什么问题,这里报出E:/workspace/NdkTest/app/src/main/cpp/ffmpeg/src/x86_64/libavcodec-57.so,会发现gradle居然主动的去加载x86_64下面的动态库,因为我并没有准备这些架构的so文件,因此需要在gradle进行平台过滤。
3)、java.lang.UnsatisfiedLinkError: dlopen failed: library "libavcodec-57.so" not found
如上图,在手机上运行,程序直接崩溃,错误提示链接libavcodec-57.so的时候失败,我最开始有怀疑是我编译出来的动态库有问题还是我的ndk有问题,最后在csdn搜索,发现原来是需要在build.gradle里面配置指定so文件的路径,我其实感到很奇怪,尼玛CMakeLists.txt不是已经指定好了吗,没办法通过soruceSets.main进行设置,我这里的路径是src/main/cpp/ffmpeg/src,这里注意的是,路径一定要精确带ABI上一级目录,目录不匹配改错误依旧
4)、[1/2] Building CXX object CMakeFiles/native-lib.dir/native-lib.cpp.o
如上图,编译的时候直接报错avcodec_register_all函数没有定义,什么鬼情况,难道动态库没有链接进去,但是检查各项配置都没有什么问题,最后不知道是在那篇文章里面发现,ffmpeg是采用的c语言编写,jni的cpp文件在调用ffmpeg的api的时候需要进行编译语言环境匹配,尼玛,恍然大悟,果断加上extern "C"
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#ifdef __cplusplus
}
#endif
5)、Error configuring CMake server (G:\SDT\Android\sdk\cmake\3.10.2.4988404\bin)
如上图,这个问题很奇怪,在网上搜索了很多出现这种问题的,有些说是ndk路径多了空格,有些说是编译工具版本不支持,先不管了,从错误信息上看cmake文件的57行进行连接的时候有问题,才发现avcodec放在native-lib前面,个人认为可能是avcodec属于导入进去的,应该依赖native-lib什么的,所以应该吧native-lib放在前面,如下
target_link_libraries( # Specifies the target library.
native-lib #注意native-lib应该放在最前面
avcodec
${log-lib})
6)、fatal error: 'libavcodec/avcodec.h' file not found
如上图,虽然native-lib.cpp能够找到对应的头文件,但是ffmpeg/include/libavcodec/avcodec.h找不到同目录下的头文件,不知道怎么回事,头文件的目录已经在cmake文件里面配置了,按理说应该studio能够自动引入呢,最后搞不定,只有按照库里面头文件的路径来放置头文件路径了,因此如下将ffmpeg的头文件放置在cpp目录下,并更改对应的配置: