盛年不重来,一日难再晨,及时当勉励,岁月不待人。这篇文章主要讲述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);
}
}
问题二:
有时候传输的过来播放声音会一卡一卡的, 为了解决这样的问题, 暂时使用了语音双缓冲机制来解决, 问题优化很明显。代码和示意图如下:
文章图片
【有朋友说要源码, 那我就贴下】 【声音采集的源码】
/**
* 实时音频录制处理类<
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();
}
}
}
}
}
推荐阅读
- Android中activity传值的两种方式
- 如何使用CSS创建波浪球效果()
- PHP如何使用cal_days_in_month()函数(代码示例)
- TCS Codevita 7 2018面试经验分享和借鉴
- C++标准模板库(STL)中如何使用排序算法()
- 操作系统中的资源分配图(RAG)详细指南
- jQuery如何使用slideToggle()方法(代码示例)
- ER图中的递归关系简要介绍
- 使用JavaScript获取数组中的第一项和最后一项