Camera2 API работает в фоновом режиме

Я использовал Camera2 API для реализации фонового видеорегистратора, работающего как сервис и записывающего с передней камеры. Для этого я создал новый SurfaceView, установите его размер 1x1 и переместите его в верхний левый угол. Мой код показан ниже. Я использую Android 5.1.

С Camera API он работал очень хорошо, но, к сожалению, частота кадров составляет всего 20 кадров в секунду (а когда я увеличиваю компенсацию экспозиции, она падает еще больше), хотя с Open Camera Приложение у меня 30 кадров в секунду также с повышенной компенсацией экспозиции. Вот почему я хочу попробовать Camera2 API (в надежде получить более высокий fps). К сожалению, я получаю следующую ошибку:

MediaRecoder: setOutputFile called in an invalid state(2)
MediaRecorder: start called in an invalid state: 2

Вот мой код:

public class RecorderServiceCamera2 extends Service implements SurfaceHolder.Callback {
    private WindowManager windowManager;
    private SurfaceView surfaceView;
    private CameraDevice mCamera;
    private MediaRecorder mediaRecorder = null;
    private CaptureRequest mCaptureRequest;
    private CameraCaptureSession mSession;

    @Override
    public void onCreate() {
        // Create new SurfaceView, set its size to 1x1, move it to the top left corner and set this service as a callback
        windowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);

        surfaceView = new SurfaceView(this);
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                1, 1,
                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                PixelFormat.TRANSLUCENT
        );
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        windowManager.addView(surfaceView, layoutParams);
        surfaceView.getHolder().addCallback(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Intent notificationIntent = new Intent(this, MainActivity.class);

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(this)
                //.setSmallIcon(R.mipmap.app_icon)
                .setContentTitle("Background Video Recorder")
                .setContentText("")
                .setContentIntent(pendingIntent).build();

        startForeground(MainActivity.NOTIFICATION_ID_RECORDER_SERVICE, notification);

        return Service.START_NOT_STICKY;
    }

    @Override
    public void surfaceCreated(final SurfaceHolder surfaceHolder) {
        mediaRecorder = new MediaRecorder();

        try {
            CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
            String[] cameras = manager.getCameraIdList();
            CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameras[1]);
            StreamConfigurationMap configs = characteristics.get(
                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = configs.getOutputSizes(MediaCodec.class);

            final Size sizeHigh = sizes[0];

            manager.openCamera(cameras[1], new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice camera) {
                    mCamera = camera;
                    mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
                    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
                    mediaRecorder.setMaxFileSize(0);
                    mediaRecorder.setOrientationHint(0);

                    mediaRecorder.setOutputFile("test.mp4");
                    try { mediaRecorder.prepare(); } catch (Exception ignored) {}
                    List<Surface> list = new ArrayList<>();
                    list.add(surfaceHolder.getSurface());

                    try {
                        CaptureRequest.Builder captureRequest = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
                        captureRequest.addTarget(surfaceHolder.getSurface());
                        mCaptureRequest = captureRequest.build();
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }

                    try {
                        mCamera.createCaptureSession(list, new CameraCaptureSession.StateCallback() {
                            @Override
                            public void onConfigured(@NonNull CameraCaptureSession session) {
                                mSession = session;
                            }

                            @Override
                            public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                                mSession = session;
                            }
                        }, null);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }

                    mediaRecorder.start();

                    try {
                        mSession.setRepeatingRequest(mCaptureRequest,
                                new CameraCaptureSession.CaptureCallback() {
                                    @Override
                                    public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
                                        super.onCaptureStarted(session, request, timestamp, frameNumber);
                                    }

                                    @Override
                                    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                                        super.onCaptureCompleted(session, request, result);
                                    }

                                    @Override
                                    public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
                                    }
                                }, null);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onDisconnected(@NonNull CameraDevice camera) {
                }

                @Override
                public void onError(@NonNull CameraDevice camera, int error) {
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    // Stop recording and remove SurfaceView
    @Override
    public void onDestroy() {
        mediaRecorder.stop();
        mediaRecorder.reset();
        mediaRecorder.release();
        mCamera.close();
        mediaRecorder= null;

        windowManager.removeView(surfaceView);
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {}

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {}

    @Override
    public IBinder onBind(Intent intent) { return null; }
}

1 ответ

Вам не нужно устанавливать SurfaceView с Camera2 или даже с Camera1 (с последним просто создайте SurfaceTexture, чтобы получить Surface, и никогда не вызывайте updateTexImage; с первым просто не включайте предварительный просмотр Surface в все, это не нужно).

Тем не менее, вы пытаетесь использовать одну и ту же поверхность как в Camera2, так и в MediaRecorder; это не работает MediaRecorder вообще не нужно рисовать на Surface для записи, вы можете просто оставить эту часть отключенной. Это только для того, чтобы MediaRecorder мог использоваться независимо от API камеры, где он управляет камерой за кулисами.

Я подозреваю, что вызов MediaRecorder.prepare() выдает ошибку, которую вы игнорируете, из-за того, что предварительный просмотр Surface уже используется, поэтому запуск позже не работает.

И да, как говорит CommonsWare, вам, вероятно, понадобится служба переднего плана в Android P.

Другие вопросы по тегам