CameraX的简单使用


基本说明

目前CameraX已经处于RC(Release Candidate)阶段了,意味着API不会有大的改动了,官方提示可以应用在实际产品中了.

CameraX对之前的Camera2进行了包装,重新设计了API,让开发者可以更简单,更快速的实现相机应用.

最简单的用法:CameraView

直接使用CameraView是最简单的方法,因为它封装了大部分的配置操作,只需调用几个简单的API即可实现相应的功能.

初始化

在使用各种功能之前,还是要进行初始化的,一般就是设置一下各种配置,默认摄像头并绑定生命周期.

cameraView.cameraLensFacing = CameraSelector.LENS_FACING_FRONT

// 绑定生命周期
cameraView.bindToLifecycle(this as LifecycleOwner)

拍照

CameraView中对ImageCapture进行了封装,只需调用最终的API即可:

                cameraView.captureMode = CameraView.CaptureMode.IMAGE
                val contentValues = ContentValues().apply {
                    // 文件名
                    put(
                        MediaStore.Images.Media.DISPLAY_NAME,
                        "KCamera_${System.currentTimeMillis()}"
                    )
                    put(MediaStore.Images.Media.MIME_TYPE, MIMEType.Image.HEIF)
                }

                val options = ImageCapture.OutputFileOptions.Builder(
                    requireContext().contentResolver,
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    contentValues
                ).build()
                cameraView.takePicture(
                    options,
                    Executors.newCachedThreadPool(),
                    object : ImageCapture.OnImageSavedCallback {
                        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                            val uri = outputFileResults.savedUri
                            ContextCompat.getMainExecutor(requireContext()).execute {
                                Toast.makeText(requireContext(), "$uri", Toast.LENGTH_SHORT).show()
                                Glide.with(requireContext())
                                    .asBitmap()
                                    .load(uri)
                                    .apply(RequestOptions.circleCropTransform())
                                    .into(binding.ivPreview)
                            }
                        }

                        override fun onError(exception: ImageCaptureException) {
                            ContextCompat.getMainExecutor(requireContext()).execute {
                                Toast.makeText(
                                    requireContext(),
                                    exception.message,
                                    Toast.LENGTH_SHORT
                                )
                                    .show()
                            }
                        }
                    })

唯一需要注意的就是别忘了指定拍摄模式:

cameraView.captureMode = CameraView.CaptureMode.IMAGE

拍摄视频

拍摄视频的代码和拍照基本上差不多,只是API不一样而已:

                cameraView.captureMode = CameraView.CaptureMode.VIDEO
                if (cameraView.isRecording) {
                    cameraView.stopRecording()
                } else {
                    val contentValues = ContentValues().apply {
                        // 文件名
                        put(
                            MediaStore.Images.Media.DISPLAY_NAME,
                            "KCamera_${System.currentTimeMillis()}"
                        )
                        put(MediaStore.Images.Media.MIME_TYPE, MIMEType.Video.MP4)
                    }
                    val options = OutputFileOptions.builder(
                        requireContext().contentResolver,
                        MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                        contentValues
                    ).build()
                    cameraView.startRecording(
                        options,
                        Executors.newCachedThreadPool(),
                        object : OnVideoSavedCallback {
                            override fun onVideoSaved(outputFileResults: OutputFileResults) {
                                Handler(Looper.getMainLooper()).post {
                                    Toast.makeText(requireContext(), "视频保存成功", Toast.LENGTH_SHORT).show()
                                }
                            }

                            override fun onError(
                                videoCaptureError: Int,
                                message: String,
                                cause: Throwable?
                            ) {
                                Handler(Looper.getMainLooper()).post {
                                    Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
                                }
                            }
                        })
                }
            }

切换摄像头

cameraView.toggleCamera()

PreviewView/ImageCapture/VideoCapture

稍微麻烦一点的使用方法就是使用PreviewView.

初始化

    private val cameraProvider by lazy { ProcessCameraProvider.getInstance(requireContext()) }
    private val imageCapture by lazy {
        ImageCapture.Builder()
            .setTargetRotation(binding.previewView.display.rotation)
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) // 低延迟/高质量
            .setFlashMode(ImageCapture.FLASH_MODE_AUTO) // 是否开启闪光灯
            .build()
    }
    private val videoCapture by lazy {
        VideoCapture.Builder()
            .build()
    }
    private var isTakingVideo = false

    private fun initCamera() {
        cameraProvider.addListener({
            val provider = cameraProvider.get()
            bindCamera(provider)
        }, ContextCompat.getMainExecutor(requireContext()))
    }

    private fun bindCamera(provider: ProcessCameraProvider) {
        val preview = Preview.Builder()
            .build()
        val cameraSelector = CameraSelector.Builder()
            .requireLensFacing(lensFacing)
            .build()
        preview.setSurfaceProvider(binding.previewView.surfaceProvider)

        imageAnalysis.setAnalyzer(Executors.newCachedThreadPool(), ImageAnalysis.Analyzer { image ->
            val width = image.width
            val height = image.height
        })

        provider.unbindAll()
        camera = provider.bindToLifecycle(this, cameraSelector, preview, imageCapture, videoCapture)
    }

拍照

            val contentValues = ContentValues().apply {
                // 文件名
                put(MediaStore.Images.Media.DISPLAY_NAME, "KCamera_${System.currentTimeMillis()}")
                put(MediaStore.Images.Media.MIME_TYPE, MIMEType.Image.HEIF)
            }

            val options = ImageCapture.OutputFileOptions.Builder(
                requireContext().contentResolver,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                contentValues
            ).build()
            imageCapture.takePicture(
                options,
                Executors.newSingleThreadExecutor(),
                object : ImageCapture.OnImageSavedCallback {
                    override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                        val uri = outputFileResults.savedUri
                        ContextCompat.getMainExecutor(requireContext()).execute {
                            Toast.makeText(requireContext(), "$uri", Toast.LENGTH_SHORT).show()
                            Glide.with(requireContext())
                                .asBitmap()
                                .load(uri)
                                .apply(RequestOptions.circleCropTransform())
                                .into(binding.ivPreview)
                        }

                    }

                    override fun onError(exception: ImageCaptureException) {
                        ContextCompat.getMainExecutor(requireContext()).execute {
                            Toast.makeText(requireContext(), exception.message, Toast.LENGTH_SHORT)
                                .show()
                        }
                    }
                })

拍摄视频

            val contentValues = ContentValues().apply {
                // 文件名
                put(MediaStore.Images.Media.DISPLAY_NAME, "KCamera_${System.currentTimeMillis()}")
                put(MediaStore.Images.Media.MIME_TYPE, MIMEType.Video.MP4)
            }
            val options = VideoCapture.OutputFileOptions.Builder(
                requireContext().contentResolver,
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                contentValues
            ).build()
            if (!isTakingVideo) {
                isTakingVideo = true
                videoCapture.startRecording(
                    options,
                    Executors.newCachedThreadPool(),
                    object : VideoCapture.OnVideoSavedCallback {
                        override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
                            Handler(Looper.getMainLooper()).post {
                                Toast.makeText(requireContext(), "视频保存成功", Toast.LENGTH_SHORT).show()
                                isTakingVideo = false
                            }
                        }

                        override fun onError(
                            videoCaptureError: Int,
                            message: String,
                            cause: Throwable?
                        ) {
                            Handler(Looper.getMainLooper()).post {
                                Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
                                isTakingVideo = false
                            }
                        }
                    })
            } else {
                videoCapture.stopRecording()
                isTakingVideo = false
            }

切换摄像头

            lensFacing = if (lensFacing == CameraSelector.LENS_FACING_BACK) {
                CameraSelector.LENS_FACING_FRONT
            } else {
                CameraSelector.LENS_FACING_BACK
            }
            bindCamera(cameraProvider.get())

遇到的问题

  • Inflater Error

    AndroidRuntime: Caused by: java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or its super classes (declaration of 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.art/javalib/core-oj.jar)
            at androidx.camera.view.PreviewView.(PreviewView.java:137)
            at androidx.camera.view.PreviewView.(PreviewView.java:215)
            at androidx.camera.view.PreviewView.(PreviewView.java:210)
                ... 40 more

    但是这个问题其实和XML文件其实没什么关系,而是Java8兼容性问题.

    解决方案如下:

            compileOptions {
                sourceCompatibility = JavaVersion.VERSION_1_8
                targetCompatibility = JavaVersion.VERSION_1_8
            }
    
            kotlinOptions {
               jvmTarget = "1.8"
            }
  • cannot save capture result to specialized location

    使用URI存储的时候,忘了指定图片的MIME类型

  • 拍摄视频的时候总是报mAudioRecorder空指针崩溃

    看看是不是没有事情RECORD_AUDIO权限.


文章作者: 姜康
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 姜康 !
评论
 上一篇
skia中的SkBitmap skia中的SkBitmap
SkBitmap是一个二维的光栅化像素数组. SkImageInfoSkImageInfo包含以下信息: 整形的宽,高 这里的宽可以认为是图像的列数,即 column count 这里的高可以认为是图像的行数, 用于描述像素格式的SkC
2021-03-21
下一篇 
“显示布局边界”的原理 “显示布局边界”的原理
设置页的处理逻辑在packages/apps/Settings/src/com/android/settings/development/ShowLayoutBoundsPreferenceController.java中有: public
2021-01-19
  目录