Why would I get NPE in this state

299 views Asked by At

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"

1

There are 1 answers

3
Simas On BEST ANSWER

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.