最近做的项目是和语音实时采集并发送,对方实时接收并播放相关,下面记录下实现的核心代码。
很多android开发者应该知道android有个MediaRecorder对象和MediaPlayer对象,用于录制和播放音频。这个弊端在于他们不能实时采集并发送出去,所以,我们只能使用AudioRecord和AudioTrack来实现。
记得申明权限:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" >
一、AudioRecord实现核心代码介绍如下:
1、先申明相关录制配置参数
private AudioRecord audioRecord;// 录音对象
private int frequence = 8000;// 采样率 8000
private int channelInConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;// 定义采样通道
private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;// 定义音频编码(16位)
private byte[] buffer = null;// 录制的缓冲数组
2、在开始录制前,我们需要初始化AudioRecord类。
// 根据定义好的几个配置,来获取合适的缓冲大小
// int bufferSize = 800;
int bufferSize = AudioRecord.getMinBufferSize(frequence,
channelInConfig, audioEncoding);
// 实例化AudioRecord
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
frequence, channelInConfig, audioEncoding, bufferSize);
// 定义缓冲数组
buffer = new byte[bufferSize];
3、准备开始录制,使用循环不断读取数据。
audioRecord.startRecording();// 开始录制
isRecording = true;// 设置录制标记为true
// 开始录制
while (isRecording) {
// 录制的内容放置到了buffer中,result代表存储长度
int result = audioRecord.read(buffer, 0, buffer.length);
/*.....result为buffer中录制数据的长度(貌似基本上都是640)。
剩下就是处理buffer了,是发送出去还是直接播放,这个随便你。*/
}
//录制循环结束后,记得关闭录制!!
if (audioRecord != null) {
audioRecord.stop();
}
二、AudioTrack代码实现介绍如下:
1、声明播放相关配置。
private AudioTrack track = null;// 录音文件播放对象
private int frequence = 8000;// 采样率 8000
private int channelInConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;// 定义采样通道
private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;// 定义音频编码(16位)
private int bufferSize = -1;// 播放缓冲大小
2、初始化AudioTrack对象(初始化一次,该对象可重复使用)
// 获取缓冲 大小
bufferSize = AudioTrack.getMinBufferSize(frequence, channelInConfig,
audioEncoding);
// 实例AudioTrack
track = new AudioTrack(AudioManager.STREAM_MUSIC, frequence,
channelInConfig, audioEncoding, bufferSize,
AudioTrack.MODE_STREAM);
3、使用AudioTrack播放语音数据。
//将语音数据写入即可。
track.write(dataArray, buffer, len);
问题一:
由于目前的项目是实时采集,实时发送,所以需要考虑到包的大小,经测试,我们使用160个byte作为一个包传递可以做到比较良好的播放效果(也就是将一份buffer拆分成四个发送)。处理代码如下:
// 将数据通过监听接口回调出去
if (audioRecordingCallback != null) {
int offset = result % MAX_DATA_LENGTH > 0 ? 1 : 0;
//将一个buffer拆分成几份小数据包 MAX_DATA_LENGTH 为包的最大byte数
for (int i = 0; i < result / MAX_DATA_LENGTH + offset; i++) {
int length = MAX_DATA_LENGTH;
if ((i + 1) * MAX_DATA_LENGTH > result) {
length = result - i * MAX_DATA_LENGTH;
}
//写到回调接口
audioRecordingCallback.onRecording(buffer, i
* MAX_DATA_LENGTH, length);
}
}
问题二:
有时候传输的过来播放声音会一卡一卡的,为了解决这样的问题,暂时使用了语音双缓冲机制来解决,问题优化很明显。代码和示意图如下:
【声音采集的源码】
/**
* 实时音频录制处理类
* 记得申明系统权限:MODIFY_AUDIO_SETTINGS、RECORD_AUDIO
* 使用实例代码:
*
*
* audioRecoderHandler = new AudioRecoderHandler(this);
* audioRecoderHandler.startRecord(new AudioRecordingCallback() {
* @Override
* public void onStopRecord(String savedPath) {
*
* }
*
* @Override
* public void onRecording(byte[] data, int startIndex, int length) {
* // TODO 录制监听。处理data即可。立即播放or发送出去,随你。
* }
* });
*
*
* @author 李长军
*
*/
@SuppressWarnings("deprecation")
public class AudioRecoderHandler {
/**
* 录音数据单次回调数组最大为多少
*/
private static int MAX_DATA_LENGTH = 160;
private AudioRecord audioRecord;// 录音对象
private boolean isRecording = false;// 标记是否正在录音中
private int frequence = 8000;// 采样率 8000
private int channelInConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;// 定义采样通道(过时,但是使用其他的又不行
private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;// 定义音频编码(16位)
private byte[] buffer = null;// 录制的缓冲数组
private File lastCacheFile = null;// 记录上次录制的文件名
public CacheCleanManager cacheManager;
public AudioRecoderHandler(Context context) {
if (context == null) {
throw new RuntimeException("Context could not be null!");
}
}
/**
* 开始录制音频
*
* @param callBackListener
* 录制过程中的回调函数
*/
public void startRecord(AudioRecordingCallback audioRecordingCallback) {
RecordTask task = new RecordTask(audioRecordingCallback);
task.execute();// 开始执行
}
/**
* 停止录制
*/
public void stoppRecord() {
isRecording = false;
}
/**
* 删除上次录制的文件(一般是用户取消发送导致删除上次录制的内容)
*
* @return true表示删除成功,false表示删除失败,一般是没有上次录制的文件,或者文件已经被删除了
*/
public boolean deleteLastRecordFile() {
boolean success = false;
if (lastCacheFile != null && lastCacheFile.exists()) {
success = lastCacheFile.delete();
}
return success;
}
/**
* 录制音频的任务类
*
* @author 李长军
*
*/
private class RecordTask extends AsyncTask<String, Integer, String> {
private AudioRecordingCallback audioRecordingCallback = null;
public RecordTask(AudioRecordingCallback audioRecordingCallback) {
this.audioRecordingCallback = audioRecordingCallback;
}
@Override
protected void onPreExecute() {
// 根据定义好的几个配置,来获取合适的缓冲大小
// int bufferSize = 800;
int bufferSize = AudioRecord.getMinBufferSize(frequence,
channelInConfig, audioEncoding);
// 实例化AudioRecord
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
frequence, channelInConfig, audioEncoding, bufferSize);
// 定义缓冲数组
buffer = new byte[bufferSize];
MAX_DATA_LENGTH = bufferSize / 2;
audioRecord.startRecording();// 开始录制
isRecording = true;// 设置录制标记为true
}
@Override
protected void onPostExecute(String result) {
audioRecord = null;
if (result == null) {
lastCacheFile = null;
} else {
lastCacheFile = new File(result);
}
if (audioRecordingCallback != null) {
audioRecordingCallback.onStopRecord(result);
}
}
@Override
protected String doInBackground(String... params) {
String tempFileName = null;
// 开始录制
while (isRecording) {
// 录制的内容放置到了buffer中,result代表存储长度
int result = audioRecord.read(buffer, 0, buffer.length);
// 将数据回调出去
if (audioRecordingCallback != null) {
int offset = result % MAX_DATA_LENGTH > 0 ? 1 : 0;
for (int i = 0; i < result / MAX_DATA_LENGTH + offset; i++) {
int length = MAX_DATA_LENGTH;
if ((i + 1) * MAX_DATA_LENGTH > result) {
length = result - i * MAX_DATA_LENGTH;
}
audioRecordingCallback.onRecording(buffer, i
* MAX_DATA_LENGTH, length);
}
}
}
if (audioRecord != null) {
audioRecord.stop();
}
return tempFileName;
}
}
/**
* 监听录制过程,用于实时获取录音数据
*
* @author 李长军
*
*/
public static interface AudioRecordingCallback {
/**
* 录音数据获取回调
*
* @param data
* 数据数组对象
* @param startIndex
* 数据其开始
* @param length
* 数据的结尾
*/
public void onRecording(byte[] data, int startIndex, int length);
/**
* 录音结束后的回调
*
* @param savedPath
* 录音文件存储的路径
*/
public void onStopRecord(String savedPath);
}
/**
* 释放资源
*/
public void release() {
if (audioRecord != null) {
audioRecord.release();
audioRecord = null;
}
}
}
【声音播放的源码】
/**
* 实时音频播放处理类
* 使用示例代码如下:
*
*
* audioPlayerHandler = new AudioPlayerHandler();
* audioPlayerHandler.prepare();// 播放前需要prepare。可以重复prepare
* // 直接将需要播放的数据传入即可
* audioPlayerHandler.onPlaying(data, 0, data.length);
*
*
* @author 李长军
*
*/
@SuppressWarnings("deprecation")
public class AudioPlayerHandler implements Runnable {
private AudioTrack track = null;// 录音文件播放对象
private boolean isPlaying = false;// 标记是否正在录音中
private int frequence = 8000;// 采样率 8000
private int channelInConfig = AudioFormat.CHANNEL_OUT_MONO;// 定义采样为双声道(过时,但是使用其他的又不行
private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;// 定义音频编码(16位)
private int bufferSize = -1;// 播放缓冲大小
private LinkedBlockingDeque