How to change the output file of a mediarecorder without stopping the mediarecorder
Asked Answered
B

3

11

I have a requirement in my project, where video is being recorded and uploaded to the server, but since mobile networks are not reliable, at the beginning what I decided to do was every 30 secs

  • stop the recorder

  • reset the recorder state

  • retrieve the file written to by the recorder and upload (multipart form data) it in a different thread.

  • change the outfile of the recorder to a new file based on the hash of the current timestamp.

  • repeat process every 30 secs

Doing this suits my needs perfectly as each of the 30sec video file sizes are not more than 1MB and upload happens smoothly.

But the problem I am facing is that every time the media recorder stops and starts again there is a delay of about 500ms, so the video that I receive at the server has these 500ms breaks every 30secs which is really bad for my current situation, so I was thinking if it would be possible to just change the file that the recorder is writing to on the fly?

Relevant code:

GenericCallback onTickListener = new GenericCallback() {
        @Override
        public void execute(Object data) {
            int timeElapsedInSecs = (int) data;
            if (timeElapsedInSecs % pingIntervalInSecs == 0) {
                new API(getActivity().getApplicationContext()).pingServer(objInterviewQuestion.getCurrentAccessToken(),
                        new NetworkCallback() {
                    @Override
                    public void execute(int response_code, Object result) {
                        // TODO: HANDLE callback
                    }
                });
            }
            if (timeElapsedInSecs % uploadIntervalInSecs == 0 && timeElapsedInSecs < maxTimeInSeconds) {
                if (timeElapsedInSecs / uploadIntervalInSecs >= 1) {
                    if(stopAndResetRecorder()) {
                        openConnectionToUploadQueue();
                        uploadQueue.add(
                                new InterviewAnswer(0,
                                        objInterviewQuestion.getQid(),
                                        objInterviewQuestion.getAvf(),
                                        objInterviewQuestion.getNext(),
                                        objInterviewQuestion.getCurrentAccessToken()));
                        objInterviewQuestion.setAvf(MiscHelpers.getOutputMediaFilePath());
                        initializeAndStartRecording();
                    }
                }
            }
        }
    };

here is initializeAndStartRecording() :

private boolean initializeAndStartRecording() {
        Log.i("INFO", "initializeAndStartRecording");
        if (mCamera != null) {
            try {

                mMediaRecorder = CameraHelpers.initializeRecorder(mCamera,
                        mCameraPreview,
                        desiredVideoWidth,
                        desiredVideoHeight);

                mMediaRecorder.setOutputFile(objInterviewQuestion.getAvf());
                mMediaRecorder.prepare();
                mMediaRecorder.start();
                img_recording.setVisibility(View.VISIBLE);

                is_recording = true;
                return true;
            } catch (Exception ex) {
                MiscHelpers.showMsg(getActivity(),
                        getString(R.string.err_cannot_start_recorder),
                        AppMsg.STYLE_ALERT);
                return false;
            }


        } else {
            MiscHelpers.showMsg(getActivity(), getString(R.string.err_camera_not_available),
                    AppMsg.STYLE_ALERT);
            return false;
        }
    }

Here is stopAndResetRecorder:

boolean stopAndResetRecorder() {
        boolean success = false;
        try {
            if (mMediaRecorder != null) {
                try {
                    //stop recording
                    mMediaRecorder.stop();
                    mMediaRecorder.reset();
                    mMediaRecorder.release();
                    mMediaRecorder = null;
                    Log.d("MediaRecorder", "Recorder Stopped");
                    success = true;
                } catch (Exception ex) {
                    if(ex != null && ex.getMessage()!=null && ex.getMessage().isEmpty()){
                        Crashlytics.log(Log.ERROR, "Failed to stop MediaRecorder", ex.getMessage());
                        Crashlytics.logException(ex);
                    }
                    success = false;
                } finally {
                    mMediaRecorder = null;
                    is_recording = false;
                    is_recording = false;
                }
            }
        } catch (Exception ex) {
            success = false;
        }
        Log.d("MediaRecorder", "Success = " + String.valueOf(success));
        return success;
    }
Bushwhack answered 12/10, 2015 at 12:44 Comment(3)
Would you please provide the code?Wallop
Upvoted as this is an interesting question. I think probably you need not to save into multiple files at recording time. You should split after the recording is done.Roderick
no the reason why I am splitting during recording time is because I want to upload the chunk while the video is recording, that way I upload small chunks during recording time and ultimately when the last chunk is submitted the upload time the user sees is very smallBushwhack
C
3

You can speed it up slightly by not calling the release() method and all of the rest of the destruction that you do in stopAndResetRecorder() (see the documentation for the MediaRecorder state machine). You also don't need to call both stop() and reset().

You could instead have an intermediate resetRecorder() function which just performed reset() then call initializeAndStartRecording(). When you finish all of your recording, you could then call stopRecorder() which would perform the destruction of your mMediaRecorder.

As I say, this will save you some time, but whether the extra overhead you currently have of destroying and re-initialising the MediaRecorder is a significant portion of the delay I don't know. Give that a try, and if it doesn't fix your problem, I'd be interested to know how much time it did/didn't save.

Catanddog answered 16/10, 2015 at 10:39 Comment(7)
apparently you only get access to the file once the recorder releases its resources that happens when you call release() on the recorder objectBushwhack
yes referring to your edit, the uploadQueue.add just adds the file to the queue to be uploaded along with the necessary parameters such as question id, the network operation of uploading is taken care of by a background processBushwhack
Sorry, I removed the edit as I didn't read your method calls properly, as once I did it was clear you were indeed adding them to a queue.Catanddog
According to this question: #23344289 trying to do a similar thing, there's always going to be a certain amount of latency when starting a MediaRecorder. If you'll have a look at @CommonsWare 's profile, they know what they're talking about when it comes to Android.Catanddog
Yes thats exactly the reason why I want to release the current file being output to and change to a new file on the fly while recording without stopping and restarting the recorder againBushwhack
Sadly I don't think that's possible. What might be possible however, is to have two mediaRecorder instances. You'll get into a slight problem where you can't set them both to the Camera source at the same time, but you could do all of the rest of the configuration on your 2nd instance while recording on the first one, then as soon as you've released the first one, assign the Camera source to the 2nd and start recording, then just ping-pong between the two mediaRecordersCatanddog
Almost 2021 here. Is this still the status quo?Religion
I
0

It seems to me that the setOutputFile calls a native method regarding to MediaRecorder's source, so I don't think there's an easy way to write into seperate files at the same time.

What about uploading it in one chunk at the end, but allow the user to do anything after starting the uploading process? Then the user wouldn't notice how much time the upload takes, and you can notify him later when the upload successed/failed.

[Edit:] Try to stream upload to server, where the server does the chunking mechanism to seperate files. Here you can have a brief explanation how to do so.

Islaen answered 16/10, 2015 at 10:24 Comment(5)
I would still prefer the hard way of doing this, maybe extend mediaRecorder and do the necessary modifications? I mean there must be a way to do this right? You get data in stream from the recorder and all you have to do is to release the current file and point the stream to the next file, the aggregation of these chunks is taken care of in the server side. I don't have enough knowledge in native programming to modify the mediaRecorder or write my ownBushwhack
The stream processing is hidden in the native part of android, so you can't get it in java side I fear. You can browse the cpp source codeIslaen
Ah sorry, you can add output stream in a way, as this answer describes, not sure whether you can change it during recording (I'm pretty sure you can't)Islaen
Referring to your edit, my boss is a little busy and hasn't enabled streaming on the server side yet so sadly streams are not an option at present, as you say the ideal way would be to stream to server, but I'm sure what I am asking in this question should be doable by someone with enough knowledge in native programmingBushwhack
Then your answer can be only solved with proper cpp knowledge, I would add a cpp tag to the question as well.Islaen
S
0

Apparently MediaRecorder.setOutputFile() also accepts a FileDescriptor.

So, if you were programming at low level (JNI) you could have represented a process's input stream as a file descriptor and in turn, had that process write to different files when desired. But that would involve managing that native "router" process from java.

Unfortunately, on java API side, you are out of luck.

Strephon answered 22/10, 2015 at 7:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.