版权声明:本文为卫伟学习总结文章,转载请注明出处!
在Android系统中,一般使用AudioRecord或者MediaRecord来采集音频。
AudioRecord:是一个比较偏底层的API,它可以获取到一帧帧PCM数据,之后可以对这些数据进行处理。
MediaRecorder:是基于AudioRecorder的API(最终还是会创建AudioRecord用来与AudioFlinger进行交互),它可以直接将采集到的音频数据转化为执行的编码格式,并保存。
直播技术采用的就是AudioRecorder采集音频数据。
一、基本API
获取最小的缓冲区大小,用于AudioRecord采集到的音频数据。
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
audioRecord构造方法: 根据具体的参数配置,请求硬件资源创建一个可以用于采集音频的AudioRecord对象。
audioSource:音频采集的来源
audioSampleRate:音频采样率
channelConfig: 声道
audioFormat: 音频采样精度,指定采样的数据的格式和每次采样的大小
bufferSizeBytes:AudioRecord采集的音频数据所存放的缓冲区大小。
public Params initAudioDevice() {
int[] sampleRates = {48000, 44100, 22050, 16000, 11025,8000,4000};
for (int sampleRate : sampleRates) {
//编码制式
int audioFormat = mConfig.audioFormat;
// stereo 立体声,
int channelConfig = mConfig.channelConfig;
int buffsize = 2 * AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig,
audioFormat, buffsize);
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
continue;
}
this.buffer = new byte[Math.min(4096, buffsize)];
return new Params(sampleRate,
channelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO ? 2 : 1);
}
return null;
}
开始采集
开始采集之后,状态为RECORDSTATE_RECORDING 。
public void startRecording ()
读取录制内存,将采集到的数据读取到缓冲区
方法调用的返回值的状态码:
情况异常:
- 1.ERROR_INVALID_OPERATION if the object wasn't properly initialized
- 2.ERROR_BAD_VALUE if the parameters don't resolve to valid data and indexes.
情况正常:the number of bytes that were read
public int read (ByteBuffer audioBuffer, int sizeInBytes)
public int read (byte[] audioData, int offsetInBytes, int sizeInBytes)
public int read (short[] audioData, int offsetInShorts, int sizeInShorts)
获取AudioRecord的状态
用于检测AudioRecord是否确保了获取适当的硬件资源。在AudioRecord对象实例化之后调用。
STATE_INITIALIZED 初始完毕
STATE_UNINITIALIZED 未初始化
public int getState ()
返回当前AudioRecord的采集状态
public static final int RECORDSTATE_STOPPED = 1; 停止状态
调用void stop()之后的状态
public static final int RECORDSTATE_RECORDING = 3;正在采集
调用startRecording()之后的状态
public int getRecordingState()
二、AudioRecord采集音频的基本流程
-
权限
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
构造一个AudioRecord对象。
开始采集
读取采集的数据
停止采集
三、Android音频开发之音频采集
构造一个AudioRecord对象
AudioRecord audioRecord = new AudioRecord(audioResource, audioSampleRate, channelConfig, audioFormat, bufferSizeInBytes);
获取bufferSizeLnBytes值
bufferSizeLnBytes是AudioRecord采集的音频数据所存放的缓冲区大小。
注意:这个大小不能随便设置,AudioRecord提供对应的API来获取这个值。
this.bufferSizeInBytes = AudioRecord.getMinBufferSize(audioSampleRate, channelConfig, audioFormat);
通过bufferSizeInBytes返回就可以知道传入给AudioRecord.getMinBufferSize的参数是否支持当前的硬件设备。
if (AudioRecord.ERROR_BAD_VALUE == bufferSizeInBytes || AudioRecord.ERROR == bufferSizeInBytes) {
throw new RuntimeException("Unable to getMinBufferSize");
}
//bufferSizeInBytes is available...
开始采集
-
在开始录音之前,首先要判断一下AudioRecord的状态是否已经初始化完毕了。
//判断AudioRecord的状态是否初始化完毕 //在AudioRecord对象构造完毕之后,就处于AudioRecord.STATE_INITIALIZED状态了。 int state = audioRecord.getState(); if (state == AudioRecord.STATE_UNINITIALIZED) { throw new RuntimeException("AudioRecord STATE_UNINITIALIZED"); }
-
开始采集
audioRecord.startRecording(); //开启线程读取数据 new Thread(recordTask).start();
读取采集的数据
上面提到,AudioRecord在采集数据时会将数据存放到缓冲区中,因此我们只需要创建一个数据流去从缓冲区中将采集的数据读取出来即可。
创建一个数据流,一边从AudioRecord中读取音频数据到缓冲区,一边将缓冲区中数据写入到数据流。
因为需要使用IO操作,因此读取数据的过程应该在子线程中执行
//创建一个流,存放从AudioRecord读取的数据
File saveFile = new File(Environment.getExternalStorageDirectory(), "audio-record.pcm");
DataOutputStream dataOutputStream = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(saveFile)));
private Runnable recordTask = new Runnable() {
@Override
public void run() {
//设置线程的优先级
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIOR
Log.i(TAG, "设置采集音频线程优先级");
final byte[] data = new byte[bufferSizeInBytes];
//标记为开始采集状态
isRecording = true;
Log.i(TAG, "设置当前当前状态为采集状态");
//getRecordingState获取当前AudioReroding是否正在采集数据的状态
while (isRecording && audioRecord.getRecordingState() == AudioRecord
//读取采集数据到缓冲区中,read就是读取到的数据量
final int read = audioRecord.read(data, 0, bufferSizeInBytes);
if (AudioRecord.ERROR_INVALID_OPERATION != read && AudioRecord.E
//将数据写入到文件中
dataOutputStream.write(buffer,0,read);
}
}
}
};
-
停止采集
/** * 停止录音 */ public void stopRecord() throws IOException { Log.i(TAG, "停止录音,回收AudioRecord对象,释放内存"); isRecording = false; if (audioRecord != null) { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); Log.i(TAG, "audioRecord.stop()"); } if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) { audioRecord.release(); Log.i(TAG, "audioRecord.release()"); } }
AudioRecord采集PCM音频原始数据完整功能实现代码:
package com.weiwei.mediarecordertest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
/**
* AudioRecord 录音采集
* AudioTrack 实现录音播放
* @author 60116
*
*/
public class StramActivity extends Activity{
private Button bt_stream_recorder;
private TextView tv_stream_msg;
private ExecutorService mExecutorService;
private long startRecorderTime, stopRecorderTime;
private volatile boolean mIsRecording = false;
private AudioRecord mAudioRecord;
private FileOutputStream mFileOutputStream;
private File mAudioRecordFile;
private byte[] mBuffer;
//buffer 值不能太大 避免OOM
private static int BUFFER_SIZE = 2048;
private boolean mIsPlaying = false;
private Handler mHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("字节流录音");
initView();
mExecutorService = Executors.newSingleThreadExecutor();
mBuffer = new byte[BUFFER_SIZE];
}
private void initView() {
bt_stream_recorder = (Button) findViewById(R.id.bt_stream_recorder);
tv_stream_msg = (TextView) findViewById(R.id.tv_stream_msg);
}
public void recorderaudio(View view) {
if(mIsRecording) {
bt_stream_recorder.setText("开始录音");
//在开始录音中如果这个值没有变false,则一直进行,当再次点击变false时,录音才停止
mIsRecording = false;
} else {
bt_stream_recorder.setText("停止录音");
//提交后台任务 执行录音逻辑
mIsRecording = true;
//提交后台任务,执行录音逻辑
mExecutorService.submit(new Runnable() {
@Override
public void run() {
startRecorder();
}
});
}
}
/**
* 开始录音
*/
private void startRecorder() {
//realeseRecorder();
if(!dostart()) recorderFail();
}
/**
* 停止录音
*/
private void stopRecorder() {
mIsRecording=false;
if (!doStop()) recorderFail();
}
private boolean dostart() {
try {
// 记录开始录音时间
startRecorderTime = System.currentTimeMillis();
//创建录音文件
mAudioRecordFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
"/recorderdemo/" + System.currentTimeMillis() + ".pcm");
if (!mAudioRecordFile.getParentFile().exists())
mAudioRecordFile.getParentFile().mkdirs();
// 创建文件输出流
mFileOutputStream = new FileOutputStream(mAudioRecordFile);
// 配置AudioRecord
int audioSource = MediaRecorder.AudioSource.MIC;
//所有支持android系统都支持
int sampleRate = 44100;
// 单声道输入
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
// pcm_16时所有android系统都支持的
int autioFormat = AudioFormat.ENCODING_PCM_16BIT;
//计算AudioRecord内部buffer最小
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, autioFormat);
//buffer不能小于最低要求,也不能小于我们每次我们读取的大小。
mAudioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, autioFormat, Math.max(minBufferSize, BUFFER_SIZE));
//开始录音
mAudioRecord.startRecording();
//循环读取数据,写入输出流中
while (mIsRecording) {
//只要还在录音就一直读取
int read = mAudioRecord.read(mBuffer, 0, BUFFER_SIZE);
if(read <= 0) {
return false;
} else {
Log.i("Tag8","mBuffer =" +mBuffer.toString());
mFileOutputStream.write(mBuffer, 0, read);
}
}
//退出循环,停止录音,释放资源
stopRecorder();
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (mAudioRecord != null) {
mAudioRecord.release();
}
}
return true;
}
private boolean recorderFail() {
mHandler.post(new Runnable() {
@Override
public void run() {
bt_stream_recorder.setText("开始录音");
tv_stream_msg.setText("录取失败,请重新录入");
mIsRecording = false;
Log.i("Tag8", "go here111111111");
}
});
return false;
}
private void realeseRecorder() {
mAudioRecord.release();
}
private boolean doStop() {
//停止录音,关闭文件输出流
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
Log.i("Tag8", "go here");
//记录结束时间,统计录音时长
stopRecorderTime = System.currentTimeMillis();
//大于3秒算成功,在主线程更新UI
final int send = (int) (stopRecorderTime - startRecorderTime) / 1000;
if (send > 3) {
mHandler.post(new Runnable() {
@Override
public void run() {
tv_stream_msg.setText("录音成功:" + send + "秒");
bt_stream_recorder.setText("开始录音");
Log.i("Tag8", "go there");
}
});
} else {
recorderFail();
return false;
}
return true;
}
/**
* 播放声音
* @param view
*/
public void player(View view) {
mExecutorService.submit(new Runnable() {
@Override
public void run() {
if (!mIsPlaying) {
Log.i("Tag8", "go here");
mIsPlaying = true;
Log.i("Tag8","mAudioRecordFile =" +mAudioRecordFile.toString());
doPlay(mAudioRecordFile);
}
}
});
}
private void doPlay(File audioFile) {
if(audioFile != null) {
Log.i("Tag8","go there");
//配置播放器
//音乐类型,扬声器播放
int streamType = AudioManager.STREAM_MUSIC;
//录音时采用的采样频率,所有播放时同样的采样频率
int sampleRate = 44100;
//单声道,和录音时设置的一样
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
// 录音使用16bit,所有播放时同样采用该方式
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//流模式
int mode = AudioTrack.MODE_STREAM;
//计算最小buffer大小
int minBufferSize=AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
//构造AudioTrack 不能小于AudioTrack的最低要求,也不能小于我们每次读的大小
AudioTrack audioTrack=new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
Math.max(minBufferSize,BUFFER_SIZE),mode);
audioTrack.setVolume(16f);
//从文件流读数据
FileInputStream inputStream = null;
try {
//循环读数据,写到播放器去播放
inputStream = new FileInputStream(audioFile);
//循环读数据,写到播放器去播放
int read;
//只要没读完,循环播放
while((read=inputStream.read(mBuffer)) > 0) {
Log.i("Tag8","read:"+read);
int ret = audioTrack.write(mBuffer, 0, read);
Log.i("Tag8","ret ==="+ret);
//检查write的返回值,处理错误
switch(ret) {
case AudioTrack.ERROR_INVALID_OPERATION:
case AudioTrack.ERROR_BAD_VALUE:
case AudioManager.ERROR_DEAD_OBJECT:
playFail();
return;
default:
break;
}
}
audioTrack.play();
Log.i("Tag8","播放成功。。。。");
} catch (Exception e) {
e.printStackTrace();
Log.i("Tag8","e =" +e.toString());
//读取失败
playFail();
} finally {
mIsPlaying = false;
Log.i("Tag8","播放完毕");
//关闭文件输入流
if(inputStream !=null){
closeStream(inputStream);
}
//播放器释放
resetQuietly(audioTrack);
}
}
}
private void closeStream(FileInputStream inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void resetQuietly(AudioTrack audioTrack) {
try {
audioTrack.stop();
audioTrack.release();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 播放失败
*/
private void playFail() {
mHandler.post(new Runnable() {
@Override
public void run() {
tv_stream_msg.setText("播放失败");
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mExecutorService != null) {
mExecutorService.shutdownNow();
}
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
获取到的PCM格式的音频文件
- 采集数据之后,保存的文件为audio-record.pcm,这个文件并不能使用普通的播放器播放。它是一个原始的文件,没有任何播放格式,因此就无法被播放器识别并播放。
- 上面的问题可以有两种解决方法
- 使用AudioTrack播放pcm格式的音频数据。
- 将pcm数据转化为wav格式数据,这样就可以被播放器识别啦。