【问题标题】:How to change the output file of a mediarecorder without stopping the mediarecorder如何在不停止 mediarecorder 的情况下更改 mediarecorder 的输出文件
【发布时间】:2016-01-09 22:53:52
【问题描述】:

我的项目中有一个需求,就是录制视频并上传到服务器,但由于移动网络不可靠,一开始我决定每 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;
    }

【问题讨论】:

  • 能否提供代码?
  • 赞成,因为这是一个有趣的问题。我认为您可能不需要在录制时保存到多个文件中。你应该在录制完成后分开。
  • 没有我在录制期间拆分的原因是因为我想在视频录制时上传块,这样我在录制期间上传小块,最终在提交最后一个块时用户看到的上传时间非常短

标签: java android c++ native-code


【解决方案1】:

您可以通过不调用release() 方法以及您在stopAndResetRecorder() 中执行的所有其他销毁操作来稍微加快它(请参阅documentation for the MediaRecorder state machine)。 您也不需要同时调用stop()reset()

您可以改为使用刚刚执行reset() 的中间resetRecorder() 函数,然后调用initializeAndStartRecording()。当您完成所有录制后,您可以调用stopRecorder(),这将销毁您的mMediaRecorder

正如我所说,这将为您节省一些时间,但是您目前销毁和重新初始化 MediaRecorder 的额外开销是否是延迟的重要部分,我不知道。试一试,如果它不能解决您的问题,我很想知道它节省/没有节省多少时间。

【讨论】:

  • 显然,只有在记录器释放其资源时,您才能访问文件,当您在记录器对象上调用 release() 时会发生这种情况
  • 是指您的编辑,uploadQueue.add只是将文件连同问题id等必要参数添加到待上传队列中,上传的网络操作由后台处理进程
  • 抱歉,我删除了编辑,因为我没有正确阅读您的方法调用,因为一旦我这样做了,很明显您确实将它们添加到队列中。
  • 根据这个问题:stackoverflow.com/questions/23343789/… 尝试做类似的事情,在启动MediaRecorder 时总会有一定的延迟。如果您查看 @CommonsWare 的个人资料,他们就会知道他们在谈论 Android。
  • 遗憾的是,我认为这是不可能的。然而,可能有两个 mediaRecorder 实例。您会遇到一个小问题,您无法同时将它们都设置为相机源,但您可以在第二个实例上完成所有其余配置,同时在第一个实例上录制,然后尽快当您发布第一个时,将相机源分配给第二个并开始录制,然后在两个 mediaRecorders 之间进行乒乓球
【解决方案2】:

在我看来,setOutputFile 调用了关于MediaRecorder's source 的本机方法,所以我认为没有一种简单的方法可以同时写入单独的文件。

如果在最后以一个块的形式上传它,但允许用户在开始上传过程后做任何事情呢?这样用户就不会注意到上传需要多少时间,您可以稍后在上传成功/失败时通知他。

[编辑:] 尝试流式上传到服务器,服务器执行分块机制来分隔文件。 Here you can have 简要说明如何操作。

【讨论】:

  • 我还是更喜欢硬性的做法,也许扩展 mediaRecorder 并进行必要的修改?我的意思是必须有办法做到这一点吗?您从记录器获取流中的数据,您所要做的就是释放当前文件并将流指向下一个文件,这些块的聚合在服务器端进行处理。我没有足够的本地编程知识来修改 mediaRecorder 或自己编写
  • 流处理隐藏在android的native部分,所以我担心在java端你不能得到它。可以浏览cppsource code
  • 抱歉,您可以通过某种方式添加输出流,如this answer describes,不确定您是否可以在录制期间更改它(我很确定您不能)
  • 参考您的编辑,我的老板有点忙,还没有在服务器端启用流式传输,所以遗憾的是,目前还不能选择流式传输,正如您所说的理想方式是流式传输到服务器,但我确定我在这个问题中提出的问题应该由具有足够本机编程知识的人来完成
  • 那么你的答案只能通过适当的cpp知识来解决,我也会在问题中添加一个cpp标签。
【解决方案3】:

显然MediaRecorder.setOutputFile() 也接受FileDescriptor

因此,如果您在低级别 (JNI) 编程,您可以将进程的输入流表示为文件描述符,然后让该进程在需要时写入不同的文件。但这将涉及从 java 管理本机“路由器”进程。

不幸的是,在 java API 方面,你运气不好。

【讨论】:

    猜你喜欢
    • 2016-05-14
    • 2014-10-25
    • 1970-01-01
    • 1970-01-01
    • 2017-03-29
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多