相机预览问题

通过SurfaceView,TextureView,GlSurfaceView显示相机预览

显示相机预览内容是每个相机类应用都会包含的功能,想要完美实现这个却并非易事。原因是,在某些特别极端情况下 camera2 API 的使用会变得很复杂,而且在不同设备上的行为还会有所不同。还好, Jetpack CameraX 库 的 PreviewView 可以帮助您解决这一问题。通过在各种 Android 设备上提供开发者友好、一致且稳定的 API,使得展示相机的预览变得不再困难。

如果要向 Android 应用中添加相机功能,您有以下三个主要选项:

CameraX 基于 Camera2 软件包构建而成。如果您需要低级别的相机控件来支持复杂用例,那么 Camera2 是一个不错的选择,但相应 API 比 CameraX 更复杂,并且您需要管理设备专属配置。与 CameraX 一样,Camera2 适用于 Android 5.0(API 级别 21)及更高版本。

CameraX 支持大多数常见的相机用例:

  • 预览:在屏幕上查看图片。
  • 图片分析:无缝访问缓冲区中的图片以便在算法中使用,例如将其传递到机器学习套件。
  • 图片拍摄:保存图片。
  • 视频拍摄:保存视频和音频。

使用SurfaceView加载相机预览界面

1,添加相机权限

<uses-permission android:name="android.permission.CAMERA" />

2,页面布局,因为我项目需要的是一个扫描页面,所以我在底布局下填充了一个SurfaceView

<SurfaceView
        android:id="@+id/mSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/view2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="60dp"
        android:background="@drawable/rpa_huli_rzpz_bianjiao_icon"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="h,16:10"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/face_confirm"
        android:layout_width="@dimen/public_120_dp"
        android:layout_height="@dimen/public_60_dp"
        android:layout_marginTop="136dp"
        android:background="@color/detail_bu"
        android:text="识别"
        android:textColor="@color/white"
        android:textSize="24sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/view2" />

3,设置SurfaceHolder.callback开启和关闭相机预览功能。

private val cpHolderCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {
        override fun surfaceCreated(holder: SurfaceHolder) {
            camera?.setPreviewDisplay(holder)
            preview()//相机预览
        }
        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int)       {}
        override fun surfaceDestroyed(holder: SurfaceHolder) {
            stopPreview()//关闭预览
        }
    }

4,初始化相机数据,加载相机预览

runOnUiThread {
            camera = Camera.open(0)
            try {
                val parameters: Camera.Parameters = camera!!.parameters
                parameters.pictureFormat = ImageFormat.JPEG//设置图片属性
                //设置相机对焦模式,FOCUS_MODE_CONTINUOUS_PICTURE使用连续对焦
                parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
                //界面属性不一样,所以设置宽高处理界面异常拉伸
                val display = windowManager.defaultDisplay
                val height = display.height
                val width = display.width
                val preSize: Camera.Size =
                    CameraUtils.getCloselyPreSize(true, width,height,
                        parameters.supportedPreviewSizes)
                parameters.setPictureSize(preSize.width, preSize.height)
                camera!!.run {
                    setParameters(parameters)
                    setPreviewDisplay(mSurfaceView.holder)
                    //设置将相机获取到的界面旋转90度显示
                    setDisplayOrientation(90)
                    startPreview()
                    cancelAutoFocus()
                }
                safeToTakePicture = true
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }

5,监听扫描按钮,获取到图片,这里因为是预览界面,所以和平时相机拍照的方法有一些区别,需要使用takePicture函数,三个参数我使用了图片回调,因为我需要得到图片。

face_confirm.setOnClickListener {
     camera?.takePicture(null, null, Camera.PictureCallback { data, _ ->
                    //data是ByteArray类型的,所以需要转换一下才能得到图片临时存放的地址
                    pictureDataBytes = data
                    stopPreview()//获取完之后停止相机预览活动
              ...
                    }).start()
                })
 }

6,ByteArray转换String函数,获取到图片的拼接地址。

private fun PictureConversion(bytes: ByteArray?): String? {
        try {
            val f: File = File.createTempFile("img", ".jpg")
            val fos = FileOutputStream(f)
            fos.write(bytes)
            fos.flush()
            fos.close()
            return f.getAbsolutePath()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return ""
    }