Android下音频录制和播放
1. 音频的捕获
在通常情况下,捕获音频有三种不同方法,每种方法都有优点和弱点。第一种方法是使用意图,这是种最简单的方式,通过一个意图利用已有的、提供录制功能的原生应用程序,它虽然最简单却最不灵活;第二种是使用MediaRecord类,它比较难于使用,但是能在原有类基础上添加更多的灵活性。最后一种方法是使用AudioRecord类,它提供了最大的灵活性,但是也最原始,使用比较复杂。
2. 使用意图捕获音频
在MediaStore.Audio.Media类中存在的变量RECORD_SOUND_ACTION可用来实现创建该意图的动作:
int Mediarecord = 0;
Intentintent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
startActivityForResult(intent,Mediarecord);//通过意图捕获音频
通过使用startActivityForResult方法,触发内置录音机,并当用户录制完成后返回录音。程序中创建了一个名为Mediarecord的常量,并将它传递给startActivityForResult,从而可以确定任何onActivityResult调用的来源。当有startActivityForResult函数触发时,任何活动在返回时都会调用onActivityResult方法,这个常量连同意图一起传入,以此来进行区分。onActivityResult方法是一个用以处理意图返回数据的一个必需的方法,在这里onActivityResult方法可以对意图返回的录音播放。
3. MediaRecord类
在AndroidSDK中包含了一个Mediarecord类,可以利用它来建立自己的音频录制功能,在创建音频功能的过程中,可以控制录制音频的时间长度等,从而有了更多的灵活性。在完成一个Mediarecord的对象的创建后,为了捕获音频,需要调用setAudioEncoderh、setAudioSource和setOutputFormat、setOutputFile的方法,方法调用顺序也是对结果有很大影响的。下图是MediaRecorder的状态机。
简单的启动mediarecord的方法:
medirecoder= newMediaRecorder();
medirecoder.setAudioSource(MediaRecorder.AudioSource.MIC);
//音频来自于麦克风录制
medirecoder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
//输出格式为3gp
medirecoder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//编码方式
File path = newFile(Environment.getExternalStorageDirectory().getAbsolutePath()+"/AudioPlay/");
path.mkdirs();//如果目录不存在则创建
try {
audiofile=File.createTempFile("record_temp",".3gp",path);
//创建临时文件
} catch(IOException e) {
e.printStackTrace();
}
medirecoder.setOutputFile(audiofile.getAbsolutePath());
try {
medirecoder.prepare();
} catch(IllegalStateException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
medirecoder.start();
//启动Mediarecord
如何停止录制,调用stop方法,并通过release释放:
medirecoder.stop();
medirecoder.release();
4. AudioRecord类
借助这个类可以操作原始音频,主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理,如会说话的汤姆猫。因为类本身没有多少内置功能,所以只需要构造一个AudioRecord类型的对象,对其传入不同的配置参数,来实现语音的实时处理,用代码来实现对音频的封装、编码和压缩。AudioRecord类的输出是PCM语音数据,无法直接保存成音频文件,不能直接被播放器播放,这也就给编程带来了一定难度。
AudioTrack用于管理单个的音频资源。 在构造AudioTrack实例时,会涉及到流类型、采样率、通道配置、音频格式、缓冲大小、播放模式等因素。AudioTrack支持STREAM_VOICE_CALL、STREAM_SYSTEM、STREAM_RING、STREAM_MUSIC和STREAM_ALARM等流类型。AudioTrack支持44100Hz、22050Hz、11025Hz等采样率。AudioTrack支持单声道(CHANNEL_OUT_MONO)、立体声(CHANNEL_OUT_STEREO)等两种通道。AudioTrack支持ENCODING_PCM_16BIT、ENCODING_PCM_8BIT等两种编码格式。AudioTrack支持两种播放模式:静态模式(staticmode)和流模式(Streamingmode)。其中静态模式由于没有从Java层向原生层传递数据造成的延迟,时延很小。当音频流较大不足以在音频缓冲中一次写入时,可采用流模式。AudioTrack的播放状态包括PLAYSTATE_STOPPED、PLAYSTATE_PAUSED、PLAYSTATE_PLAYING等。AudioTrack实例的状态包括STATE_INITIALIZED、STATE_NO_STATIC_DATA、STATE_UNINITIALIZED等。
向音频缓冲中添加数据的方法为write()。在设置音频缓冲时,其大小与采样率、通道和音频格式有关。其计算公式为:缓冲大小=最小帧数×(通道==CHANNEL_OUT_STEREO?2:1)×(音频格式==PCM16?2:1)。而最小帧数则受制于采样率和音频设备的延迟等因素。
4.实现播放功能
Android支持多种用于播放的音频文件格式和编解码器。在MediaPlayer中,根据数据源为元数据、音频文件、音频流的不同情况,有着相应的处理过程。基本的过程如下:
1)播放raw中音频文件
如果音频文件位于Android工程的“res/raw”文件夹下。播放音频文件的过程为:
<span style="white-space:pre"> </span> MediaPlayer mp=MediaPlayer.create(context, R.raw.sound_file);
mp.start();
2)播放音频文件
如果音频文件的路径为PATH_OF_FILE,播放音频文件的过程为:
MediaPlayer mp=newMediaPlayer();
try {
mp.setDataSource(PATH_OF_FILE); //设置数据源
mp.prepare();
} catch(IllegalArgumentException e) {
// TODOAuto-generated catch block
e.printStackTrace();
} catch(SecurityException e) {
// TODOAuto-generated catch block
e.printStackTrace();
} catch(IllegalStateException e) {
// TODOAuto-generated catch block
e.printStackTrace();
} catch(IOException e) {
// TODOAuto-generated catch block
e.printStackTrace();
}
mp.start();
3)播放音频流
如果音频文件的URL地址为URL_ADDRESS,播放音频流的过程为:
MediaPlayer mp=newMediaPlayer();
try {
mp.setDataSource(URL_ADDRESS);
mp.prepare();
}catch (IllegalArgumentException e) {
e.printStackTrace();
}catch (SecurityException e) {
e.printStackTrace();
}catch (IllegalStateException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
} //设置数据源
mp.start();
可以通过下面的语句完成对存储完成的临时存储的录音文件的读取。
mediplayer.setDataSource(audiofile.getAbsolutePath());
4)实现音量的控制
将对音量的控制写成了一个独立的方法,并且采用了动态布局,在MainActivity的代码中将控制条添加进布局中。
<span style="color:#0000c0;"> </span><span style="color: rgb(0, 0, 192); white-space: pre;"> </span> LinearLayout mLayout = new LinearLayout(this);
mLayout.setOrientation(LinearLayout.VERTICAL);
LP_MW = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);
mLayout.setLayoutParams(LP_MW);
protected void audioVolume (){
setVolumeControlStream(AudioManager.STREAM_MUSIC);
final AudioManager am = ( AudioManager) getSystemService(MainActivity.AUDIO_SERVICE);
final SeekBar volume = new SeekBar(this);
final TextView textview1 = new TextView(this);
volume.setLayoutParams(LP_MW);
volume.setMax(am.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
volume.setProgress(am.getStreamVolume(AudioManager.STREAM_MUSIC));
volume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar arg0) {
}
@Override
public void onStartTrackingTouch(SeekBar arg0) {
}
@Override
public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
am.setStreamVolume(AudioManager.STREAM_MUSIC, arg1, 0);//why set am to final type
textview1.setText("Volume:"+arg1+"%");
}
});
mLayout.addView(textview1);//mLayout是布局文件
mLayout.addView(volume);
}
项目完成,上效果图:
对于这个项目其实还可以深入,我还想研究一下类内部究竟是如何降噪的,播放时的扩音,总之,不能总是习惯类提供给我们的便利,对于一些算法还是值得去研究一下。