您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Android錄音mp3格式的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
Android錄音支持的格式有amr、aac,但這兩種音頻格式在跨平臺上表現并不好。
MP3顯然才是跨平臺的最佳選擇。
項目地址
GavinCT/AndroidMP3Recorder
實現思路概述
在分析代碼前,我們需要明確幾個問題
1. 如何最終生成MP3
實現MP3格式最好是借助Lame這個成熟的解決方案。
對于Android來說,需要借助JNI來調用Lame的C語言代碼,實現音頻格式的轉化。
2. 如何獲取最初的音頻數據
AudioRecord類可以直接幫助我們獲取音頻數據。
3. 如何進行轉換
網上有代碼是先錄制后轉為MP3,這種效率比較低。因為如果錄音時間過長,轉換時間就會相應變長,用戶在存儲錄音時需要等待的時間就會變長。
Samsung Developers先錄后轉示例代碼
顯然,這種方案是不可取的。
我們需要的是邊錄邊轉的實現方式,這樣在停止錄音進行存儲的時候,就不會花費太長時間。
實現代碼介紹
既然是錄音,我們上面也提到了需要使用AudioRecord類,我們就從這個類的構造器開始說起
構造器
public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
構造器參數很多,我們一點一點來看:
audioSource : 聲源,一般使用MediaRecorder.AudioSource.MIC表示來自于麥克風
sampleRateInHz :官方明確說到只有44100Hz是所有設備都支持的。其他22050、16000和11025只能在某些設備上使用。
channelConfig : 有立體聲(CHANNEL_IN_STEREO)和單聲道(CHANNEL_IN_MONO)兩種。但只有單聲道(CHANNEL_IN_MONO)是所有設備都支持的。
audioFormat : 有ENCODING_PCM_16BIT和ENCODING_PCM_8BIT兩種音頻編碼格式。同樣的,官方聲明只有ENCODING_PCM_16BIT是所有設備都支持的。
bufferSizeInBytes : 錄音期間聲音數據的寫入緩沖區大小(單位是字節)。
其實從上面的解釋可以看到,類的參數很多,但為了保證在所有設備上可以使用,我們真正需要填寫的只有一個參數:bufferSizeInBytes
,其他都可以使用通用的參數而不用自己費心來選擇。
在深究bufferSizeInBytes
該傳入什么之前,我們先略過這一段,先來說一下錄音的讀取與轉換。
錄音的讀取與轉換策略
錄音的讀取其實和UDP差不多,需要不斷的讀取數據。
既然是不斷,那么我們當然需要循環讀取,意味著我們需要一個線程來單獨讀取錄音,避免阻塞主線程。
還和UDP差不多的是,如果不及時讀取,數據超過緩沖區大小,會造成這段錄音數據的丟失。
上面提到過,我們想要實現的是邊錄邊轉。那么問題來了,如果我們讀取完數據后接著將數據傳給Lame進行MP3編碼,Lame的編碼時間是不確定的,是不是有可能造成數據的丟失呢?
答案當然是有可能,所以我們不能巧合編程。
我們需要另外一個線程,即數據編碼線程來專門進行MP3編碼,而當前的錄音讀取線程只負責讀取錄音PCM數據。
有了兩條線程,我們還需要確認一點,什么時候編碼線程開始處理數據?
編碼線程處理數據的時機
傳統的方法是當線程中有數據的時候開始處理,這就需要在這個線程里面不斷循環查看是否有數據需要處理,有數據就開始處理,沒有數據我們可以暫時休息幾毫秒(當然一直不sleep也可以,但造成的系統消耗太多)。
這種方式顯然也是低效的,因為無論我們讓線程休息多久都可以判定為不合理。因為我們并不知道準確的時間。
那么還有別的方法么?
顯然錄音這個類是知道什么時候該處理數據,什么時候可以休息。
Don't call me , I will call you.
是的,我們應該去看看有沒有監聽器,讓錄音來通知編碼線程開始工作。
AudioRecord為我們提供了這樣的方法:
public int setPositionNotificationPeriod (int periodInFrames) Added in API level 3 Sets 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.
設置通知周期。 以幀為單位。
到這里,我們可以回來來解釋bufferSizeInBytes
大小的傳入了。
緩沖區的大小
其實AudioRecord類提供了一個方便的方法getMinBufferSize來獲取緩沖區的大小。
public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)
這里的3個參數,其實我們都可以從構造器的參數里看到,因此傳入并沒有什么問題。
但關鍵在如上面我們設置了周期單位,如果獲得的緩沖區大小不是周期單位的整數倍呢?
不是整數倍當然會如我們猜想的一樣造成數據丟失,因此我們還需要一些數據的糾正來保證緩沖區大小是整數倍。
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; }
講完了數據的獲取線程和編碼線程,我們來仔細看看幫助我們實現MP3編碼的功臣:Lame
Lame的獲取與編譯
Lame在線下載地址
步驟
解壓libmp3lame 到jni目錄.
拷貝 lame.h (include目錄下)
創建Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := mp3lame LOCAL_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.c include $(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);
。如果忘了替換,編譯時會報出以下錯誤:
[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
編譯庫文件。可能會報出警告,忽略即可。
Lame需要對外提供的方法
init 初始化
inSamplerate : 輸入采樣頻率 Hz
inChannel : 輸入聲道數
outSamplerate : 輸出采樣頻率 Hz
outBitrate : Encoded bit rate. KHz
quality : MP3音頻質量。0~9。 其中0是最好,非常慢,9是最差。
推薦:
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相關,因為是mono單聲,所以是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
bufferLeft : 左聲道數據
bufferRight:右聲道數據
samples :每個聲道輸入數據大小
mp3buf :用于接收轉換后的數據。7200 + (1.25 * buffer_l.length)
這里需要解釋一下:
Task task = mTasks.remove(0); short[] buffer = task.getData(); int readSize = task.getReadSize(); int encodedSize = LameUtil.encode(buffer, buffer, readSize, mMp3Buffer);
左右聲道 :當前聲道選的是單聲道,因此兩邊傳入一樣的buffer。
輸入數據大小 :錄音線程讀取到buffer中的數據不一定是占滿的,所以read方法會返回當前大小size,即前size個數據是有效的音頻數據,后面的數據是以前留下的廢數據。 這個size同樣需要傳入到Lame編碼器中用于編碼。
mp3的buffer:官方規定了計算公式:7200 + (1.25 * buffer_l.length)。(可以在lame.h文件中看到)
flush
將MP3結尾信息寫入buffer中。
傳入參數:mp3buf至少7200字節。這里還是用以前定義的mp3buf來傳入,避免創建過多的數組。
close
關閉釋放Lame
OK,到這里,核心的轉換代碼就完成了,我們再來點錦上添花的東西。
音量
一般我們在做錄音的時候,都會有一個需求,根據音量的大小顯示一個動畫,讓錄音顯得更生動一些。
當然,我在這個庫里也提供了。
那么怎么來計算音量呢?
我參考了三星的音量計算。
總結如下:
/** * @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); } };
感謝各位的閱讀!關于“Android錄音mp3格式的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。