1. 程式人生 > >android jni 包裹檔案(jni wrapper) 以 speex 庫為例

android jni 包裹檔案(jni wrapper) 以 speex 庫為例

參考資料:

1 http://code.google.com/p/android-recorder/downloads/list  這個是一個android recorder ,使用speex編碼,程式碼很乾淨,推薦一讀
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中,重新整理一下工程,就可以運行了。