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

Я пытаюсь обновить проект камеры до Android N, и, следовательно, я перемещаю свой старый CameraCaptureSession в ReprocessableCaptureSession. Я сделал это, и он работает нормально, но с этой новой функцией я могу использовать CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG шаблон в моем устройстве, и я могу повторно обрабатывать кадры с reprocessCaptureRequest.

Вот где моя проблема появляется. Потому что я не нахожу никакого примера, и я действительно не понимаю небольшую документацию о том, как использовать reprocessCaptureRequest:

Каждый повторный процесс CaptureRequest обрабатывает один буфер из входной поверхности CameraCaptureSession для всех выходных поверхностей, включенных в запрос захвата повторной обработки. Входные изображения для повторной обработки должны создаваться из одного или нескольких выходных изображений, полученных с одного и того же устройства камеры. Приложение может обеспечить ввод изображений на устройство камеры через queueInputImage(Image). Приложение должно использовать результат захвата одного из этих выходных изображений для создания запроса захвата повторной обработки, чтобы устройство камеры могло использовать информацию для достижения оптимального качества изображения повторной обработки. Для устройств камеры, которые поддерживают только 1 выходную поверхность, отправка повторного запроса CaptureRequest с несколькими выходными целями приведет к CaptureFailure.

Я попытался взглянуть на тесты CTS о камере в google.sources, но они делают то же самое, что и я. Использование множества imageReaders, сохранение TotalCaptureResult картинок в LinkedBlockingQueue<TotalCaptureResult>, А позже просто звоню

TotalCaptureResult totalCaptureResult = state.captureCallback.getTotalCaptureResult();
CaptureRequest.Builder reprocessCaptureRequest = cameraStore.state().cameraDevice.createReprocessCaptureRequest(totalCaptureResult);
reprocessCaptureRequest.addTarget(state.yuvImageReader.getSurface());
sessionStore.state().session.capture(reprocessCaptureRequest.build(), null, this.handlers.bg());

Но это всегда выдает мне RuntimeException:java.lang.RuntimeException: Capture failed: Reason 0 in frame 170,

Я просто хочу знать, как правильно работать с ReprocessableCaptureSession, потому что я уже все перепробовал и не знаю, что делаю неправильно.

1 ответ

Решение

Наконец я нашел решение сделать мой reprocessableCaptureSession Работа. Я использую с архитектурой Flux, так что не смущайтесь, когда видите Dispatcher.dispatch(action)Просто посмотрите на это как на обратный звонок. Итак, вот мой код:

Первый Как создается сессия:

 //Configure preview surface
     Size previewSize = previewState.previewSize;
     previewState.previewTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());

     ArrayList<Surface> targets = new ArrayList<>();
     for (SessionOutputTarget outputTarget : state.outputTargets) {
        Surface surface = outputTarget.getSurface();
        if (surface != null) targets.add(surface);
     }
     targets.add(previewState.previewSurface);
     CameraCharacteristics cameraCharacteristics = cameraStore.state().availableCameras.get(cameraStore.state().selectedCamera);
     Size size = CameraCharacteristicsUtil.getYuvOutputSizes(cameraCharacteristics).get(0);

     InputConfiguration inputConfiguration = new InputConfiguration(size.getWidth(),
        size.getHeight(), ImageFormat.YUV_420_888);

     CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
           if (sessionId != currentSessionId) {
              Timber.e("Session opened for an old open request, skipping. Current %d, Request %d", currentSessionId, sessionId);
              //performClose(session);
              return;
           }

           try {
              session.getInputSurface();
              //This call is irrelevant,
              //however session might have closed and this will throw an IllegalStateException.
              //This happens if another camera app (or this one in another PID) takes control
              //of the camera while its opening
           } catch (IllegalStateException e) {
              Timber.e("Another process took control of the camera while creating the session, aborting!");
           }

           Dispatcher.dispatchOnUi(new SessionOpenedAction(session));
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
           if (sessionId != currentSessionId) {
              Timber.e("Configure failed for an old open request, skipping. Current %d, request %d", currentSessionId, sessionId);
              return;
           }

           Timber.e("Failed to configure the session");
           Dispatcher.dispatchOnUi(new SessionFailedAction(session, new IllegalStateException("onConfigureFailed")));
        }
     };

     if (state.outputMode == OutputMode.PHOTO) {
        cameraState.cameraDevice.createReprocessableCaptureSession(inputConfiguration, targets, sessionStateCallback, handlers.bg());
     } else if (state.outputMode == OutputMode.VIDEO) {
        cameraState.cameraDevice.createCaptureSession(targets, sessionStateCallback, handlers.bg());
     }

  } catch (IllegalStateException | IllegalArgumentException e) {
     Timber.e(e, "Something went wrong trying to start the session");
  } catch (CameraAccessException e) {
     //Camera will throw CameraAccessException if another we try to open / close the
     //session very fast.
     Timber.e("Failed to access camera, it was closed");
  }

Фотосессия как была создана с 4 поверхностями (предварительный просмотр, YUV(вход), JPEG и RAW). После этого я настраиваю свой imageWriter:

   Dispatcher.subscribe(Dispatcher.VERY_HIGH_PRIORITY, SessionOpenedAction.class)
     .filter(a -> isInPhotoMode())
     .subscribe(action -> {
           PhotoState newState = new PhotoState(state());
           newState.zslImageWriter = ImageWriter.newInstance(action.session.getInputSurface(), MAX_REPROCESS_IMAGES);
           setState(newState);
     });

Хорошо, теперь у нас есть ImageWriter и сессия создана. Нет, мы начинаем потоковую передачу с повторяющегося запроса:

         CaptureRequest.Builder captureRequestBuilder =
            cameraStore.state().cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);

         captureRequestBuilder.addTarget(previewStore.state().previewSurface);
         captureRequestBuilder.addTarget(photoStore.state().yuvImageReader.getSurface());
         state.session.setRepeatingRequest(captureRequestBuilder.build(), state.zslCaptureCallback, handlers.bg());

Чтобы не добавлять много кода, просто скажите, что zslCaptureCallback - это пользовательский обратный вызов, который сохраняется в LinkedBlockingQueue<TotalCaptureRequest> X последних TotalCaptureRequests. Кроме того, я делаю то же самое с yuvImageReader(входной), сохраняя последние X изображений в очереди.

Наконец, вот мой метод "сделай фото":

try {
        //Retrieve the last image stored by the zslImageReader
        Image image = zslImageReaderListener.getImage();
        //Retrieve the last totalCaptureResult from the zslCaptureCallback and create a reprocessableCaptureRequest with it
        TotalCaptureResult captureResult = sessionStore.state().zslCaptureCallback.getCaptureResult(image.getTimestamp());
        CaptureRequest.Builder captureRequest = cameraStore.state().cameraDevice.createReprocessCaptureRequest(captureResult);
        //Add the desired target and values to the captureRequest
        captureRequest.addTarget(state().jpegImageReader.getSurface());
        //Queued back to ImageWriter for future consumption.
        state.zslImageWriter.queueInputImage(image);
        //Drain all the unused and queued CapturedResult from the CaptureCallback
        sessionStore.state().zslCaptureCallback.drain();
        //Capture the desired frame
        CaptureRequest futureCaptureResult = captureRequest.build();
        sessionStore.state().session.capture(futureCaptureResult, new CameraCaptureSession.CaptureCallback() {
           @Override
           public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                          @NonNull CaptureRequest request,
                                          @NonNull TotalCaptureResult result) {
              Dispatcher.dispatchOnUi(new PhotoStatusChangedAction(PhotoState.Status.SUCCESS));
           }

           @Override
           public void onCaptureFailed(@NonNull CameraCaptureSession session,
                                       @NonNull CaptureRequest request,
                                       @NonNull CaptureFailure failure) {
              super.onCaptureFailed(session, request, failure);
              Exception captureFailedException = new RuntimeException(
                 String.format("Capture failed: Reason %s in frame %d, was image captured? -> %s",
                    failure.getReason(),
                    failure.getFrameNumber(),
                    failure.wasImageCaptured()));
              Timber.e(captureFailedException, "Cannot take mediaType, capture failed!");

              Dispatcher.dispatchOnUi(new PhotoStatusChangedAction(PhotoState.Status.ERROR, captureFailedException));
           }
        }, this.handlers.bg());

        //Capture did not blow up, we are taking the photo now.
        newState.status = PhotoState.Status.TAKING;

     } catch (CameraAccessException | InterruptedException| IllegalStateException | IllegalArgumentException | SecurityException e) {
        Timber.e(e, "Cannot take picture, capture error!");
        newState.status = PhotoState.Status.ERROR;
     }
Другие вопросы по тегам