android 移植 ffmpeg (二) 原始碼分析 JNI程式設計說明
阿新 • • 發佈:2019-01-28
例子原始碼
測試例子源地址: 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.” 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型別簽名
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 |
<pre name="code" class="java">String 對應 <span style="font-family: Arial, Helvetica, sans-serif;">jstring </span>
<span style="font-family: Arial, Helvetica, sans-serif;">其它 對應 jobject</span>
函式定義三要素
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);
}