4

I am trying to record audio from the device.

I created an AudioRecord object and manage it over the cycle of the activity.

When my app goes to background it stops, and when in foreground it continues.

When the recording is running, I want to get the samples from the recorder to a byte array

This is the code I use to do it:

private void startRecorder() {
    Log.d(TAG, "before start recording");
    myBuffer = new byte[2048];
    audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    audioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_DTMF, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    myRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 2048);

    myThread = new Thread() {
        public void run() {
            while (true) {
                if (myRecorder != null && myRecorder.getState() == AudioRecord.RECORDSTATE_RECORDING) {
                    myRecorder.read(myBuffer, 0, 2048);
                    recordingSampleNumber++;
                    if (recordingSampleNumber % 10 == 0) {
                        Log.d(TAG, "recording sample number:" + recordingSampleNumber);
                    }
                }
            }
        }
    };
    myThread.setPriority(Thread.MAX_PRIORITY);

    myRecorder.startRecording();
    myThread.start();
    Log.d(TAG, "after start recording");
}

My problem is: Every once in a while I get the following error :

06-22 11:44:21.057: E/AndroidRuntime(17776): Process: com.example.microphonetestproject2, PID: 17776
06-22 11:44:21.057: E/AndroidRuntime(17776): java.lang.NullPointerException: Attempt to invoke virtual method 'int android.media.AudioRecord.getState()' on a null object reference
06-22 11:44:21.057: E/AndroidRuntime(17776):    at com.example.microphonetestproject2.MicrophoneTestApp$3.run(MicrophoneTestApp.java:108)

my question is: Why would I get an NPE on myRecorder.getState() when just half a line before that i wrote "if myRecorder!=null"

Lena Bru
  • 13,521
  • 11
  • 61
  • 126
  • 2
    because you are setting it to null somewhere .... and do not stop the loop.. and it is multithreading ... and: `myRecorder != null && myRecorder.getState() == AudioRecord.RECORDSTATE_RECORDING` is not atomic – Selvin Jun 22 '15 at 08:59
  • http://stackoverflow.com/a/25781861/2783386 use this answer – Ando Masahashi Jun 22 '15 at 09:06

1 Answers1

1

This looks like a concurrency problem.

It seems that after the check myRecorder != null, the variable is actually set to null in a different thread, which is possible because as you probably know threads run in parallel.

I'd recommend you lock on the object and execute your loop. Then, no one will access it out of place, i.e. unexpectedly.

while (true) {
    synchronized (myRecorder) {
        if (myRecorder != null && myRecorder.getState() == AudioRecord.RECORDSTATE_RECORDING) {
            myRecorder.read(myBuffer, 0, 2048);
            recordingSampleNumber++;
            if (recordingSampleNumber % 10 == 0) {
                Log.d(TAG, "recording sample number:" + recordingSampleNumber);
            }
        }
    }
}

Although this may fix your problem, you should deal with it with in other ways, e.g. instead of setting the variable to null to cancel the thread, use the built in interrupt and join methods:

private Thread mRecorderThread;

private void startRecorder() {
    myRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000,
            AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 2048);

    mRecorderThread = new Thread() {
        public void run() {
            while (true) {
                if (!isInterrupted() && myRecorder.getState() == AudioRecord
                        .RECORDSTATE_RECORDING) {
                    myRecorder.read(myBuffer, 0, 2048);
                    recordingSampleNumber++;
                    if (recordingSampleNumber % 10 == 0) {
                        Log.d(TAG, "recording sample number:" + recordingSampleNumber);
                    }
                }
            }
        }
    };
    mRecorderThread.setPriority(Thread.MAX_PRIORITY);
    myRecorder.startRecording();
    mRecorderThread.start();
    Log.d(TAG, "after start recording");
}

private void stopRecorder() {
    mRecorderThread.interrupt();
    // Wait for the thread to finish (for the interruption to take effect)
    try {
        mRecorderThread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    myRecorder.stop();
}

interrupt() as you may have understood, interrupts the thread, but it does not kill it instantly. With the help of join() you can wait for the Thread to finish after you interrupted it.

Simas
  • 43,548
  • 10
  • 88
  • 116
  • `synchronized (myRecorder) { while (true) { ... } }` makes myRecorder unusable from any other thread ... should be: `while (true) { synchronized (myRecorder) { ... } }` – Selvin Jun 22 '15 at 09:30
  • @Selvin whoops you're right. Wanted to write the synchronized block inside the loop. – Simas Jun 22 '15 at 09:33
  • still if `myRecorder.read(..)` is blocking call ... it is hard to write good MT code :) – Selvin Jun 22 '15 at 09:34