(二)Android音頻錄制研究

上一篇實現了Android端文字的傳輸 點擊打開鏈接,由于此系列要實現Android端語音的傳輸,所以這篇就先研究一下Android端語音的錄制。先上效果圖吧:

這是主頁就是幾個按鈕:音頻的錄制分為文件錄制和字節流錄制,

(1)文件采用Media Record錄制和Media Player播放

(2)字節流采用Audio Record錄制和Audio Track播放

(3)音量可視化就是實時獲取音量大小,顯示到屏幕上面

(4)簡單實現聲音的變速,加速播放和減速播放

上代碼:

(1)文件錄制

需要說明的是錄音JNI函數不具備線程安全性,所以采用了單線程的線程池

executorService = Executors.newSingleThreadExecutor();

因為錄音線程在子線程,錄音失敗和成功與主線程交互,采用了Handler

mainThreadHandler = new Handler(Looper.getMainLooper());

tvSpeak.setOnTouchListener(new View.OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

//按下按鈕開始錄制

startRecord();

break;

case MotionEvent.ACTION_UP:

case MotionEvent.ACTION_CANCEL:

//松開按鈕結束錄制

stopRecord();

break;

}

return true;

}

});

private void startRecord() {

tvSpeak.setText("正在說話");

//提交后臺任務,執行錄音邏輯

executorService.submit(new Runnable() {

@Override

public void run() {

//釋放之前錄音的recorder

releaseRecorder();

//執行錄音邏輯,如果失敗 提示用戶

if (!doStart()) {

recordFail();

}

}

});

}

private void stopRecord() {

tvSpeak.setText("按住說話");

//提交后臺任務,執行停止邏輯

executorService.submit(new Runnable() {

@Override

public void run() {

//執行停止錄音邏輯,失敗就要提醒用戶

if (!doStop()) {

recordFail();

}

//釋放recorder

releaseRecorder();

}

});

}

private boolean doStart() {

try {

//創建mediaRecorder

mediaRecorder = new MediaRecorder();

//創建錄音文件

mAudioFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyUdpDemo/" + System.currentTimeMillis() + ".m4a");

mAudioFile.getParentFile().mkdirs();

mAudioFile.createNewFile();

//配置Media Recorder

mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);

mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

mediaRecorder.setAudioSamplingRate(44100);

mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

mediaRecorder.setAudioEncodingBitRate(96000);

//設置錄音文件的位置

mediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());

//開始錄音

mediaRecorder.prepare();

mediaRecorder.start();

//記錄開始錄音時間 用于統計時長

mStartRecordTime = System.currentTimeMillis();

} catch (IOException e) {

e.printStackTrace();

return false;

}

return true;

}

private boolean doStop() {

//停止錄音

try {

mediaRecorder.stop();

//記錄停止時間

mStopRecordTime=System.currentTimeMillis();

//只接受超過三秒的錄音

final int second = (int) (mStopRecordTime - mStartRecordTime)/1000;

if (second > 3) {

mainThreadHandler.post(new Runnable() {

@Override

public void run() {

tvLog.setText(tvLog.getText() + "\n錄音成功" + second + "秒");

}

});

}

//停止成功

} catch (Exception e) {

e.printStackTrace();

return false;

}

return true;

}

private void recordFail() {

mAudioFile = null;

//要在主線程執行

mainThreadHandler.post(new Runnable() {

@Override

public void run() {

Toast.makeText(FileActivity.this, "錄音失敗", Toast.LENGTH_SHORT).show();

}

});

}

private void releaseRecorder() {

//檢查mediaRecorder不為空

if (mediaRecorder != null) {

mediaRecorder.release();

mediaRecorder = null;

}

}

錄音成功后,下面就是播放了:

@OnClick(R.id.play)

public void onViewClicked() {

if (mAudioFile != null && !isPlaying) {

play.setText("停止");

executorService.submit(new Runnable() {

@Override

public void run() {

startPlay(mAudioFile);

}

});

} else {

play.setText("播放");

executorService.submit(new Runnable() {

@Override

public void run() {

stopPlay();

}

});

}

}

private void startPlay(File audioFile) {

//配置播放器

mMediaPlayer = new MediaPlayer();

try {

//設置聲音文件

mMediaPlayer.setDataSource(audioFile.getAbsolutePath());

//設置監聽回掉

mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

@Override

public void onCompletion(MediaPlayer mp) {

stopPlay();

}

});

mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

@Override

public boolean onError(MediaPlayer mp, int what, int extra) {

//提示用戶 釋放播放器

playFail();

stopPlay();

return true;

}

});

//配置音量 是否循環

mMediaPlayer.setVolume(1, 1);

mMediaPlayer.setLooping(false);

//準備 開始

mMediaPlayer.prepare();

mMediaPlayer.start();

} catch (RuntimeException e) {

e.printStackTrace();

//異常處理防止閃退

playFail();

} catch (IOException e) {

e.printStackTrace();

}

}

private void stopPlay() {

//重置播放狀態

isPlaying = false;

play.setText("播放");

if (mMediaPlayer != null) {

mMediaPlayer.setOnCompletionListener(null);

mMediaPlayer.setOnErrorListener(null);

mMediaPlayer.stop();

mMediaPlayer.reset();

mMediaPlayer.release();

mMediaPlayer = null;

}

}

private void playFail() {

mainThreadHandler.post(new Runnable() {

@Override

public void run() {

Toast.makeText(FileActivity.this, "播放失敗", Toast.LENGTH_SHORT).show();

}

});

}

在onDestroy方法里面注銷

@Override

protected void onDestroy() {

super.onDestroy();

//activity銷毀時停止后臺任務 避免后臺任務

executorService.shutdown();

releaseRecorder();

stopPlay();

}

至此文件的錄制和播放就結束了

(2)字節流錄制

@OnClick({R.id.btnStart, R.id.play})

public void onViewClicked(View view) {

switch (view.getId()) {

case R.id.btnStart:

if (mIsRecording) {

btnStart.setText("開始");

mIsRecording = false;

} else {

btnStart.setText("停止");

mIsRecording = true;

executorService.submit(new Runnable() {

@Override

public void run() {

if (!startRecord()) {

recordFail();

}

}

});

}

break;

case R.id.play:

//檢查播放狀態 防止重復播放

if (mAudioFile != null && !isPlaying) {

isPlaying = true;

executorService.submit(new Runnable() {

@Override

public void run() {

startPlay(mAudioFile);

}

});

}

break;

}

}

private boolean startRecord() {

try {

//創建錄音文件

mAudioFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyUdpDemo/" + System.currentTimeMillis() + ".pcm");

mAudioFile.getParentFile().mkdirs();

mAudioFile.createNewFile();

//創建文件輸出流

fileOutputStream = new FileOutputStream(mAudioFile);

//配置Audio Record

int minBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

//buffer不能小于最低要求,也不能小于我們每次讀取的大小

mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, Math.max(minBufferSize, BUFFERSIZE));

//開始錄音

mAudioRecord.startRecording();

//記錄開始錄音時間,用于統計時長

mStartTime = System.currentTimeMillis();

//循環讀取數據,寫到輸出流中

while (mIsRecording) {

int read = mAudioRecord.read(buffer, 0, BUFFERSIZE);

//返回值是這次讀到了多少

if (read > 0) {

//讀取失敗

fileOutputStream.write(buffer, 0, read);

} else {

//讀取失敗

return false;

}

}

//退出循環,停止錄音,釋放資源

return stopRecord();

} catch (IOException e) {

e.printStackTrace();

return false;

} finally {

//釋放Audio Record

if (mAudioRecord != null) {

mAudioRecord.release();

}

}

}

private boolean stopRecord() {

try {

//停止錄音 關閉文件輸出流

mAudioRecord.stop();

mAudioRecord.release();

mAudioRecord = null;

fileOutputStream.close();

//記錄結束時間 統計時長

mStopTime = System.currentTimeMillis();

final int second = (int) ((mStopTime - mStartTime) / 1000);

//大于3秒的成功 在主線程改變UI

if (second > 3) {

mMainHandler.post(new Runnable() {

@Override

public void run() {

tvLog.setText(tvLog.getText() + "\n錄音成功" + second + "秒");

}

});

}

} catch (IOException e) {

e.printStackTrace();

return false;

}

return true;

}

private void recordFail() {

mMainHandler.post(new Runnable() {

@Override

public void run() {

Toast.makeText(StreamActivity.this, "錄音失敗", Toast.LENGTH_SHORT).show();

//重置錄音狀態 UI狀態

mIsRecording = false;

btnStart.setText("開始");

}

});

}

private void startPlay(File mAudioFile) {

//配置播放器

//揚聲器播放

int streamType = AudioManager.STREAM_MUSIC;

//播放的采樣頻率 和錄制的采樣頻率一樣

int sampleRate = 44100;

//和錄制的一樣的

int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

//流模式

int mode = AudioTrack.MODE_STREAM;

//錄音用輸入單聲道? 播放用輸出單聲道

int channelConfig = AudioFormat.CHANNEL_OUT_MONO;

int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);

AudioTrack audioTrack = new AudioTrack(streamType, sampleRate, channelConfig, audioFormat, Math.max(minBufferSize, BUFFERSIZE), mode);

audioTrack.play();

//從文件流讀數據

FileInputStream fileInputStream = null;

try {

fileInputStream = new FileInputStream(mAudioFile);

int read;

while ((read = fileInputStream.read(buffer)) > 0) {

int ret = audioTrack.write(buffer, 0, read);

//檢查write 返回值 錯誤處理

switch (ret) {

case AudioTrack.ERROR_BAD_VALUE:

case AudioTrack.ERROR_INVALID_OPERATION:

case AudioTrack.ERROR_DEAD_OBJECT:

playFail();

break;

default:

break;

}

}

} catch (RuntimeException | IOException e) {

e.printStackTrace();

playFail();

} finally {

//關閉文件流

isPlaying = false;

if (fileInputStream != null) {

closeQuatily(fileInputStream);

}

resetAudioTrack(audioTrack);

}

}

private void playFail() {

mAudioFile = null;

mMainHandler.post(new Runnable() {

@Override

public void run() {

Toast.makeText(StreamActivity.this, "播放失敗", Toast.LENGTH_SHORT).show();

}

});

}

private void resetAudioTrack(AudioTrack audioTrack) {

try {

audioTrack.stop();

audioTrack.release();

} catch (RuntimeException e) {

e.printStackTrace();

}

}

private void closeQuatily(FileInputStream fileInputStream) {

try {

fileInputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

protected void onDestroy() {

super.onDestroy();

executorService.shutdownNow();

}

(3)音頻可視化

主要就是獲取音量大小,劃分等級進行顯示

public void getRecordVolume() {

if (mediaRecorder != null) {

}

int maxAmplitude;

//獲取音量大小

try {

maxAmplitude = mediaRecorder.getMaxAmplitude();

} catch (RuntimeException e) {

e.printStackTrace();

//異常發生后 用一個隨機數代表當前音量大小

maxAmplitude = random.nextInt();

}

final int level = maxAmplitude / (MAXAMPLITUDE / MAXLEVEL);

//把音量規劃到五個等級

//把等級顯示到UI上面

mainThreadHandler.post(new Runnable() {

@Override

public void run() {

refreshVolume(level);

}

});

//如果仍在錄音,就隔一段時間再次獲取音量大小

if (isRecording) {

executorService.schedule(new Runnable() {

@Override

public void run() {

getRecordVolume();

}

}, 50, TimeUnit.MILLISECONDS);

}

}

private void refreshVolume(int level) {

for (int i = 0; i < 5; i++) {

imageViewList.get(i).setVisibility(i < level ? View.VISIBLE : View.GONE);

}

}

(4)音頻變速播放

添加三個支持的播放采樣率

private static final int[] SUPPORTSAMPLERATE = {11025, 22050, 44100};

錄制的時候采用中間的頻率,點擊不同的播放按鈕,采用不同的播放頻率

case R.id.play:

//檢查播放狀態 防止重復播放

play(SUPPORTSAMPLERATE[1]);

break;

case R.id.playFast:

//檢查播放狀態 防止重復播放

play(SUPPORTSAMPLERATE[2]);

break;

case R.id.playSlowly:

//檢查播放狀態 防止重復播放

play(SUPPORTSAMPLERATE[0]);

break;

到此就結束啦!下一篇將會進行音頻的傳輸研究了

源碼地址:點擊打開鏈接

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容