MediaRecorder和AudioRecord都可以录制音频。
区别是MediaRecorder录制的音频文件是经过压缩后的,需要设置编码器。
并且录制的音频文件可以用系统自带的Music播放器播放。
而AudioRecord录制的是PCM格式的音频文件,需要用AudioTrack来播放,AudioTrack更接近底层。
在用MediaRecorder进行录制音视频时,最终还是会创建AudioRecord用来与AudioFlinger进行交互。
已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp
优点:大部分以及集成,直接调用相关接口即可,代码量小
缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件
package com.sinogeo.util;
import java.io.IOException;
import android.media.MediaRecorder;
import android.os.Handler;
import android.util.Log;
/**
* 录音工具类
*/
public class MediaRecoderUtils {
private static MediaRecoderUtils mInstance;
private String filePath;
private MediaRecorder mMediaRecorder;
private final String TAG = "MediaRecord";
public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10;
private OnMediaStatusUpdateListener mediaStatusUpdateListener;
/**需要传入包括文件名的完整文件路径,后缀为.amr*/
public synchronized static MediaRecoderUtils getInstance(String path) {
if (mInstance == null) {
mInstance = new MediaRecoderUtils(path);
}
return mInstance;
}
private MediaRecoderUtils(String path) {
this.filePath = path;
}
private long startTime;
private long endTime;
/**
* 开始录音 使用amr格式 录音文件
*
* @return
*/
public void startRecord() {
// 开始录音
/* ①Initial:实例化MediaRecorder对象 */
if (mMediaRecorder == null)
mMediaRecorder = new MediaRecorder();
try {
/* ②setAudioSource/setVedioSource */
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
/* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
/*
* ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
* ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
*/
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setAudioSamplingRate(16000);
/* ③准备 */
mMediaRecorder.setOutputFile(filePath);
mMediaRecorder.setMaxDuration(MAX_LENGTH);
mMediaRecorder.prepare();
/* ④开始 */
mMediaRecorder.start();
// AudioRecord audioRecord.
/* 获取开始时间* */
startTime = System.currentTimeMillis();
updateMicStatus();
Log.i("ACTION_START", "startTime" + startTime);
} catch (IllegalStateException e) {
Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
} catch (IOException e) {
Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
}
}
/**
* 停止录音
*/
public long stopRecord() {
if (mMediaRecorder == null)
return 0L;
endTime = System.currentTimeMillis();
Log.i("ACTION_END", "endTime" + endTime);
mMediaRecorder.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
Log.i("ACTION_LENGTH", "Time" + (endTime - startTime));
return endTime - startTime;
}
private final Handler mHandler = new Handler();
private Runnable mUpdateMicStatusTimer = new Runnable() {
public void run() {
updateMicStatus();
}
};
/**
* 更新话筒状态
*/
private int BASE = 1;
private int SPACE = 100;// 间隔取样时间
public void setOnMediaStatusUpdateListener(OnMediaStatusUpdateListener mediaStatusUpdateListener) {
this.mediaStatusUpdateListener = mediaStatusUpdateListener;
}
private void updateMicStatus() {
if (mMediaRecorder != null) {
double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE;
double db = 0;// 分贝
if (ratio > 1) {
db = 20 * Math.log10(ratio);
if (null != mediaStatusUpdateListener) {
mediaStatusUpdateListener.onUpdate(db);
}
}
mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
}
}
public interface OnMediaStatusUpdateListener {
public void onUpdate(double db);
}
}
//获取实例,传入保存音频的路径,需注意要传入包括完整文件名的路径,例如/storage/emulated/0/Geoprocessing/yinfu.amr
MediaRecoderUtils recoderUtils = MediaRecoderUtils.getInstance(mPath);
//开始
recoderUtils.startRecord();
//结束
recoderUtils.stopRecord();
//如需监听分贝
recoderUtils.setOnMediaStatusUpdateListener(listener);
主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)
优点:语音的实时处理,可以用代码实现各种音频的封装
缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩
需注意的是讯飞的听写与转写功能,如果你需要上传音频实现转文字功能,上传的也是AudioRecord生成的录音文件。下图截自讯飞demo中对上传音频转文字功能的音频文件限制。
package com.sinogeo.util;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Message;
/**
* @author RenoY3
* @version 创建时间:2018年5月2日 下午3:21:47 类说明:录音为讯飞可转为文字的音频格式文件
*/
public class AudioRecoderUtils {
private static AudioRecoderUtils mInstance;
private AudioRecord recorder;
// 录音源
private static int audioSource = MediaRecorder.AudioSource.MIC;
// 录音的采样频率
private static int audioRate = 16000;
// 录音的声道,单声道
private static int audioChannel = AudioFormat.CHANNEL_IN_MONO;
// 量化的深度
private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
// 缓存的大小
private static int bufferSize = AudioRecord.getMinBufferSize(audioRate, audioChannel, audioFormat);
// 记录播放状态
private boolean isRecording = false;
// 数字信号数组
private byte[] noteArray;
// PCM文件
private File pcmFile;
// WAV文件
private File wavFile;
// 文件输出流
private OutputStream os;
// 文件根目录
private String basePath;
// wav文件目录
private String outFileName;
// pcm文件目录
private String inFileName;
// 分贝监听
private OnAudioStatusUpdateListener audioStatusUpdateListener;
private AudioRecoderUtils(String path) {
basePath = path.replace(path.split("/")[path.split("/").length - 1], "");
outFileName = path;
inFileName = path.replace(".wav", ".pcm");
createFile();// 创建文件
recorder = new AudioRecord(audioSource, audioRate, audioChannel, audioFormat, bufferSize);
}
/** 需要传入包括文件名的完整文件路径,后缀为.wav */
public synchronized static AudioRecoderUtils getInstance(String path) {
if (mInstance == null) {
mInstance = new AudioRecoderUtils(path);
}
return mInstance;
}
// 读取录音数字数据线程
class WriteThread implements Runnable {
public void run() {
writeData();
}
}
// 开始录音
public void startRecord() {
isRecording = true;
recorder.startRecording();
new Thread(new WriteThread()).start();
}
// 停止录音
public void stopRecord() {
isRecording = false;
recorder.stop();
convertWaveFile();
}
// 将数据写入文件夹,文件的写入没有做优化
private void writeData() {
noteArray = new byte[bufferSize];
// 建立文件输出流
try {
os = new BufferedOutputStream(new FileOutputStream(pcmFile));
} catch (IOException e) {
}
while (isRecording == true) {
int recordSize = recorder.read(noteArray, 0, bufferSize);
long v = 0;
for (int i = 0; i < noteArray.length; i++) {
v += noteArray[i] * noteArray[i];
}
// 平方和除以数据总长度,得到音量大小。
double mean = v / (double) recordSize;
// 此为音量值
double volume = 10 * Math.log10(mean);
if (audioStatusUpdateListener != null) {
mHandler.sendEmptyMessage((int)volume);
}
if (recordSize > 0) {
try {
os.write(noteArray);
} catch (IOException e) {
}
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
}
}
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
audioStatusUpdateListener.onUpdate(msg.what);
}
};
// 这里得到可播放的音频文件
private void convertWaveFile() {
FileInputStream in = null;
FileOutputStream out = null;
long totalAudioLen = 0;
long totalDataLen = totalAudioLen + 36;
long longSampleRate = AudioRecoderUtils.audioRate;
int channels = 1;
long byteRate = 16 * AudioRecoderUtils.audioRate * channels / 8;
byte[] data = new byte[bufferSize];
try {
in = new FileInputStream(inFileName);
out = new FileOutputStream(outFileName);
totalAudioLen = in.getChannel().size();
// 由于不包括RIFF和WAV
totalDataLen = totalAudioLen + 36;
WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* 任何一种文件在头部添加相应的头文件才能够确定的表示这种文件的格式,wave是RIFF文件结构,每一部分为一个chunk,其中有RIFF WAVE
* chunk, FMT Chunk,Fact chunk,Data chunk,其中Fact chunk是可以选择的,
*/
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
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';// WAVE
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// FMT Chunk
header[12] = 'f'; // 'fmt '
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;
// 编码方式 10H为PCM编码格式
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);
// 音频数据传送速率,采样率*通道数*采样深度/8
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) (1 * 16 / 8);
header[33] = 0;
// 每个样本的数据位数
header[34] = 16;
header[35] = 0;
// Data chunk
header[36] = 'd';// data
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);
}
// 创建文件夹,首先创建目录,然后创建对应的文件
private void createFile() {
File baseFile = new File(basePath);
if (!baseFile.exists()){
baseFile.mkdirs();
}
pcmFile = new File(inFileName);
wavFile = new File(outFileName);
if (pcmFile.exists()) {
pcmFile.delete();
}
if (wavFile.exists()) {
wavFile.delete();
}
try {
pcmFile.createNewFile();
wavFile.createNewFile();
} catch (IOException e) {
}
}
public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
this.audioStatusUpdateListener = audioStatusUpdateListener;
}
public interface OnAudioStatusUpdateListener {
public void onUpdate(double db);
}
}
调用方法与上面类似,录制完成后会生成.wav和.pcm格式的俩个内容相同的音频文件:
//获取实例,传入保存音频的路径,需注意要传入包括完整文件名的路径,例如/storage/emulated/0/Geoprocessing/yinfu.wav
AudioRecoderUtils recoderUtils = AudioRecoderUtils.getInstance(mPath);
//开始
recoderUtils.startRecord();
//结束
recoderUtils.stopRecord();
//如需监听分贝
recoderUtils.setOnAudioStatusUpdateListener(listener);