2012-02-23 56 views
14

我想录制现场音频并播放它。就用户界面而言,该应用程序只有三个按钮:一个用于开始录制和流式播放,一个用于播放预先录制的文件和用于停止当前任务的最后一个(录制/播放)。为此,我分别使用了AudioRecordAudioTrack类。我的计划看起来像....现场录音和Android和线程和回调处理

/** * @author阿米特 * */

public class AudioRecorder extends Activity { 
    private String LOG_TAG = null; 

    /* variables which are required to generate and manage the UI of the App */ 
    // private RecordButton mRecordButton = null; 
    private Button recordBtn, stopBtn, playBtn; 

    /* 
    * variables which are required for the actual functioning of the recording 
    * and playing 
    */ 
    private AudioRecord recorder = null; 
    private AudioTrack player = null; 
    private AudioManager audioManager = null; 
    private int recorderBufSize, recordingSampleRate; 
    private int trackBufSize; 
    private short[] audioData; 
    private boolean isRecording = false, isPlaying = false; 
    private Thread startRecThread; 

    private AudioRecord.OnRecordPositionUpdateListener posUpdateListener; 

    /** 
    * constructor method for initializing the variables 
    */ 
    public AudioRecorder() { 
     super(); 
     LOG_TAG = "Constructor"; 
     recorderBufSize = recordingSampleRate = trackBufSize = 0; 

     // init function will initialize all the necessary variables ... 
     init(); 

     if (recorder != null && player != null) { 
      Log.e(LOG_TAG, "recorder and player initialized"); 
      audioData = new short[recorderBufSize/2]; // since we r reading shorts 

     } else { 
      Log.e(LOG_TAG, "Problem inside init function "); 
     } 
     posUpdateListener = new AudioRecord.OnRecordPositionUpdateListener() { 
      int numShortsRead = 0; 

      @Override 
      public void onPeriodicNotification(AudioRecord rec) { 
       // TODO Auto-generated method stub 
//    String LOG_TAG = Thread.currentThread().getName(); 
//    Log.e(LOG_TAG, "inside position listener"); 
       audioData = new short[recorderBufSize/2]; // divide by 2 since now we are reading shorts 
       numShortsRead = rec.read(audioData, 0, audioData.length); 
       player.write(audioData, 0, numShortsRead); 

      } 

      @Override 
      public void onMarkerReached(AudioRecord recorder) { 
       // TODO Auto-generated method stub 
       Log.e(LOG_TAG, "Marker Reached"); 
      } 
     }; 
     // listener will be called every time 160 frames are reached 
     recorder.setPositionNotificationPeriod(160); 
     recorder.setRecordPositionUpdateListener(posUpdateListener); 

     Log.e(LOG_TAG, "inside constructor"); 
    } 

    private void init() { 
     LOG_TAG = "initFunc"; 
     // int[] mSampleRates = new int[] { 8000, 11025, 22050, 44100 }; 
     short audioFormat = AudioFormat.ENCODING_PCM_16BIT; 
     // for (int rate : mSampleRates) { 
     this.recordingSampleRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); 
     try { 
      // Log.d(LOG_TAG, "Attempting rate " + rate + "Hz, bits: " + 
      // audioFormat); 
      int bufrSize = AudioRecord.getMinBufferSize(this.recordingSampleRate, 
        AudioFormat.CHANNEL_IN_MONO, audioFormat); 

       // lets find out the minimum required size for AudioTrack 
      int audioTrackBufSize = AudioTrack.getMinBufferSize(this.recordingSampleRate, 
        AudioFormat.CHANNEL_OUT_MONO, audioFormat); 

      if (bufrSize != AudioRecord.ERROR_BAD_VALUE 
        && bufrSize != AudioRecord.ERROR) { 
       // check if we can instantiate and have a success 
       if(audioTrackBufSize >= bufrSize){ 
        this.recorderBufSize = audioTrackBufSize; 
       }else{ 
        this.recorderBufSize = bufrSize; 
       } 

       AudioRecord rec = new AudioRecord(
         MediaRecorder.AudioSource.DEFAULT, this.recordingSampleRate, 
         AudioFormat.CHANNEL_IN_MONO, audioFormat, this.recorderBufSize); 

       if (rec != null 
         && rec.getState() == AudioRecord.STATE_INITIALIZED) { 

        // storing variables for future use . . . 
//     this.recordingSampleRate = rate; 
//     this.recorderBufSize = bufrSize; 

        Log.e(LOG_TAG, 
          "Returning..(rate:channelConfig:audioFormat:recorderBufSize)" 
            + this.recordingSampleRate + ":" + AudioFormat.CHANNEL_IN_MONO 
            + ":" + audioFormat + ":" + this.recorderBufSize); 

        // Now create an instance of the AudioTrack 
//     int audioTrackBufSize = AudioTrack.getMinBufferSize(rate, 
//       AudioFormat.CHANNEL_OUT_MONO, audioFormat); 

        Log.e(LOG_TAG, "Audio Record/Track/Final buf size :" + bufrSize + "/ " +audioTrackBufSize + "/ "+this.recorderBufSize); 


        this.player = new AudioTrack(AudioManager.STREAM_MUSIC, 
          this.recordingSampleRate, AudioFormat.CHANNEL_OUT_MONO, audioFormat, 
          this.recorderBufSize, AudioTrack.MODE_STREAM); 

        this.recorder = rec; 
        this.player.stop(); 
        this.player.flush(); 
        this.player.setPlaybackRate(this.recordingSampleRate); 
        return; 
       } 
      } 
     } catch (IllegalArgumentException e) { 
      Log.d(LOG_TAG, this.recordingSampleRate + "Exception, keep trying.", e); 
     } catch (Exception e) { 
      Log.e(LOG_TAG, this.recordingSampleRate + "Some Exception!!", e); 
     } 
     // for loop for channel config ended here. . . . 
     // for loop for audioFormat ended here. . . 
     // }// for loop for sampleRate 
     return; 
    } 

    private void startPlaying() { 
     LOG_TAG = "startPlaying"; 

     Log.e(LOG_TAG, "start Playing"); 
    } 

    private void stopPlaying() { 
     LOG_TAG = "stopPlaying"; 

     Log.e(LOG_TAG, "stop Playing"); 
    } 

    private void startRecording() { 
     LOG_TAG = "startRecording"; 

     /* start a separate recording thread from here . . . */ 
     startRecThread = new Thread() { 
      @Override 
      public void run() { 
       // TODO Auto-generated method stub 
       android.os.Process 
         .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 
//    String LOG_TAG = Thread.currentThread().getName(); 
       if(recorder.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING){ 
        recorder.startRecording(); 
       } 
//    Log.e(LOG_TAG, "running" +recorder.getRecordingState()); 
       while (recorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { 
        recorder.read(audioData, 0, audioData.length); 
        try { 

         Thread.sleep(1000); // sleep for 2s 
        } catch (InterruptedException e) { 
         // TODO Auto-generated catch block 
         Log.e("run Method", "recorder thread is interrupted"); 
         e.printStackTrace(); 
        } 
       } 
      } 
     }; 
     setVolumeControlStream(AudioManager.STREAM_MUSIC); 
     audioManager.setSpeakerphoneOn(false); 
     player.flush();  
     player.play(); 
     startRecThread.start(); 

     Log.e(LOG_TAG, "start Recording"); 
    } 

    private void stopRecording() { 
     LOG_TAG = "stopRecording"; 
     recorder.stop(); 

     if (startRecThread != null && startRecThread.isAlive()) {   
      startRecThread.destroy(); 
      startRecThread = null; 
     } 

     player.stop(); 
     player.flush(); 
     Log.e(LOG_TAG, "stop Recording"); 
    } 

    private void stop() { 
     if (isRecording) { 
      isRecording = false; 
      stopRecording(); 
     } 
     if (isPlaying) { 
      isPlaying = false; 
      stopPlaying(); 
     } 
     recordBtn.setEnabled(true); 
     playBtn.setEnabled(true); 
    } 

    @Override 
    public void onCreate(Bundle icicle) { 
     super.onCreate(icicle); 
     LOG_TAG = "onCreate"; 
//  Log.e(LOG_TAG, "Create Called"); 
     // getting the audio service 
     audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); 
     LinearLayout ll = new LinearLayout(this); 

     // creating Buttons one by one . . . . 
     // button to start the recording process 
     recordBtn = new Button(this); 
     recordBtn.setText("Record"); 
     recordBtn.setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       // TODO Auto-generated method stub 
       recordBtn.setEnabled(false); 
       playBtn.setEnabled(false); 
       isRecording = true; 
       startRecording(); 
      } 
     }); 
     // single button to stop recording and playing as applicable 
     stopBtn = new Button(this); 
     stopBtn.setText("Stop"); 
     stopBtn.setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       // TODO Auto-generated method stub 
       stop(); 
      } 
     }); 
     // button to play the recorded sound 
     playBtn = new Button(this); 
     playBtn.setText("Play"); 
     playBtn.setOnClickListener(new OnClickListener() { 

      @Override 
      public void onClick(View v) { 
       // TODO Auto-generated method stub 
       // reverse the isPlaying 
       isPlaying = true; 
       recordBtn.setEnabled(false); 
       playBtn.setEnabled(false); 
       startPlaying(); 
      } 
     }); 

     ll.addView(recordBtn, new LinearLayout.LayoutParams(
       ViewGroup.LayoutParams.WRAP_CONTENT, 
       ViewGroup.LayoutParams.WRAP_CONTENT, 1)); 

     ll.addView(playBtn, new LinearLayout.LayoutParams(
       ViewGroup.LayoutParams.WRAP_CONTENT, 
       ViewGroup.LayoutParams.WRAP_CONTENT, 1)); 

     ll.addView(stopBtn, new LinearLayout.LayoutParams(
       ViewGroup.LayoutParams.WRAP_CONTENT, 
       ViewGroup.LayoutParams.WRAP_CONTENT, 1)); 

     setContentView(ll); 
    } 

    @Override 
    protected void onDestroy() { 
     // Clean up code . .. 
     super.onDestroy(); 
     if (recorder != null) 
      recorder.release(); 
     if (startRecThread!=null && startRecThread.isAlive()) 
      startRecThread.destroy(); 
     if (recorder != null) 
      recorder.release(); 
     if (player != null) 
      player.release(); 
     startRecThread = null; 
     recorder = null; 
     player = null; 
     recordBtn = null; 
     stopBtn = null; 
     playBtn = null; 
     audioData = null; 
     System.gc(); 
    } 

} 

正如你可能会看到startPlaying()stopPlaying()功能尚未实现,从而让我们不要谈论他们。目前,我只是试图录制和播放。当我运行该程序时,它播放录制的音频,但音频显示来自远方。另一个问题是应用程序的UI线程挂起,虽然我有一个单独的线程来阅读音频。请帮助....

+1

选择一种码率,配置和格式。然后坚持下去,或者让你的init函数接受属性并切换它。 – L7ColWinters 2012-02-23 13:32:35

+0

@ L7ColWinters感谢和PLZ看到编辑的问题.... – aProgrammer 2012-02-28 11:57:00

+1

是你想要的功能,可以在录音时说些什么,它会自动播放而无需打游戏?这就是你现在的代码所做的,尽管它确实挂起并且发挥了很高的音调,这可能是反馈。 – L7ColWinters 2012-02-28 16:01:50

回答

19

如果您的要求是在录音时它应该播放(意味着循环回音频),在您的while循环线程中,您正在存储录制的数据(audioData bufer),您自己可以复制它在while循环中播放器对象(player.write(audioData,0,numShortsRead);)。你说像你的UI线程卡住了,这可能是因为你给了音频记录线程更多的优先权。

检查,我使用上面的循环回收要求的代码下面

boolean m_isRun=true; 
public void loopback() { 
     // Prepare the AudioRecord & AudioTrack 
     try { 
      buffersize = AudioRecord.getMinBufferSize(SAMPLE_RATE, 
        AudioFormat.CHANNEL_CONFIGURATION_MONO, 
        AudioFormat.ENCODING_PCM_16BIT); 

      if (buffersize <= BUF_SIZE) { 
       buffersize = BUF_SIZE; 
      } 
      Log.i(LOG_TAG,"Initializing Audio Record and Audio Playing objects"); 

      m_record = new AudioRecord(MediaRecorder.AudioSource.MIC, 
        SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO, 
        AudioFormat.ENCODING_PCM_16BIT, buffersize * 1); 

      m_track = new AudioTrack(AudioManager.STREAM_ALARM, 
        SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO, 
        AudioFormat.ENCODING_PCM_16BIT, buffersize * 1, 
        AudioTrack.MODE_STREAM); 

      m_track.setPlaybackRate(SAMPLE_RATE); 
     } catch (Throwable t) { 
      Log.e("Error", "Initializing Audio Record and Play objects Failed "+t.getLocalizedMessage()); 
     } 

     m_record.startRecording(); 
     Log.i(LOG_TAG,"Audio Recording started"); 
     m_track.play(); 
     Log.i(LOG_TAG,"Audio Playing started"); 

     while (m_isRun) { 
      m_record.read(buffer, 0, BUF_SIZE); 
      m_track.write(buffer, 0, buffer.length); 
     } 

     Log.i(LOG_TAG, "loopback exit"); 
    } 

    private void do_loopback() { 
     m_thread = new Thread(new Runnable() { 
      public void run() { 
       loopback(); 
      } 
     }); 

一件事,如果你的要求是几秒钟的记录,然后玩,是玩你的记录应该重新开始,同时,你可以用一个延时处理程序线程超时,在那个线程中你可以停止记录复制缓冲区,然后开始记录。

+1

如何决定BUF_SIZE值?我也用过positionUpdateListener(),你对它的重要性有什么看法......还有一件事是什么是while循环的停止条件?你在过程中主线程是否挂起? – aProgrammer 2012-03-01 09:40:42

+1

停止条件,我按照我的要求使用adb命令中的keyevent。在这种情况下,我将“m_isRun”设置为false,您可以使用超时线程或按钮事件。我不认为主线程停止,以前我使用按钮事件而不是keyevents。如果这确实是一个环回要求,我不认为位置更新也是必需的。 – candy 2012-03-01 13:36:12

+1

而在BUF_SIZE的情况下,当我简单地使用getMinBufferSize()时,它给缓冲区没有足够的问题。一些在网络链接中提到的缓冲区大小应该大于SAMPLE_RATE。我继续这个。但我不能说这是缓冲区大小问题的完美解决方案 – candy 2012-03-01 13:36:25