Fork me on GitHub

Jetpack之CameraX的使用

Camera2的使用#

  1. 已将 CameraX 依赖项加入到您的项目中。
  2. 已显示相机取景器(使用 Preview 用例)
  3. 已能够拍摄照片,并可将图像保存到存储空间(使用 ImageCapture 用例)
  4. 已实现对来自相机的画面帧进行实时分析(使用 ImageAnalysis 用例)

具体步骤#

  • 1.配置依赖
   //cameraX
    implementation "androidx.camera:camera-core:1.1.0"
    implementation "androidx.camera:camera-camera2:1.1.0"
    implementation "androidx.camera:camera-lifecycle:1.1.0"
    implementation "androidx.camera:camera-view:1.1.0"
  • 2.布局配置
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.camera.view.PreviewView
        android:id="@+id/preview_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <View
        android:id="@+id/action_area"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:background="#50000000" />

    <ImageView
        android:id="@+id/take_photo_img"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/take_photo"
        app:layout_constraintTop_toTopOf="@id/action_area"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <ImageView
        android:id="@+id/switch_camera"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:src="@drawable/switch_camera"
        android:layout_marginEnd="30dp"
        app:layout_constraintTop_toTopOf="@id/action_area"
        app:layout_constraintBottom_toBottomOf="@id/action_area"
        app:layout_constraintEnd_toEndOf="parent" />

    <ImageView
        android:id="@+id/result_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000000"
        android:visibility="invisible" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • 3.Camera的工具类
class EasyCamera(
    val lifecycleOwner: LifecycleOwner,
    val context: Context,
    val previewView: PreviewView
) {
    // CameraX 相关
    var camera: Camera? = null
    var cameraExecutor: ExecutorService? = null
    var preview: Preview? = null

    // 摄像头 前置/后置 默认后置
    @LensFacing
    var lensFacing: Int = CameraSelector.LENS_FACING_BACK

    private var width = 0
    private var height = 0

    // 照片比例 默认16:9
    var ratio: Int = AspectRatio.RATIO_16_9

    // 旋转角度 默认无
    var rotation: Int = 0

    //相机提供者
    var cameraProvider: ProcessCameraProvider? = null
    var videoCapture: VideoCapture? = null

    init {
        cameraExecutor = Executors.newSingleThreadExecutor()
        lifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
            if (event === Lifecycle.Event.ON_DESTROY) {
                cameraProvider?.unbindAll()
                cameraProvider?.shutdown()
                cameraExecutor?.shutdown()
            }
        })
        buildCamera()
        camera?.cameraInfo?.cameraState?.observe(lifecycleOwner) { t ->
            t?.let {
                if (it.type != CameraState.Type.OPEN) {
                    cameraProvider?.unbindAll()
                }
            }
        }
    }

    fun release() {
        cameraProvider?.unbindAll()
    }

    fun switchCamera(@LensFacing lensFacing: Int) {
        this.lensFacing = lensFacing
        if (this.lensFacing != lensFacing) {
            buildCamera()
        }
    }

    /**
     * 拍照
     * 默认路径是应用外置存储空间
     */
    fun takePicture(
        callback: (Uri?, ImageCaptureException?) -> Unit
    ) {
        val outputFile = File(
            context.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
            String.format("IMG_%s%s", System.currentTimeMillis(), ".jpg")
        )
        takePicture(
            ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
            outputFile,
            null,
            callback
        )
    }

    /**
     *拍照 自定义图片路径
     */
    fun takePicture(
        imageFile: File,
        callback: (Uri?, ImageCaptureException?) -> Unit
    ) {
        takePicture(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY, imageFile, null, callback)
    }

    /**
     * 配置相机拍照图片的路径
     * @param captureMode 相机模式 拍照模式:速度优先/质量优先 默认质量优先
     * @param imageFile 图片输出文件
     * @param imageAnalysis 图像实时分析
     * @param callback 拍照完成的回调 主线程
     */
    fun takePicture(
        @ImageCapture.CaptureMode captureMode: Int,
        imageFile: File,
        imageAnalysis: ImageAnalysis? = null,
        callback: (Uri?, ImageCaptureException?) -> Unit
    ) {
        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.CAMERA
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        val imageCapture = ImageCapture.Builder()
            .setCaptureMode(captureMode)
            .setTargetAspectRatio(ratio)
            .setTargetRotation(rotation)
            .build()
        try {
            imageAnalysis?.let {
                camera = cameraProvider?.bindToLifecycle(
                    lifecycleOwner,
                    CameraSelector.Builder().requireLensFacing(lensFacing).build(),
                    preview,
                    imageCapture, it
                )
            } ?: let {
                camera = cameraProvider?.bindToLifecycle(
                    lifecycleOwner,
                    CameraSelector.Builder().requireLensFacing(lensFacing).build(),
                    preview,
                    imageCapture
                )
            }
            setFocus(width.toFloat() / 2, height.toFloat() / 2)
            val metadata = ImageCapture.Metadata().apply {
                isReversedHorizontal = (lensFacing == CameraSelector.LENS_FACING_FRONT)
            }
            val options = ImageCapture.OutputFileOptions.Builder(imageFile)
                .setMetadata(metadata)
                .build()
            imageCapture.takePicture(
                options, cameraExecutor!!, object : ImageCapture.OnImageSavedCallback {
                    override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                        previewView.post {
                            callback.invoke(outputFileResults.savedUri, null)
                        }
                    }

                    override fun onError(exception: ImageCaptureException) {
                        previewView.post {
                            callback.invoke(null, exception)
                        }
                    }
                }
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 录像
     * @param videoFile 图片输出文件
     * @param imageAnalysis 图像实时分析
     * @param callback 录像完成的回调
     */
    @SuppressLint("RestrictedApi")
    fun startRecord(
        imageAnalysis: ImageAnalysis? = null,
        callback: (Uri?, Exception?) -> Unit
    ) {
        val outputFile = File(
            context.getExternalFilesDir(Environment.DIRECTORY_MOVIES),
            String.format("video_%s%s", System.currentTimeMillis(), ".mp4")
        )
        startRecord(outputFile, null, callback)
    }

    /**
     * 录像
     * @param videoFile 图片输出文件
     * @param imageAnalysis 图像实时分析
     * @param callback 录像完成的回调
     */
    @SuppressLint("RestrictedApi")
    fun startRecord(
        videoFile: File,
        imageAnalysis: ImageAnalysis? = null,
        callback: (Uri?, Exception?) -> Unit
    ) {
        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.RECORD_AUDIO
            ) != PackageManager.PERMISSION_GRANTED &&
            ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.CAMERA
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        videoCapture = VideoCapture.Builder()
            .setTargetAspectRatio(ratio)//设置相机比例
            .setAudioBitRate(1024)//设置音频的码率
            .setAudioChannelCount(2)
            .setAudioMinBufferSize(3 * 1024)
            .setVideoFrameRate(25)//视频帧率  越高视频体积越大
            .setBitRate(3 * 1024 * 1024)//设置视频的比特率  越大视频体积越大
            .build()
        try {
            imageAnalysis?.let {
                camera = cameraProvider?.bindToLifecycle(
                    lifecycleOwner,
                    CameraSelector.Builder().requireLensFacing(lensFacing).build(),
                    preview,
                    videoCapture, it
                )
            } ?: let {
                camera = cameraProvider?.bindToLifecycle(
                    lifecycleOwner,
                    CameraSelector.Builder().requireLensFacing(lensFacing).build(),
                    preview,
                    videoCapture
                )
            }
            setFocus(width.toFloat() / 2, height.toFloat() / 2)

            val options = VideoCapture.OutputFileOptions.Builder(videoFile).build()
            videoCapture!!.startRecording(
                options,
                cameraExecutor!!,
                object : VideoCapture.OnVideoSavedCallback {
                    override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
                        previewView.post {
                            callback.invoke(outputFileResults.savedUri, null)
                        }
                    }

                    override fun onError(
                        videoCaptureError: Int,
                        message: String,
                        cause: Throwable?
                    ) {
                        previewView.post {
                            callback.invoke(null, Exception(message, cause))
                        }
                    }
                })
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 停止录制
     */
    @SuppressLint("RestrictedApi")
    fun stopRecord() {
        videoCapture?.let {
            it.stopRecording()
            videoCapture = null
        }
    }

    /**
     * 图像分析
     * @param analyzer 图像实时分析
     */
    @SuppressLint("RestrictedApi")
    fun analyzerCapture(
        analyzer: ImageAnalysis.Analyzer
    ) {
        try {
            val imageAnalysis = ImageAnalysis.Builder()
                .setTargetResolution(Size(width, height))
                .build().also {
                    it.setAnalyzer(Executors.newSingleThreadExecutor(), analyzer)
                }
            camera = cameraProvider?.bindToLifecycle(
                lifecycleOwner,
                CameraSelector.Builder().requireLensFacing(lensFacing).build(),
                preview, imageAnalysis
            )
            setFocus(width.toFloat() / 2, height.toFloat() / 2)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun buildCamera() {
        ratio = matchPreviewRatio()
        height = width * ratio
        setOrientationListener()
        ProcessCameraProvider.getInstance(context).apply {
            addListener({
                cameraProvider = this.get()
                preview = Preview.Builder()
                    .setTargetAspectRatio(ratio)
                    .setTargetRotation(rotation)
                    .build().apply {
                        setSurfaceProvider(previewView.surfaceProvider)
                    }
                cameraProvider?.unbindAll()
                camera = cameraProvider?.bindToLifecycle(
                    lifecycleOwner,
                    CameraSelector.Builder().requireLensFacing(lensFacing).build(),
                    preview
                )
            }, ContextCompat.getMainExecutor(context))
        }
    }

    fun setFocus(x: Float, y: Float) {
        val factory: MeteringPointFactory = SurfaceOrientedMeteringPointFactory(
            width.toFloat(), height.toFloat()
        )
        val autoFocusPoint = factory.createPoint(x, y)

        camera?.cameraControl?.startFocusAndMetering(
            FocusMeteringAction.Builder(
                autoFocusPoint,
                FocusMeteringAction.FLAG_AF
            ).apply {
                //auto-focus every 1 seconds
                setAutoCancelDuration(1, TimeUnit.SECONDS)
            }.build()
        )
    }

    /**
     * 设置缩放比例
     */
    @SuppressLint("RestrictedApi")
    fun setZoomRatio(zoomRatio: Float) {
        var future: ListenableFuture<Void>? = null
        future = if (zoomRatio > getMaxZoomRatio()) {
            camera?.cameraControl?.setZoomRatio(1.0f)
        } else {
            camera?.cameraControl?.setZoomRatio(zoomRatio)
        }
        future?.apply {
            Futures.addCallback(future, object : FutureCallback<Void?> {
                override fun onSuccess(result: Void?) {}
                override fun onFailure(t: Throwable) {}
            }, CameraXExecutors.directExecutor())
        }
    }

    fun getZoomRatio(): Float {
        return camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
    }

    fun getMaxZoomRatio(): Float {
        return camera?.cameraInfo?.zoomState?.value?.maxZoomRatio ?: 0F
    }

    //预览比例
    private fun matchPreviewRatio(): Int {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).apply {
                width = currentWindowMetrics.bounds.width()
                height = currentWindowMetrics.bounds.height()
            }
        } else {
            context.resources.displayMetrics.apply {
                width = widthPixels
                height = heightPixels
            }
        }
        val previewRatio = width.coerceAtLeast(height).toDouble() / width.coerceAtMost(height)
        return if (abs(previewRatio - AspectRatio.RATIO_4_3) <= abs(previewRatio - AspectRatio.RATIO_16_9)) {
            AspectRatio.RATIO_4_3
        } else AspectRatio.RATIO_16_9
    }

    //图片预览是否旋转角度
    private fun setOrientationListener(): Int {
        object : OrientationEventListener(previewView.context) {
            override fun onOrientationChanged(orientation: Int) {
                rotation = when (orientation) {
                    in 45..134 -> {
                        Surface.ROTATION_270
                    }
                    in 135..224 -> {
                        Surface.ROTATION_180
                    }
                    in 225..314 -> {
                        Surface.ROTATION_90
                    }
                    else -> {
                        Surface.ROTATION_0
                    }
                }
            }
        }.enable()
        return rotation
    }
}
  • 4.调用
PreviewView previewView = findViewById(R.id.preview_view);
EasyCamera  easyCamera = EasyCamera(
            lifecycleOwner = lifecycleOwner,
            context = this.context,
            previewView = previewView
        )
//切换相机
easyCamera.switchCamera();
//拍照 file和analyzer  可有可无 无的话走默认配置
easyCamera.takePicture(file,analyzer,回调)
//录像 需要声音的权限 file和analyzer  可有可无 无的话走默认配置 
easyCamera.startRecord(file,analyzer,回调)
//实时图像分析 可以自定义ImageAnalysis.Analyzer来做图像分析
analyzer = new QRCodeAnalyzer(easyCamera, 回调);//自定义的二维码扫描
easyCamera.analyzerCapture(analyzer);
analyzer.resetAnalyzer();//重新扫描识别

作者:kevin2022

出处:https://www.cnblogs.com/kevin2022/p/16665473.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

你可以在这里自定义其他内容

posted @   KevinAt2022  阅读(425)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu