一.为什么用JNI
1.JNI扩展了java虚拟机的能力,驱动开发(wifi-hotspot)。操作手机网卡,把手机网卡的状态置为混杂模式
2.Native code 效率高,数学运算,实时渲染的游戏上上,音视频压缩
android下的极品飞车。重力引擎,碰撞效果的模拟
3.复用一些大公司开发好的类库如 (人脸识别,文件压缩)
开源的CC代码库,openv intel开发的图形和视频实时处理的方法库,人脸识别
rmvb 视屏AVI 视屏和音频编解码的代码库 ffmpeg
opengl c 代码库 用于游戏界面
4.特殊的业务场景
二.初识NDK
1.创建一个android工程
用javah -jni 生成JNI样式的头文件
例:Toast.makeText(this, new String(getStringFromC().getBytes("iso-8859-1"),
"utf-8"), 1).show();
//将iso-8859-1编码的字符串转化为 utf-8编码格式
java和C间互传数据
char * cusername =
Jstring2CStr( env,
username)
char * cuserpassword =
Jstring2CStr(env,
password)
int
result =
login(char *usernam, char *passworld);
if(result == 200){
//验证成功
}else if(result == 400){
//验证失败
}
jclass dpclass = (*env)->FindClass(env,"com/example/ndkcallback/DataProvider");
if(dpclass == 0){
LOGI("find class error");
return ;
}
LOGI("find class !");
//2.寻找里面的方法 jmethodID (*GetMethodID)(JNIEnv *,jclass, const char *, const char *);
jmethodID methodID = (*env)->GetMethodID(env,dpclass,"helloFromJava()","()V");
if(methodID == 0){
LOGI("find method error");
return ;
}
LOGI("find method !");
//第一个参数为env,第二个参数为取得的类名,第三个参数为函数名,第四个参数为()为函数所带的参数,V为返回值为空
//3.调用里面的方法 void (*callVoidMethod)(JNIEnv *,jobject, jmethodID,...)
(*env)->CallVoidMethod(env,obj,methodID);
//第一个参数:env,第二个为obj,第三个为取得的方法名
};
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callImethod2
(JNIEnv *env, jobject obj){
//在C代码里面调用java代码里面的方法
//java 反射
//1.找到java代码的class文件jclass (*FindClass)(JNIEnv*, const char *)
jclass dpclass = (*env)->FindClass(env,"com/example/ndkcallback/DataProvider");
if(dpclass == 0){
LOGI("find class error");
return ;
}
LOGI("find class !");
//2.寻找里面的方法 jmethodID (*GetMethodID)(JNIEnv *,jclass, const char *, const char *);
jmethodID methodID2 = (*env)->GetMethodID(env,dpclass,"Add","(II)I");
if(methodID2 == 0){
LOGI("find method error");
return ;
}
LOGI("find method !");
//第一个参数为env,第二个参数为取得的类名,第三个参数为函数名,第四个参数为()为函数所带的参数,V为返回值为空
//3.调用里面的方法jint (*CallIntMethod)(JNIEnv *,jobject,jmethodID,...);
jint result = (*env)->CallIntMethod(env,obj,methodID2,5,10);
//第一个参数:env,第二个为obj,第三个为取得的方法名
LOGI("result = %d",result);
};
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callImethod3
(JNIEnv *env, jobject obj){
//在C代码里面调用java代码里面的方法
//java 反射
//1.找到java代码的class文件jclass (*FindClass)(JNIEnv*, const char *)
jclass dpclass = (*env)->FindClass(env,"com/example/ndkcallback/DataProvider");
if(dpclass == 0){
LOGI("find class error");
return ;
}
LOGI("find class !");
//2.寻找里面的方法 jmethodID (*GetMethodID)(JNIEnv *,jclass, const char *, const char *);
jmethodID methodID3 = (*env)->GetMethodID(env,dpclass,"printString","(Ljava/lang/String;)V");
if(methodID3 == 0){
LOGI("find method error");
return ;
}
LOGI("find method !");
//第一个参数为env,第二个参数为取得的类名,第三个参数为函数名,第四个参数为()为函数所带的参数,V为返回值为空
//3.调用里面的方法 void (*callVoidMethod)(JNIEnv *,jobject, jmethodID,...)
(*env)->CallVoidMethod(env,obj,methodID3,(*env)->NewStringUTF(env,"Hello in C"));
//第一个参数:env,第二个为obj,第三个为取得的方法名
//LOGI("result = %d",result);
};
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callImethod4
(JNIEnv * env, jobject obj){
//在C代码里面调用java代码里面的方法
//java 反射
//1.找到java代码的class文件jclass (*FindClass)(JNIEnv*, const char *)
jclass dpclass = (*env)->FindClass(env,"com/example/ndkcallback/DataProvider");
if(dpclass == 0){
LOGI("find class error");
return ;
}
LOGI("find class !");
//2.寻找里面的方法 jmethodID (*GetMethodID)(JNIEnv *,jclass, const char *, const char *);
//注意:如果要寻找的方法是静态的方法那就不能直接获取method的ID
//jmethodID methodID4 = (*env)->GetMethodID(env,dpclass,"printStaticStr","(Ljava/lang/String;)V");
jmethodID (*GetStaticMethodID)(JNIEnv *,jclass,const char *,const char*);
jmethodID StaticMethod = (*env)->GetStaticMethodID(env,dpclass,"printStaticStr","(Ljava/lang/String;)V");
if(StaticMethod == 0){
LOGI("find method error");
return ;
}
LOGI("find method !");
//第一个参数为env,第二个参数为取得的类名,第三个参数为函数名,第四个参数为()为函数所带的参数,V为返回值为空
//3.调用里面的方法 void (*callStaticVoidMethod)(JNIEnv *,jclass, jmethodID,...)
(*env)->CallStaticVoidMethod(env,dpclass,StaticMethod,(*env)->NewStringUTF(env,"Hello static in C"));
//第一个参数:env,第二个为obj,第三个为取得的方法名
//LOGI("result = %d",result);
};
复杂多类中调用native方法
jobject (*AllocObject)(JNIEnv *, jclass
(*env)->AllocObject (env,dpclass);
1.JNI扩展了java虚拟机的能力,驱动开发(wifi-hotspot)。操作手机网卡,把手机网卡的状态置为混杂模式
2.Native code 效率高,数学运算,实时渲染的游戏上上,音视频压缩
3.复用一些大公司开发好的类库如 (人脸识别,文件压缩)
4.特殊的业务场景
二.初识NDK
1.创建一个android工程
2.JAVA代码中声明native方法
public native String helloFromJNI();
3.创建jni目录,编写C代码,方法名字要对应
4.编写Android.mk文件(编译的配置文件)
5.NDK编译生成动态库。
6.JAVA代码load动态库,调用native代码。
用javah -jni 生成JNI样式的头文件
Android,mk
对C语言的代码库编译的Makefile文件的书写
# 交叉编译器 在编译C代码C++代码依赖的配置文件 linux下makefile的语法的子集
# LOCAL_PATH是定义的一个变量:(call my-dir)把当前 Android.mk的路径获取出来付给LOCAL_PATH
LOCAL_PATH := $(call my-dir)
# 变量的初始化操作。特点:不会初始化LOCAL_PATH的变量
(也就是不会初始化这句声明前的代码中的变量)
include $(CLEAR_VARS)
#LOCAL_MODULE:生成模块的名字;libHello.so加lib前缀和.so后缀是linux下makefile约定的。
LOCAL_MODULE
:=
Hello
LOCAL_SRC_FILES :=
Hello.c
include $(BUILD_SHARED_LIBRARY)
这个是指定你要编译哪些文件
不需要指定头文件,引用哪些依赖,因为编译器会自动找到这些依赖 自动编译
include $(BUILD_SHARED_LIBRARY) BUILD_STATIC_LIBRARY
编译后生成的库类型,如果是静态库.a配置include $(BUILD_STATIC_LIBRARY)
别的参数
LOCAL_CPP_EXTENSION := cc //指定C++文件的扩展名
LOCAL_MODULE := ndkfoo
LOCAL_SRC_FILES := ndkfoo.cc
LOCAL_LDLIBS += -llog -lvmsagent -lmpnet - lmpxml -lH26Android //指定加载一些别的什么库
这个是指定你要编译哪些文件
不需要指定头文件,引用哪些依赖,因为编译器会自动找到这些依赖 自动编译
include $(BUILD_SHARED_LIBRARY) BUILD_STATIC_LIBRARY
编译后生成的库类型,如果是静态库.a配置include $(BUILD_STATIC_LIBRARY)
别的参数
LOCAL_CPP_EXTENSION := cc
LOCAL_MODULE
LOCAL_SRC_FILES := ndkfoo.cc
LOCAL_LDLIBS += -llog
NDK常见错误的处理
1.由于粗心大意将 android.mk写错,这将导致linux模拟器中的ndk-build工具找不到编译文件
$ ndk-build
Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk
/cygdrive/f/NDK/build/core/add-application.mk:133: *** Android NDK: Aborting...
。 停止。
/cygdrive/f/NDK/build/core/add-application.mk:133: *** Android NDK: Aborting...
2.
android.mk文件中的配置信息 不全或出现错误。
$ ndk-build
/cygdrive/f/NDK/build/core/build-shared-library.mk:23: *** Android NDK: Missing LOCAL_MODULE before including BUILD_SHARED_LIBRARY in jni/Android.mk
。 停止。
/cygdrive/f/NDK/build/core/build-shared-library.mk:23: *** Android NDK: Missing LOCAL_MODULE before including BUILD_SHARED_LIBRARY in jni/Android.mk
3.C语言语法出现了错误,编译不会通过。
$ ndk-build
Compile thumb : Hello <= Hello.c
jni/Hello.c: In function 'Java_com_example_ndkhelloworld_MainActivity_helloFromC':
jni/Hello.c:9: error: 'err' undeclared (first use in this function)
jni/Hello.c:9: error: (Each undeclared identifier is reported only once
jni/Hello.c:9: error: for each function it appears in.)
jni/Hello.c:9: error: expected ';' before 'c'
/cygdrive/f/NDK/build/core/build-binary.mk:240: recipe for target `obj/local/armeabi/objs/Hello/Hello.o' failed
make: *** [obj/local/armeabi/objs/Hello/Hello.o] Error 1
Compile thumb
jni/Hello.c: In function 'Java_com_example_ndkhelloworld_MainActivity_helloFromC':
jni/Hello.c:9: error: 'err' undeclared (first use in this function)
jni/Hello.c:9: error: (Each undeclared identifier is reported only once
jni/Hello.c:9: error: for each function it appears in.)
jni/Hello.c:9: error: expected ';' before 'c'
/cygdrive/f/NDK/build/core/build-binary.mk:240: recipe for target `obj/local/armeabi/objs/Hello/Hello.o' failed
make: *** [obj/local/armeabi/objs/Hello/Hello.o] Error 1
4.java层的提示C代码库没有找到,C代码库没有加载进来。(静态代码库出现了问题)
Caused by: java.lang.UnsatisfiedLinkError: Library Hel1o not found
5.C代码函数签名出现错误。(仔细检查代码名书写的错误)
Caused by: java.lang.reflect.InvocationTargetExceptio
n
Caused by: java.lang.UnsatisfiedLinkError: hello_From_C
6.逻辑性的错误,比如使用了已经回收的内存空间,访问了没权限访问的内存空间。
断点执行找到出错点。通过log方式来观察错误点。
在C语言中使用logcat
Android.mk文件增加LOCAL_LDLIBS += -llog
C代码中增加
#include<android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS
__
);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS
__
);
LOGI("info\n");
LOGN("debug\n");
NDK中处理中文问题
将C语言写的代码换成UTF8格式的编码,GBK的代码会产生异常。
1.ndk-r4-crystal4版本一样会产生错误。因为低版本以iso-8859-1读取数据,在代码中显示的转码。
//将iso-8859-1编码的字符串转化为 utf-8编码格式
java和C间互传数据
1.将java语言中提供的String类型数据转化成C语言中 *char类型的转化函数
char*
Jstring2CStr(JNIEnv*
env,
jstring
jstr)
{
char*
rtn
=
NULL;
jclass
clsstring
=
(*env)->FindClass(env,"java/lang/String");
//找到string的class
jstring
strencode
=
(*env)->NewStringUTF(env,"GB2312");
//得到一个java字符串"GB2312"
jmethodID
mid
=
(*env)->GetMethodID(env,clsstring,
"getBytes",
"(Ljava/lang/String;)[B");
//从String类包找到名为"getBytes"的方法,这个方法的签名:(参数)返回值,参数:String 返回值:[B
bytes类型的数组
jbyteArray
barr=
(jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");
//参数1,:env,参数二:要转换的字符串,参数三:上面取得的函数ID,参数四:要转换的数据类型。转换成Byte数组
jsize
alen
=
(*env)->GetArrayLength(env,barr);
//得到数组的长度
jbyte*
ba
=
(*env)->GetByteArrayElements(env,barr,JNI_FALSE);
//获取byte数组中的每一个元素,并放入一个内存空间里
if(alen
>
0)
{
rtn
=
(char*)malloc(alen+1);
//"\0"
申请一块内存空间,这块内存空间的大小为:alen+1既多加一个“\0”的长度
memcpy(rtn,ba,alen);
//将ba里面的内容拷贝到char * rtn这个指针所指的内存里
rtn[alen]=0;
//把rtn数组里的最后一个元素赋值为0
}
(*env)->ReleaseByteArrayElements
(env,barr,ba,alen+1);
//释放ByteArray数组所占的空间
JNIenv
env , jinArray barr ba(刚才申请的空间赋给了谁)
alen+1数组的长度
return rtn;
//返回char * rtn的指针 地址
}
char*
{
}
2.NDK中提供的取得INT数组长度的函数:int len = (*env)->GetArrayLength(env,intArray);
3.NDK中定义一个int类型数组的函数:jint* intarr = (*env)->GetIntArrayElements(env,intArray,0);// 取得数组的首地址
//env为传进来的参数(java虚拟机结构体C实现的指针 包含有很多jni方法),intArray为已有的一个数组,0表示可以拷贝。
//jobject obj
代表的是调用这个C代码的java对象
//对于在java中声明的静态方法,传来的参数是jclass .传来的是类
//jobject
//对于在java中声明的静态方法,传来的参数是jclass .传来的是类
C语言
申请的空间如果显示的释放掉了,但是在java中又引用
企业用JNI开发的流程
例:阿里旺旺安装版的程序安全级别比较高,登入模块它里面使用C,C++来写的并把这些模块编译成代码库,
login(char *usernam, char *passworld){
}
javah 生成native方法的签名
jint Java_xxx_xxx_xxx_xxx_Login(JNIENV *env,jobject obj,jstring username,jstring password){
//生成名由Java+java中定义本地接口所在的包名+类名+方法名
}
C语言函数模块调用java空方法
使用javap 打印出java中函数的签名。
public class DataProvider {
public void helloFromJava(){
System.out.println("hello from java");
}
public int Add(int x,int y){
return x+y;
}
public void printString(String s){
System.out.println("java"+s);
}
public static void printStaticStr(String s){
System.out.println("java"+s);
}
//让C代码调用对应的java代码
public native void callImethod1();
public native void callImethod2();
public native void callImethod3();
//调用静态的C代码
public native void callImethod4();
}
使用javap 打印出java中函数的签名。
}
C语言中的实现
#include<stdio.h>
#include<jni.h>
#include "com_example_ndkcallback_DataProvider.h"
#include<android/log.h>
#include<malloc.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#include<stdio.h>
#include<jni.h>
#include "com_example_ndkcallback_DataProvider.h"
#include<android/log.h>
#include<malloc.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callImethod1
(JNIEnv *env, jobject obj){
//在C代码里面调用java代码里面的方法
//java 反射
//1.找到java代码的class文件jclass (*FindClass)(JNIEnv*, const char *)
};
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callImethod2
};
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callImethod3
};
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callImethod4
};
jobject (*AllocObject)(JNIEnv *, jclass
(*env)->AllocObject
(?????)ANDROID音频采集回放旅程.
基本流程为:
audiorecord采集
->filter
->g.711编码
->RTP送
->RTP收
->g.711解码
->filter
->回放
其中
"filter->g.711编码->RTP送->RTP收->g.711解码->filter
"
框架为已有的MEDIASTREAM框架,C代码.
采集和回放均是用JAVA语言,ANDROID类实现.
现在问题是:
1.采集和回放端如何与已有的MEDIASTREAM框架FILTER通信,音频数据如何从JAVA传送给C语言,可不可以直接调用?
2.
audiorecord采集
->filter
filter
->回放
能不能用SOCKET建立通信?