Live Audio Recording and Playing in Android and Thread & callback handling

20,498

Solution 1

If your requirement is while it is recording it should play(means looping back audio), In your while loop thread, you are storing the recorded data (audioData bufer), there itself you can copy it to player object with in the while loop (player.write(audioData, 0, numShortsRead);). You said like your UI thread is stuck, it might be because of you are giving more priority to Audio record thread.

Check the below the code which I used for above loop back requirement

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

One more thing, If your requirement is record for few seconds and then play, while it is playing your record should start again, you can do that with a delay handler thread with a time out, In that thread you can stop recording copy the buffer, then start recording.

Solution 2

My suggestion is to use Async task other wise known as painless threading. That's the best way to leverage from the threading in Android. You can make use of background processing, pre-execute and post execute methods by dividing your existing program.

http://android-developers.blogspot.ca/2009/05/painless-threading.html

Share:
20,498
Amit
Author by

Amit

"Be a good person, but never try to prove it." #SOreadytohelp I am a professional programmer and a Certified Scrum Master (CSM) currently working as Full Stack Java Developer. Technical Skills Java (Core and Advaced), Spring Security, MicroServices, AWS, C, C++, Android, JSP, JavaScript, jQuery, AngularJS, HTML,JSP, PL/SQL, Hibernate etc. Areas of Interest Any kind of programming, R &amp; D which pays enough money Java based Application Development i.e. Core Java, J2EE, J2ME, JNI, Web Development Software designing and Solution analysis for cloud plateforms. Have knowledge of AWS architecture but can work on Azure or Google cloud too if case 1 above is true :). Web Services and Microservices Development. Web application security and related frameworks and projects like prevention against OWASP's Top 10 vulnerabilities. Algorithm optimization, fine tuning, research &amp; development.

Updated on February 09, 2020

Comments

  • Amit
    Amit over 4 years

    I want to record the live audio and play it.As far as UI is concerned the app just has three buttons:one for start recording and streaming it, one for playing a pre recorded file and the last one for stopping the current task(recording / playing). For that purpose I have used AudioRecord and AudioTrack classes for recording and playing respectively. My Program looks like....

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

    As you might see that startPlaying() and stopPlaying() functions are not yet implemented so lets not talk about them. Currently I am just trying to record and play.When I run the program It plays the recorded audio but the audio appears coming from a distance. Another problem is that UI thread of the app hangs though I have a separate thread for reading the Audio . Please help....

    • L7ColWinters
      L7ColWinters about 12 years
      choose one rate, config, and format. Then stick with it or make your init function take in the property and switch through it.
    • Amit
      Amit about 12 years
      @L7ColWinters thanks and plz see the edited question....
    • L7ColWinters
      L7ColWinters about 12 years
      is your desired functionality to be able to say something while recording and it automatically plays it back without hitting play? Thats what your current code does do, although it does hang and play a very high pitch squeel, which might be feedback.
    • Amit
      Amit about 12 years
      @L7ColWinters, Yes thats all I want (to record and play it back, the play button will be used for playing a file which is not yet implemented), You got my problems why my program hangs and plays such a bad audio.... do I need to use same buffer size for AudioRecord and AudioTrack both ? plz help...
    • Amit
      Amit about 12 years
      @L7ColWinters, plz see the updated startRecording().... in which I call Thread.sleep(20) this sleeps the recorder thread for 20ms ( I read somewhere that 20ms is the perfect delay... for such type of apps..... Thanks once again...
    • Amit
      Amit about 12 years
      It seems in somewhat working condition when I made both the Recorder and Player with equal bufferSize... However the UI thread still hangs (and I wonder why ?) though I have made a separate thread for recording.... can you help in it ?
    • Rahul Baradia
      Rahul Baradia about 12 years
      Can you please post your running code. I am unable to do this since 2days.. or provide me a link through which i can get help. Thank you. +100 for this question.
    • Amit
      Amit almost 12 years
      @Tech.Rahul, the code pasted in candy's answer also works, however I have a diff code since my app is quite big... I suggest first you try candy's code and if that doesn't work. I will paste my Code as a separate answer... Good luck buddy, and don't get nervous it takes time at the beginning level.
    • Rahul Baradia
      Rahul Baradia almost 12 years
      @anDroider -- thanks a lot.. i ll try & let you know..
    • Rahul Baradia
      Rahul Baradia almost 12 years
      @anDroider : please can you post your code. I am not able to solve it. Thanks..
    • Amit
      Amit almost 12 years
      have u set the necessary permission in the AndroidManifiest.xml, I have assigned following permissions <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    • Rahul Baradia
      Rahul Baradia almost 12 years
      ya I am using that.. can u please post your code..
    • Rahul Baradia
      Rahul Baradia almost 12 years
      stackoverflow.com/questions/10619675/… .... this is my question which i asked. I am getting some errors can you help me out. thanks
  • Amit
    Amit about 12 years
    how u decide BUF_SIZE value ? I have also used positionUpdateListener() what are your views on its importance... and one more thing what is the stop condition of while loop ? and does you main thread hangs during the process ?
  • candy
    candy about 12 years
    for stop condition, I am using keyevent from adb commands as per my requirement. In that I am making "m_isRun" to false, you can use a time out thread, or a button event. I don't think that main thread stops, previously I was using button events instead of keyevents. if it is exactly a loopback requirement, I dont think position update is also required.
  • candy
    candy about 12 years
    And In BUF_SIZE case, when I was simply using getMinBufferSize(), it was giving buffer not sufficient issues. some where in net links it is mentioned like buffer size should larger than SAMPLE_RATE. I go on this. But I can not say that this is perfect solution for buffer size issue
  • Sandra
    Sandra over 11 years
    Can you just explain what this m_isRun variable is for? I also want to do audio recording and playing simultaneously, and I am a beginner in this area so I need a little help:) +1 for your answer
  • candy
    candy over 11 years
    m_isRun is a simple boolean variable which I used to start/stop record-play
  • Sandra
    Sandra over 11 years
    I followed your example and my app works for about 2 seconds, if I say something I hear my voice back, and suddenly the stream is finished, you hear nothing, and this message is displayed over and over int the log: obtainBuffer() track 0x16ab30 disabled, restarting. Did you have these kind of difficulties or know how to solve the problem? Thank you for your answer and trouble.
  • candy
    candy over 11 years
    try changing the buffer size.
  • Arunraj Shanmugam
    Arunraj Shanmugam over 6 years
    I tried this code, but noise and echo gets build while using. How to avoid it?