Android Camera2, снимайте постоянно

Мне нужно фотографировать постоянно с Camera2 API. Он отлично работает на устройствах высокого класса (например, Nexus 5X), но на более медленных (например, Samsung Galaxy A3) предварительный просмотр зависает.

Код немного длинный, поэтому я публикую только самые важные части:

Метод вызван, чтобы начать мой предварительный просмотр:

private void startPreview() {

    SurfaceTexture texture = mTextureView.getSurfaceTexture();

    if(texture != null) {

        try {

            // We configure the size of default buffer to be the size of camera preview we want.
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

            // This is the output Surface we need to start preview.
            Surface surface = new Surface(texture);

            // We set up a CaptureRequest.Builder with the output Surface.
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);

            // Here, we create a CameraCaptureSession for camera preview.
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {

                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {

                        // If the camera is already closed, return:
                        if (mCameraDevice == null) { return; }

                        // When the session is ready, we start displaying the preview.
                        mCaptureSession = cameraCaptureSession;

                        // Auto focus should be continuous for camera preview.
                        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                        mPreviewRequest = mPreviewRequestBuilder.build();

                        // Start the preview
                        try { mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mPreviewBackgroundHandler); }
                        catch (CameraAccessException e) { e.printStackTrace(); }
                    }

                    @Override
                    public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                        Log.e(TAG, "Configure failed");
                    }
                }, null
            );
        }
        catch (CameraAccessException e) { e.printStackTrace(); }
    }
}

Метод, призванный сделать снимок:

private void takePicture() {

    try {

        CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(mImageReader.getSurface());
        mCaptureSession.capture(captureBuilder.build(), null, mCaptureBackgroundHandler);
    }
    catch (CameraAccessException e) { e.printStackTrace(); }
}

А вот и мой ImageReader:

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {

    @Override
    public void onImageAvailable(final ImageReader reader) {

        mSaveBackgroundHandler.post(new Runnable() {

            @Override
            public void run() {

                // Set the destination file:
                File destination = new File(getExternalFilesDir(null), "image_" + mNumberOfImages + ".jpg");
                mNumberOfImages++;

                // Acquire the latest image:
                Image image = reader.acquireLatestImage();

                // Save the image:
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);

                FileOutputStream output = null;
                try {
                    output = new FileOutputStream(destination);
                    output.write(bytes);
                }
                catch (IOException e) { e.printStackTrace(); }
                finally {

                    image.close();

                    if (null != output) {

                        try { output.close(); }
                        catch (IOException e) { e.printStackTrace(); }
                    }
                }

                // Take a new picture if needed:
                if(mIsTakingPictures) {
                    takePicture();
                }
            }
        });
    }
};

У меня есть кнопка, которая переключает mIsTakingPictures логическое значение, и делает первый вызов takePicture.

Напомним, я использую 3 темы:

  • один для предварительного просмотра
  • один для захвата
  • один для сохранения изображения

Что может быть причиной этого замораживания?

2 ответа

Невозможно избежать потери кадров при предварительном просмотре, когда вы постоянно снимаете изображения на слабых устройствах. Единственный способ избежать этого - на устройствах, которые поддерживают TEMPLATE_ZERO_SHUTTER_LAG и используя reprocessableCaptureSession, Документация об этом довольно ужасна, и найти способ реализовать ее может быть одиссея. У меня есть эта проблема несколько месяцев назад, и, наконец, я нашел способ ее реализации:

Как использовать reprocessCaptureRequest с API-интерфейсом camera2

В этом ответе вы также можете найти некоторые тесты Google CTS, которые также реализуют ReprocessableCaptureSession и снимать некоторые серийные снимки с помощью шаблона ZSL.

Наконец, вы также можете использовать CaptureBuilder с вашей поверхностью предварительного просмотра и прикрепленной поверхностью считывателя изображений, в этом случае ваш предварительный просмотр будет продолжать работать все время, а также вы будете сохранять каждый кадр как новое изображение. Но у вас все равно будет проблема заморозки.

Я также попытался реализовать захват пакета, используя обработчик, который отправляет новый capture вызывать каждые 100 миллисекунд, эта вторая опция была довольно хорошей по производительности и позволяла избежать потери частоты кадров, но вы не получите столько захватов в секунду, сколько два ImageReader вариант.

Надеюсь, что мой ответ вам немного поможет, API 2 все еще немного сложен, и здесь не так много примеров или информации об этом.

Одна вещь, которую я заметил на младших устройствах: предварительный просмотр останавливается после захвата, даже при использовании камеры 1 api, поэтому его необходимо перезапустить вручную, что приводит к небольшому замораживанию при захвате изображения с высоким разрешением.

Но камера 2 api предоставляет возможность получить необработанное изображение при съемке (это было невозможно на устройствах, которые у меня есть при использовании камеры 1 (Huawei P7, Sony Xperia E5, wiko UFeel)). Использование этой функции намного быстрее, чем захват JPEG (возможно, из-за сжатия JPEG), поэтому предварительный просмотр можно перезапустить раньше, а предварительный просмотр будет короче. Конечно, используя это решение, вам придется конвертировать изображение из YUV в JPEG в фоновом режиме.

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