android开发实现语音数据实时采集/播放

盛年不重来,一日难再晨,及时当勉励,岁月不待人。这篇文章主要讲述android开发实现语音数据实时采集/播放相关的知识,希望能为你提供帮助。
最近做的项目是和语音实时采集并发送, 对方实时接收并播放相关, 下面记录下实现的核心代码。
很多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); } }

问题二:
有时候传输的过来播放声音会一卡一卡的, 为了解决这样的问题, 暂时使用了语音双缓冲机制来解决, 问题优化很明显。代码和示意图如下:
android开发实现语音数据实时采集/播放

文章图片

【有朋友说要源码, 那我就贴下】 【声音采集的源码】
/** * 实时音频录制处理类< br/> * 记得申明系统权限: MODIFY_AUDIO_SETTINGS、RECORD_AUDIO< br/> * 使用实例代码: < br/> * * < pre> * audioRecoderHandler = new AudioRecoderHandler(this); * audioRecoderHandler.startRecord(new AudioRecordingCallback() { *& #064; Override *public void onStopRecord(String savedPath) { * *} * *& #064; Override *public void onRecording(byte[] data, int startIndex, int length) { *// TODO 录制监听。处理data即可。立即播放or发送出去, 随你。 *} * }); * < /pre> * * @ 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; }}}

【android开发实现语音数据实时采集/播放】【声音播放的源码】
/** * 实时音频播放处理类< br/> * 使用示例代码如下:< br/> * * < pre> * audioPlayerHandler = new AudioPlayerHandler(); * audioPlayerHandler.prepare(); // 播放前需要prepare。可以重复prepare * // 直接将需要播放的数据传入即可 * audioPlayerHandler.onPlaying(data, 0, data.length); * < /pre> * * @ 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< Object> dataQueue = new LinkedBlockingDeque< > (); // 互斥信号量 private Semaphore semaphore = new Semaphore(1); // 是否释放资源的标志位 private boolean release = false; public AudioPlayerHandler() { // 获取缓冲 大小 bufferSize = AudioTrack.getMinBufferSize(frequence, channelInConfig, audioEncoding); // 实例AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, frequence, channelInConfig, audioEncoding, bufferSize, AudioTrack.MODE_STREAM); track.setStereoVolume(AudioTrack.getMaxVolume(), AudioTrack.getMaxVolume()); try { // 默认需要抢占一个信号量。防止播放进程执行 semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } // 开启播放线程 new Thread(this).start(); }/** * 播放, 当有新数据传入时, * * @ param data *语音byte数组 * @ param startIndex *开始的偏移量 * @ param length *数据长度 */ public synchronized void onPlaying(byte[] data, int startIndex, int length) { if (AudioTrack.ERROR_BAD_VALUE = = bufferSize) {// 初始化错误 return; } try { dataQueue.putLast(data); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } }/** * 准备播放 */ public void prepare() { if (track != null & & !isPlaying) { track.play(); isPlaying = true; }}/** * 停止播放 */ public void stop() { if (track != null) { track.stop(); isPlaying = false; } }/** * 释放资源 */ public void release() { release = true; semaphore.release(); if (track != null) { track.release(); track = null; } }@ Override public void run() { while (true) { if (release) { return; } if (dataQueue.size() > 0) { byte[] data = (byte[]) dataQueue.pollFirst(); track.write(data, 0, data.length); } else { try { semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }


    推荐阅读