上一篇实现了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;
到此就结束啦!下一篇将会进行音频的传输研究了
源码地址:点击打开链接