Android CameraX 使用教程

Google 原来提供了Camera,后面又推出了Camera2,然后几年前在Jetpack里面推出了CameraX

CameraX其实内部是封装Camera2的。但是它比Camera2使用起来更加简便,性能也比Camera2提高了。

废话少说,直接上代码:

要用CameraX,第一是要引入其 dependencies

    // CameraX core library
    def camerax_version = '1.1.0-beta01'
    implementation "androidx.camera:camera-core:$camerax_version"

    // CameraX Camera2 extensions
    implementation "androidx.camera:camera-camera2:$camerax_version"

    // CameraX Lifecycle library
    implementation "androidx.camera:camera-lifecycle:$camerax_version"

    // CameraX View class
    implementation "androidx.camera:camera-view:$camerax_version"

进而上个布局;

<?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=".CameraActivity">

    <!--预览的控件-->
    <androidx.camera.view.PreviewView
        android:id="@+id/preview_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:scaleType="fillCenter"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/switchCamera"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:layout_marginTop="10dp"
        android:text="切换相机"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">
    </androidx.appcompat.widget.AppCompatButton>

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvFps"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/switchCamera"
        app:layout_constraintLeft_toLeftOf="parent"
        android:textSize="20sp"
        android:gravity="center"
        android:padding="5dp"
        android:text="12456"
        android:textColor="@color/white">
    </androidx.appcompat.widget.AppCompatTextView>

</androidx.constraintlayout.widget.ConstraintLayout>

然后上个Activity代码:



import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.SystemClock
import android.util.Log
import android.view.View
import androidx.appcompat.widget.AppCompatTextView
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat

class CameraActivity : AppCompatActivity() {

    companion object{
        private const val TAG = "CameraActivity"
    }

    private var cameraProvider: ProcessCameraProvider? = null
    private var previewUseCase: Preview? = null
    private lateinit var previewView: PreviewView
    private lateinit var tvFps: AppCompatTextView
    private var cameraSelector: CameraSelector? = null
    private var lensFacing = CameraSelector.LENS_FACING_FRONT
    private var camera: Camera? = null
    private var analysisUseCase: ImageAnalysis? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)
        previewView = findViewById(R.id.preview_view)
        tvFps = findViewById(R.id.tvFps)
        findViewById<View>(R.id.switchCamera).setOnClickListener {
            switchCamera()
        }
        cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()

        if (PermissionsUtil.allPermissionsGranted(this)){
            start()
        }else{
            PermissionsUtil.requestPermission(this)
        }
    }

    private fun start() {
        /*
        通过 ProcessCameraProvider 获取 cameraProvider
        cameraProvider 就是我们持有的相机实例
        */
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(Runnable {
            cameraProvider = cameraProviderFuture.get()
            startCamera()
        }, ContextCompat.getMainExecutor(this))
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (PermissionsUtil.allPermissionsGranted(this)){
            start()
        }
    }

    /**
     *      启动相机
     * */
    private fun startCamera() {
        if (cameraProvider != null) {
            cameraProvider!!.unbindAll()
            /*
            这一步是绑定预览界面,如果不需要预览界面,这一步克注释掉
            CameraX优势体验之一:预览界面可以根据开发者需求去取舍,而Camera1和Camera2则必须要预览界面
            */
            bindPreviewUseCase()
            // 这一步是绑定相机预览数据,可以获得相机每一帧的数据
            bindAnalysisUseCase()
        }
    }

    /**
     * 绑定预览界面。不需要预览界面可以不调用
     * */
    private fun bindPreviewUseCase() {
        if (previewUseCase != null) {
            cameraProvider!!.unbind(previewUseCase)
        }

        val builder = Preview.Builder()
        builder.setTargetAspectRatio(AspectRatio.RATIO_16_9).build()
        previewUseCase = builder.build()
        previewUseCase!!.setSurfaceProvider(previewView.surfaceProvider)
        cameraProvider!!.bindToLifecycle(
            this,
            cameraSelector!!,
            previewUseCase
        )
    }

    private var lastTime = 0L
    private var lastShowTime = 0L
    private var count = 0
    private var maxFrameMs = 0L
    private var minFrameMs = Long.MAX_VALUE


    private fun bindAnalysisUseCase() {
        if (analysisUseCase != null) {
            cameraProvider!!.unbind(analysisUseCase)
        }
        val builder = ImageAnalysis.Builder().setTargetAspectRatio(AspectRatio.RATIO_16_9)
        analysisUseCase = builder.build()
        analysisUseCase?.setAnalyzer(
            ContextCompat.getMainExecutor(this)
        ) { imageProxy: ImageProxy ->
            count += 1
            val currentTime = SystemClock.elapsedRealtime()
            val d = currentTime - lastShowTime
            maxFrameMs = maxFrameMs.coerceAtLeast(d)
            minFrameMs = minFrameMs.coerceAtMost(d)
            if ((currentTime - lastTime) >= 1000){
                tvFps.text = "fps: ${count}, 两帧最大:${maxFrameMs}, 最小:${minFrameMs}"
                lastTime = currentTime
                count = 0
                maxFrameMs = 0
                minFrameMs = Long.MAX_VALUE
            }
            lastShowTime = currentTime
            Log.d(TAG,"ImageProxy: 宽高: ${imageProxy.width} * ${imageProxy.height}")

            //必须close,相机才会下发下一帧数据,否则会一直阻塞相机下发数据
            imageProxy.close()
        }
        camera = cameraProvider!!.bindToLifecycle(
            this,
            cameraSelector!!,
            analysisUseCase
        )
    }

    private fun switchCamera() {
        if (cameraProvider == null) {
            return
        }
        val newLensFacing =
            if (lensFacing == CameraSelector.LENS_FACING_FRONT) {
                CameraSelector.LENS_FACING_BACK
            } else {
                CameraSelector.LENS_FACING_FRONT
            }
        val newCameraSelector = CameraSelector.Builder().requireLensFacing(newLensFacing).build()
        try {
            if (cameraProvider!!.hasCamera(newCameraSelector)) {
                lensFacing = newLensFacing
                cameraSelector = newCameraSelector
                startCamera()
                return
            }
        } catch (e: CameraInfoUnavailableException) {
            // Falls through
        }
    }
}

添加权限工具类代码

object PermissionsUtil {

    private const val TAG = "PermissionsUtil"
    private const val PERMISSION_REQUESTS = 1

    fun requestPermission(context: Context) {
        if (!allPermissionsGranted(context)) {
            runtimePermissions(context)
        }
    }

    private fun isPermissionGranted(context: Context, permission: String?): Boolean {
        if (ContextCompat.checkSelfPermission(context, permission!!) ==
            PackageManager.PERMISSION_GRANTED
        ) {
            return true
        }
        return false
    }

    fun allPermissionsGranted(context: Context): Boolean {
        for (permission in requiredPermissions(context)) {
            if (!isPermissionGranted(context, permission)) {
                return false
            }
        }
        return true
    }

    private fun requiredPermissions(context: Context): Array<String?> {
        val info =
            context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
        val ps = info.requestedPermissions
        if (ps != null && ps.isNotEmpty()) {
            ps
        } else {
            arrayOfNulls(0)
        }
        return ps
    }

    private fun runtimePermissions(context: Context) {
        val allNeededPermissions: MutableList<String?> = ArrayList()
        for (permission in requiredPermissions(context)) {
            if (!isPermissionGranted(context, permission)) {
                allNeededPermissions.add(permission)
            }
        }
        if (allNeededPermissions.isNotEmpty()) {
            ActivityCompat.requestPermissions(
                getActivity(context),
                allNeededPermissions.toTypedArray(),
                PERMISSION_REQUESTS
            )
        }
    }

    @SuppressLint("UseRequireInsteadOfGet")
    private fun getActivity(context: Context) = when (context) {
        is Fragment -> {
            (context as Fragment).activity!!
        }
        is Activity -> {
            context
        }
        else -> {
            throw RuntimeException("context 必须是 Activity 或者 Fragment ")
        }
    }
}

运行起来看下效果

ok,没问题

有问题请留言!

demo地址 https://github.com/LeoLiang23/cameraDemo

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值