监听器
监听录音开始,录音结束,音量改变的监听器。
public interface AudioRecorderListener {
void onRecordStart();
void onVolumeChanged(double volume);
void onRecordFinished(String path,long length);
}
音频录制管理器
可以指定配置音频格式,麦克风,采样率声道等相关格式。并对原始格式进行相应转换,同时还可以回调音频音量大小
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
import androidx.annotation.RequiresApi;
import com.koolearn.ieltspro2019.utils.function.PreferencesUtil;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 科大讯飞需要的特殊格式音频管理类
* 音量获取相关
* https://www.cnblogs.com/renhui/p/11704635.html
*
* 音频录制相关
* https://blog.csdn.net/u011932309/article/details/103852669
*/
public class AudioRecorderManager {
private boolean isRecording ;
//指的是麦克风
private int audioSource = MediaRecorder.AudioSource.MIC;
//指定采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
private int sampleRateInHz = 16000;
//指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量 (单声道CHANNEL_IN_MONO就是一个喇叭 ,双声道CHANNEL_IN_STEREO两个喇叭 )
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
//指定音频量化位数 ,在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 ;
private AudioRecorderListener listener;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static AudioRecorderManager getInstance(){
if (null == manager){
synchronized (AudioRecorderManager.class){
if (null == manager){
manager = new AudioRecorderManager() ;
}
}
}
return manager ;
}
private AudioRecorderManager(){
/*实例化音频捕获的实例*/
audioRecord = initAudioRecord();
// initPlayRecord();
}
private AudioRecord initAudioRecord(){
if(audioRecord==null){
audioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,bufferSizeInBytes);
}
return audioRecord;
}
public void setListener(AudioRecorderListener listener) {
this.listener = listener;
}
/**
* 开始录音
*/
public void startRecording(){
/**
* 还没准备
* AudioRecord.STATE_UNINITIALIZED ;
*
* 准备完成
* AudioRecord.STATE_INITIALIZED
*
* 开始采集之后,状态自动变为
* AudioRecord.RECORDSTATE_RECORDING
*
* 停止采集时调用mAudioRecord.stop()停止录音。
* AudioRecord.RECORDSTATE_STOPPED
*
*/
initAudioRecord();
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(listener)).start();
}
}
public class RecorderRunnable implements Runnable{
private AudioRecorderListener listener;
public RecorderRunnable(AudioRecorderListener listener) {
this.listener = listener;
}
@Override
public void run() {
isRecording = true ;
try {
String rootPath = PreferencesUtil.getDownLoadRootPath();
File file=new File(rootPath,"kypl-record.pcm");
if (file.exists()){
file.delete();
}else{
file.createNewFile();
}
/*开始录音*/
audioRecord.startRecording();
listener.onRecordStart();
byte[] buffer = new byte[bufferSizeInBytes];
/*缓存输入流*/
BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(file));
DataOutputStream outputStream=new DataOutputStream(bufferedOutputStream);
long start=System.currentTimeMillis();
while (isRecording && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){
if (!isPause){//录音开关
/*如果处于录音状态 那么不断的读取录音结果*/
int bufferReadResult= audioRecord.read(buffer, 0 ,bufferSizeInBytes);
for (int i = 0; i < bufferReadResult; i++) {
outputStream.write(buffer[i]);
}
short[] shorts= byteArray2ShortArray(buffer);
long v = 0;
// 将 buffer 内容取出。进行平方和运算
for (int i = 0; i < shorts.length; i++) {
v += shorts[i] * shorts[i];
}
// 平方和除以数据总长度,得到音量大小。
double mean = v / (double) bufferReadResult;
double volume = 10 * Math.log10(mean);
listener.onVolumeChanged(volume);
Log.d("--->", "分贝值 = " + volume + "dB");
}
}
long length = System.currentTimeMillis()-start;
/*转换成wav*/
PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(sampleRateInHz,channelConfig,audioFormat);
/*转换后文件*/
File outFile=new File(rootPath,"kypl-record.wav");
if (outFile.exists()){
outFile.delete();
}
pcmToWavUtil.pcmToWav(file.getAbsolutePath(),outFile.getAbsolutePath());
Log.d("--->", "转换后文件地址 = " + outFile.getAbsolutePath());
// /*播放文件地址*/
// String playFilePath = outFile.getAbsolutePath() ;
bufferedOutputStream.close();
outputStream.close();
listener.onRecordFinished(outFile.getAbsolutePath(),length);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// byte数组转short数组
public static short[] byteArray2ShortArray(byte[] data) {
short[] retVal = new short[data.length/2];
for (int i = 0; i < retVal.length; i++) {
retVal[i] = (short) ((data[i * 2] & 0xff) | (data[i * 2 + 1] & 0xff) << 8);
}
return retVal;
}
/**
* 是否暂停开关
*/
private boolean isPause;
/**
* 暂停
*/
public void pause(){
isPause = true ;
}
/**
* 恢复
*/
public void resume(){
isPause = false ;
}
/**
* 停止录音
*/
public void stopRecord() {
isRecording = false;
//停止录音,回收AudioRecord对象,释放内存
if (audioRecord != null) {
try{//此处捕捉关闭时的异常。
audioRecord.stop();
audioRecord.release();
}catch (Exception e){
e.printStackTrace();
}finally {
audioRecord = null;
}
}
}
}
PCM转Wav格式工具类转换器
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.util.Log;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
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);
}
}