基於上一篇中提到的google網站的一份代碼(http://code.google.com/p/android-ilbc/)這個需要git下載,我上傳了一份在CSDN,
稍微進行了修改:下載連結:(http://download.csdn.net/detail/ranxiedao/4450917)。
現在開始講解代碼結構搭建環節:
要求:
環境:Ubuntu 12.04 (其他Linux環境皆可),Android 2.2 及以上系統
工具:Elicpse 3.7 ,Android NDK r7 ,Android SDK r7
1. 建立工程:
開啟Eclipse,建立一個Android 程式,名稱為 AndroidILBC,要求SDK 使用2.2及以上版本,因為Android是從2.2開始正式支援NDK 開發。
2. 添加底層代碼:
將下載的源碼中的 jni 檔案夾複製到建立的工程的根目錄下,此時,代碼結構如下:
3. jni 目錄下的Android.mk 的內容為:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libilbc
codec_dir := iLBC_RFC3951 #ilbc 原始碼的目錄
LOCAL_SRC_FILES := \
$(codec_dir)/anaFilter.c \
$(codec_dir)/constants.c \
$(codec_dir)/createCB.c \
$(codec_dir)/doCPLC.c \
$(codec_dir)/enhancer.c \
$(codec_dir)/filter.c \
$(codec_dir)/FrameClassify.c \
$(codec_dir)/gainquant.c \
$(codec_dir)/getCBvec.c \
$(codec_dir)/helpfun.c \
$(codec_dir)/hpInput.c \
$(codec_dir)/hpOutput.c \
$(codec_dir)/iCBConstruct.c \
$(codec_dir)/iCBSearch.c \
$(codec_dir)/iLBC_decode.c \
$(codec_dir)/iLBC_encode.c \
$(codec_dir)/LPCdecode.c \
$(codec_dir)/LPCencode.c \
$(codec_dir)/lsf.c \
$(codec_dir)/packing.c \
$(codec_dir)/StateConstructW.c \
$(codec_dir)/StateSearchW.c \
$(codec_dir)/syntFilter.c
LOCAL_C_INCLUDES += $(common_C_INCLUDES)
LOCAL_PRELINK_MODULE := false
include $(BUILD_STATIC_LIBRARY)
# Build JNI wrapper
include $(CLEAR_VARS)
LOCAL_MODULE := libilbc-codec #產生的 .so庫名,可以自行修改
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
$(codec_dir)
LOCAL_SRC_FILES := ilbc-codec.c
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
LOCAL_STATIC_LIBRARIES := libilbc
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
4. 程式碼分析:
開啟jni 檔案夾下的 ilbc-codec.c 檔案,裡面總共只有五個函數,負責音訊轉碼器的初始化,以及音訊編碼和解碼。其中的三個方法:
jint Java_com_googlecode_androidilbc_Codec_init(
JNIEnv *env, jobject this, jint mode)
和
jint Java_com_googlecode_androidilbc_Codec_encode(
JNIEnv *env, jobject this,
jbyteArray sampleArray, jint sampleOffset, jint sampleLength,
jbyteArray dataArray, jint dataOffset)
和
jint Java_com_googlecode_androidilbc_Codec_decode(
JNIEnv *env, jobject this,
jbyteArray dataArray, jint dataOffset, jint dataLength,
jbyteArray sampleArray, jint sampleOffset)
根據這三個函數的名稱就可以知道,使用來讓 Java層代碼調用的三個函數,現在我們對這三個函數進行改造(僅僅是換個函數名稱而已)
5. 寫Java層native 方法:
在程式的Java層代碼中,建立一個包,用於放 NDK 的java層代碼,比如我建一個名為 xmu.swordbearer.audio 的包,裡面建立一個類:
AudioCodec.java ,在這個類中只負責對底層C函數進行調用,相當於一個工具類。建立三個 public staic native int 方法:
package xmu.swordbearer.audio;
public class AudioCodec {
// initialize decoder and encoder
public static native int audio_codec_init(int mode);
// encode
public static native int audio_encode(byte[] sample, int sampleOffset,
int sampleLength, byte[] data, int dataOffset);
// decode
public static native int audio_decode(byte[] data, int dataOffset,
int dataLength, byte[] sample, int sampleLength);
}
三個方法分別用於初始化,音頻編碼,音頻解碼,在這裡只需聲明為 native 方法,不用寫任何代碼;
6. 編譯.h 標頭檔:(如果不會,請參考之前的文章)
開啟終端,定位到第 5步建立的 AudioCodec.java 目錄下,如下:
這一步很關鍵,進入到src目錄後,就要帶上 AudioCodec 這個類的包名,此例中的包名為: xmu.swordbearer.audio如果上述步
驟正確,就會在該包下產生一個 xmu_swordbearer_audio_AudioCodec.h 的標頭檔,內容如下:
/*
* Class: xmu_swordbearer_audio_AudioCodec
* Method: audio_codec_init
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_xmu_swordbearer_audio_AudioCodec_audio_1codec_1init
(JNIEnv *, jclass, jint);
/*
* Class: xmu_swordbearer_audio_AudioCodec
* Method: audio_encode
* Signature: ([BII[BI)I
*/
JNIEXPORT jint JNICALL Java_xmu_swordbearer_audio_AudioCodec_audio_1encode
(JNIEnv *, jclass, jbyteArray, jint, jint, jbyteArray, jint);
/*
* Class: xmu_swordbearer_audio_AudioCodec
* Method: audio_decode
* Signature: ([BII[BI)I
*/
JNIEXPORT jint JNICALL Java_xmu_swordbearer_audio_AudioCodec_audio_1decode
(JNIEnv *, jclass, jbyteArray, jint, jint, jbyteArray, jint);
第4步中分析的三個方法修改,開啟jni 下的 ilbc-codec.c 檔案,,把那三個名稱分別用剛剛產生的這三個方法名替換,具體對應如下:
Java_com_googlecode_androidilbc_Codec_init
改為:
Java_xmu_swordbearer_audio_AudioCodec_audio_1codec_1init
Java_com_googlecode_androidilbc_Codec_encode
改為:
Java_xmu_swordbearer_audio_AudioCodec_audio_1encode
Java_com_googlecode_androidilbc_Codec_decode
改為:
Java_xmu_swordbearer_audio_AudioCodec_audio_1decode
僅是一個 複製,粘貼的過程!!!
當然,如果你寫的JAVA代碼的包名或者方法名不一樣,那產生的 .h 檔案中的方法也就不一樣,這就是為什麼編譯好一個.so庫後,不能隨
便修改 native方法所在類的包名,因為方法名會也就改變了.
7. 編譯 .so 庫
下來就是要編譯產生 .so 庫了,正如上面Android.mk檔案中寫的,最終編譯產生的庫是 libilbc-codec.so,編譯方法如下:
開啟終端,定位到 jni 檔案夾下面,輸入 ndk-build ,斷行符號,會看到如下情景:
看到倒數第二行了嗎? libs/armeabi/libilbc-codec.so ,說明已經產生了我們需要的動態庫,這時你會發現在工程的根目錄下多了一個libs 的
檔案夾,裡面有個armeabi 目錄,開啟後就有一個 libilbc-codec.so 的檔案:
得到這個庫之後,我們所有與底層有關的工作全部完成,被Linux 虐了的人可以馬上轉戰Windows下,後續工作已經不需要在Liunx下進行了。
OK ,庫編譯完成了,後續將會示範音訊採集以及如何通過Java來調用 底層編解碼 函數。
8.總結:
光這個編譯過程我研究了兩天才跑通,就像上一篇所提到的,開始使用同學給的demo,儘管可以運行,但是程式的擴充性不好,你不可
能寫個程式後,裡面還夾雜一個詭異的包名,而這個包名你連碰都不敢碰,再加上各種代碼的嵌套,總結了一句話----自己動手,豐衣足食!
經過幾番研究後,對Android.mk 檔案的編寫積累了一些經驗,NDK 編譯過程逐漸順手,現在可以任意修改自己的代碼,如果哪裡需要改
進,就可以直接該代碼,重新編譯一個 .so 庫出來,甚至這個庫隨便用在其他程式中都可以。
Android NDK 的確提供了一個非常好的平台,從做視頻時的FFMPEG移植,到現在的ILBC庫的移植,用的都是C代碼,以後如果轉戰到
遊戲開發,說不定會有更廣闊的天地.
整整花了三個小時來寫這片文章,因為自己也是剛剛學會,一邊寫代碼,一邊又去看 ilbc源碼,從頭到尾順了一遍,ilbc真正的代碼就是幾
千行,不多,但是精。要想掌握得花一定的時間。目前這系列教程只是完成了底層的開發,接下來將完善整個系統,文章中有寫的不好的,希
望多多指教,互相學習!
--------------------------------