7.6 捕获和播放原始音频的示例

    下面是一个完整的示例,其中使用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章中将看到,当需要实现任何类型的音频处理或者需要合成音频时,这是值得付出努力的。

posted on 2014-09-02 08:51  宁静致远,一览众山小  阅读(288)  评论(0编辑  收藏  举报

导航