erleizh / VideoRecorder

高性能任意尺寸视频录制 (相机支持的最大预览尺寸),分段录制,删除,canvas
Apache License 2.0
66 stars 20 forks source link

Is it possible to record in background (Service)? #1

Closed anonym24 closed 6 years ago

anonym24 commented 6 years ago

We can do it with FFmpeg and just encode bytes from onPreviewFrame With FFmpeg it's easy because we don't work with SurfaceView and other stuff which works only for Activity So we can easily set onPreviewFrame for Camera in service (of course initially we create Camera in Activity but when we go background we start Service and pass object of Camera to that Service and just continue using onPreviewFrame callback for recording bytes in Service)

anonym24 commented 6 years ago

Is something like this would be possible for MediaCodec/GLES stuff?

erleizh commented 6 years ago

这个项目暂时不适用 . 需要后台录制的话, 如果不需要加水印可以考虑在AudioVideoRecordingSample 的基础上修改 ,

如果需要加水印 , 那么也可以在这个项目的基础上修改 , 通过 OffscreenSurface.java 这个理论上是可以的

erleizh commented 6 years ago

@anonym24 我本地最新的代码似乎可以支持离屏录制 , 我下午将尝试一下 .

erleizh commented 6 years ago

@anonym24 我试了试 , 可以的 , 待会我将更新代码

erleizh commented 6 years ago

@anonym24 MultiPartRecorderFragment.java

    private void initRecorder() {
//         ICameraPreview cameraPreview = new DefaultCameraPreview(mTextureView);
        ICameraPreview cameraPreview = new OffscreenCameraPreview(getContext(), 1920, 1920);

        Camera.CameraBuilder cameraBuilder = new Camera.CameraBuilder(getActivity())
                .useDefaultConfig()
                .setPreviewSize(new Size(2048, 1536))
                .setRecordingHint(true)
                .setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);

        VideoRecorder.Builder builder = new VideoRecorder.Builder(cameraPreview)
                .setCallbackHandler(new CallbackHandler())
                .setLogFPSEnable(false)
                .setCameraBuilder(cameraBuilder)
                .setOutPutPath(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), File.separator + "VideoRecorder").getAbsolutePath())
                .setFrameRate(30)
                .setChannelCount(1);

        MultiPartRecorder.Builder multiBuilder = new MultiPartRecorder.Builder(builder);
        mRecorder = multiBuilder
                .addPartListener(new MultiPartRecorder.VideoPartListener() {
                    @Override
                    public void onRecordVideoPartStarted(MultiPartRecorder.Part part) {
//                LogUtil.logd("onRecordVideoPartStarted \t" + part.toString());
                    }

                    @Override
                    public void onRecordVideoPartSuccess(MultiPartRecorder.Part part) {
//                LogUtil.logd("onRecordVideoPartSuccess \t" + part.toString());
                    }

                    @Override
                    public void onRecordVideoPartFailure(MultiPartRecorder.Part part) {
                        LogUtil.loge("onRecordVideoPartFailure \t" + part.file);
                        mRecorderIndicator.removePart(part.file.getAbsolutePath());
                        mRecorder.removePart(part.file.getAbsolutePath());
                    }
                })
                .setMergeListener(new MultiPartRecorder.VideoMergeListener() {
                    @Override
                    public void onStart() {
                        LogUtil.logd("merge onStart");
                    }

                    @Override
                    public void onSuccess(File outFile) {
                        LogUtil.logd("merge Success \t" + outFile);
                        Intent intent = new Intent(Intent.ACTION_VIEW);
                        if (Build.VERSION.SDK_INT >= 24) {
                            intent.setDataAndType(FileProvider.getUriForFile(getContext().getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", outFile), "video/*");
                            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                        } else {
                            intent.setDataAndType(Uri.fromFile(outFile), "video/*");
                        }
                        startActivity(intent);
                    }

                    @Override
                    public void onError(Exception e) {
                        LogUtil.logd("merge Error \t" + e.toString());
                    }

                    /**
                     * 合并进度
                     *
                     * @param value 0 - 1
                     */
                    @Override
                    public void onProgress(float value) {
                        LogUtil.logd("merge onProgress \t" + value);
                    }

                })
                .setFileFilter(new MultiPartRecorder.FileFilter() {
                    @Override
                    public boolean filter(MultiPartRecorder.Part part) {
                        return part.duration > 1500;
                    }
                })
                .build();

        mCameraController = mRecorder.getCameraController();
    }

你可以尝试放到service里面 , 应该是可以的

anonym24 commented 6 years ago

I guess we need to use two SurfaceTexture at the same time one is used for preview (to show unchanged frames on user's display) another is used for recording which can be used in service (background recording)

when activity becomes invisible we should stop to draw unchanged frames on the first SurfaceTexture for preview but we should continue to draw frames on the second SurfaceTexture for video recording

erleizh commented 6 years ago

@anonym24 你或许可以尝试一下 android.view.TextureView#setSurfaceTexture , 自己创建SurfaceTexture , 然后设置到TextureView 我不确定可不可以

anonym24 commented 6 years ago

I also want to try with Camera2 API, it has great feature - addTarget: https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L689

Legacy Camera doesn't allow to use setPreviewTexture and setPreviewDisplay at the same time, but it seems Camera2 allows something like this, you can set many targets, but I'm not sure, need to test it

erleizh commented 6 years ago

你试试修改OffscreenCameraPreview getSurface 方法 , 让他返回自己创建的SurfaceTexture 参考 ,com.erlei.videorecorder.recorder.CameraGLRenderer#initSurfaceTexture

然后在 onSurfaceTextureDestroyed retrun false , 让活动销毁时不销毁SurfaceTexture , 一直保存这个 SurfaceTexture

anonym24 commented 6 years ago

Seems Camera2 isn't good (many phone manufactures didn't care about this API) - https://stackoverflow.com/questions/52374888/camera2-1440x1080-is-maximum

Though it was good to set two SurfaceTexture for preview targets, I set one from your RenderThread and another from TextureView and it was working ok but this issue with bad resolution spoiled everything

So I will try to use what we have with Legacy Camera...

anonym24 commented 6 years ago

yes onSurfaceTextureDestroyed return false worked nice, thanks

to draw frame without effects on preview and with effects on mediacodec surface (during video recording) I modified CameraGLRenderer's onDrawFrame method, now it takes one boolean to set if draw with effects or not