2012-02-23 8 views
14

Voglio registrare l'audio dal vivo e riprodurlo. Per quanto riguarda l'interfaccia utente, l'app ha solo tre pulsanti: uno per avviare la registrazione e lo streaming, uno per giocare un file pre-registrato e l'ultimo per fermare l'attività corrente (registrazione/riproduzione). A tale scopo ho utilizzato le classi AudioRecord e AudioTrack rispettivamente per la registrazione e la riproduzione. Il mio programma si presenta come ....Registrazione audio dal vivo e riproduzione in Android e gestione thread e callback

/** * @author Amit * */

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(); 
    } 

} 

Come si può vedere che startPlaying() e stopPlaying() funzioni non sono ancora implementate in modo da permette di non parlare loro. Attualmente sto solo cercando di registrare e riprodurre. Quando eseguo il programma, riproduce l'audio registrato ma l'audio viene visualizzato da una distanza. Un altro problema è che il thread dell'interfaccia dell'applicazione si blocca anche se ho un thread separato per leggere l'audio. Per favore aiuto ....

+1

scegliere uno dei tassi, configurazione, e il formato. Quindi seguiteli o fate in modo che la vostra funzione init prenda la proprietà e la passi attraverso. – L7ColWinters

+0

@ L7ColWinters ringrazia e vedere la domanda modificata .... – aProgrammer

+1

è la funzionalità desiderata per poter dire qualcosa durante la registrazione e la riproduce automaticamente senza toccare il gioco? Questo è ciò che fa il tuo codice attuale, anche se si blocca e riproduce un squeel molto alto, che potrebbe essere un feedback. – L7ColWinters

risposta

19

Se il tuo requisito è mentre sta registrando dovrebbe suonare (significa looping audio), Nel tuo thread del ciclo while, stai memorizzando i dati registrati (audioData bufer), lì da solo puoi copiare a giocatore oggetto con nel ciclo while (player.write (audioData, 0, numShortsRead);). Hai detto che il tuo thread UI è bloccato, potrebbe essere perché stai dando più priorità al thread del record Audio.

Controllare il sotto il codice che ho usato per sopra loop back requisito

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(); 
      } 
     }); 

Ancora una cosa, se la vostra esigenza è record per alcuni secondi e poi giocare, mentre si sta giocando il record dovrebbe ricominciare da capo, puoi farlo con un thread di gestione del ritardo con un timeout, in quel thread puoi interrompere la registrazione copia il buffer, quindi avviare la registrazione.

+1

come si decide il valore BUF_SIZE? Ho anche usato positionUpdateListener() quali sono le tue opinioni sulla sua importanza ... e ancora una cosa qual è la condizione di stop del ciclo while? e il thread principale si blocca durante il processo? – aProgrammer

+1

per la condizione di arresto, sto usando keyevent dai comandi adb secondo il mio requisito. In questo sto facendo "m_isRun" su false, puoi usare un thread timeout o un evento button. Non penso che il thread principale si fermi, in precedenza stavo usando eventi pulsante invece di keyjevents. se è esattamente un requisito di loopback, non penso che sia richiesto anche l'aggiornamento della posizione. – candy

+1

E nel caso BUF_SIZE, quando stavo semplicemente usando getMinBufferSize(), stava dando al buffer problemi non sufficienti. alcuni in cui nei collegamenti di rete è menzionato come la dimensione del buffer dovrebbe essere maggiore di SAMPLE_RATE. Vado su questo. Ma non posso dire che questa sia la soluzione perfetta per il problema delle dimensioni del buffer – candy