Search code examples
javaandroidaudio-recordingmediarecorder

Why am I getting an IllegalStateException when stopping MediaRecorder?


To get a better understanding of audio recording and playback in Android, I've implemented a clone of the code in the android dev tutorial https://developer.android.com/guide/topics/media/mediarecorder#sample-code

No errors occur upon starting the recording, however, I have yet to verify if any recording is actually taking place. Then when I stop recording, the app crashes to the previous activity and on a subsequent attempt the "app keeps crashing" dialog pops up and the app exits.

I have <uses-permission android:name="android.permission.RECORD_AUDIO" /> in the AndroidManifest

private void onRecord(boolean start) {
        if (start) {
            startRecording();
        } else {
            stopRecording();   // Line 53 //
        }
    }

private void startRecording() {
        recorder = new MediaRecorder();
        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        recorder.setOutputFile(fileName);
        System.out.println(fileName);
        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

        try {
            recorder.prepare();
        } catch (IOException e) {
            Log.e(LOG_TAG, "prepare() failed");
            //e.printStackTrace();
        }
    }

private void stopRecording() {
        recorder.stop();       // Line 98 //
        recorder.release();
        recorder = null;
    }

class RecordButton extends AppCompatButton {
        boolean mStartRecording = true;

        OnClickListener clicker = new OnClickListener() {
            public void onClick(View v) {
                onRecord(mStartRecording);
                if(mStartRecording) {
                    setText(R.string.stop_recording);
                } else {
                    setText(R.string.start_recording);
                }
                mStartRecording = !mStartRecording;
            }
        };

        public RecordButton(Context ctx){
            super(ctx);
            setText(R.string.start_recording);
            setOnClickListener(clicker);
        }
    }

Logcat output:

2020-02-14 10:57:09.789 23619-23619/? E/Zygote: accessInfo : 1
2020-02-14 10:57:09.820 23619-23619/? E/.xxxxxx.xxxxxx: Unknown bits set in runtime_flags: 0x8000
2020-02-14 10:57:12.925 23619-23619/xx.xxxxxxx.xxxxxx.xxxxxxx E/MediaRecorder: stop called in an invalid state: 8
2020-02-14 10:57:12.927 23619-23619/xx.xxxxxxx.xxxxxx.xxxxxxx E/AndroidRuntime: FATAL EXCEPTION: main
    Process: xx.xxxxxxx.xxxxxx.xxxxxxx, PID: 23619
    java.lang.IllegalStateException
        at android.media.MediaRecorder._stop(Native Method)
        at android.media.MediaRecorder.stop(MediaRecorder.java:1440)
        at xx.xxxxxxx.xxxxxx.xxxxxxx.RecordActivity.stopRecording(RecordActivity.java:98)
        at xx.xxxxxxx.xxxxxx.xxxxxxx.RecordActivity.onRecord(RecordActivity.java:53)
        at xx.xxxxxxx.xxxxxx.xxxxxxx.RecordActivity.access$000(RecordActivity.java:21)
        at xx.xxxxxxx.xxxxxx.xxxxxxx.RecordActivity$RecordButton$1.onClick(RecordActivity.java:108)
        at android.view.View.performClick(View.java:7866)
        at android.widget.TextView.performClick(TextView.java:14952)
        at android.view.View.performClickInternal(View.java:7839)
        at android.view.View.access$3600(View.java:886)
        at android.view.View$PerformClick.run(View.java:29363)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:237)
        at android.app.ActivityThread.main(ActivityThread.java:7811)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)

I can provide any relevant additional code or details if needed

EDIT Permission is requested with the following:

    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case REQUEST_RECORD_AUDIO_PERMISSION:
                permissionToRecordAccepted  = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                break;
        }
        if (!permissionToRecordAccepted ) finish();

    }

and inside onCreate():

ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION);

EDIT 2 * I've verified that the audio recording file is being created but is 0 B in size


Solution

  • Media Player

    in the below flowchart, stop() valid states are {Prepared, Started, Stopped, Paused, PlaybackCompleted}. other states(Idle,Initialized,Error) are NOT valid states in order to call stop(). otherwise, IllegalStateException thrown.

    enter image description here

    Therefore, for Ensure we should set states listeners for the MediaPlayer. Because the Prepared state is initialized earlier than by setOnPreparedListener for player we can safely stop the player.

    ...
    boolean isPrepared = false;
    ...
    recorder.setOnPreparedListener ....
    {
    ...
    isPrepared = true;
    ...
    }
    ...
    private void stopRecording() {
        if(isPrepared){
            player.stop();  
            ...
        }
    }
    

    I made the code summarized for better understanding.

    Media Recorder

    in the below flowchart, stop() is available only if you are in recording state. otherwise, calling stop() throws IllegalStateException.

    enter image description here

    in order to safely stop or release recorder resources, we have to make sure the recorder has been started.

    recorder.start();   // Recording is now started 
    

    for your specific code

    boolean isRecording = false;
    private void startRecording() {
            recorder = new MediaRecorder();
            recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            recorder.setOutputFile(fileName);
            System.out.println(fileName);
            recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    
            try {
                recorder.prepare();
                recorder.start();
                isRecording = true;
            } catch (IOException e) {
                Log.e(LOG_TAG, "prepare() failed");
                //e.printStackTrace();
            }
    }
    ....
    private void stopRecording() {
        if(isRecording){
            recorder.stop();  
        }
        recorder.reset();   // You can reuse the object by going back to setAudioSource() step
        recorder.release(); // Now the object cannot be reused
        isRecording = false;
    }
    

    if we are in the recording state stop it then release it otherwise just release it.