Android錄音支持的格式有amr、aac,但這兩種音頻格式在跨平臺(tái)上表現(xiàn)并不好。
MP3顯然才是跨平臺(tái)的最佳選擇。
項(xiàng)目地址
實(shí)現(xiàn)思路概述
在分析代碼前,我們需要明確幾個(gè)問題
1. 如何最終生成MP3
實(shí)現(xiàn)MP3格式最好是借助Lame這個(gè)成熟的解決方案。
對(duì)于Android來說,需要借助JNI來調(diào)用Lame的C語言代碼,實(shí)現(xiàn)音頻格式的轉(zhuǎn)化。
2. 如何獲取最初的音頻數(shù)據(jù)
AudioRecord類可以直接幫助我們獲取音頻數(shù)據(jù)。
3. 如何進(jìn)行轉(zhuǎn)換
網(wǎng)上有代碼是先錄制后轉(zhuǎn)為MP3,這種效率比較低。因?yàn)槿绻浺魰r(shí)間過長(zhǎng),轉(zhuǎn)換時(shí)間就會(huì)相應(yīng)變長(zhǎng),用戶在存儲(chǔ)錄音時(shí)需要等待的時(shí)間就會(huì)變長(zhǎng)。
Samsung Developers先錄后轉(zhuǎn)示例代碼
顯然,這種方案是不可取的。
我們需要的是邊錄邊轉(zhuǎn)的實(shí)現(xiàn)方式,這樣在停止錄音進(jìn)行存儲(chǔ)的時(shí)候,就不會(huì)花費(fèi)太長(zhǎng)時(shí)間。
實(shí)現(xiàn)代碼介紹
既然是錄音,我們上面也提到了需要使用AudioRecord類,我們就從這個(gè)類的構(gòu)造器開始說起
構(gòu)造器
public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
構(gòu)造器參數(shù)很多,我們一點(diǎn)一點(diǎn)來看:
其實(shí)從上面的解釋可以看到,類的參數(shù)很多,但為了保證在所有設(shè)備上可以使用,我們真正需要填寫的只有一個(gè)參數(shù):bufferSizeInBytes,其他都可以使用通用的參數(shù)而不用自己費(fèi)心來選擇。
在深究bufferSizeInBytes該傳入什么之前,我們先略過這一段,先來說一下錄音的讀取與轉(zhuǎn)換。
錄音的讀取與轉(zhuǎn)換策略
錄音的讀取其實(shí)和UDP差不多,需要不斷的讀取數(shù)據(jù)。
既然是不斷,那么我們當(dāng)然需要循環(huán)讀取,意味著我們需要一個(gè)線程來單獨(dú)讀取錄音,避免阻塞主線程。
還和UDP差不多的是,如果不及時(shí)讀取,數(shù)據(jù)超過緩沖區(qū)大小,會(huì)造成這段錄音數(shù)據(jù)的丟失。
上面提到過,我們想要實(shí)現(xiàn)的是邊錄邊轉(zhuǎn)。那么問題來了,如果我們讀取完數(shù)據(jù)后接著將數(shù)據(jù)傳給Lame進(jìn)行MP3編碼,Lame的編碼時(shí)間是不確定的,是不是有可能造成數(shù)據(jù)的丟失呢?
答案當(dāng)然是有可能,所以我們不能巧合編程。
我們需要另外一個(gè)線程,即數(shù)據(jù)編碼線程來專門進(jìn)行MP3編碼,而當(dāng)前的錄音讀取線程只負(fù)責(zé)讀取錄音PCM數(shù)據(jù)。
有了兩條線程,我們還需要確認(rèn)一點(diǎn),什么時(shí)候編碼線程開始處理數(shù)據(jù)?
編碼線程處理數(shù)據(jù)的時(shí)機(jī)
傳統(tǒng)的方法是當(dāng)線程中有數(shù)據(jù)的時(shí)候開始處理,這就需要在這個(gè)線程里面不斷循環(huán)查看是否有數(shù)據(jù)需要處理,有數(shù)據(jù)就開始處理,沒有數(shù)據(jù)我們可以暫時(shí)休息幾毫秒(當(dāng)然一直不sleep也可以,但造成的系統(tǒng)消耗太多)。
這種方式顯然也是低效的,因?yàn)闊o論我們讓線程休息多久都可以判定為不合理。因?yàn)槲覀儾⒉恢罍?zhǔn)確的時(shí)間。
那么還有別的方法么?
顯然錄音這個(gè)類是知道什么時(shí)候該處理數(shù)據(jù),什么時(shí)候可以休息。
Don't call me , I will call you.
是的,我們應(yīng)該去看看有沒有監(jiān)聽器,讓錄音來通知編碼線程開始工作。
AudioRecord為我們提供了這樣的方法:
public int setPositionNotificationPeriod (int periodInFrames)Added in API level 3Sets the period at which the listener is called, if set with setRecordPositionUpdateListener(OnRecordPositionUpdateListener) or setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler). It is possible for notifications to be lost if the period is too small.
設(shè)置通知周期。 以幀為單位。
到這里,我們可以回來來解釋bufferSizeInBytes大小的傳入了。
緩沖區(qū)的大小
其實(shí)AudioRecord類提供了一個(gè)方便的方法getMinBufferSize來獲取緩沖區(qū)的大小。
public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)
這里的3個(gè)參數(shù),其實(shí)我們都可以從構(gòu)造器的參數(shù)里看到,因此傳入并沒有什么問題。
但關(guān)鍵在如上面我們?cè)O(shè)置了周期單位,如果獲得的緩沖區(qū)大小不是周期單位的整數(shù)倍呢?
不是整數(shù)倍當(dāng)然會(huì)如我們猜想的一樣造成數(shù)據(jù)丟失,因此我們還需要一些數(shù)據(jù)的糾正來保證緩沖區(qū)大小是整數(shù)倍。
mBufferSize = AudioRecord.getMinBufferSize(DEFAULT_SAMPLING_RATE, DEFAULT_CHANNEL_CONFIG, DEFAULT_AUDIO_FORMAT.getAudioFormat());int bytesPerFrame = DEFAULT_AUDIO_FORMAT.getBytesPerFrame();/* Get number of samples. Calculate the buffer size * (round up to the factor of given frame size) * 使能被整除,方便下面的周期性通知 * */int frameSize = mBufferSize / bytesPerFrame;if (frameSize % FRAME_COUNT != 0) { frameSize += (FRAME_COUNT - frameSize % FRAME_COUNT); mBufferSize = frameSize * bytesPerFrame;}講完了數(shù)據(jù)的獲取線程和編碼線程,我們來仔細(xì)看看幫助我們實(shí)現(xiàn)MP3編碼的功臣:Lame
Lame的獲取與編譯
步驟
解壓libmp3lame 到j(luò)ni目錄.
拷貝 lame.h (include目錄下)
創(chuàng)建Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := mp3lameLOCAL_SRC_FILES := bitstream.c fft.c id3tag.c mpglib_interface.c presets.c quantize.c reservoir.c tables.c util.c VbrTag.c encoder.c gain_analysis.c lame.c newmdct.c psymodel.c quantize_pvt.c set_get.c takehiro.c vbrquantize.c version.cinclude $(BUILD_SHARED_LIBRARY)
刪除非.c/.h文件:GNU autotools, Makefile.am Makefile.in libmp3lame_vc8.vcproj logoe.ico depcomp, folders i386 等無用文件。
編輯 jni/utils.h。把extern ieee754_float32_t fast_log2(ieee754_float32_t x);替換為extern float fast_log2(float x);。如果忘了替換,編譯時(shí)會(huì)報(bào)出以下錯(cuò)誤:
[armeabi] Compile thumb : mp3lame <= bitstream.cIn 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
編譯庫文件。可能會(huì)報(bào)出警告,忽略即可。
Lame需要對(duì)外提供的方法
推薦:
2 :near-best quality, not too slow
5 :good quality, fast
7 :ok quality, really fast
private static final int DEFAULT_LAME_MP3_QUALITY = 7;/** * 與DEFAULT_CHANNEL_CONFIG相關(guān),因?yàn)槭莔ono單聲,所以是1 */private static final int DEFAULT_LAME_IN_CHANNEL = 1;/** * Encoded bit rate. MP3 file will be encoded with bit rate 32kbps */ private static final int DEFAULT_LAME_MP3_BIT_RATE = 32; /** Initialize lame buffer* mp3 sampling rate is the same as the recorded pcm sampling rate * The bit rate is 32kbps* */LameUtil.init(DEFAULT_SAMPLING_RATE, DEFAULT_LAME_IN_CHANNEL, DEFAULT_SAMPLING_RATE, DEFAULT_LAME_MP3_BIT_RATE, DEFAULT_LAME_MP3_QUALITY);
encode
這里需要解釋一下:
Task task = mTasks.remove(0);short[] buffer = task.getData();int readSize = task.getReadSize();int encodedSize = LameUtil.encode(buffer, buffer, readSize, mMp3Buffer);
flush
將MP3結(jié)尾信息寫入buffer中。
傳入?yún)?shù):mp3buf至少7200字節(jié)。這里還是用以前定義的mp3buf來傳入,避免創(chuàng)建過多的數(shù)組。
close
關(guān)閉釋放Lame
OK,到這里,核心的轉(zhuǎn)換代碼就完成了,我們?cè)賮睃c(diǎn)錦上添花的東西。
音量
一般我們?cè)谧鲣浺舻臅r(shí)候,都會(huì)有一個(gè)需求,根據(jù)音量的大小顯示一個(gè)動(dòng)畫,讓錄音顯得更生動(dòng)一些。
當(dāng)然,我在這個(gè)庫里也提供了。
那么怎么來計(jì)算音量呢?
我參考了三星的音量計(jì)算。
總結(jié)如下:
/*** 此計(jì)算方法來自samsung開發(fā)范例* * @param buffer* @param readSize*/private void calculateRealVolume(short[] buffer, int readSize) { int sum = 0; for (int i = 0; i < readSize; i++) { sum += buffer[i] * buffer[i]; } if (readSize > 0) { double amplitude = sum / readSize; mVolume = (int) Math.sqrt(amplitude); }};關(guān)于最大音量
其實(shí)對(duì)于音量,我不是特別明白。
最大音量在三星的代碼中給出的是4000,但是我在實(shí)際的測(cè)試中發(fā)現(xiàn),這個(gè)計(jì)算公式得出的音量大小一般都在1500以內(nèi)。
因此在我提供的錄音庫里面,我把最大音量規(guī)定為了2000。
這塊兒歡迎大家來提寶貴意見。
MP3錄音實(shí)現(xiàn)參考
yhirano/Mp3VoiceRecorderSampleForAndroid
日本人寫的,感覺他的判斷不完善,有點(diǎn)巧合編程的意思,也或許是我沒看懂。
talzeus/AndroidMp3Recorder
比較嚴(yán)謹(jǐn)?shù)拇a。主要依據(jù)這個(gè)庫進(jìn)行的修改。
存在的問題:
AudioRecord傳入?yún)?shù)很多沒有按Android規(guī)定傳入。如采樣頻率使用了22050Hz。
使用了自己構(gòu)造的RingBuffer,看這有點(diǎn)頭暈。 我在庫里使用List來存儲(chǔ)未編碼的音頻數(shù)據(jù),更容易理解。
沒有提供音量大小。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注