下面是一个完整的示例,其中使用AudioRecord录制音频,并使用AudioTrack播放音频。通过使用AsyncTask,每个操作都在他们各自的线程中工作,所以他们并不会导致在主线程中运行的应用程序变得无响应。
1 package com.nthm.androidtestActivity; 2 3 import java.io.DataInputStream; 4 import java.io.DataOutputStream; 5 import java.io.File; 6 import java.io.FileInputStream; 7 import java.io.FileNotFoundException; 8 import java.io.FileOutputStream; 9 import java.io.IOException; 10 import com.nthm.androidtest.R; 11 import android.app.Activity; 12 import android.media.AudioFormat; 13 import android.media.AudioManager; 14 import android.media.AudioRecord; 15 import android.media.AudioTrack; 16 import android.media.MediaRecorder; 17 import android.os.AsyncTask; 18 import android.os.Bundle; 19 import android.os.Environment; 20 import android.view.View; 21 import android.view.View.OnClickListener; 22 import android.widget.Button; 23 import android.widget.TextView; 24 25 public class AltAudioRecorder extends Activity implements OnClickListener {
我们定义了两个内部类——一个用于录制,另一个用于播放。每个类都扩展了AsyncTask。
1 private RecordAudio recordTask; 2 private PlayAudio playTask; 3 private Button startRecordingButton; 4 private Button stopRecordingButton; 5 private Button startPlaybackButton; 6 private Button stopPlaybackButton; 7 private TextView statusText; 8 private File recordingFile;
我们将使用布尔值来跟踪是否应该录制或播放,这些值将用于录制和播放任务中的循环部分。
1 private boolean isPlaying=false; 2 private boolean isRecording=false;
下面是用来定义AudioRecord和AudioTrack对象配置的变量。
1 private int frequency=11025; 2 private int channelConfiguration=AudioFormat.CHANNEL_CONFIGURATION_MONO; 3 private int audioEncoding=AudioFormat.ENCODING_PCM_16BIT; 4 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.altaudiorecorder); 9 statusText=(TextView) findViewById(R.id.StatusTextView); 10 startRecordingButton=(Button) findViewById(R.id.StartRecordingButton); 11 stopRecordingButton=(Button) findViewById(R.id.StopRecordingButton); 12 startPlaybackButton=(Button) findViewById(R.id.StartPlaybackButton); 13 stopPlaybackButton=(Button) findViewById(R.id.StopPlaybackButton); 14 15 startRecordingButton.setOnClickListener(this); 16 stopRecordingButton.setOnClickListener(this); 17 startPlaybackButton.setOnClickListener(this); 18 stopPlaybackButton.setOnClickListener(this); 19 20 stopRecordingButton.setEnabled(false); 21 startPlaybackButton.setEnabled(false); 22 stopPlaybackButton.setEnabled(false);
最后在构造函数中创建将要录制和播放的文件。在当前情况下,将在SD卡上与应用程序相关联的首选位置中创建文件。
1 File path=new File(Environment.getExternalStorageDirectory().getAbsoluteFile()+"video"); 2 path.mkdirs(); 3 try{ 4 recordingFile=File.createTempFile("recording", ".pcm",path); 5 }catch(Exception e){ 6 e.printStackTrace(); 7 } 8 }
onClick方法处理由用户生成的按钮单击事件。每个事件都对应一个具体的方法。
1 @Override 2 public void onClick(View v) { 3 if(v==startRecordingButton){ 4 record(); 5 }else if(v==stopRecordingButton){ 6 stopRecording(); 7 }else if(v==startPlaybackButton){ 8 play(); 9 }else if(v==stopPlaybackButton){ 10 stopPlaying(); 11 } 12 }
为了启动播放,我们将构造一个新的PlayAudio对象,并调用继承自AsyncTask的execute方法。
1 private void play(){ 2 startPlaybackButton.setEnabled(true); 3 4 playTask=new PlayAudio(); 5 playTask.execute(); 6 stopPlaybackButton.setEnabled(true); 7 }
为了停止播放,只须将isPlaying布尔值设置为false。这将导致PlayAudio对象的循环结束。
1 private void stopPlaying(){ 2 isPlaying=false; 3 stopPlaybackButton.setEnabled(false); 4 startPlaybackButton.setEnabled(true); 5 }
为了开始录制,我们将构造一个RecordAudio对象,并调用它的execute方法。
1 private void record(){ 2 startRecordingButton.setEnabled(false); 3 stopRecordingButton.setEnabled(true); 4 startPlaybackButton.setEnabled(true); 5 recordTask=new RecordAudio(); 6 recordTask.execute(); 7 }
为了停止录制,只须将isRecording布尔值设定为false。这将使得RecordAudio对象停止循环,并执行任何清除工作。
1 private void stopRecording(){ 2 isRecording=false; 3 }
下面是PlayAudio内部类。这个类扩展了AsyncTask,并使用一个AudioTrack对象来播放音频。
1 private class PlayAudio extends AsyncTask<Void, Integer, Void>{ 2 @Override 3 protected Void doInBackground(Void... params) { 4 isPlaying=true; 5 int bufferSize=AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding); 6 7 8 short [] audiodata=new short[bufferSize/4]; 9 try { 10 DataInputStream dis=new DataInputStream(new FileInputStream(recordingFile)); 11 AudioTrack audioRtack=new AudioTrack(AudioManager.STREAM_MUSIC, frequency, channelConfiguration, audioEncoding, bufferSize, AudioTrack.MODE_STREAM); 12 audioRtack.play(); 13 try { 14 while(isPlaying&&dis.available()>0){ 15 int i=0; 16 while(dis.available()>0&&i<audiodata.length){ 17 audiodata[i]=dis.readShort(); 18 i++; 19 } 20 audioRtack.write(audiodata, 0, audiodata.length); 21 } 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } 25 try { 26 dis.close(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 startPlaybackButton.setEnabled(false); 31 stopPlaybackButton.setEnabled(true); 32 } catch (FileNotFoundException e) { 33 e.printStackTrace(); 34 } 35 return null; 36 } 37 38 }
最后是RecordAudio类,它同样也扩展了AsyncTask。这个类在后台运行一个AudioRecord对象,并调用publishProgress方法来使用录制进度提示更新UI。
1 private class RecordAudio extends AsyncTask<Void, Integer, Void>{ 2 3 @Override 4 protected Void doInBackground(Void... params) { 5 isRecording=true; 6 try { 7 DataOutputStream dos=new DataOutputStream(new FileOutputStream(recordingFile)); 8 9 int bufferSize=AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding); 10 AudioRecord audioRecord=new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize); 11 short[]buffer=new short[bufferSize]; 12 audioRecord.startRecording(); 13 int r=0; 14 while(isRecording){ 15 int bufferReadResult=audioRecord.read(buffer, 0,bufferSize); 16 for(int i=0;i<bufferReadResult;i++){ 17 dos.writeShort(buffer[i]); 18 } 19 publishProgress(new Integer(r)); 20 r++; 21 } 22 audioRecord.stop(); 23 dos.close(); 24 }catch(Exception e){ 25 e.printStackTrace(); 26 } 27 return null; 28 }
当调用publishProgress时,onProgressUpdate方法将会被调用。
1 @Override 2 protected void onProgressUpdate(Integer... values) { 3 super.onProgressUpdate(values); 4 statusText.setText(values[0].toString()); 5 }
当完成doInBackground方法时,随后将调用onPostExecute方法。
1 @Override 2 protected void onPostExecute(Void result) { 3 super.onPostExecute(result); 4 startRecordingButton.setEnabled(true); 5 stopRecordingButton.setEnabled(false); 6 startPlaybackButton.setEnabled(true); 7 } 8 } 9 }
下面是用于上述示例的布局XML:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical" 5 > 6 <TextView 7 android:id="@+id/StatusTextView" 8 android:text="Status" 9 android:layout_width="fill_parent" 10 android:layout_height="wrap_content" 11 android:textSize="35dip"></TextView> 12 13 <Button 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:id="@+id/StartRecordingButton" 17 android:text="Start Recording"/> 18 <Button 19 android:layout_width="wrap_content" 20 android:layout_height="wrap_content" 21 android:id="@+id/StopRecordingButton" 22 android:text="Stop Recording"/> 23 <Button 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:id="@+id/StartPlaybackButton" 27 android:text="Play Recording"/> 28 <Button 29 android:layout_width="wrap_content" 30 android:layout_height="wrap_content" 31 android:id="@+id/StopPlaybackButton" 32 android:text="Stop Playback"/> 33 </LinearLayout>
同时需要将下面这些权限添加到AndroidMainfest.xml。
1 <uses-permission android:name="android.permission.RECORD_AUDIO"/> 2 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
正如我们已经看到的那样,使用AudioRecord和AudioTrack类来创建捕获和播放应用程序要比使用MediaPlayer和MediaPlayer类更加复杂。但是我们在第8章中将看到,当需要实现任何类型的音频处理或者需要合成音频时,这是值得付出努力的。