Android录音--AudioRecord、MediaRecorder的使用
Android提供了两个API用于实现录音功能:android.media.AudioRecord、android.media.MediaRecorder。
网上有很多谈论这两个类的资料。现在大致总结下:
1、AudioRecord
主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)
优点:语音的实时处理,可以用代码实现各种音频的封装
缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩
示例:
使用AudioRecord类录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右(已写测试代码)
2、MediaRecorder
已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API=16).amr.3gp
优点:大部分以及集成,直接调用相关接口即可,代码量小
缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件
示例:
使用MediaRecorder类录音,输出amr格式文件。录音20s,输出的音频文件大概为33K(已写测试代码)
3、音频格式比较
WAV格式:录音质量高,但是压缩率小,文件大
AAC格式:相对于mp3,AAC格式的音质更佳,文件更小;有损压缩;一般苹果或者AndroidSDK4.1.2(API16)及以上版本支持播放
AMR格式:压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音
至于常用的mp3格式,使用MediaRecorder没有该视频格式输出。一些人的做法是使用AudioRecord录音,然后编码成wav格式,再转换成mp3格式
再贴上一些测试工程。
功能描述:
1、点击“录音WAV文件”,开始录音。录音完成后,生成文件/sdcard/FinalAudio.wav
2、点击“录音AMR文件”,开始录音。录音完成后,生成文件/sdcard/FinalAudio.amr
3、点击“停止录音”,停止录音,并显示录音输出文件以及该文件大小。
大致代码如下:
1、AudioRecord录音,封装成WAV格式
packagecom.example.audiorecordtest;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileNotFoundException;
importjava.io.FileOutputStream;
importjava.io.IOException;
importandroid.media.AudioFormat;
importandroid.media.AudioRecord;
publicclassAudioRecordFunc{
//缓冲区字节大小
privateintbufferSizeInBytes=0;
//AudioName裸音频数据文件,麦克风
privateStringAudioName="";
//NewAudioName可播放的音频文件
privateStringNewAudioName="";
privateAudioRecordaudioRecord;
privatebooleanisRecord=false;//设置正在录制的状态
privatestaticAudioRecordFuncmInstance;
privateAudioRecordFunc(){
}
publicsynchronizedstaticAudioRecordFuncgetInstance()
{
if(mInstance==null)
mInstance=newAudioRecordFunc();
returnmInstance;
}
publicintstartRecordAndFile(){
//判断是否有外部存储设备sdcard
if(AudioFileFunc.isSdcardExit())
{
if(isRecord)
{
returnErrorCode.E_STATE_RECODING;
}
else
{
if(audioRecord==null)
creatAudioRecord();
audioRecord.startRecording();
//让录制状态为true
isRecord=true;
//开启音频文件写入线程
newThread(newAudioRecordThread()).start();
returnErrorCode.SUCCESS;
}
}
else
{
returnErrorCode.E_NOSDCARD;
}
}
publicvoidstopRecordAndFile(){
close();
}
publiclonggetRecordFileSize(){
returnAudioFileFunc.getFileSize(NewAudioName);
}
privatevoidclose(){
if(audioRecord!=null){
System.out.println("stopRecord");
isRecord=false;//停止文件写入
audioRecord.stop();
audioRecord.release();//释放资源
audioRecord=null;
}
}
privatevoidcreatAudioRecord(){
//获取音频文件路径
AudioName=AudioFileFunc.getRawFilePath();
NewAudioName=AudioFileFunc.getWavFilePath();
//获得缓冲区字节大小
bufferSizeInBytes=AudioRecord.getMinBufferSize(AudioFileFunc.AUDIO_SAMPLE_RATE,
AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT);
//创建AudioRecord对象
audioRecord=newAudioRecord(AudioFileFunc.AUDIO_INPUT,AudioFileFunc.AUDIO_SAMPLE_RATE,
AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT,bufferSizeInBytes);
}
classAudioRecordThreadimplementsRunnable{
@Override
publicvoidrun(){
writeDateTOFile();//往文件中写入裸数据
copyWaveFile(AudioName,NewAudioName);//给裸数据加上头文件
}
}
/**
*这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频,
*如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的裸数据进行处理,比如你要做一个爱说话的TOM
*猫在这里就进行音频的处理,然后重新封装所以说这样得到的音频比较容易做一些音频的处理。
*/
privatevoidwriteDateTOFile(){
//new一个byte数组用来存一些字节数据,大小为缓冲区大小
byte[]audiodata=newbyte[bufferSizeInBytes];
FileOutputStreamfos=null;
intreadsize=0;
try{
Filefile=newFile(AudioName);
if(file.exists()){
file.delete();
}
fos=newFileOutputStream(file);//建立一个可存取字节的文件
}catch(Exceptione){
e.printStackTrace();
}
while(isRecord==true){
readsize=audioRecord.read(audiodata,0,bufferSizeInBytes);
if(AudioRecord.ERROR_INVALID_OPERATION!=readsize&&fos!=null){
try{
fos.write(audiodata);
}catch(IOExceptione){
e.printStackTrace();
}
}
}
try{
if(fos!=null)
fos.close();//关闭写入流
}catch(IOExceptione){
e.printStackTrace();
}
}
//这里得到可播放的音频文件
privatevoidcopyWaveFile(StringinFilename,StringoutFilename){
FileInputStreamin=null;
FileOutputStreamout=null;
longtotalAudioLen=0;
longtotalDataLen=totalAudioLen+36;
longlongSampleRate=AudioFileFunc.AUDIO_SAMPLE_RATE;
intchannels=2;
longbyteRate=16*AudioFileFunc.AUDIO_SAMPLE_RATE*channels/8;
byte[]data=newbyte[bufferSizeInBytes];
try{
in=newFileInputStream(inFilename);
out=newFileOutputStream(outFilename);
totalAudioLen=in.getChannel().size();
totalDataLen=totalAudioLen+36;
WriteWaveFileHeader(out,totalAudioLen,totalDataLen,
longSampleRate,channels,byteRate);
while(in.read(data)!=-1){
out.write(data);
}
in.close();
out.close();
}catch(FileNotFoundExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
}
/**
*这里提供一个头信息。插入这些信息就可以得到可以播放的文件。
*为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav
*音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有
*自己特有的头文件。
*/
privatevoidWriteWaveFileHeader(FileOutputStreamout,longtotalAudioLen,
longtotalDataLen,longlongSampleRate,intchannels,longbyteRate)
throwsIOException{
byte[]header=newbyte[44];
header[0]='R';//RIFF/WAVEheader
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;//4bytes:sizeof'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);//blockalign
header[33]=0;
header[34]=16;//bitspersample
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);
}
}
2、MediaRecorder录音,输出amr格式音频
packagecom.example.audiorecordtest;
importjava.io.File;
importjava.io.IOException;
importandroid.media.MediaRecorder;
publicclassMediaRecordFunc{
privatebooleanisRecord=false;
privateMediaRecordermMediaRecorder;
privateMediaRecordFunc(){
}
privatestaticMediaRecordFuncmInstance;
publicsynchronizedstaticMediaRecordFuncgetInstance(){
if(mInstance==null)
mInstance=newMediaRecordFunc();
returnmInstance;
}
publicintstartRecordAndFile(){
//判断是否有外部存储设备sdcard
if(AudioFileFunc.isSdcardExit())
{
if(isRecord)
{
returnErrorCode.E_STATE_RECODING;
}
else
{
if(mMediaRecorder==null)
createMediaRecord();
try{
mMediaRecorder.prepare();
mMediaRecorder.start();
//让录制状态为true
isRecord=true;
returnErrorCode.SUCCESS;
}catch(IOExceptionex){
ex.printStackTrace();
returnErrorCode.E_UNKOWN;
}
}
}
else
{
returnErrorCode.E_NOSDCARD;
}
}
publicvoidstopRecordAndFile(){
close();
}
publiclonggetRecordFileSize(){
returnAudioFileFunc.getFileSize(AudioFileFunc.getAMRFilePath());
}
privatevoidcreateMediaRecord(){
/*①Initial:实例化MediaRecorder对象*/
mMediaRecorder=newMediaRecorder();
/*setAudioSource/setVedioSource*/
mMediaRecorder.setAudioSource(AudioFileFunc.AUDIO_INPUT);//设置麦克风
/*设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default
*THREE_GPP(3gp格式,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
*/
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
/*设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default*/
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
/*设置输出文件的路径*/
Filefile=newFile(AudioFileFunc.getAMRFilePath());
if(file.exists()){
file.delete();
}
mMediaRecorder.setOutputFile(AudioFileFunc.getAMRFilePath());
}
privatevoidclose(){
if(mMediaRecorder!=null){
System.out.println("stopRecord");
isRecord=false;
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder=null;
}
}
}
3、其他文件
AudioFileFunc.java
packagecom.example.audiorecordtest;
importjava.io.File;
importandroid.media.MediaRecorder;
importandroid.os.Environment;
publicclassAudioFileFunc{
//音频输入-麦克风
publicfinalstaticintAUDIO_INPUT=MediaRecorder.AudioSource.MIC;
//采用频率
//44100是目前的标准,但是某些设备仍然支持22050,16000,11025
publicfinalstaticintAUDIO_SAMPLE_RATE=44100;//44.1KHz,普遍使用的频率
//录音输出文件
privatefinalstaticStringAUDIO_RAW_FILENAME="RawAudio.raw";
privatefinalstaticStringAUDIO_WAV_FILENAME="FinalAudio.wav";
publicfinalstaticStringAUDIO_AMR_FILENAME="FinalAudio.amr";
/**
*判断是否有外部存储设备sdcard
*@returntrue|false
*/
publicstaticbooleanisSdcardExit(){
if(Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
returntrue;
else
returnfalse;
}
/**
*获取麦克风输入的原始音频流文件路径
*@return
*/
publicstaticStringgetRawFilePath(){
StringmAudioRawPath="";
if(isSdcardExit()){
StringfileBasePath=Environment.getExternalStorageDirectory().getAbsolutePath();
mAudioRawPath=fileBasePath+"/"+AUDIO_RAW_FILENAME;
}
returnmAudioRawPath;
}
/**
*获取编码后的WAV格式音频文件路径
*@return
*/
publicstaticStringgetWavFilePath(){
StringmAudioWavPath="";
if(isSdcardExit()){
StringfileBasePath=Environment.getExternalStorageDirectory().getAbsolutePath();
mAudioWavPath=fileBasePath+"/"+AUDIO_WAV_FILENAME;
}
returnmAudioWavPath;
}
/**
*获取编码后的AMR格式音频文件路径
*@return
*/
publicstaticStringgetAMRFilePath(){
StringmAudioAMRPath="";
if(isSdcardExit()){
StringfileBasePath=Environment.getExternalStorageDirectory().getAbsolutePath();
mAudioAMRPath=fileBasePath+"/"+AUDIO_AMR_FILENAME;
}
returnmAudioAMRPath;
}
/**
*获取文件大小
*@parampath,文件的绝对路径
*@return
*/
publicstaticlonggetFileSize(Stringpath){
FilemFile=newFile(path);
if(!mFile.exists())
return-1;
returnmFile.length();
}
}
4、其他文件
ErrorCode.java
packagecom.example.audiorecordtest;
importandroid.content.Context;
importandroid.content.res.Resources.NotFoundException;
publicclassErrorCode{
publicfinalstaticintSUCCESS=1000;
publicfinalstaticintE_NOSDCARD=1001;
publicfinalstaticintE_STATE_RECODING=1002;
publicfinalstaticintE_UNKOWN=1003;
publicstaticStringgetErrorInfo(ContextvContext,intvType)throwsNotFoundException
{
switch(vType)
{
caseSUCCESS:
return"success";
caseE_NOSDCARD:
returnvContext.getResources().getString(R.string.error_no_sdcard);
caseE_STATE_RECODING:
returnvContext.getResources().getString(R.string.error_state_record);
caseE_UNKOWN:
default:
returnvContext.getResources().getString(R.string.error_unknown);
}
}
}
5、string.xml
<?xmlversion="1.0"encoding="utf-8"?> <resources> <stringname="app_name">AudioRecordTest</string> <stringname="hello_world">测试AudioRecord,实现录音功能</string> <stringname="menu_settings">Settings</string> <stringname="view_record_wav">录音WAV文件</string> <stringname="view_record_amr">录音AMR文件</string> <stringname="view_stop">停止录音</string> <stringname="error_no_sdcard">没有SD卡,无法存储录音数据</string> <stringname="error_state_record">正在录音中,请先停止录音</string> <stringname="error_unknown">无法识别的错误</string> </resources>
6、主程序MainActivity
packagecom.example.audiorecordtest;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.os.Handler;
importandroid.os.Message;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.Button;
importandroid.widget.TextView;
publicclassMainActivityextendsActivity{
privatefinalstaticintFLAG_WAV=0;
privatefinalstaticintFLAG_AMR=1;
privateintmState=-1;//-1:没再录制,0:录制wav,1:录制amr
privateButtonbtn_record_wav;
privateButtonbtn_record_amr;
privateButtonbtn_stop;
privateTextViewtxt;
privateUIHandleruiHandler;
privateUIThreaduiThread;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewByIds();
setListeners();
init();
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifitispresent.
getMenuInflater().inflate(R.menu.activity_main,menu);
returntrue;
}
privatevoidfindViewByIds(){
btn_record_wav=(Button)this.findViewById(R.id.btn_record_wav);
btn_record_amr=(Button)this.findViewById(R.id.btn_record_amr);
btn_stop=(Button)this.findViewById(R.id.btn_stop);
txt=(TextView)this.findViewById(R.id.text);
}
privatevoidsetListeners(){
btn_record_wav.setOnClickListener(btn_record_wav_clickListener);
btn_record_amr.setOnClickListener(btn_record_amr_clickListener);
btn_stop.setOnClickListener(btn_stop_clickListener);
}
privatevoidinit(){
uiHandler=newUIHandler();
}
privateButton.OnClickListenerbtn_record_wav_clickListener=newButton.OnClickListener(){
publicvoidonClick(Viewv){
record(FLAG_WAV);
}
};
privateButton.OnClickListenerbtn_record_amr_clickListener=newButton.OnClickListener(){
publicvoidonClick(Viewv){
record(FLAG_AMR);
}
};
privateButton.OnClickListenerbtn_stop_clickListener=newButton.OnClickListener(){
publicvoidonClick(Viewv){
stop();
}
};
/**
*开始录音
*@parammFlag,0:录制wav格式,1:录音amr格式
*/
privatevoidrecord(intmFlag){
if(mState!=-1){
Messagemsg=newMessage();
Bundleb=newBundle();//存放数据
b.putInt("cmd",CMD_RECORDFAIL);
b.putInt("msg",ErrorCode.E_STATE_RECODING);
msg.setData(b);
uiHandler.sendMessage(msg);//向Handler发送消息,更新UI
return;
}
intmResult=-1;
switch(mFlag){
caseFLAG_WAV:
AudioRecordFuncmRecord_1=AudioRecordFunc.getInstance();
mResult=mRecord_1.startRecordAndFile();
break;
caseFLAG_AMR:
MediaRecordFuncmRecord_2=MediaRecordFunc.getInstance();
mResult=mRecord_2.startRecordAndFile();
break;
}
if(mResult==ErrorCode.SUCCESS){
uiThread=newUIThread();
newThread(uiThread).start();
mState=mFlag;
}else{
Messagemsg=newMessage();
Bundleb=newBundle();//存放数据
b.putInt("cmd",CMD_RECORDFAIL);
b.putInt("msg",mResult);
msg.setData(b);
uiHandler.sendMessage(msg);//向Handler发送消息,更新UI
}
}
/**
*停止录音
*/
privatevoidstop(){
if(mState!=-1){
switch(mState){
caseFLAG_WAV:
AudioRecordFuncmRecord_1=AudioRecordFunc.getInstance();
mRecord_1.stopRecordAndFile();
break;
caseFLAG_AMR:
MediaRecordFuncmRecord_2=MediaRecordFunc.getInstance();
mRecord_2.stopRecordAndFile();
break;
}
if(uiThread!=null){
uiThread.stopThread();
}
if(uiHandler!=null)
uiHandler.removeCallbacks(uiThread);
Messagemsg=newMessage();
Bundleb=newBundle();//存放数据
b.putInt("cmd",CMD_STOP);
b.putInt("msg",mState);
msg.setData(b);
uiHandler.sendMessageDelayed(msg,1000);//向Handler发送消息,更新UI
mState=-1;
}
}
privatefinalstaticintCMD_RECORDING_TIME=2000;
privatefinalstaticintCMD_RECORDFAIL=2001;
privatefinalstaticintCMD_STOP=2002;
classUIHandlerextendsHandler{
publicUIHandler(){
}
@Override
publicvoidhandleMessage(Messagemsg){
//TODOAuto-generatedmethodstub
Log.d("MyHandler","handleMessage......");
super.handleMessage(msg);
Bundleb=msg.getData();
intvCmd=b.getInt("cmd");
switch(vCmd)
{
caseCMD_RECORDING_TIME:
intvTime=b.getInt("msg");
MainActivity.this.txt.setText("正在录音中,已录制:"+vTime+"s");
break;
caseCMD_RECORDFAIL:
intvErrorCode=b.getInt("msg");
StringvMsg=ErrorCode.getErrorInfo(MainActivity.this,vErrorCode);
MainActivity.this.txt.setText("录音失败:"+vMsg);
break;
caseCMD_STOP:
intvFileType=b.getInt("msg");
switch(vFileType){
caseFLAG_WAV:
AudioRecordFuncmRecord_1=AudioRecordFunc.getInstance();
longmSize=mRecord_1.getRecordFileSize();
MainActivity.this.txt.setText("录音已停止.录音文件:"+AudioFileFunc.getWavFilePath()+"\n文件大小:"+mSize);
break;
caseFLAG_AMR:
MediaRecordFuncmRecord_2=MediaRecordFunc.getInstance();
mSize=mRecord_2.getRecordFileSize();
MainActivity.this.txt.setText("录音已停止.录音文件:"+AudioFileFunc.getAMRFilePath()+"\n文件大小:"+mSize);
break;
}
break;
default:
break;
}
}
};
classUIThreadimplementsRunnable{
intmTimeMill=0;
booleanvRun=true;
publicvoidstopThread(){
vRun=false;
}
publicvoidrun(){
while(vRun){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
mTimeMill++;
Log.d("thread","mThread........"+mTimeMill);
Messagemsg=newMessage();
Bundleb=newBundle();//存放数据
b.putInt("cmd",CMD_RECORDING_TIME);
b.putInt("msg",mTimeMill);
msg.setData(b);
MainActivity.this.uiHandler.sendMessage(msg);//向Handler发送消息,更新UI
}
}
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。