如何在拍照时使CameraX预览停止?

5
我有一个使用自定义CameraX的流程,如下:
  • 打开相机实时预览
  • 点击按钮拍照
  • 点击按钮后触发一系列操作 (路径转换为位图、旋转图像、自动裁剪图像、保存到设备)
  • 在所有操作成功完成后,将图像发送到其他Fragment并在Glide中显示
问题是,在运行step 3的所有操作时有2秒延迟,并且相机预览仍然保持实时(不会冻结或锁定)。如何使相机预览在运行过程中冻结或锁定?
这是我用Camera X运行相机预览的代码:
class CameraFragment : Fragment() {

        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_camera, container, false)
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            viewFinder.post { setupCamera() }
        }

        private fun setupCamera() {
            CameraX.unbindAll()
            CameraX.bindToLifecycle(
                this,
                buildPreviewUseCase(),
                buildImageCaptureUseCase()
            )
        }

        private fun buildPreviewUseCase(): Preview {
            val preview = Preview(
                UseCaseConfigBuilder.buildPreviewConfig(
                    viewFinder.display
                )
            )
            preview.setOnPreviewOutputUpdateListener { previewOutput ->
                updateViewFinderWithPreview(previewOutput)
                correctPreviewOutputForDisplay(previewOutput.textureSize)
            }
            return preview
        }

        private fun buildImageCaptureUseCase(): ImageCapture {
            val capture = ImageCapture(
                UseCaseConfigBuilder.buildImageCaptureConfig(
                    viewFinder.display
                )
            )
            cameraCaptureImageButton.setOnClickListener {
                capture.takePicture(
                    FileCreator.createTempFile(JPEG_FORMAT),
                    Executors.newSingleThreadExecutor(),
                    object : ImageCapture.OnImageSavedListener {
                        override fun onImageSaved(file: File) {
                            // I want make a freeze camera preview when execute this before launch *launchGalleryFragment(path)*
                            val bitmap = BitmapFactory.decodeFile(file.absolutePath)
                            val rotatedBitmap = bitmap.rotate(90)
                            val croppedImage = cropImage(rotatedBitmap, viewFinder, rectangle)
                            val path = saveImage(croppedImage)
                            requireActivity().runOnUiThread {
                                launchGalleryFragment(path)
                            }
                        }

                        override fun onError(
                            imageCaptureError: ImageCapture.ImageCaptureError,
                            message: String,
                            cause: Throwable?
                        ) {
                            Toast.makeText(requireContext(), "Error: $message", Toast.LENGTH_LONG)
                                .show()
                            Log.e("CameraFragment", "Capture error $imageCaptureError: $message", cause)
                        }
                    })
            }
            return capture
        }

        private fun launchGalleryFragment(path: String) {
            val action = CameraFragmentDirections.actionLaunchGalleryFragment(path)
            findNavController().navigate(action)
        }

    }
1个回答

6
也许你可以尝试解除预览用例的绑定:
版本1.0.0-alpha06:CameraX.unbind(preview); 版本 > 1.0.0-alpha07:cameraProvider.unbind(preview); 在你的情况下,你需要将预览用例保存到一个变量中,然后再解除绑定:
// Declare the preview use case variable (as in the CameraXBasic example)
private var preview: Preview? = null

然后实例化变量(就像你做的那样):

private fun buildPreviewUseCase(): Preview {
    preview = Preview(
        UseCaseConfigBuilder.buildPreviewConfig(
            viewFinder.display
        )
    )
    preview.setOnPreviewOutputUpdateListener { previewOutput ->
        updateViewFinderWithPreview(previewOutput)
        correctPreviewOutputForDisplay(previewOutput.textureSize)
    }
    return preview
}

然后,当您想要冻结预览时,只需解除用例的绑定即可:
CameraX.unbind(preview);
编辑 正如@Billda在这篇帖子中所说:CameraX - unbinding预览UseCase时崩溃

要冻结预览,您不应解除绑定Preview usecase。目前可能有API提供此功能,但推荐的方法是从ImageAnalysis中存储最新的帧,并将其放入重叠预览的ImageView中。

因此,我决定更新我的答案,提供使用ImageAnalysis(1.0.0-beta02)实现分析器的另一种解决方案。

1- 创建FreezeAnalyzer类:

class FreezeAnalyzer(private val callback: FreezeCallback) : ImageAnalysis.Analyzer {
    private var flag = false

    override fun analyze(image: ImageProxy) {
        if(flag){
            flag = false
            val bitmap = toBitmap(image)
            callback.onLastFrameCaptured(bitmap)
        }
        image.close()
    }

    fun freeze(){
        flag = true
    }

    private fun toBitmap(image: ImageProxy): Bitmap {
        // Convert the imageProxy to Bitmap
        // ref https://dev59.com/GVMI5IYBdhLWcg3wNIsg
        // ISSUE, on my android 7 when converting the imageProxy to Bitmap I have a problem with the colors...
        var bitmap = ...

        // Rotate the bitmap
        val rotationDegrees = image.imageInfo.rotationDegrees.toFloat()
        if (rotationDegrees != 0f) {
            val matrix = Matrix()
            matrix.postRotate(rotationDegrees)
            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
        }
        return bitmap
    }
}

2- XML

<androidx.camera.view.PreviewView
    android:id="@+id/preview_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
<ImageView
    android:id="@+id/image_view"
    android:visibility="invisible"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

3- 初始化图像分析器

val resolutionSize = Size(preview_view.width, preview_view.height)

// Set up analyser
imageAnalysis = ImageAnalysis.Builder().apply {
    setTargetResolution(resolutionSize)
    setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
}.build()

val analyzer = FreezeAnalyzer(object : FreezeCallback {
    override fun onLastFrameCaptured(bitmap: Bitmap) {
        runOnUiThread {
            preview_view.visibility = View.INVISIBLE
            image_view.visibility = View.VISIBLE
            image_view.setImageBitmap(bitmap)
        }
    }
})
imageAnalysis.setAnalyzer(executor, analyzer)

4- 绑定图像分析用例

try {
    val camera = cameraProvider.bindToLifecycle(
        this,
        cameraSelector,
        preview,
        imageAnalysis,
        imageCapture
    )
    preview.setSurfaceProvider(preview_view.createSurfaceProvider(camera.cameraInfo))
}

5- 捕捉图像

btn_capture.setOnClickListener {
    file = File(externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
    val outputFileOptions: ImageCapture.OutputFileOptions =
        ImageCapture.OutputFileOptions.Builder(file!!).build()
    analyzer.freeze()
    imageCapture.takePicture(outputFileOptions, executor, onImageSavedCallback)
}

6- 发布

btn_release.setOnClickListener {
    preview_view.visibility = View.VISIBLE
    image_view.visibility = View.INVISIBLE
}

我希望这能对你有所帮助,我不是专家,如果您有改进建议,欢迎提出!


我应该在哪里添加 CameraX.unbind(preview) 函数?我尝试在 onImageSaved() 中添加,但它会崩溃。 - R Rifa Fauzi Komara
是的,在 onImageSaved() 中,我使用了 CameraXBasic 示例进行了测试,并且它可以正常工作。我认为这是由于您的执行器引起的,您可以尝试像 CameraXBasic 示例一样使用 ContextCompat.getMainExecutor(requireContext()) - LaurentP22
是的,在我将那个加入我的代码后,它可以工作。但是,为什么在我点击相机按钮后不立即冻结?仍然存在一定的延迟时间。 - R Rifa Fauzi Komara
谢谢您的回答。我想使用它,但是我的Android Studio无法识别FreezeCallback。我需要一些非标准依赖吗? - Sk1X1
也许使用ImageCapture.OnImageSavedCallback.onImageSaved(ImageCapture.OutputFileResults),将outputFileResults.getSavedUri设置给ImageView.setImageURI(Uri)。 第一步是保存; 第二步是读取。 - 卡尔斯路西法
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接