(二)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;

到此就结束啦!下一篇将会进行音频的传输研究了

源码地址:点击打开链接

你可能感兴趣的:((二)Android音频录制研究)