android jni 包裹檔案(jni wrapper) 以 speex 庫為例
參考資料:
1 http://code.google.com/p/android-recorder/downloads/list 這個是一個android recorder ,使用speex編碼,程式碼很乾淨,推薦一讀
2 http://andrewbrobinson.com/2011/11/28/a-jni-wrapper-for-speex-on-android/ 這個是國外大牛不爽另外一位大牛沒開放jni wrapper,所以自己寫了一個POST(參照我的NDK
編譯SPEEX一文)
感謝以上朋友的分享!
可能有很多朋友還不知道 jni wrapper 是用來幹什麼的,如果有玩過windows 驅動的朋友應該很容易理解,其實就是一個驅動檔案,用於作為 Java 與 .so 庫之間的介面 (.so 就是我們將C/C++檔案編譯出來的庫) , 那麼驅動檔案的話其實就是需要你把要用的函式用特定格式寫出來,而對於使用者呢,不需要知道驅動檔案函式的格式怎麼寫,只需要知道怎麼使用這個類,有什麼成員函式就夠了,所以簡單的來說,就是完成這個工作。
所以,我們應該有大至的瞭解了,需要做兩件事,一,寫一個驅動,二,寫一個類
準備工作: 新建一個android 應用工程,下載最新的speex原始碼,解壓後取出libspeex與include放入$project/jni/
因為類比較簡單,大家看起來也很親切,所以我們先介紹類: 先在你的$project/src/$package/ 新建一個類,如下圖,我建了Speex.java
這個類很簡單吧,首先,我們要呼叫庫,庫的名字叫“speex”,eclips 會自動去掉libspeex.so的字首,所以不要寫成"libspeex".(注:org.slf4j是一個log的庫,有興趣的可以下下來,沒有的話可以註釋掉) ,這個類裡面有幾個函式,如public native int open(int compression),注意,要加上native來表示這是呼叫native編譯出來的庫。
package com.ultraman;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class Speex {
/* quality
* 1 : 4kbps (very noticeable artifacts, usually intelligible)
* 2 : 6kbps (very noticeable artifacts, good intelligibility)
* 4 : 8kbps (noticeable artifacts sometimes)
* 6 : 11kpbs (artifacts usually only noticeable with headphones)
* 8 : 15kbps (artifacts not usually noticeable)
*/
private static final int DEFAULT_COMPRESSION = 8;
private Logger log = LoggerFactory.getLogger(Speex.class);
Speex() {
}
public void init() {
load();
open(DEFAULT_COMPRESSION);
log.error("speex opened");
}
private void load() {
try {
System.loadLibrary("speex");
} catch (Throwable e) {
e.printStackTrace();
}
}
public native int open(int compression);
public native int getFrameSize();
public native int decode(byte encoded[], short lin[], int size);
public native int encode(short lin[], int offset, byte encoded[], int size);
public native void close();
}
類寫完了,那麼接下來怎麼去寫驅動呢,用一些小工具來幫我們把格式先寫好吧,javah ,只要安裝過JDK的就有了
進入$project/,
>javah -classpath bin/classes -jni com.ultraman.Speex
classpath 是你存放類的目錄,-jni 生成的標頭檔案放到jni資料夾裡。 類的名稱(package_name + class_name)
開啟標頭檔案,那麼就可以看到幫我們寫好的那個類中函式對應驅動函式的名字,如:
encode()---->JNIEXPORT jbyteArray JNICALL Java_com_speex_ultraman_speex_encode (JNIEnv *, jobject, jshortArray);
然後建立jni wrapper檔案,在$project/jni/下新建speex_jni.cpp,然後為在類中的成員函式簽名寫出實現。如下:
#include <jni.h>
#include <string.h>
#include <unistd.h>
#include <speex/speex.h>
static int codec_open = 0;
static int dec_frame_size;
static int enc_frame_size;
static SpeexBits ebits, dbits;
void *enc_state;
void *dec_state;
static JavaVM *gJavaVM;
extern "C"
JNIEXPORT jint JNICALL Java_com_ultraman_Speex_open
(JNIEnv *env, jobject obj, jint compression) {
int tmp;
if (codec_open++ != 0)
return (jint)0;
speex_bits_init(&ebits);
speex_bits_init(&dbits);
enc_state = speex_encoder_init(&speex_nb_mode);
dec_state = speex_decoder_init(&speex_nb_mode);
tmp = compression;
speex_encoder_ctl(enc_state, SPEEX_SET_QUALITY, &tmp);
speex_encoder_ctl(enc_state, SPEEX_GET_FRAME_SIZE, &enc_frame_size);
speex_decoder_ctl(dec_state, SPEEX_GET_FRAME_SIZE, &dec_frame_size);
return (jint)0;
}
extern "C"
JNIEXPORT jint Java_com_ultraman_Speex_encode
(JNIEnv *env, jobject obj, jshortArray lin, jint offset, jbyteArray encoded, jint size) {
jshort buffer[enc_frame_size];
jbyte output_buffer[enc_frame_size];
int nsamples = (size-1)/enc_frame_size + 1;
int i, tot_bytes = 0;
if (!codec_open)
return 0;
speex_bits_reset(&ebits);
for (i = 0; i < nsamples; i++) {
env->GetShortArrayRegion(lin, offset + i*enc_frame_size, enc_frame_size, buffer);
speex_encode_int(enc_state, buffer, &ebits);
}
//env->GetShortArrayRegion(lin, offset, enc_frame_size, buffer);
//speex_encode_int(enc_state, buffer, &ebits);
tot_bytes = speex_bits_write(&ebits, (char *)output_buffer,
enc_frame_size);
env->SetByteArrayRegion(encoded, 0, tot_bytes,
output_buffer);
return (jint)tot_bytes;
}
extern "C"
JNIEXPORT jint JNICALL Java_com_ultraman_Speex_decode
(JNIEnv *env, jobject obj, jbyteArray encoded, jshortArray lin, jint size) {
jbyte buffer[dec_frame_size];
jshort output_buffer[dec_frame_size];
jsize encoded_length = size;
if (!codec_open)
return 0;
env->GetByteArrayRegion(encoded, 0, encoded_length, buffer);
speex_bits_read_from(&dbits, (char *)buffer, encoded_length);
speex_decode_int(dec_state, &dbits, output_buffer);
env->SetShortArrayRegion(lin, 0, dec_frame_size,
output_buffer);
return (jint)dec_frame_size;
}
extern "C"
JNIEXPORT jint JNICALL Java_com_ultraman_Speex_getFrameSize
(JNIEnv *env, jobject obj) {
if (!codec_open)
return 0;
return (jint)enc_frame_size;
}
extern "C"
JNIEXPORT void JNICALL Java_com_ultraman_Speex_close
(JNIEnv *env, jobject obj) {
if (--codec_open != 0)
return;
speex_bits_destroy(&ebits);
speex_bits_destroy(&dbits);
speex_decoder_destroy(dec_state);
speex_encoder_destroy(enc_state);
}
然後,在jni目錄下新建一個Android.mk,用於NDK編譯speex庫檔案以及我們的驅動檔案:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libspeex
LOCAL_CFLAGS = -DFIXED_POINT -DUSE_KISS_FFT -DEXPORT="" -UHAVE_CONFIG_H
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SRC_FILES := \
./speex_jni.cpp \
./libspeex/bits.c \
./libspeex/buffer.c \
./libspeex/cb_search.c \
./libspeex/exc_10_16_table.c \
./libspeex/exc_10_32_table.c \
./libspeex/exc_20_32_table.c \
./libspeex/exc_5_256_table.c \
./libspeex/exc_5_64_table.c \
./libspeex/exc_8_128_table.c \
./libspeex/fftwrap.c \
./libspeex/filterbank.c \
./libspeex/filters.c \
./libspeex/gain_table.c \
./libspeex/gain_table_lbr.c \
./libspeex/hexc_10_32_table.c \
./libspeex/hexc_table.c \
./libspeex/high_lsp_tables.c \
./libspeex/jitter.c \
./libspeex/kiss_fft.c \
./libspeex/kiss_fftr.c \
./libspeex/lpc.c \
./libspeex/lsp.c \
./libspeex/lsp_tables_nb.c \
./libspeex/ltp.c \
./libspeex/mdf.c \
./libspeex/modes.c \
./libspeex/modes_wb.c \
./libspeex/nb_celp.c \
./libspeex/preprocess.c \
./libspeex/quant_lsp.c \
./libspeex/resample.c \
./libspeex/sb_celp.c \
./libspeex/scal.c \
./libspeex/smallft.c \
./libspeex/speex.c \
./libspeex/speex_callbacks.c \
./libspeex/speex_header.c \
./libspeex/stereo.c \
./libspeex/vbr.c \
./libspeex/vq.c \
./libspeex/window.c \
include $(BUILD_SHARED_LIBRARY)
因為是編譯靜態庫,NDK還需要新建一個Application.mk
APP_ABI := armeabi armeabi-v7a
接著進入/jni/incllude/speex/ ,如果沒有speex_config_types.h ,就新建一個,修改內容為:
#ifndef __SPEEX_TYPES_H__
#define __SPEEX_TYPES_H__
typedef short spx_int16_t;
typedef unsigned short spx_uint16_t;
typedef int spx_int32_t;
typedef unsigned int spx_uint32_t;
#endif
最後在cygwin裡進入你的$project/
>ndk-build
等待編譯成功。。。
成功之後,你會看到你的$project目錄下會生成一個libs資料夾,裡面存放了libspeex.so。
然後在eclipse中,重新整理一下工程,就可以運行了。