Android提供了两个API用于录音的实现:MediaRecorder
和AudioRecord
。
MediaRecorder
:录制的音频文件是经过压缩后的,需要设置编码器。并且录制的音频文件可以用系统自带的Music播放器播放。MediaRecorder
已经集成了录音、编码、压缩等,并支持少量的录音音频格式,但是这也是他的缺点,支持的格式过少并且无法实时处理音频数据。AudioRecord
:主要实现对音频实时处理以及边录边播功能,相对MediaRecorder
比较专业,输出是PCM
语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩。MediaRecorder
因为已经集成了录音、编码、压缩等功能,所以使用起来相对比较简单。
MediaRecorder
使用起来相对简单,音频编码可以根据自己实际需要自己设定,文件名防止重复,使用了日期时分秒的结构,audioSaveDir
是文件存储目录,可自行设定。
public void startRecord() {
// 开始录音
/* ①Initial:实例化MediaRecorder对象 */
if (mMediaRecorder == null)
mMediaRecorder = new MediaRecorder();
try {
/* ②setAudioSource/setVedioSource */
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
/*
* ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
* ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
*/
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
/* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
fileName = DateFormat.format("yyyyMMdd_HHmmss", Calendar.getInstance(Locale.CHINA)) + ".m4a";
if (!FileUtils.isFolderExist(FileUtils.getFolderName(audioSaveDir))) {
FileUtils.makeFolders(audioSaveDir);
}
filePath = audioSaveDir + fileName;
/* ③准备 */
mMediaRecorder.setOutputFile(filePath);
mMediaRecorder.prepare();
/* ④开始 */
mMediaRecorder.start();
} catch (IllegalStateException e) {
LogUtil.i("call startAmr(File mRecAudioFile) failed!" + e.getMessage());
} catch (IOException e) {
LogUtil.i("call startAmr(File mRecAudioFile) failed!" + e.getMessage());
}
}
MediaRecorder使用时需实例化,所以在不用时一定要即时释放,以免造成内存泄漏。
public void stopRecord() {
try {
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
filePath = "";
} catch (RuntimeException e) {
LogUtil.e(e.toString());
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
File file = new File(filePath);
if (file.exists())
file.delete();
filePath = "";
}
}
MediaRecorder
实现录音还是比较简单的,代码量相对较少,较为简明,但是有不足之处,比如输出文件格式选择较少,录音过程不能暂停等。
AndioRecord
类的主要功能是让各种 Java 应用能够管理音频资源,以便它们通过此类能够录制平台的声音输入硬件所收集的声音。此功能的实现就是通过 “pulling 同步”(reading读取)AudioRecord 对象的声音数据来完成的。在录音过程中,应用所需要做的就是通过后面三个类方法中的一个去及时地获取 AudioRecord 对象的录音数据。 AudioRecord 类提供的三个获取声音数据的方法分别是 read(byte[], int, int), read(short[], int, int), read(ByteBuffer, int)
。无论选择使用那一个方法都必须事先设定方便用户的声音数据的存储格式。 开始录音的时候,一个 AudioRecord
需要初始化一个相关联的声音buffer
,这个 buffer
主要是用来保存新的声音数据。这个 buffer
的大小,我们可以在对象构造期间去指定。它表明一个 AudioRecord
对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化 buffer
容量的数据。 采集工作很简单,我们只需要构造一个AudioRecord
对象,然后传入各种不同配置的参数即可。
AudioRecord.getMinBufferSize
获取最小的缓冲区。(将音频采集到缓冲区中然后再从缓冲区中读取)。public class AudioRecorder {
private static AudioRecorder audioRecorder;
// 音频源:音频输入-麦克风
private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;
// 采样率
// 44100是目前的标准,但是某些设备仍然支持22050,16000,11025
// 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
private final static int AUDIO_SAMPLE_RATE = 16000;
// 音频通道 单声道
private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO;
// 音频格式:PCM编码
private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
// 缓冲区大小:缓冲区字节大小
private int bufferSizeInBytes = 0;
// 录音对象
private AudioRecord audioRecord;
// 录音状态
private Status status = Status.STATUS_NO_READY;
// 文件名
private String fileName;
// 录音文件集合
private List<String> filesName = new ArrayList<>();
private AudioRecorder() {
}
//单例模式
public static AudioRecorder getInstance() {
if (audioRecorder == null) {
audioRecorder = new AudioRecorder();
}
return audioRecorder;
}
/**
* 创建录音对象
*/
public void createAudio(String fileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
// 获得缓冲区字节大小
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
channelConfig, audioFormat);
audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
this.fileName = fileName;
}
/**
* 创建默认的录音对象
* @param fileName 文件名
*/
public void createDefaultAudio(String fileName) {
mContext = ctx;
mHandler = handler;
// 获得缓冲区字节大小
bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,
AUDIO_CHANNEL, AUDIO_ENCODING);
audioRecord = new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);
this.fileName = fileName;
status = Status.STATUS_READY;
}
/**
* 开始录音
* @param listener 音频流的监听
*/
public void startRecord(final RecordStreamListener listener) {
if (status == Status.STATUS_NO_READY || TextUtils.isEmpty(fileName)) {
throw new IllegalStateException("录音尚未初始化,请检查是否禁止了录音权限~");
}
if (status == Status.STATUS_START) {
throw new IllegalStateException("正在录音");
}
Log.d("AudioRecorder","===startRecord==="+audioRecord.getState());
audioRecord.startRecording();
new Thread(new Runnable() {
@Override
public void run() {
writeDataTOFile(listener);
}
}).start();
}
/**
* 停止录音
*/
public void stopRecord() {
Log.d("AudioRecorder","===stopRecord===");
if (status == Status.STATUS_NO_READY || status == Status.STATUS_READY) {
throw new IllegalStateException("录音尚未开始");
} else {
audioRecord.stop();
status = Status.STATUS_STOP;
release();
}
}
/**
* 取消录音
*/
public void canel() {
filesName.clear();
fileName = null;
if (audioRecord != null) {
audioRecord.release();
audioRecord = null;
}
status = Status.STATUS_NO_READY;
}
/**
* 释放资源
*/
public void release() {
Log.d("AudioRecorder","===release===");
//假如有暂停录音
try {
if (filesName.size() > 0) {
List<String> filePaths = new ArrayList<>();
for (String fileName : filesName) {
filePaths.add(FileUtils.getPcmFileAbsolutePath(fileName));
}
//清除
filesName.clear();
//将多个pcm文件转化为wav文件
mergePCMFilesToWAVFile(filePaths);
} else {
//这里由于只要录音过filesName.size都会大于0,没录音时fileName为null
//会报空指针 NullPointerException
// 将单个pcm文件转化为wav文件
//Log.d("AudioRecorder", "=====makePCMFileToWAVFile======");
//makePCMFileToWAVFile();
}
} catch (IllegalStateException e) {
throw new IllegalStateException(e.getMessage());
}
if (audioRecord != null) {
audioRecord.release();
audioRecord = null;
}
status = Status.STATUS_NO_READY;
}
/**
* 将音频信息写入文件
* @param listener 音频流的监听
*/
private void writeDataTOFile(RecordStreamListener listener) {
// new一个byte数组用来存一些字节数据,大小为缓冲区大小
byte[] audiodata = new byte[bufferSizeInBytes];
FileOutputStream fos = null;
int readsize = 0;
try {
String currentFileName = fileName;
if (status == Status.STATUS_PAUSE) {
//假如是暂停录音 将文件名后面加个数字,防止重名文件内容被覆盖
currentFileName += filesName.size();
}
filesName.add(currentFileName);
File file = new File(FileUtils.getPcmFileAbsolutePath(currentFileName));
if (file.exists()) {
file.delete();
}
fos = new FileOutputStream(file);// 建立一个可存取字节的文件
} catch (IllegalStateException e) {
Log.e("AudioRecorder", e.getMessage());
throw new IllegalStateException(e.getMessage());
} catch (FileNotFoundException e) {
Log.e("AudioRecorder", e.getMessage());
}
//将录音状态设置成正在录音状态
status = Status.STATUS_START;
while (status == Status.STATUS_START) {
readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {
try {
fos.write(audiodata);
if (listener != null) {
//用于拓展业务
listener.recordOfByte(audiodata, 0, audiodata.length);
}
} catch (IOException e) {
Log.e("AudioRecorder", e.getMessage());
}
}
}
try {
if (fos != null) {
fos.close();// 关闭写入流
}
} catch (IOException e) {
Log.e("AudioRecorder", e.getMessage());
}
}
}
AudioRecorder
录音声音数据从音频硬件中被读出,编码格式为 PCM
格式,但 PCM
语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩。下面实现PCM
语音数据转为 WAV
文件。
/**
* 将一个pcm文件转化为wav文件
* @param pcmPath pcm文件路径
* @param destinationPath 目标文件路径(wav)
* @param deletePcmFile 是否删除源文件
* @return
*/
public static boolean makePCMFileToWAVFile(String pcmPath, String destinationPath, boolean deletePcmFile) {
byte buffer[] = null;
int TOTAL_SIZE = 0;
File file = new File(pcmPath);
if (!file.exists()) {
return false;
}
TOTAL_SIZE = (int) file.length();
// 填入参数,比特率等等。这里用的是16位单声道 8000 hz
WaveHeader header = new WaveHeader();
// 长度字段 = 内容的大小(TOTAL_SIZE) +
// 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
header.fileLength = TOTAL_SIZE + (44 - 8);
header.FmtHdrLeth = 16;
header.BitsPerSample = 16;
header.Channels = 2;
header.FormatTag = 0x0001;
header.SamplesPerSec = 8000;
header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);
header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
header.DataHdrLeth = TOTAL_SIZE;
byte[] h = null;
try {
h = header.getHeader();
} catch (IOException e1) {
Log.e("PcmToWav", e1.getMessage());
return false;
}
if (h.length != 44) // WAV标准,头部应该是44字节,如果不是44个字节则不进行转换文件
return false;
// 先删除目标文件
File destfile = new File(destinationPath);
if (destfile.exists())
destfile.delete();
// 合成的pcm文件的数据,写到目标文件
try {
buffer = new byte[1024 * 4]; // Length of All Files, Total Size
InputStream inStream = null;
OutputStream ouStream = null;
ouStream = new BufferedOutputStream(new FileOutputStream(
destinationPath));
ouStream.write(h, 0, h.length);
inStream = new BufferedInputStream(new FileInputStream(file));
int size = inStream.read(buffer);
while (size != -1) {
ouStream.write(buffer);
size = inStream.read(buffer);
}
inStream.close();
ouStream.close();
} catch (FileNotFoundException e) {
Log.e("PcmToWav", e.getMessage());
return false;
} catch (IOException ioe) {
Log.e("PcmToWav", ioe.getMessage());
return false;
}
if (deletePcmFile) {
file.delete();
}
Log.i("PcmToWav", "makePCMFileToWAVFile success!" + new SimpleDateFormat("yyyy-MM-dd hh:mm").format(new Date()));
return true;
}
AudioRecorder
录音相比较 MediaRecorder
使用起来会麻烦一些,但优点也是显而易见的,AudioRecorder
录音时直接操纵硬件获取音频流数据,该过程是实时处理,可以用代码实现各种音频的封装,同时也可实现暂停功能。