上一篇實現了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;
到此就結束啦!下一篇將會進行音頻的傳輸研究了
源碼地址:點擊打開鏈接