前言
总听前辈们说,写博客是利人利己的好事,这总好事,必然要试试。但是头一次写博客,真不知道如何下手,就当练手了。
因为最近项目当中用到了录音的功能,所以就Android 录音来试试。
Android 小白 ,大神请多指教。
参考链接
https://blog.youkuaiyun.com/hesong1120/article/details/79043482
https://www.cnblogs.com/MMLoveMeMM/articles/3444718.html
https://www.jianshu.com/p/90c77197f1d4
https://www.jianshu.com/p/a72deab95b4c
简介
Android提供了两个API用于实现录音功能
一个是AudioRecord 另一个是MedioRecorder
一 AudioRecord
AudioRecord 输出的是PCM语音数据,是无法直接播放的,必须进行编码和压缩才能够播放。可以将PCM语音数据前拼接头文件,转为wav格式进行播放。
二 MedioRecorder
其实是对audioRecord的封装,集成了录音,编码,压缩,获取到的语音数据转换为文件可以直接进行播放
三 WAV和PCM的联系
Android手机要进行音频编辑操作(比如裁剪,插入,合成等),通常都是需要将音频文件解码为WAV格式的音频文件或者PCM文件来进行拼接处理。
PCM(Pulse Code Modulation—-脉码调制录音),PCM录音就是将声音等模拟信号变成数字信号,学过模拟电路和数字电路的人应该比较懂这个,其实就是一个没有压缩的编码方式。
WAV格式是微软公司开发的一种声音文件格式,也叫波形声音文件,是最早的数字音频格式,支持许多压缩算法,支持多种音频位数、采样频率和声道。
AudioRecord实现
audioRecord方式录取音频,获取的时PCM编码的音频流,需要我们在线程中循环的获取音频数据
我们首先定义一部分参数 因为我们项目中使用了科大讯飞的语音识别,所以按照要求我们使用16000的采样率,和单声道
下面上代码
/**
* 音频采样率
*/
public static int SAMPLE_RATE = 16000;
/**
* 单声道
*/
public final static int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
/**
* 16比特
*/
public final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
/**
* 音频录制实例
*/
protected AudioRecord audioRecord;
/**
* 录制线程
*/
private Thread recordThread;
/**
* 输出的文件路径
*/
private String pcmPath;
private String wavPath;
/**
* 缓冲区大小
*/
private int bufferSize = 0;
/**
* 是否正在录制
*/
private boolean isRecording = false;
/**
* 构造方法传递路径
*
* @param context
*/
public AudioRecordRecorder(Context context) {
pcmPath = AudioRecordFilePath.getRawFilePath(context);
wavPath = AudioRecordFilePath.getWavFilePath(context);
}
/**
* 初始化操作
**/
public void initRecorder() {
if (null != audioRecord) {
audioRecord.release();
}
try {
// 获得缓冲区字节大小
bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AUDIO_FORMAT) * 10;
//实例化录制实例
audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, bufferSize);
} catch (Exception e) {
e.printStackTrace();
}
}
我这里将缓冲区设置的比较大,是为了防止最终读取的数据有失真的情况
/**
* 开始录制
**/
public int recordStart() {
if (isRecording) {
return RECORD_STATE.STATE_RECORDING;
} else if (audioRecord != null && audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
try {
recordThread = new Thread(new AudioRecordRunnable());
recordThread.start();
return RECORD_STATE.STATE_SUCCESS;
} catch (Exception e) {
e.printStackTrace();
}
}
return RECORD_STATE.STATE_ERROR;
}
开始录制,我开启了一个线程 目的时为了循环读取数据
/**
* 读取线程
*/
private class AudioRecordRunnable implements Runnable {
@Override
public void run() {
audioRecord.startRecording();
isRecording = true;
writeDateToFile();
copyWaveFile(pcmPath, wavPath);
}
}
/**
* 将录取的音频写入文件 此时的音频并不能播放
*/
private void writeDateToFile() {
FileOutputStream outputStream = null;
BufferedOutputStream bufferedOutputStream = null;
DataOutputStream dataOutputStream = null;
try {
if (!TextUtils.isEmpty(pcmPath)) {
outputStream = new FileOutputStream(pcmPath);
bufferedOutputStream = new BufferedOutputStream(outputStream);
dataOutputStream = new DataOutputStream(bufferedOutputStream);
}
// 此处其实可以使用byte数组来实现 ,这样也能避免为大小端的处理,而我时为了获取声音分贝值回调,偷个懒,使用了short数组,这个看个人
short[] audioBuffer = new short[bufferSize];
while (isRecording && audioRecord != null) {
audioSize = audioRecord.read(audioBuffer, 0, bufferSize);
if (audioSize > 0) {
//写入文件
if (outputStream != null) {
for (int i = 0; i < audioSize; i++) {
//此处一定要要进行一下大小端处理 不然音频会失真
dataOutputStream.writeShort(Short.reverseBytes(audioBuffer[i]));
}
}
//音量大小回调
getMicState(audioBuffer);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
close(outputStream);
outputStream = null;
}
}
/**
* 这里得到可播放的音频文件
*
* @param inFilename
* @param outFilename
*/
private void copyWaveFile(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;
long totalDataLen;
long longSampleRate = SAMPLE_RATE;
int channels = 1;
long byteRate = 16 * SAMPLE_RATE * channels / 8;
byte[] data = new byte[bufferSize];
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();
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
uiPlayer.audioRecordFinish();
AudioRecordFilePath.deleteFile(pcmPath);
long duration = getWavLength(wavPath);
uiPlayer.getWavFilePath(wavPath);
uiPlayer.getWavFileDuration(duration);
}
}
/**
* 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。
* 这个头文件我时上网上搜的一个,如何大家使用不好使,可以自行百度
* @param out
* @param totalAudioLen 音频文件长度
* @param totalDataLen 加入头文件以后数据长度
* @param longSampleRate 采样率
* @param channels 通道数
* @param byteRate 位率
* @throws IOException
*/
private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
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);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 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);
header[32] = (byte) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = 16; // bits per sample
header[35] = 0;
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);
}
@Override
public void recordStop() {
try {
if (audioRecord != null) {
isRecording = false;
try {
if (recordThread != null) {
recordThread.join();
recordThread = null;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//释放资源
recordRelease();
}
} catch (Exception e) {
e.printStackTrace();
uiPlayer.audioRecordFail();
}
}
/**
* 释放资源
*/
private void recordRelease() {
if (audioRecord != null) {
if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
audioRecord.stop();
}
audioRecord.release();
audioRecord = null;
}
}
/**
* 声音长度
*/
private int audioSize;
/**
* 获取音量分贝值
*/
private void getMicState(short[] audioVolume) {
if (audioSize > 0) {
long v = 0;
for (int i = 0; i < audioVolume.length; i++) {
v += audioVolume[i] * audioVolume[i];
}
// 平方和除以数据总长度,得到音量大小。
double mean = v / (double) audioSize;
double volume = 10 * Math.log10(mean);
}
完结
就先写到这里啦,真的是第一次,如果错误,请大家多多指正。也希望能给刚刚使用的道友一些帮助。
这里面参考了很多前辈的文章,就不一一列举了。很感谢前辈们替我们负重前行。