1. 程式人生 > >錄音轉換Mp3-Lame4Android 上篇-帶詳細SO庫編譯教程

錄音轉換Mp3-Lame4Android 上篇-帶詳細SO庫編譯教程

在Android與IOS上面如果做錄音功能,一般手機錄製出來的音訊格式都不是MP3,為了兩個平臺的APP的錄音檔案一致,需要選擇一種兩個平臺都支援播放且佔用儲存空間不會太大的音訊檔案格式,這裡MP3就符合這一需求。我們這裡選擇libmp3lame把AudioRecord音訊流直接轉換成MP3格式。
本文使用eclipse進行開發
那下面就開始我們的前期工作

1.給eclipse增加NDK開發支援

我這裡使用的不是Cygwin,而是谷歌提供的android-ndk-r10e,大家自行下載並配置環境變數,這裡就不多說。

2.下載Lame原始碼

3.建立工程(這裡我們建立一個Java工程)

工程結構如下
這裡寫圖片描述
我們在這裡建立一個名稱為jni的資料夾,目錄樹如下圖
這裡寫圖片描述
1. 我們將上文下載好的lame原始碼解壓,複製lame-3.99.5/libmp3lame 到jni目錄裡,改名為lame-3.99.5_libmp3lame(這個名字大家隨意取,後面要用到)

2. 將原始碼中lame.h (include目錄下),拷貝到jni/lame-3.99.5_libmp3lame/lame.h

3. 對lame-3.99.5_libmp3lame下的原始碼進行處理,處理如下
a. 刪除非.c/.h檔案: Makefile.am、Makefile.in、depcomp、logoe.ico、
b. 刪除資料夾i386及其目錄下的檔案
c. 編輯 util.h檔案。
這裡寫圖片描述

把extern ieee754_float32_t fast_log2(ieee754_float32_t x);替換為extern float fast_log2(float x);。如果忘了替換,編譯時會報出以下錯誤:

[armeabi] Compile thumb : mp3lame <= bitstream.c
In file included from jni/bitstream.c:36:0:
jni/util.h:574:5: error: unknown type name ‘ieee754_float32_t’
jni/util.h:574:40: error: unknown type name ‘ieee754_float32_t’
make.exe: *

[obj/local/armeabi/objs/mp3lame/bitstream.o] Error 1

d. 編輯set_get.h檔案

這裡寫圖片描述

e. 編輯vector\xmm_quantize_sub.c檔案,標頭檔案引用#include “lame.h”修改為#include “../lame.h”

d跟e不處理的話編譯的時候會出現找不到lame.h的錯誤

4. 編寫Android.mk檔案和Application.mk檔案
下面我把程式碼貼上來
Android.mk :

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LAME_LIBMP3_DIR := lame-3.99.5_libmp3lame

LOCAL_MODULE    := mp3lame
LOCAL_SRC_FILES := $(LAME_LIBMP3_DIR)/bitstream.c $(LAME_LIBMP3_DIR)/fft.c $(LAME_LIBMP3_DIR)/id3tag.c $(LAME_LIBMP3_DIR)/mpglib_interface.c $(LAME_LIBMP3_DIR)/presets.c $(LAME_LIBMP3_DIR)/quantize.c $(LAME_LIBMP3_DIR)/reservoir.c $(LAME_LIBMP3_DIR)/tables.c $(LAME_LIBMP3_DIR)/util.c $(LAME_LIBMP3_DIR)/VbrTag.c $(LAME_LIBMP3_DIR)/encoder.c $(LAME_LIBMP3_DIR)/gain_analysis.c $(LAME_LIBMP3_DIR)/lame.c $(LAME_LIBMP3_DIR)/newmdct.c $(LAME_LIBMP3_DIR)/psymodel.c $(LAME_LIBMP3_DIR)/quantize_pvt.c $(LAME_LIBMP3_DIR)/set_get.c $(LAME_LIBMP3_DIR)/takehiro.c $(LAME_LIBMP3_DIR)/vbrquantize.c $(LAME_LIBMP3_DIR)/version.c com_csf_lame4android_utils_FLameUtils.c

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

注意com_csf_lame4android_utils_FLameUtils.c
注意,這個記得要帶上,這個是我們通過java 通過jni呼叫c的入口類,這個檔案我們等會再說,先補下Android.mk的知識

Android.mk介紹:
Android.mk:用於配置編譯生成的so庫名、引用的標頭檔案(.h檔案)、需要編譯的.c檔案和.a靜態庫檔案等。要掌握jni,必須瞭解Android.mk裡面變數的作用和規範。

(1)LOCAL_PATH: 這個變數用於給出當前檔案的路徑。
必須在 Android.mk 的開頭定義,可以這樣使用:LOCAL_PATH := (callmydir)src (call src),那麼就會得到 src 目錄的完整路徑
這個變數不會被$(CLEAR_VARS)清除,因此每個 Android.mk 只需要定義一次(即使在一個檔案中定義了幾個模組的情況下)。

(2)LOCAL_MODULE: 這是模組的名字,它必須是唯一的,而且不能包含空格。
必須在包含任一的$(BUILD_XXXX)指令碼之前定義它。模組的名字決定了生成檔案的名字。

(3)LOCAL_SRC_FILES: 這是要編譯的原始碼檔案列表。
只要列出要傳遞給編譯器的檔案,因為編譯系統自動計算依賴。注意原始碼檔名稱都是相對於 LOCAL_PATH的,你可以使用路徑部分,例如:
LOCAL_SRC_FILES := foo.c toto/bar.c\ Hello.c
檔案之間可以用空格或Tab鍵進行分割,換行請用”\”
如果是追加原始碼檔案的話,請用LOCAL_SRC_FILES +=
注意:可以LOCAL_SRC_FILES := $(call all-subdir-Java-files)這種形式來包含local_path目錄下的所有java檔案。

(4)LOCAL_C_INCLUDES: 可選變數,表示標頭檔案的搜尋路徑。
預設的標頭檔案的搜尋路徑是LOCAL_PATH目錄。

(5)LOCAL_STATIC_LIBRARIES: 表示該模組需要使用哪些靜態庫,以便在編譯時進行連結。

(6)LOCAL_SHARED_LIBRARIES: 表示模組在執行時要依賴的共享庫(動態庫),在連結時就需要,以便在生成檔案時嵌入其相應的資訊。
注意:它不會附加列出的模組到編譯圖,也就是仍然需要在Application.mk 中把它們新增到程式要求的模組中。

(7)LOCAL_LDLIBS: 編譯模組時要使用的附加的連結器選項。這對於使用‘-l’字首傳遞指定庫的名字是有用的。
例如,LOCAL_LDLIBS := -lz表示告訴連結器生成的模組要在載入時刻連結到/system/lib/libz.so
可檢視 docs/STABLE-APIS.TXT 獲取使用 NDK發行版能連結到的開放的系統庫列表。

( 8 )LOCAL_MODULE_PATH 和 LOCAL_UNSTRIPPED_PATH
在 Android.mk 檔案中, 還可以用LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH指定最後的目標安裝路徑.
不同的檔案系統路徑用以下的巨集進行選擇:
TARGET_ROOT_OUT:表示根檔案系統。
TARGET_OUT:表示 system檔案系統。
TARGET_OUT_DATA:表示 data檔案系統。
用法如:LOCAL_MODULE_PATH :=$(TARGET_ROOT_OUT)
至於LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH的區別,暫時還不清楚。

(9)LOCAL_JNI_SHARED_LIBRARIES:定義了要包含的so庫檔案的名字,如果程式沒有采用jni,不需要
LOCAL_JNI_SHARED_LIBRARIES := libxxx 這樣在編譯的時候,NDK自動會把這個libxxx打包進apk; 放在youapk/lib/目錄下

Application.mk

APP_ABI := all
APP_MODULES := mp3lame
APP_CFLAGS += -DSTDC_HEADERS
#APP_ABI:=x86_64
#APP_PLATFORM := android-21

編譯所有的架構的so檔案

Application.mk介紹

Application.mk:用於配置JNI生成該CPU使用的so ,檔案格式如下:

APP_ABI := armeabi armeabi-v7a x86 arm64-v8a x86_64 mips mips64

TARGET_CPU_API :=armeabi armeabi-v7a x86 arm64-v8a x86_64 mips mips64

或者 配置所有

APP_ABI := all

APP_CFLAGS += -DSTDC_HEADERS
Application.mk裡要加APP_CFLAGS += -DSTDC_HEADERS,不然會報錯undefined reference to `bcopy’等錯誤

5. 通過jni編寫java呼叫的本地方法

這裡寫圖片描述

通過上圖瞭解,看我圈中的三個檔案,他們之間有什麼關係呢,如果使用jni的同學,應該不需要我多說,不清楚的同學可以看下我之前寫的一篇文章瞭解http://blog.csdn.net/q919233914/article/details/52218391
我們來看程式碼:
FLameUtils.java
挑幾個主要的方法講下
這裡寫圖片描述
在FLameUtils中我們需要呼叫到initEncoder、destroyEncoder、encodeFile三個方法,意味著我們的中間檔案com_csf_lame4android_utils_FLameUtils.c 要通過jni定義並實現這三個方法。那怎麼定義呢?
a. 編譯FLameUtils.java 得到FLameUtils.class
我們可以在工程bin下找到對應的class檔案
這裡寫圖片描述
b.通過FLameUtils.class得到對應的.c的標頭檔案com_csf_lame4android_utils_FLameUtils.h

javah -classpath . -jni com.csf.lame4android.utils.FLameUtils

我們在dos下鍵入到bin下,通過上面命令生成.h檔案
這裡寫圖片描述
這裡寫圖片描述
c.將com_csf_lame4android_utils_FLameUtils.h複製到jni目錄下
d.建立.c檔案編寫程式碼定義並實現initEncoder、destroyEncoder、encodeFile這三個方法

void Java_com_csf_lame4android_utils_FLameUtils_initEncoder(JNIEnv *env,
        jobject jobj, jint in_num_channels, jint in_samplerate, jint in_brate,
        jint in_mode, jint in_quality)

void Java_com_csf_lame4android_utils_FLameUtils_destroyEncoder(
        JNIEnv *env, jobject jobj)

void Java_com_csf_lame4android_utils_FLameUtils_encodeFile(JNIEnv *env,
        jobject jobj, jstring in_source_path, jstring in_target_path)

我們以initEncoder為例,
這裡寫圖片描述
再看看之前的com_csf_lame4android_utils_FLameUtils.h檔案
這裡寫圖片描述
再看看我們FLameUtils.java的包名類名
com.csf.lame4android.utils.FLameUtils
是不是有一種豁然開朗的感覺,.h是將FLameUtils的initEncoder 這個native方法按一定固定格式宣告
以Java_開頭,後面拼上包名類名方法名,將連線符“.”轉成“_”。
.c檔案則是以.h檔案中宣告的方法名來實現。所以直接從.h檔案中將三個方法名複製到.c檔案中並實現即可。這裡話說的有點多,主要是怕有的同學沒有接觸過jni,補充下基礎知識。
注意:這裡方法名稱跟後面java通過FLameUtils實現轉碼的功能會有掛鉤,所以FLameUtils中的包名類名方法名(上文定義那三個)都是不可以改變的。所以我們一般打成jar包(AndroidDemo中將以jar形式提供使用,本文不講怎麼打jar,感興趣的朋友私聊我)
那我們就接著編寫com_csf_lame4android_utils_FLameUtils.c中的邏輯,呼叫lame庫中的方法進行方法實現,因為程式碼長度問題,這裡就不貼上程式碼,後面我會附上Demo,大家直接下載檢視。
前期準備已經完成
6.編譯so庫
我們應該怎麼來編譯so庫呢,這就得用到前文配置好的NDK 環境。
我們需要建立一個Builder,工程右鍵點選,選擇Build path -Configure build path - Builders ,點選new
這裡寫圖片描述
下面我就不文字說明了,直接上圖
這裡寫圖片描述
切換到Refresh tab
這裡寫圖片描述
切換到Build options tab
這裡寫圖片描述
這裡寫圖片描述
這樣編譯器就建立完畢,將編譯器Up置頂
這裡寫圖片描述
點選OK,就開始編譯so檔案,不出錯誤的話,會在工程下libs跟obj資料夾
這裡寫圖片描述
哈哈,終於是到了尾聲了。

4.整合使用,建立Android工程測試

我們將FLameUtil打成jar包,命名為flame.jar。
建立一個Android工程,將flame.jar和libs的所有資源拷貝到該工程的libs資料夾中。
使用FLameUtil中的raw2mp3方法進行格式轉換
這裡寫圖片描述

demo效果如下,
這裡寫圖片描述

錄音並轉換後在儲存盤根目錄下

這裡寫圖片描述

好了,文章就到此結束了,可能篇幅有點長
下面貼下Project和Demo的原始碼地址
Lame4Android 和Lame4AndroidDemo
如果有什麼疑問,或者發現存在上面問題,請告知我下,我們一起探討,謝謝!
上文忘了說,最後提醒下,記得新增錄音許可權

<uses-permission android:name="android.permission.RECORD_AUDIO" />

昨天忘記說了,對於跑在6.0系統的機器上,要在應用中賦予錄音許可權,不然錄音會失敗!後面有時間我再增加對於許可權這塊的處理。