android 移植 ffmpeg (二) 源码分析 JNI编程说明


例子源码

测试例子源地址: https://github.com/roman10/android-ffmpeg-tutorial 

JNI接口编程简要说明

JNI作为一种编程接口,是解决Java语言与C/C++语言之间的通信问题。
我们知道,Java代码编译的结果是字节码,这种码只能在Java虚拟机上运行,而C/C++编译最后的结果是机器码,能够直接在cpu上运行。要想解决字节码与机器码通信的问题,Java设计出JNI接口来让虚拟机来支持。要理解这个接口,我们得先从函数签名说起。

函数签名

函数签名是我自己随性说的,觉得这么理解会好些。可以理解为一个函数签名就代码一个函数。
记得以前研究C/C++动态库时,碰到的一个难点就是C的动态库与C++的动态库是不一样的。理由就是C++支持重载,而C语言不支持。要想生成C语言的动态库,需要加上 extern "C". 玩过C动态库的,对extern "C"肯定不陌生(都是泪呀),生成动态库时需要用它,而使用动态库时也得在声明中加上:extern "C",表示这是C的函数。
想想python 之弹的第一句话“Beautiful is better than ugly.”  而 extern "C"这么难看的代码出自我手,还真有点难为情。
     扯远了, 这种纠结主要还是C和C++毕竟不是同一种语言。而每种语言编译时代码函数的函数签名是不一样的引起的。
 int add(int a, int b) {
     return a + b;
   }

以上面的add为例,C语言表示为 add, 而C++表示为 add_int_int.  C++这么表示就是为了支持重载,比如再有一个函数为 int add(int a){return a;} 时,签名就是 add_int, 好作区分。
而编译器是通过函数签名来查找指定函数的。 如果是C++编译器,而调用C的函数,就得告诉编译器这是个C函数,要用查找C函数的方法来找查这个C函数,所以需要加上 extern "C" 语句。
同理,Java为了使用C/C++的函数,也需要一个类似 extern "C"的标识来告诉Java编译器,这个是C/C++代码的函数,这个标识叫做 native.
native的作用就是告诉Java编译器,要在当前加载的库中查找与此函数定义相匹配的函数。这样就解决了JNI接品中函数查找问题。

JNI类型签名

native定义了函数查找的位置,而类型查找就得看类型签名了。
数据类型:
  

Java 类型

类型签名

boolean

Z

byte

B

char

C

short

S

int

I

long

L

float

F

double

D

L全限定名;,比如String, 其签名为Ljava/lang/util/String;

数组

[类型签名, 比如 [B

      函数类型
      格式: 

(类型签名1类型签名2...)返回值类型签名 

如String toString() 函数:"()Ljava/lang/String;"

函数定义规范

JNI为了能够让C代码能够访问Java代码,规定了JNI函数定义时,要必须传两个参数,位于函数参数的第1,第2个参数。以例子中的代码为例:
C/C++声明:
jint naMain(JNIEnv *pEnv, jobject pObj, jobject pMainAct, jstring pFileName, jint pNumOfFrames) 
Java 声明:
	private static native int naMain(MainActivity pObject, String pVideoFileName, int pNumOfFrames);
JNIEnv 表示整个JNI环境,通过他可以操作整修JAVA环境。
jobject pObj: 表示调用这个函数的类环境,如果以静态函数方式调用,则这个表示是声明此方法的类,如果作为普通函数,就表示为调用的类对象。因为调用方法由Java代码来确定,是不是静态方式调用,不确定。 所以感觉此参数运用得不多。 例子中是以静态方式调用的,所以传进来的就是MainActivity类。
其实的参数根据类形对应方式就一一对应了。

类型对应方式

基本类型对应方式:

Java类型

本地类型

说明

boolean

jboolean

无符号,8位

byte

jbyte

无符号,8位

char

jchar

无符号,16位

short

jshort

有符号,16位

int

jint

有符号,32位

long

jlong

有符号,64位

float

jfloat

32位

double

jdouble

 

void

void

N/A

类类型对应方式:
String 对应 jstring 
其它    对应  jobject
 
   

函数定义三要素

JNI中通过下面的三要素来定义一个接口
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

例子中对应的代码有:
JNINativeMethod nm[1];
	nm[0].name = "naMain";
	nm[0].signature = "(Lroman10/tutorial/android_ffmpeg_tutorial01/MainActivity;Ljava/lang/String;I)I";
	nm[0].fnPtr = (void*)naMain;

可通过   RegisterNatives注册接口, 来让Java代码调用此函数。
jclass cls = (*env)->FindClass(env, "roman10/tutorial/android_ffmpeg_tutorial01/MainActivity");
	//Register methods with env->RegisterNatives.
	(*env)->RegisterNatives(env, cls, nm, 1);

当然JNI中还有另一种不用 RegisterNatives方法, 就是特殊的函数定义,本人感觉这种方式太麻烦!
规则如下:
一: 前缀: Java_
二:类的全限定名, 用下划线进行分隔(_):  roman10_tutorial_android_ffmpeg_tutorial01_MainActivity  (原来名字有下划线时怎么处理?)
三:再加下划线分隔符
四: 方法名:naMain
五:对于重载的本地方法,加上两个下划线(“__”), 后跟mangled参数签名。

我试了把naMain按这个规划测试,没测试通,不知道是什么原因!
反正给我感觉这方法比较麻烦。

回调JAVA函数


 JNI传入  JNIEnv的目的就是能够用C的代码调用Java环境。例子中有几个地方调用了Java的函数。 注意都是用了签名。

1 查找Java的类,并向Java类中注册方法
jclass cls = (*env)->FindClass(env, "roman10/tutorial/android_ffmpeg_tutorial01/MainActivity");
	//Register methods with env->RegisterNatives.
	(*env)->RegisterNatives(env, cls, nm, 1);

2. 创建Bitmap类对象。
jclass javaBitmapClass = (jclass)(*pEnv)->FindClass(pEnv, "android/graphics/Bitmap");
	jmethodID mid = (*pEnv)->GetStaticMethodID(pEnv, javaBitmapClass, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
	。。。
jclass bitmapConfigClass = (*pEnv)->FindClass(pEnv, "android/graphics/Bitmap$Config");
	jobject javaBitmapConfig = (*pEnv)->CallStaticObjectMethod(pEnv, bitmapConfigClass,
			(*pEnv)->GetStaticMethodID(pEnv, bitmapConfigClass, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"), jConfigName);
	//create the bitmap
	return (*pEnv)->CallStaticObjectMethod(pEnv, javaBitmapClass, mid, pWidth, pHeight, javaBitmapConfig);

3. 回调类的方法
void SaveFrame(JNIEnv *pEnv, jobject pObj, jobject pBitmap, int width, int height, int iFrame) {
	char szFilename[200];
	jmethodID sSaveFrameMID;
	jclass mainActCls;
	sprintf(szFilename, "/sdcard/android-ffmpeg-tutorial01/frame%d.jpg", iFrame);
	mainActCls = (*pEnv)->GetObjectClass(pEnv, pObj);
	sSaveFrameMID = (*pEnv)->GetMethodID(pEnv, mainActCls, "saveFrameToPath", "(Landroid/graphics/Bitmap;Ljava/lang/String;)V");
	LOGI("call java method to save frame %d", iFrame);
	jstring filePath = (*pEnv)->NewStringUTF(pEnv, szFilename);
	(*pEnv)->CallVoidMethod(pEnv, pObj, sSaveFrameMID, pBitmap, filePath);
	LOGI("call java method to save frame %d done", iFrame);
}


















你可能感兴趣的:(android)