Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android MP3錄音實現

Android MP3錄音實現

編輯:關於Android編程

Android錄音支持的格式有amr、aac,但這兩種音頻格式在跨平台上表現並不好。 MP3顯然才是跨平台的最佳選擇。 近期由於項目需要,實現了這個需求,代碼托管在Github上,歡迎拍磚   項目地址 GavinCT/AndroidMP3Recorder   使用方法見:README.md   推薦直接下載libs.zip集成到自己項目裡。   【溫馨提示:只下載此zip可以使用Chrome插件GitHub Mate】   實現思路概述 在分析代碼前,我們需要明確幾個問題   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,到這裡,核心的轉換代碼就完成了,我們再來點錦上添花的東西。   音量 一般我們在做錄音的時候,都會有一個需求,根據音量的大小顯示一個動畫,讓錄音顯得更生動一些。 當然,我在這個庫裡也提供了。 那麼怎麼來計算音量呢? 我參考了三星的音量計算。 總結如下:   /** * 此計算方法來自samsung開發范例 *  * @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); } }; 關於最大音量 其實對於音量,我不是特別明白。 最大音量在三星的代碼中給出的是4000,但是我在實際的測試中發現,這個計算公式得出的音量大小一般都在1500以內。 因此在我提供的錄音庫裡面,我把最大音量規定為了2000。 這塊兒歡迎大家來提寶貴意見。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved