Java – MediaRecorder.start() throws an IllegalStateException

MediaRecorder.start() throws an IllegalStateException… here is a solution to the problem.

MediaRecorder.start() throws an IllegalStateException

I’ve read similar posts but haven’t found an answer to my question.

I’m writing an application with 2 different MediaRecorders. One for noise detection and the other for recording. What I want to do is – when the first MediaRecorder detects a noise level above 4.0 (which I’m detecting using Google’s SoundMeter class), it will start another MediaRecorder and start recording. If the volume is lower than 4.0 for 10 seconds, stop recording and continue listening. All this is done in an AsynTask, in an endless while(true) loop, where it is only broken when the corresponding button is clicked.

The detection works fine, but throws an IllegalStateException when start() is called on the recording MediaRecorder.

This is an asynchronous task:

private class NoiseDetection extends AsyncTask {
    double currentSoundInputLevel;

@Override
    protected Object doInBackground(Object[] params) {
        int i = 0;
        soundMeter = new SoundMeter();
        try {
            soundMeter.start();
        } catch (IOException e) {
            Log.e("error", e.getMessage());
        }
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Log.e("error", e.getMessage());
            }
            if(isCancelled()){
                soundMeter.stop();
                if(currentlyRecording) {
                    soundRecorder.stop();
                }
                break;
            }
            currentSoundInputLevel = soundMeter.getAmplitudeEMA();
            if(!currentlyRecording && currentSoundInputLevel > 4.0){
                soundRecorder = new SoundRecorder();
                try {
                    soundRecorder.start(getFileNameString());
                    currentlyRecording = true;
                } catch (IOException e) {
                    Log.e("error", e.getMessage());
                }
            } else if(currentlyRecording && currentSoundInputLevel < 4.0) {
                i++;
                if(i > 10) {
                    soundRecorder.stop();
                }
            }
        }
        return null;
    }
}

Here is the audio recorder:

public class SoundRecorder {
    private MediaRecorder mRecorder = null;

public void start(String fileName) throws IOException {
        if (mRecorder == null) {
            mRecorder = new MediaRecorder();
            mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
            mRecorder.setOutputFile(Environment.getExternalStorageDirectory().getPath() + "/" + fileName);
            mRecorder.prepare();
            mRecorder.start();
        }
    }

public void stop() {
        if (mRecorder != null) {
            mRecorder.stop();
            mRecorder.release();
            mRecorder = null;
        }
    }
}

mRecorder.start(); Throws an exception.

I

think the problem lies in the idea of doing everything in this while loop, but I haven’t figured out a better way to achieve the above goal.
Also, I tried different OutputFormats and AudioEncoders without success. (Quotehttps://stackoverflow.com/a/23065021/1826152).

Another note that may be useful is that the file is actually created in the sdcard directory.

The phone I used for development was the Nexus 5. The permissions in the Android List are as follows:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Update:

Now I’m trying to remove the SoundRecorder action from the while loop by creating a RecordingHandler. The new code for doInBackground() is as follows:

    protected Object doInBackground(Object[] params) {
        int i = 0;
        soundMeter = new SoundMeter();
        RecordingHandler recordingHandler = null;
        try {
            soundMeter.start();
        } catch (IOException e) {
            Log.e("error", e.getMessage());
        }
        while(true){
            if(isCancelled()){
                soundMeter.stop();
                if(currentlyRecording && recordingHandler != null){
                    recordingHandler.kill();
                }
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Log.e("error", e.getMessage());
            }
            if(!currentlyRecording && soundMeter.getAmplitudeEMA() > 4.0){
                recordingHandler =  new RecordingHandler(deviceId);
                currentlyRecording = true;
                recordingHandler.run();
            }
        }
        return null;
    }

The RecordingHandler itself is as follows:

public class RecordingHandler implements Runnable {

SoundRecorder soundRecorder;
    SoundMeter soundMeter;
    String deviceID;
    boolean isKilled = false;

public RecordingHandler(String deviceID){
        this.soundRecorder = new SoundRecorder();
        this.soundMeter = new SoundMeter();
        this.deviceID = deviceID;
    }

@Override
    public void run() {
        int i = 0;
        try {
            soundMeter.start();
            soundRecorder.start(getFileNameString());
        } catch (IOException e) {
            Log.e("error", e.getMessage());
        }
        while(true){
            if(isKilled){
                break;
            }
            if(soundMeter.getAmplitudeEMA() < 4.0){
                i++;
                if(i > 10){
                    break;
                }
            } else {
                i = 0;
            }
        }
        soundMeter.stop();
        soundRecorder.stop();
        EavesDrop.currentlyRecording = false;
    }

public void kill(){
        this.isKilled = true;
    }

private String getFileNameString() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return deviceID + "_" + sdf.format(new Date());
    }
}

Now throw IllegalStateException from the Recordinghandler – in soundMeter.start(); Yes.
Considering that this soundMeter object is basically no longer processed in a loop, the idea that the while loop is the culprit should be eliminated. Is there anything I’m missing? Could the problem be that multiple MediaRecorders are working at the same time? As you can see, it is SoundMeter that throws the exception now, not SoundRecorder. In fact – no matter which start() call I put first in RecordingHandler, the same IllegalStateException is thrown.

The problem may be related to Android: Two Instances of Media recorder at the same time are related, unfortunately there is no answer.

Any further help would be appreciated!

Solution

Well, it looks like I solved the problem. The problem seems to be getting multiple instances of MediaRecorder working at the same time. What I’ve done is that I don’t use a separate class for instrumentation and logging now, so that the logger now does its own instrumentation.

First, I started the initial SoundMeter, which listened until the input was over level 4.0. Then I stop the initial SoundMeter and make a new SoundMeter (with a different output directory), which starts recording and recording until the level is below 4.0 for about 10 seconds. The second SoundMeter then stops and the background task can start the initial SoundMeter again.

Here is the code that solves my problem,
Asynchronous tasks:

    protected Object doInBackground(Object[] params) {
        int i = 0;
        soundMeter = new SoundMeter();
        RecordingHandler recordingHandler = null;
        try {
            soundMeter.start("/dev/null");
        } catch (IOException e) {
            Log.e("error", e.getMessage());
        }
        while(true){
            if(isCancelled()){
                soundMeter.stop();
                if(currentlyRecording && recordingHandler != null){
                    recordingHandler.kill();
                }
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Log.e("error", e.getMessage());
            }
            if(!currentlyRecording && soundMeter.getAmplitudeEMA() > 4.0){
                soundMeter.stop();
                recordingHandler =  new RecordingHandler(deviceId);
                currentlyRecording = true;
                recordingHandler.run();
            } else if(!currentlyRecording && !soundMeter.isRunning()){
                try {
                    soundMeter.start("/dev/null");
                } catch (IOException e) {
                    Log.e("error", e.getMessage());
                }
            }
        }
        return null;
    }
}

and RecordingHandler.run().

@Override
public void run() {
    int i = 0;
    try {
        soundMeter.start(getFileNameString());
    } catch (IOException e) {
        Log.e("error", e.getMessage());
    }
    while(true){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Log.e("error", e.getMessage());
        }
        if(isKilled){
            break;
        }
        if(soundMeter.getAmplitudeEMA() < 4.0){
            i++;
            if(i > 10){
                break;
            }
        } else {
            i = 0;
        }
    }
    soundMeter.stop();
    EavesDrop.currentlyRecording = false;
}

The documentation is located http://developer.android.com/reference/android/media/MediaRecorder.html, under the release() method documentation, it discusses that some devices do not support multiple instances. So, it could be a device-specific issue.

Related Problems and Solutions