亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android錄音mp3格式實(shí)例詳解

 更新時(shí)間:2018年03月26日 11:55:13   投稿:wdc  
本文講述了Android錄音成MP3格式實(shí)現(xiàn)思路概述,需要的朋友可以參考下

Android錄音支持的格式有amr、aac,但這兩種音頻格式在跨平臺(tái)上表現(xiàn)并不好。
MP3顯然才是跨平臺(tái)的最佳選擇。

項(xiàng)目地址

GavinCT/AndroidMP3Recorder

實(shí)現(xiàn)思路概述

在分析代碼前,我們需要明確幾個(gè)問題

1. 如何最終生成MP3

實(shí)現(xiàn)MP3格式最好是借助Lame這個(gè)成熟的解決方案。
對(duì)于Android來(lái)說,需要借助JNI來(lái)調(diào)用Lame的C語(yǔ)言代碼,實(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)來(lái)看:

  • audioSource : 聲源,一般使用MediaRecorder.AudioSource.MIC表示來(lái)自于麥克風(fēng)
  • sampleRateInHz :官方明確說到只有44100Hz是所有設(shè)備都支持的。其他22050、16000和11025只能在某些設(shè)備上使用。
  • channelConfig : 有立體聲(CHANNEL_IN_STEREO)和單聲道(CHANNEL_IN_MONO)兩種。但只有單聲道(CHANNEL_IN_MONO)是所有設(shè)備都支持的。
  • audioFormat : 有ENCODING_PCM_16BIT和ENCODING_PCM_8BIT兩種音頻編碼格式。同樣的,官方聲明只有ENCODING_PCM_16BIT是所有設(shè)備都支持的。
  • bufferSizeInBytes : 錄音期間聲音數(shù)據(jù)的寫入緩沖區(qū)大?。▎挝皇亲止?jié))。

其實(shí)從上面的解釋可以看到,類的參數(shù)很多,但為了保證在所有設(shè)備上可以使用,我們真正需要填寫的只有一個(gè)參數(shù):bufferSizeInBytes,其他都可以使用通用的參數(shù)而不用自己費(fèi)心來(lái)選擇。
在深究bufferSizeInBytes該傳入什么之前,我們先略過這一段,先來(lái)說一下錄音的讀取與轉(zhuǎn)換。

錄音的讀取與轉(zhuǎn)換策略

錄音的讀取其實(shí)和UDP差不多,需要不斷的讀取數(shù)據(jù)。
既然是不斷,那么我們當(dāng)然需要循環(huán)讀取,意味著我們需要一個(gè)線程來(lái)單獨(dú)讀取錄音,避免阻塞主線程。
還和UDP差不多的是,如果不及時(shí)讀取,數(shù)據(jù)超過緩沖區(qū)大小,會(huì)造成這段錄音數(shù)據(jù)的丟失。
上面提到過,我們想要實(shí)現(xiàn)的是邊錄邊轉(zhuǎn)。那么問題來(lái)了,如果我們讀取完數(shù)據(jù)后接著將數(shù)據(jù)傳給Lame進(jìn)行MP3編碼,Lame的編碼時(shí)間是不確定的,是不是有可能造成數(shù)據(jù)的丟失呢?
答案當(dāng)然是有可能,所以我們不能巧合編程。
我們需要另外一個(gè)線程,即數(shù)據(jù)編碼線程來(lái)專門進(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(wú)論我們讓線程休息多久都可以判定為不合理。因?yàn)槲覀儾⒉恢罍?zhǔn)確的時(shí)間。
那么還有別的方法么?
顯然錄音這個(gè)類是知道什么時(shí)候該處理數(shù)據(jù),什么時(shí)候可以休息。
Don't call me , I will call you.
是的,我們應(yīng)該去看看有沒有監(jiān)聽器,讓錄音來(lái)通知編碼線程開始工作。
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.

設(shè)置通知周期。 以幀為單位。

到這里,我們可以回來(lái)來(lái)解釋bufferSizeInBytes大小的傳入了。

緩沖區(qū)的大小

其實(shí)AudioRecord類提供了一個(gè)方便的方法getMinBufferSize來(lái)獲取緩沖區(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ù)的糾正來(lái)保證緩沖區(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ù)的獲取線程和編碼線程,我們來(lái)仔細(xì)看看幫助我們實(shí)現(xiàn)MP3編碼的功臣:Lame

Lame的獲取與編譯

Lame在線下載地址

步驟

解壓libmp3lame 到j(luò)ni目錄.
拷貝 lame.h (include目錄下)
創(chuàng)建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 等無(wú)用文件。
編輯 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.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

編譯庫(kù)文件。可能會(huì)報(bào)出警告,忽略即可。

Lame需要對(duì)外提供的方法

  • init 初始化
  • inSamplerate : 輸入采樣頻率 Hz
  • inChannel : 輸入聲道數(shù)
  • outSamplerate : 輸出采樣頻率 Hz
  • outBitrate : Encoded bit rate. KHz
  • quality : MP3音頻質(zhì)量。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相關(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

  • bufferLeft : 左聲道數(shù)據(jù)
  • bufferRight:右聲道數(shù)據(jù)
  • samples :每個(gè)聲道輸入數(shù)據(jù)大小
  • mp3buf :用于接收轉(zhuǎn)換后的數(shù)據(jù)。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);
  • 左右聲道 :當(dāng)前聲道選的是單聲道,因此兩邊傳入一樣的buffer。
  • 輸入數(shù)據(jù)大小 :錄音線程讀取到buffer中的數(shù)據(jù)不一定是占滿的,所以read方法會(huì)返回當(dāng)前大小size,即前size個(gè)數(shù)據(jù)是有效的音頻數(shù)據(jù),后面的數(shù)據(jù)是以前留下的廢數(shù)據(jù)。 這個(gè)size同樣需要傳入到Lame編碼器中用于編碼。
  • mp3的buffer:官方規(guī)定了計(jì)算公式:7200 + (1.25 * buffer_l.length)。(可以在lame.h文件中看到)

flush

將MP3結(jié)尾信息寫入buffer中。
傳入?yún)?shù):mp3buf至少7200字節(jié)。這里還是用以前定義的mp3buf來(lái)傳入,避免創(chuàng)建過多的數(shù)組。

close

關(guān)閉釋放Lame

OK,到這里,核心的轉(zhuǎn)換代碼就完成了,我們?cè)賮?lái)點(diǎn)錦上添花的東西。

音量

一般我們?cè)谧鲣浺舻臅r(shí)候,都會(huì)有一個(gè)需求,根據(jù)音量的大小顯示一個(gè)動(dòng)畫,讓錄音顯得更生動(dòng)一些。
當(dāng)然,我在這個(gè)庫(kù)里也提供了。
那么怎么來(lái)計(jì)算音量呢?
我參考了三星的音量計(jì)算。
總結(jié)如下:

/**
* 此計(jì)算方法來(lái)自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)。
因此在我提供的錄音庫(kù)里面,我把最大音量規(guī)定為了2000。
這塊兒歡迎大家來(lái)提寶貴意見。

MP3錄音實(shí)現(xiàn)參考

yhirano/Mp3VoiceRecorderSampleForAndroid
日本人寫的,感覺他的判斷不完善,有點(diǎn)巧合編程的意思,也或許是我沒看懂。

talzeus/AndroidMp3Recorder
比較嚴(yán)謹(jǐn)?shù)拇a。主要依據(jù)這個(gè)庫(kù)進(jìn)行的修改。

存在的問題:

AudioRecord傳入?yún)?shù)很多沒有按Android規(guī)定傳入。如采樣頻率使用了22050Hz。

使用了自己構(gòu)造的RingBuffer,看這有點(diǎn)頭暈。 我在庫(kù)里使用List來(lái)存儲(chǔ)未編碼的音頻數(shù)據(jù),更容易理解。

沒有提供音量大小。

相關(guān)文章

最新評(píng)論