Android音頻開(kāi)發(fā)之錄制音頻(WAV及MP3格式)
附GitHub源碼:MultimediaExplore
首先看下音頻錄制跟播放效果簡(jiǎn)圖:

上面是錄音:長(zhǎng)按即可錄音,支持聲波動(dòng)畫(huà),右滑刪除等。支持錄制pcm、wav、mp3格式音頻。
下面是播放:點(diǎn)擊左邊揚(yáng)聲器icon,開(kāi)始播放剛錄制的本地音頻文件【也支持在線音頻播放】,支持播放進(jìn)度,支持切換播放模式(聽(tīng)筒/揚(yáng)聲器/耳機(jī))等。
一、音頻錄制權(quán)限:
無(wú)論在做開(kāi)發(fā)任何功能之前,總得先添加及申請(qǐng)相關(guān)權(quán)限,后續(xù)的工作才能正常進(jìn)行下去。音頻錄制所需權(quán)限如下,而且要在代碼中動(dòng)態(tài)申請(qǐng)這些敏感權(quán)限,同意后才能正常錄制:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
二、錄音文件的配置:
通過(guò)第一節(jié)講到音頻的基礎(chǔ)概念可知,在錄制音頻前應(yīng)先進(jìn)行錄制的相關(guān)配置,它直接決定了錄音文件的音頻質(zhì)量、文件大小、音頻格式等。
/**
* 錄音音頻的相關(guān)配置
*/
private void initConfig() {
recordConfig = new RecordConfig();
//采樣位寬
recordConfig.setEncodingConfig(AudioFormat.ENCODING_PCM_16BIT);
//錄音格式
recordConfig.setFormat(RecordConfig.RecordFormat.MP3);
// recordConfig.setFormat(RecordConfig.RecordFormat.WAV);
//采樣頻率
recordConfig.setSampleRate(16000);
String recordDir = String.format(Locale.getDefault(), "%s/Record/zhongyao/",
Environment.getExternalStorageDirectory().getAbsolutePath());
//存儲(chǔ)目錄
recordConfig.setRecordDir(recordDir);
AudioRecordManager.getInstance().setCurrentConfig(recordConfig);
}
三、音頻錄制:
音頻錄制類主要有兩個(gè)封裝類:分別是AudioRecorder 、AudioRecordManager。
AudioRecorder:主要是使用系統(tǒng)的AudioRecord來(lái)進(jìn)行錄音。并把錄制好的音頻文件進(jìn)行合并,轉(zhuǎn)碼等,生成我們所需的音頻文件。該文件是全局單例的,保證音頻錄制類只有一個(gè)實(shí)例。
AudioRecordManager:對(duì)AudioRecorder的封裝管理,與外界交互均通過(guò)此類來(lái)完成,包括錄音的各種生命周期控制調(diào)用等。減少了外界與AudioRecorder的直接交互,已達(dá)到對(duì)錄音類的更好的管理,此類也是一個(gè)全局單例類。
1、錄音對(duì)象初始化:
這里主要根據(jù)之前的錄音配置,生成 bufferSizeInBytes【緩沖區(qū)字節(jié)大小】,和audioRecord對(duì)象。
/**
* 創(chuàng)建默認(rèn)的錄音對(duì)象
*/
public void prepareRecord() {
// 獲得緩沖區(qū)字節(jié)大小
if (bufferSizeInBytes == 0) {
bufferSizeInBytes = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig());
}
if (audioRecord == null) {
audioRecord = new AudioRecord(AUDIO_INPUT, currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSizeInBytes);
}
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_PREPARE;
}
2、錄制wav音頻文件:
wav音頻文件是無(wú)損的,所以音質(zhì)會(huì)接近原生,但也正是因?yàn)槭菬o(wú)損的,所以wav音頻文件幾乎沒(méi)有壓縮,相對(duì)來(lái)說(shuō)會(huì)比較大。
錄制wav音頻得先進(jìn)行錄制采用,獲得pcm文件,然后把pcm文件合并,最后再轉(zhuǎn)成wav音頻文件。
(1)開(kāi)始錄制pcm文件:
private void startPcmRecorder() {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_START;
notifyState();
Logger.d(TAG, "開(kāi)始錄制 Pcm");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(tmpFile);
audioRecord.startRecording();
byte[] byteBuffer = new byte[bufferSizeInBytes];
while (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_START) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
notifyData(byteBuffer);
fos.write(byteBuffer, 0, end);
fos.flush();
}
audioRecord.stop();
files.add(tmpFile);
if (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_STOP) {
makeFile();
} else {
Logger.d(TAG, "取消錄制...");
}
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
notifyError("錄音失敗");
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (audioRecordStatus != AudioRecordStatus.AUDIO_RECORD_PAUSE) {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_IDLE;
notifyState();
Logger.d(TAG, "錄音結(jié)束");
}
}
(2)合并生成的多個(gè)pcm文件:
/**
* 合并pcm文件
*/
private void mergePcmFile() {
boolean mergeSuccess = mergePcmFiles(resultFile, files);
if (!mergeSuccess) {
notifyError("合并失敗");
}
}
(3)將合并好的pcm文件轉(zhuǎn)成wav文件:
/**
* 添加Wav頭文件
*/
private void makeWav() {
if (!FileUtil.isFile(resultFile) || resultFile.length() == 0) {
return;
}
byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());
WavUtils.writeHeader(resultFile, header);
}
3、錄制MP3音頻文件
相比WAV音頻文件而言,MP3音頻文件,就更加常見(jiàn),商業(yè)上使用的也比較多,就是因?yàn)镸P3音頻時(shí)經(jīng)過(guò)壓縮的,文件大小只有WAV的十二分之一,但是音質(zhì)上幾乎沒(méi)有較大的差異性。當(dāng)對(duì)音質(zhì)沒(méi)有極高要求的情況下,如錄音文件,MP3格式是極好的選擇。
(1)開(kāi)始錄制音頻緩存:
這里有開(kāi)啟一個(gè)線程Mp3EncodeThread,將錄音產(chǎn)生的字節(jié)數(shù)組byteBuffer不斷的進(jìn)行編解碼生成MP3文件。
private void startMp3Recorder() {
audioRecordStatus = AudioRecordStatus.AUDIO_RECORD_START;
notifyState();
try {
audioRecord.startRecording();
short[] byteBuffer = new short[bufferSizeInBytes];
while (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_START) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
if (mp3EncodeThread != null) {
mp3EncodeThread.addChangeBuffer(new Mp3EncodeThread.ChangeBuffer(byteBuffer, end));
}
notifyData(ByteUtils.toBytes(byteBuffer));
}
audioRecord.stop();
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
notifyError("錄音失敗");
}
if (audioRecordStatus != AudioRecordStatus.AUDIO_RECORD_PAUSE) {
if (audioRecordStatus == AudioRecordStatus.AUDIO_RECORD_CANCEL) {
deleteMp3Encoded();
} else {
stopMp3Encoded();
}
} else {
Logger.d(TAG, "暫停");
}
}
(2)MP3音頻編解碼:
Android原生的音頻錄制,支持直接生成WAV文件,但其實(shí)是不支持直接生成MP3文件的。這里對(duì)應(yīng)MP3編解碼,主要用到了開(kāi)源的 libmp3lame.so 這個(gè)音頻編解碼庫(kù)。以下是lame編解碼方法及Mp3Encoder類:
MP3編解碼方法:
private void lameData(ChangeBuffer changeBuffer) {
if (changeBuffer == null) {
return;
}
short[] buffer = changeBuffer.getData();
int readSize = changeBuffer.getReadSize();
if (readSize > 0) {
int encodedSize = Mp3Encoder.encode(buffer, buffer, readSize, mp3Buffer);
if (encodedSize < 0) {
Logger.e(TAG, "Lame encoded size: " + encodedSize);
}
try {
os.write(mp3Buffer, 0, encodedSize);
} catch (IOException e) {
Logger.e(e, TAG, "Unable to write to file");
}
}
}
Mp3Encoder類:
public class Mp3Encoder {
static {
System.loadLibrary("mp3lame");
}
public native static void close();
public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);
public native static int flush(byte[] mp3buf);
public native static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);
public static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate) {
init(inSampleRate, outChannel, outSampleRate, outBitrate, 7);
}
}
(3)生成libmp3lame.so:
本項(xiàng)目提供的源碼中有l(wèi)ame的jni源碼,如果想生成libmp3lame.so文件,供自己的項(xiàng)目使用,那么需要對(duì)Mp3Encoder.c 和Mp3Encoder.h文件進(jìn)行修改,然后通過(guò)ndk build 生成該so文件。

修改的內(nèi)容主要是把文件中的Mp3Encoder路徑改成自己項(xiàng)目中的Mp3Encoder的路徑,否則會(huì)找不到相關(guān)的native方法。

另外一般情況下,cpu類型支持 armeabi-v7a 、arm64-v8a 兩種即可,如果想支持其他的可在Application.mk中添加。

上面文件修改完畢,通過(guò)ndk-build指令即可編譯生成對(duì)應(yīng)的 libmp3lame.so 文件。
將so不同CPU類型的文件放置 jniLibs 下,或者通過(guò)sourceSets配置的路徑下,如:
sourceSets.main {
jni.srcDirs = []//disable automatic ndk-build call
jniLibs.srcDirs = ['libs']
}
即可進(jìn)行MP3音頻格式的錄制。
四、音頻錄制管理【AudioRecordManager】:
通過(guò)全局單例模式的AudioRecorderManager與業(yè)務(wù)進(jìn)行交互,即保證了音頻錄制實(shí)例的單一性,又能較好的對(duì)音頻生命周期等進(jìn)行較好的管理。?
/**
* Create by zhongyao on 2021/8/18
* Description:音頻錄制管理類
*/
public class AudioRecordManager {
private static final String TAG = "AudioRecordManager";
private AudioRecordManager() {
}
public static AudioRecordManager getInstance() {
return AudioRecordManagerHolder.instance;
}
public static class AudioRecordManagerHolder {
public static AudioRecordManager instance = new AudioRecordManager();
}
public void setCurrentConfig(RecordConfig recordConfig) {
AudioRecorder.getInstance().setCurrentConfig(recordConfig);
}
public RecordConfig getCurrentConfig() {
return AudioRecorder.getInstance().getCurrentConfig();
}
/**
* 錄音狀態(tài)監(jiān)聽(tīng)回調(diào)
*/
public void setRecordStateListener(RecordStateListener listener) {
AudioRecorder.getInstance().setRecordStateListener(listener);
}
/**
* 錄音數(shù)據(jù)監(jiān)聽(tīng)回調(diào)
*/
public void setRecordDataListener(RecordDataListener listener) {
AudioRecorder.getInstance().setRecordDataListener(listener);
}
/**
* 錄音可視化數(shù)據(jù)回調(diào),傅里葉轉(zhuǎn)換后的頻域數(shù)據(jù)
*/
public void setRecordFftDataListener(RecordFftDataListener recordFftDataListener) {
AudioRecorder.getInstance().setRecordFftDataListener(recordFftDataListener);
}
/**
* 錄音文件轉(zhuǎn)換結(jié)束回調(diào)
*/
public void setRecordResultListener(RecordResultListener listener) {
AudioRecorder.getInstance().setRecordResultListener(listener);
}
/**
* 錄音音量監(jiān)聽(tīng)回調(diào)
*/
public void setRecordSoundSizeListener(RecordSoundSizeListener listener) {
AudioRecorder.getInstance().setRecordSoundSizeListener(listener);
}
public void setStatus(AudioRecordStatus curAudioRecordStatus) {
switch (curAudioRecordStatus) {
case AUDIO_RECORD_IDLE:
break;
case AUDIO_RECORD_PREPARE:
AudioRecorder.getInstance().prepareRecord();
break;
case AUDIO_RECORD_START:
AudioRecorder.getInstance().startRecord();
break;
case AUDIO_RECORD_PAUSE:
AudioRecorder.getInstance().pauseRecord();
break;
case AUDIO_RECORD_STOP:
AudioRecorder.getInstance().stopRecord();
break;
case AUDIO_RECORD_CANCEL:
AudioRecorder.getInstance().cancelRecord();
break;
case AUDIO_RECORD_RELEASE:
AudioRecorder.getInstance().releaseRecord();
break;
default:
break;
}
}
public AudioRecordStatus getStatus() {
return AudioRecorder.getInstance().getAudioRecordStatus();
}
public String getAudioPath() {
return AudioRecorder.getInstance().getAudioPath();
}
}
以上就是Android音頻開(kāi)發(fā)之錄制音頻(WAV及MP3格式)的詳細(xì)內(nèi)容,更多關(guān)于Android 音頻錄制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android 中viewpager+fragment仿微信底部TAG完美漸變
這篇文章主要介紹了android 中viewpager+fragment仿微信底部TAG完美漸變,需要的朋友可以參考下2017-05-05
Flutter學(xué)習(xí)之構(gòu)建、布局及繪制三部曲
這篇文章主要給大家介紹了關(guān)于Flutter學(xué)習(xí)之構(gòu)建、布局及繪制三部曲的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Android自定義view實(shí)現(xiàn)輸入框效果
這篇文章主要為大家詳細(xì)介紹了Android自定義view實(shí)現(xiàn)輸入框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03
一文了解Android?ViewModelScope?如何自動(dòng)取消協(xié)程
這篇文章主要介紹了一文了解Android?ViewModelScope?如何自動(dòng)取消協(xié)程,文章圍繞主題站展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定參考價(jià)值,感興趣的小伙伴可以參考一下2022-07-07
Android仿打開(kāi)微信紅包動(dòng)畫(huà)效果實(shí)現(xiàn)代碼
這篇文章主要介紹了Android仿打開(kāi)微信紅包動(dòng)畫(huà)效果實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-12-12
解決Android studio3.6安裝后gradle Download失敗(構(gòu)建不成功)
這篇文章主要介紹了解決Android studio3.6安裝后gradle Download失敗(構(gòu)建不成功),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Android RecyclerView實(shí)現(xiàn)數(shù)據(jù)列表展示效果
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView實(shí)現(xiàn)數(shù)據(jù)列表展示效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android應(yīng)用開(kāi)發(fā)之代碼混淆
Android項(xiàng)目中的混淆很easy,之所以寫這篇總結(jié)是由于近期發(fā)現(xiàn)公司的代碼居然沒(méi)有混淆,反編譯后代碼隨手可得。很震驚。2014-07-07

