如何在不停止MediaRecorder的情况下更改输出文件

11

我的项目有一个需求,需要录制视频并上传到服务器,但由于移动网络不稳定,所以我最初决定的做法是每30秒:

  • 停止录制器

  • 重置录制器状态

  • 检索录制器写入的文件,并在不同的线程中上传(多部分表单数据)。

  • 根据当前时间戳的哈希值将录制器的输出文件更改为新文件。

  • 每30秒重复该过程

这样做非常适合我的需求,因为每个30秒的视频文件大小都不超过1MB,并且上传顺利。

但我面临的问题是,每次媒体录制器停止并重新启动时,会有大约500毫秒的延迟,所以我在服务器上接收到的视频每30秒会有这些500毫秒的间隙,这对我目前的情况非常糟糕。因此,我在考虑是否可能在运行过程中修改录制器正在写入的文件?

相关代码:

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();
                    }
                }
            }
        }
    };

这里是 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;
        }
    }

这里是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;
    }

请提供代码,好吗? - Yassin Hajaj
这是一个有趣的问题,我已经点赞了。我认为在录制时可能不需要保存到多个文件中,而是应该在录制完成后再进行拆分。 - Krypton
我在录制视频时分割文件的原因是为了能够在录制过程中上传这些小块,这样用户就可以在上传最后一块时看到非常短的上传时间。 - Bhargav
3个回答

3
您可以通过不调用release()方法和在stopAndResetRecorder()中进行的所有其他销毁来略微加快速度(请参见MediaRecorder状态机的文档)。 您也不需要同时调用stop()reset()
相反,您可以拥有一个中间的resetRecorder()函数,它只执行reset(),然后调用initializeAndStartRecording()。当您完成所有录制时,您可以调用stopRecorder(),这将执行mMediaRecorder的销毁。
正如我所说,这会为您节省一些时间,但目前销毁和重新初始化MediaRecorder的额外开销是否占延迟的重要部分,我不知道。尝试一下,如果不能解决您的问题,我很想知道它能够节省多少时间。

1
显然,只有在录音机释放其资源时,您才能访问该文件,这发生在对录音机对象调用release()时。 - Bhargav
是的,关于您的编辑,uploadQueue.add只是将文件添加到队列中,以及必要的参数,如问题ID。上传的网络操作由后台进程处理。 - Bhargav
抱歉,我删除了编辑,因为我没有正确阅读您的方法调用。一旦我这样做了,很明显您确实将它们添加到队列中。 - Matt Taylor
根据这个问题:http://stackoverflow.com/questions/23343789/android-mediarecorder-multiple-files 尝试做类似的事情时,启动MediaRecorder时总会有一定的延迟。如果您查看@CommonsWare的个人资料,他们在涉及Android方面的知识是非常专业的。 - Matt Taylor
1
很遗憾,我认为这是不可能的。然而,可能的是拥有两个 mediaRecorder 实例。你会遇到一个小问题,即无法同时将它们都设置为相机源,但是你可以在第一个实例上进行所有其他配置,同时在第二个实例上进行录制,然后一旦你释放了第一个实例,就将相机源分配给第二个实例并开始录制,然后在两个 mediaRecorder 之间来回切换。 - Matt Taylor
显示剩余2条评论

0

对我来说,setOutputFile似乎调用了一个涉及到MediaRecorder的源的本地方法,所以我认为没有简单的方法可以同时写入到不同的文件中。

那如果最后将它一次性上传呢,但是允许用户在开始上传过程后进行其他操作?这样用户就不会注意到上传所花费的时间,然后你可以在上传成功/失败后通知他。

[编辑:]尝试将流上传到服务器,让服务器进行分块机制来分开文件。在这里你可以找到如何做到这一点的简要说明。


我仍然更喜欢用较困难的方式来完成这个任务,也许可以扩展mediaRecorder并进行必要的修改?我的意思是肯定有办法做到这一点,对吧?你可以从录音机中获取流数据,然后只需释放当前文件并将流指向下一个文件,这些块的聚合在服务器端处理。我没有足够的本地编程知识来修改mediaRecorder或编写自己的程序。 - Bhargav
流处理被隐藏在Android的本地部分中,因此我担心您无法在Java端获取它。您可以浏览cpp源代码(https://github.com/android/platform_frameworks_base/blob/master/media/jni/android_media_MediaRecorder.cpp)。 - abbath
啊抱歉,你可以按照这个答案描述的方式添加输出流,但我不确定在录制过程中是否可以更改它(我相信你不能)。 - abbath
关于您的编辑,我的老板有点忙,还没有在服务器端启用流式传输,因此目前流式传输不可行,正如您所说,最理想的方法是流式传输到服务器,但我相信对于具有足够本地编程知识的人来说,我在这个问题中所问的应该是可行的。 - Bhargav
那么你的问题只能通过正确的C++知识来解决,我也会在问题中添加一个C++标签。 - abbath

0

显然,MediaRecorder.setOutputFile()也接受FileDescriptor

所以,如果您在低层次(JNI)上编程,可以将进程的输入流表示为文件描述符,并在需要时将该流写入不同的文件中。但是这将涉及从Java管理本地“路由器”进程。

不幸的是,在Java API方面,您运气不佳。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接