如何使用带有camera2 API的reprocessCaptureRequest

Fra*_*cia 3 java camera android image android-camera2

我正在尝试将相机项目更新到 Android N,因此我将旧的CameraCaptureSession移动到ReprocessableCaptureSession。我做到了并且工作正常,但是有了这个新功能,我可以CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG在我的设备中使用模板,并且可以使用reprocessCaptureRequest重新处理帧。

这是我的问题出现的地方。因为我没有找到任何示例,而且我不太理解有关如何使用的小文档reprocessCaptureRequest

每个重新处理 CaptureRequest 都会处理从 CameraCaptureSession 的输入 Surface 到重新处理捕获请求中包含的所有输出 Surface 的一个缓冲区。重新处理输入图像必须从同一相机设备捕获的一幅或多幅输出图像生成。应用程序可以通过queueInputImage(Image)向相机设备提供输入图像。应用程序必须使用这些输出图像之一的捕获结果来创建重新处理捕获请求,以便相机设备可以使用该信息来实现最佳的重新处理图像质量。对于仅支持 1 个输出 Surface 的相机设备,提交具有多个输出目标的重新处理 CaptureRequest 将导致 CaptureFailure。

我试图在google.sources中查看有关相机的 CTS 测试,但他们的做法与我相同。使用多个 imageReader,将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());
Run Code Online (Sandbox Code Playgroud)

但它总是抛出一个 RuntimeException: java.lang.RuntimeException: Capture failed: Reason 0 in frame 170,

我只想知道哪种方法是使用 ReprocessableCaptureSession 的正确方法,因为我已经尝试了所有方法,但我不知道我做错了什么。

Fra*_*cia 5

最后我找到了完成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");
  }
Run Code Online (Sandbox Code Playgroud)

照片会话使用 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);
     });
Run Code Online (Sandbox Code Playgroud)

好的,现在我们已经创建了 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());
Run Code Online (Sandbox Code Playgroud)

为了不添加大量代码,只需说 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;
     }
Run Code Online (Sandbox Code Playgroud)