在android中,實現錄音與語音播放的功能算是比較簡單的,但是作為參考,還是很有必要將語音相關的知識做一個簡要的記錄。
首先,在android中,支持錄音支持兩種方式。主要包括:字節(jié)流模式和文件流模式。用文件流模式進行錄音操作比較簡單,而且相對來說,因為其封裝性比較好,錄制下的文件也會比較小。但是相對于文件流模式,就沒有字節(jié)流模式那么靈活,但是想要用好字節(jié)流模式還是需要下一點功夫的。
下面開始介紹文件流模式的語音操作:
文件流模式
我們來看錄音部分的實現,首先我們實現開始錄音的部分:
在正式編碼之前,還是需要對其進行一個簡要的說明。一般來說,錄音功能的實現是在jin層,而在這一層中,是用單線程實現的。如果我們在編碼的對錄音api進行多線程操作,會導致程序直接閃退,并且我們是無法在java層對其異常進行捕獲的。所以,我們必須使用單線程以保證錄音的正常運行。
一般來說,開始錄音的步驟也就三個,代碼如下:
releaseRecord();//釋放可能沒釋放的錄音相關資源
if (!doStartRecord()) {//真正的開始錄音的函數,開始錄音成功返回true,否則返回false
recordFail(); //開始失敗,向用戶提示開始錄音失敗
}
接下來我們來看看上述三個方法的實現:
實現是釋放相關資源的方法releaseRecord:
if (mMediaRecorder != null) {
mMediaRecorder.release();
mMediaRecorder = null;
}
其中的mMediaRecorder 的MediaRecorder的全局變量。
接下來是真正實現開始錄音的實現邏輯doStartRecord()
private boolean doStartRecord() {
try {
mMediaRecorder = new MediaRecorder();
mAudioFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice/"
+ System.currentTimeMillis() + ".m4a");
mAudioFile.getParentFile().mkdirs();
mAudioFile.createNewFile();
//設置從麥克風采集聲音
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//保存文件為mp4的格式
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//設置所有android系統(tǒng)都支持的采樣頻率
mMediaRecorder.setAudioSamplingRate(44100);
//設置acc的編碼方式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//設置比較好的音質
mMediaRecorder.setAudioEncodingBitRate(96000);
mMediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());
mMediaRecorder.prepare();
mMediaRecorder.start();
mStartRecordTime = System.currentTimeMillis();
} catch (IOException | RuntimeException e) {
e.printStackTrace();
return false;
}finally {
if(mAudioRecord != null){
mAudioRecord.release();
}
}
return true;
}
這一部分代碼比較多,但是關鍵部分我都給出了注釋,相信理解起來也不算難吧。這一部分我們實現的主要是在sdcrad根目錄下新建一個voice的目錄,然后在新建一個以==.m4a==為后綴名的文件。在配置mAudioRecord的相關參數后,將收集到的錄音存放到之前的文件中。如果一切都順利的話,就返回true ,表示開始錄音成功。
最后就是提示用戶錄音實現的邏輯recordFail
mAudioFile = null;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(VioceActivity.this, "錄音失敗", Toast.LENGTH_SHORT).show();
}
}, 100);
這里的邏輯比較簡單,但是值得注意的是,因為我們開始錄音方法是在一個非主線程的線程中執(zhí)行的,所以我們需要借助hander來實現界面提示的效果。這里的mHander是一個局部變量,其初始化放在OnCreate()方法中。
mHandler = new Handler(Looper.getMainLooper());
綜上,開始錄音的所有邏輯已經全部實現。接下來實現的是結束錄音的實現邏輯:
主題的邏輯如下:
if (!doStopRecord()) {//實現 停止錄音的真正邏輯,成功返回true,否則返回false
recordFail();//提示用戶錄音失敗
}
這里的doStopRecord實現邏輯如下:
mMediaRecorder.stop();
mEndRecordTime = System.currentTimeMillis();
final int seond = (int) ((mEndRecordTime - mStartRecordTime) / 1000);
if (seond < 3) {
recordFail();
return false;
} else {
mHandler.post(new Runnable() {
@Override
public void run() {
mText.setText("錄音" + seond + "成功!");
}
});
}
} catch (RuntimeException e) {
e.printStackTrace();
return false;
}
return true;
其實我們實現停止錄音的邏輯也很簡單,首先調用mMediaRecorder.stop();停止錄音,然后對錄音時間是否大于3s進行判斷,若大于3s,則表示錄音有效,提示用戶,錄音成功。
綜上,我們文件流的錄音的所有代碼已經實現完畢。接下來我們實現對其進行播放。如果需要參考全部的代碼,請戳這里。
private void doPlay(File mAudioFile) {
//配置播放器 MediaPlayer
mMediaPlayer = new MediaPlayer();
try{
//設置聲音文件
mMediaPlayer.setDataSource(mAudioFile.getAbsolutePath());
//設置監(jiān)聽回調
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
stopPlay();
}
});
//設置出錯的監(jiān)聽器
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
playFail();
//提示用戶
stopPlay();
//釋放播放器
return true;
}
});
//配置音量,是否循環(huán)
mMediaPlayer.setVolume(1,1);
mMediaPlayer.setLooping(false);
mMediaPlayer.prepare();
mMediaPlayer.start();
}catch (Exception e){
e.printStackTrace();
playFail();
stopPlay();
}
}
整體來說,基于文件的錄音是比較容易實現的。下面介紹如何通過字節(jié)流模式實現錄音。
字節(jié)流模式錄音
開始錄音:主要邏輯startRecord2()的實現
private boolean startRecord2() {
try {
mAudioFile2 = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice/"
+ System.currentTimeMillis() + ".pcm");
mAudioFile2.getParentFile().mkdirs();
mAudioFile2.createNewFile();
mFileOutputStream = new FileOutputStream(mAudioFile2);
//配置AudioRecord
//從麥克風采集數據
int audioSource = MediaRecorder.AudioSource.MIC;
//采集頻率
int sampleRate = 44100;
//單聲道輸入
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
//設置pcm(脈沖編碼調制 Pulse Code Modulation)編碼格式
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//計算AudioRecord 內存buffer最小的大小
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat);
//創(chuàng)建AudioRecord對象
mAudioRecord = new AudioRecord(audioSource,sampleRate,channelConfig,audioFormat,Math.max(BUFFER_SIZE,minBufferSize));
mAudioRecord.startRecording();
mStartRecordTime = System.currentTimeMillis();
//循環(huán)讀取數據,寫到輸出流中
while(mIsRecord){
int read = mAudioRecord.read(mBuffer,0,BUFFER_SIZE);
if(read >0 ){
//讀取文件寫到文件中
mFileOutputStream.write(mBuffer,0,read);
}else{
return false;
}
}
} catch (IOException | RuntimeException e) {
e.printStackTrace();
return false;
}
//停止錄音
return true;
}
停止錄音的doStopRecord()實現:
private boolean doStopRecord() {
try {
mMediaRecorder.stop();
mEndRecordTime = System.currentTimeMillis();
final int seond = (int) ((mEndRecordTime - mStartRecordTime) / 1000);
if (seond < 3) {
recordFail();
return false;
} else {
mHandler.post(new Runnable() {
@Override
public void run() {
mText.setText("錄音" + seond + "成功!");
}
});
}
} catch (RuntimeException e) {
e.printStackTrace();
return false;
}
return true;
}
對其中相關參數的說名:
private boolean mIsRecord = false;
private final int BUFFER_SIZE = 2048;//緩存區(qū)的大小
private byte[] mBuffer;
private FileOutputStream mFileOutputStream;
private AudioRecord mAudioRecord;
private File mAudioFile2;
接下來,實現的是對其字節(jié)流模式錄制的文件進行播放:doPlay2()主題類的實現:
private void doPlay2(File mAudioFile) {
//聲音類型,揚聲器播放
int steamType = AudioManager.STREAM_MUSIC;
//采樣頻率
int sampleRate = 44100;
//MONO 表示單聲道 錄音輸入單聲道 播放也使用單聲道
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
//錄音使用16bit 所以播放也使用同樣的格式
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//流模式
int mode = AudioTrack.MODE_STREAM;
//計算需要最小buffer的大小
int minBufferSize =AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
AudioTrack audioTrack = new AudioTrack(steamType,sampleRate,channelConfig,audioFormat,
Math.max(minBufferSize,BUFFER_SIZE),mode);
//從文件流中讀取數據
FileInputStream inputStream = null;
try{
inputStream = new FileInputStream(mAudioFile2);
int read;
//循環(huán)讀取數據,寫到播放器去播放
audioTrack.play();
while((read = inputStream.read(mBuffer)) > 0){
int ret = audioTrack.write(mBuffer,0,read);
switch (ret){
case AudioTrack.ERROR:
case AudioTrack.ERROR_BAD_VALUE:
case AudioTrack.ERROR_INVALID_OPERATION:
case AudioTrack.ERROR_DEAD_OBJECT:
playFail();
return ;
default:
break;
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
mIsPlaying = false;
if(inputStream != null)
closeQuietly(inputStream);
resetQuietly(audioTrack);
}
}
千言萬語肯定不如直接代碼來的直接了當,所以的代碼實現點這里
實現的效果如下: