android平台上的音频采集一般就两种方式:
1、使用MediaRecorder进行音频采集。
MediaRecorder 是基于 AudioRecorder 的 API(最终还是会创建AudioRecord用来与AudioFlinger进行交互) ,它可以直接将采集到的音频数据转化为执行的编码格式,并保存。这种方案相较于调用系统内置的用用程序,便于开发者在UI界面上布局,而且系统封装的很好,便于使用,唯一的缺点是使用它录下来的音频是经过编码的,没有办法的得到原始的音频。同时MediaRecorder即可用于音频的捕获也可以用于视频的捕获相当的强大。实际开发中没有特殊需求的话,用的是比较多的!
2、使用AudioRecord进行音频采集
AudioRecord 是一个比较偏底层的API,它可以获取到一帧帧PCM数据,之后可以对这些数据进行处理。AudioRecord这种方式采集最为灵活,使开发者最大限度的处理采集的音频,同时它捕获到的音频是原始音频PCM格式的!像做变声处理的需要就必须要用它收集音频。
3、实例化AudioRecord
audioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,
参数介绍
// audioResource:音频采集的来源
// audioSampleRate:音频采样率
// channelConfig:声道
// audioFormat:音频采样精度,指定采样的数据的格式和每次采样的大小。
// bufferSizeInBytes:AudioRecord 采集到的音频数据所存放的缓冲区大小。获取最小的缓冲区大小,用于存放
AudioRecorderManager .class
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Environment;
import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.Executors;
public class AudioRecorderManager {
// audioResource:音频采集的来源
// audioSampleRate:音频采样率
// channelConfig:声道
// audioFormat:音频采样精度,指定采样的数据的格式和每次采样的大小。
// bufferSizeInBytes:AudioRecord 采集到的音频数据所存放的缓冲区大小。获取最小的缓冲区大小,用于存放AudioRecord采集到的音频数据。
private boolean isRecording ;
//指的是麦克风
private int audioSource = MediaRecorder.AudioSource.MIC;
//指定采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
private int sampleRateInHz = 441000;
//指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量 (单声道CHANNEL_IN_MONO就是一个喇叭 ,双声道CHANNEL_IN_STEREO两个喇叭 )
private int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
//指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制,它实际上是原始音频样本。
//因此可以设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实。
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。
//特别注意:这个大小不能随便设置,AudioRecord 提供对应的 API 来获取这个值。
private int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
/**/
private AudioRecord audioRecord ;
private static AudioRecorderManager manager ;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static AudioRecorderManager newInstance(){
if (null == manager){
synchronized (AudioRecorderManager.class){
if (null == manager){
manager = new AudioRecorderManager() ;
}
}
}
return manager ;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private AudioRecorderManager(){
/*实例化音频捕获的实例*/
audioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,bufferSizeInBytes);
initPlayRecord();
}
/**
* 开始录音
*/
public void startRecording(){
/**
* 还没准备
* AudioRecord.STATE_UNINITIALIZED ;
*
* 准备完成
* AudioRecord.STATE_INITIALIZED
*
* 开始采集之后,状态自动变为
* AudioRecord.RECORDSTATE_RECORDING
*
* 停止采集时调用mAudioRecord.stop()停止录音。
* AudioRecord.RECORDSTATE_STOPPED
*
*/
if (AudioRecord.ERROR_BAD_VALUE == bufferSizeInBytes || AudioRecord.ERROR == bufferSizeInBytes) {
throw new RuntimeException("Unable to getMinBufferSize");
}
//bufferSizeInBytes is available...
//或者检测AudioRecord是否确保了获得适当的硬件资源。
if (audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
throw new RuntimeException("The AudioRecord is not uninitialized");
}
if (audioRecord.getState()==AudioRecord.STATE_INITIALIZED){
new Thread(new RecorderRunnable()).start();
}
}
public class RecorderRunnable implements Runnable{
@Override
public void run() {
isRecording = true ;
try {
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath();
File file=new File(rootPath,System.currentTimeMillis()+".pcm");
if (!file.exists()){
file.createNewFile();
}
/*开始录音*/
audioRecord.startRecording();
byte[] bytes = new byte[bufferSizeInBytes];
/*缓存输入流*/
BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(file));
DataOutputStream outputStream=new DataOutputStream(bufferedOutputStream);
while (isRecording && audioRecord.getState() == AudioRecord.RECORDSTATE_RECORDING){
if (!isPause){//录音开关
/*如果处于录音状态 那么不断的读取录音结果*/
int bufferReadResult= audioRecord.read(bytes, 0 ,bufferSizeInBytes);
for (int i = 0; i < bufferReadResult; i++) {
outputStream.write(bytes[i]);
}
}
}
/*转换成wav*/
PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(sampleRateInHz,channelConfig,audioFormat);
/*转换后文件*/
File outFile=new File(rootPath,System.currentTimeMillis()+".wav");
pcmToWavUtil.pcmToWav(file.getAbsolutePath(),outFile.getAbsolutePath());
/*播放文件地址*/
playFilePath = outFile.getAbsolutePath() ;
bufferedOutputStream.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 是否暂停开关
*/
private boolean isPause;
/**
* 暂停
*/
public void pause(){
isPause = true ;
}
/**
* 恢复
*/
public void resume(){
isPause = false ;
}
/**
* 停止录音
*/
public void stopRecord() {
isRecording = false;
//停止录音,回收AudioRecord对象,释放内存
if (audioRecord != null) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
}
AudioTrack mAudioTrack;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void initPlayRecord(){
AudioFormat audioF=new AudioFormat.Builder()
.setSampleRate(sampleRateInHz)
.setEncoding(audioFormat)
.setChannelMask(channelConfig)
.build();
AudioAttributes mAudioAttributes=new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
mAudioTrack=new AudioTrack(mAudioAttributes,audioF,bufferSizeInBytes, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE);
}
/**
* 播放录音
*/
String playFilePath ;
public void playRecord(Context context){
if (TextUtils.isEmpty(playFilePath)){
Toast.makeText(context,"播放音频文件不存在!",Toast.LENGTH_LONG).show();
return;
}
mAudioTrack.play();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
FileInputStream fileInputStream=new FileInputStream(playFilePath);
byte[] tempBuffer=new byte[bufferSizeInBytes];
while (fileInputStream.available()>0){
int readCount= fileInputStream.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION||readCount==AudioTrack.ERROR_BAD_VALUE){
continue;
}
if (readCount!=0&&readCount!=-1){
mAudioTrack.write(tempBuffer,0,readCount);
}
}
Log.e("TAG","end");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
给音频文件添加头部信息,并且转换格式成wav
音频录制完成后,这个时候去存储目录找到音频文件部分,会提示无法播放文件.其实是因为没有加入音频头部信息.一般通过麦克风采集的录音数据都是PCM格式的,即不包含头部信息,播放器无法知道音频采样率、位宽等参数,导致无法播放,显然是非常不方便的。pcm转换成wav,我们只需要在pcm的文件起始位置加上至少44个字节的WAV头信息即可
偏移地址 命名 内容
00-03 ChunkId "RIFF"
04-07 ChunkSize 下个地址开始到文件尾的总字节数(此Chunk的数据大小)
08-11 fccType "WAVE"
12-15 SubChunkId1 "fmt ",最后一位空格。
16-19 SubChunkSize1 一般为16,表示fmt Chunk的数据块大小为16字节
20-21 FormatTag 1:表示是PCM 编码
22-23 Channels 声道数,单声道为1,双声道为2
24-27 SamplesPerSec 采样率
28-31 BytesPerSec 码率 :采样率 * 采样位数 * 声道个数,bytePerSecond = sampleRate * (bitsPerSample / 8) * channels
32-33 BlockAlign 每次采样的大小:位宽*声道数/8
34-35 BitsPerSample 位宽
36-39 SubChunkId2 "data"
40-43 SubChunkSize2 音频数据的长度
44-... data 音频数据
写入头部信息的工具类
PcmToWavUtil .class
public class PcmToWavUtil {
private static final String TAG = "PcmToWavUtil";
/**
* 缓存的音频大小
*/
private int mBufferSize;
/**
* 采样率
*/
private int mSampleRate;
/**
* 声道数
*/
private int mChannel;
/**
* @param sampleRate sample rate、采样率
* @param channel channel、声道
* @param encoding Audio data format、音频格式
*/
PcmToWavUtil(int sampleRate, int channel, int encoding) {
this.mSampleRate = sampleRate;
this.mChannel = channel;
this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
}
/**
* pcm文件转wav文件
*
* @param inFilename 源文件路径
* @param outFilename 目标文件路径
*/
public void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;//总录音长度
long totalDataLen;//总数据长度
long longSampleRate = mSampleRate;
int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
long byteRate = 16 * mSampleRate * channels / 8;
byte[] data = new byte[mBufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
out.flush();
}
Log.e(TAG, "pcmToWav: 停止处理");
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加入wav文件头
*/
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
//WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// format = 1
header[20] = 1;
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// block align
header[32] = (byte) (2 * 16 / 8);
header[33] = 0;
// bits per sample
header[34] = 16;
header[35] = 0;
//data
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}
来源:CSDN
作者:MrJiangBao
链接:https://blog.csdn.net/u011932309/article/details/103852669