Android Camera2,连续拍照

标签 android freeze preview camera2

我需要使用Camera2 API连续拍照。它在高端设备(例如 Nexus 5X)上运行良好,但在较慢的设备(例如三星 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 bool 值,并进行第一个 takePicture 调用。

回顾一下,我使用了 3 个线程:

  • 一个用于预览
  • 一个用于捕获
  • 用于保存图像

造成这种卡住的原因是什么?

最佳答案

当您一直在性能较差的设备上拍摄图像时,不可能避免预览中出现帧丢失的情况。避免这种情况的唯一方法是在支持 TEMPLATE_ZERO_SHUTTER_LAG 的设备上并使用 reprocessableCaptureSession。关于此的文档非常糟糕,找到一种实现它的方法可能是一个艰难的过程。几个月前我遇到了这个问题,最后我找到了实现它的方法:

How to use a reprocessCaptureRequest with camera2 API

在该答案中,您还可以找到一些 Google CTS 测试,它也实现了 ReprocessableCaptureSession 并使用 ZSL 模板拍摄一些突发捕获。

最后,您还可以使用带有预览表面和图像读取器表面的CaptureBuilder,在这种情况下,您的预览将始终继续工作,并且您会将每个帧保存为新图片。但您仍然会遇到卡住问题。

我还尝试使用处理程序实现突发捕获,该处理程序每​​ 100 毫秒调度一次新的 capture 调用,第二个选项在性能方面非常好,并且避免了帧速率丢失,但您不会得到每秒捕获多次,例如两个 ImageReader 选项。

希望我的回答对您有一点帮助,API 2 还是有点复杂,并且没有太多关于它的示例或信息。

关于Android Camera2,连续拍照,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42215106/

相关文章:

android - 让用户通过点击视频来播放视频

c# - 如何使用 async/await 查找 'hangs' 的哪个方法?

Erlang/OTP 框架的 error_logger 在相当高的负载下挂起

java - getPreferredPreviewSizeForVideo() 是获取正确预览尺寸的好方法吗?

twitter - 如何确保链接支持 Twitter 预览?

android - 无法让android将GeoPoint GPS坐标数据正确写入文件

java - HttpUrlConnection:Cookie 显示在 CookieStore 中,但不显示在 POST header 中

android - 在 AsyncTask 中测试一个涉及监听器回调的类

c - 当进程被终止时,有没有办法让线程从无限信号量等待中出来?

android - 在 Eclipse 中预览所有 Android XML 布局文件