Android Mediarecorder setNextOutputFile IllegalStateException

4

I am trying to split my audio recording with android mediarecorder into multiple files, but whenever I set the next output file, I got an illegalstate exception.

Here's the code:

    private void getMediaRecorderReady(String filePath, FileDescriptor nextFile) {
    bufferSize = 88200;
    recorder = new MediaRecorder();
    recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
    recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
    recorder.setAudioEncodingBitRate(64000);
    recorder.setAudioSamplingRate(44100);
    recorder.setAudioChannels(1);
    recorder.setOutputFile(filePath);
    recorder.setMaxDuration(1000);
    recorder.setMaxFileSize(100000);
    try {
        recorder.prepare();
        recorder.setNextOutputFile(nextFile);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

And here's the error:

E/MediaRecorder: setNextOutputFile failed: -38
E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
              Process: com.example.a49164.teachmi, PID: 31554
              java.lang.RuntimeException: An error occurred while executing doInBackground()
                  at android.os.AsyncTask$3.done(AsyncTask.java:353)
                  at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
                  at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
                  at java.util.concurrent.FutureTask.run(FutureTask.java:271)
                  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
                  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
                  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
                  at java.lang.Thread.run(Thread.java:764)
               Caused by: java.lang.IllegalStateException
                  at android.media.MediaRecorder._setNextOutputFile(Native Method)
                  at android.media.MediaRecorder.setNextOutputFile(MediaRecorder.java:878)
                  at com.example.a49164.teachmi.ExComThread.getMediaRecorderReady(ExComThread.java:189)
                  at com.example.a49164.teachmi.ExComThread.soundTransfer(ExComThread.java:211)
                  at com.example.a49164.teachmi.ExComThread.doInBackground(ExComThread.java:82)
                  at com.example.a49164.teachmi.ExComThread.doInBackground(ExComThread.java:46)
                  at android.os.AsyncTask$2.call(AsyncTask.java:333)
                  at java.util.concurrent.FutureTask.run(FutureTask.java:266)
                  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) 
                  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
                  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
                  at java.lang.Thread.run(Thread.java:764) 

So, after doing some digging, the error -38 equals to "-ENOSYS" or "INVALID_OPERATION", which seems to happen if the recorder is null or prepare hasn't been called (but I do call prepare() first).

Here's some native android code:

enum {
OK                = 0,    // Everything's swell.
NO_ERROR          = 0,    // No errors.

UNKNOWN_ERROR       = 0x80000000,
NO_MEMORY           = -ENOMEM,
INVALID_OPERATION   = -ENOSYS,
BAD_VALUE           = -EINVAL,
BAD_TYPE            = 0x80000001,
NAME_NOT_FOUND      = -ENOENT,
PERMISSION_DENIED   = -EPERM,
NO_INIT             = -ENODEV,
ALREADY_EXISTS      = -EEXIST,
DEAD_OBJECT         = -EPIPE,
FAILED_TRANSACTION  = 0x80000002,
JPARKS_BROKE_IT     = -EPIPE,
#if !defined(HAVE_MS_C_RUNTIME)
BAD_INDEX           = -EOVERFLOW,
NOT_ENOUGH_DATA     = -ENODATA,
WOULD_BLOCK         = -EWOULDBLOCK, 
TIMED_OUT           = -ETIMEDOUT,
UNKNOWN_TRANSACTION = -EBADMSG,
#else    
BAD_INDEX           = -E2BIG,
NOT_ENOUGH_DATA     = 0x80000003,
WOULD_BLOCK         = 0x80000004,
TIMED_OUT           = 0x80000005,
UNKNOWN_TRANSACTION = 0x80000006,
#endif    
FDS_NOT_ALLOWED     = 0x80000007,
};

Native nextoutputfile:

status_t MediaRecorder::setNextOutputFile(int fd)
{
ALOGV("setNextOutputFile(%d)", fd);
if (mMediaRecorder == NULL) {
    ALOGE("media recorder is not initialized yet");
    return INVALID_OPERATION;
}
// It appears that if an invalid file descriptor is passed through
// binder calls, the server-side of the inter-process function call
// is skipped. As a result, the check at the server-side to catch
// the invalid file descritpor never gets invoked. This is to workaround
// this issue by checking the file descriptor first before passing
// it through binder call.
int flags = fcntl(fd, F_GETFL);
if (flags == -1) {
    ALOGE("Fail to get File Status Flags err: %s", strerror(errno));
}
// fd must be in read-write mode or write-only mode.
if ((flags & (O_RDWR | O_WRONLY)) == 0) {
    ALOGE("File descriptor is not in read-write mode or write-only mode");
    return BAD_VALUE;
}
status_t ret = mMediaRecorder->setNextOutputFile(fd);
if (OK != ret) {
    ALOGE("setNextOutputFile failed: %d", ret);
}
return ret;
}

I may be wrong about the fact that -38 does equal -ENOSYS, but still, I have no idea why it's not working, since I'm passing a file descriptor and call it after prepare, as stated in the android documentation.

I really welcome your help, thanks !

EDIT: I tried to remove the setNextOutputFile(nextFile) right after prepare and only keep the one here:

    recorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
        @Override
        public void onInfo(MediaRecorder mr, int infoCode, int extra) {
            System.out.println("----------------------------------------onMaxFileSizeApproaching " + infoCode);
            if (infoCode == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
                System.out.println("max sized reached");
                String path = Environment.getExternalStorageDirectory().getPath() + "/dataSound" + currFile + ".aac";
                File nextFile = new File(path);
                try {
                    FileOutputStream fos = new FileOutputStream(nextFile);
                    recorder.setNextOutputFile(fos.getFD());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                String currPath = Environment.getExternalStorageDirectory().getPath() + "/dataSound" + (currFile - 1) + ".aac";
                sendDataPath = currPath;
                currFile++;
            }
        }
    });

but to no avail...

android
mediarecorder
asked on Stack Overflow Jul 1, 2018 by Angelo • edited Jul 1, 2018 by Angelo

2 Answers

1

This mean that the setNextOutputFile() is called before prepare() finish it's own work.

So you should wait until your file prepared as discussed in the documentation.

The file is not used until switching to that output. Application will receive MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED

You should listen for the file info like:

recorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
        @Override
        public void onInfo(MediaRecorder mediaRecorder, int what, int extra) {
            if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)
                recorder.setNextOutputFile(nextFile);
        }
    });

Also when the next output file is used. Application will not be able to set a new output file if the previous one has not been used. Application is responsible for cleaning up unused files after stop() is called.

answered on Stack Overflow Jul 1, 2018 by (unknown user) • edited Jul 1, 2018 by (unknown user)
0

As per the documentation, setNextOutputFile API must be called right after receiving on the MediaRecorder.OnInfoListener a "what" code of MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING

mediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
    @Override
    public void onInfo(MediaRecorder mr, int what, int extra) {
        Toast.makeText(RecordActivity.this, "MediaRecorder onInfo:"+what, Toast.LENGTH_LONG).show();
        Log.w("RecordActivity","MediaRecorder onInfo:"+what);
        if (what == MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING) {

            File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), "MyRecordings");
            if (!mediaStorageDir.exists()){
                mediaStorageDir.mkdirs();
            }
            Date date= new java.util.Date();
            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
                    .format(date.getTime());
            String mediaFilePath = mediaStorageDir.getPath() + File.separator +
                    "VID_"+ timeStamp + ".mp4";
            File mediaFile = new File(mediaFilePath);

            Log.e("RecordActivity",mediaFilePath);
            try {
                RandomAccessFile f = new RandomAccessFile(mediaFile, "rw");
                try {
                    mediaRecorder.setNextOutputFile(f.getFD());

                } finally {
                    f.close();
                }
            } catch(Exception ex) {
                Log.e("RecordActivity","",ex);
            }
        }
    }

});
answered on Stack Overflow Apr 8, 2019 by Bharat Biswal

User contributions licensed under CC BY-SA 3.0