Camera 知识大全

535 阅读5分钟

Camera权限申请

1、在清单文件中配置。

    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

2、动态权限申请。

Manifest.permission.CAMERA

照相机页面全屏实现

1、采用 Theme.MaterialComponents.DayNight.NoActionBa 主题。

2、在页面中,setContentView之前,写上

        this.requestWindowFeature(Window.FEATURE_NO_TITLE)
        this.getWindow().setFlags(
            WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN
        )

SurfaceView

如何监听SurfaceView的生命周期?

(1)首先获得SurfaceVIew的holder,然后增加CallBack

(2)首次启动会执行surfaceCreated 和 surfaceChanged 方法

(3)如果横竖屏切换,surfaceCreated 和 surfaceChanged、和surfaceDestroyed 方法。

        val holder = surfaceView.holder
        holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(p0: SurfaceHolder) {
                Log.e(TAG, "surfaceCreated")
                val camera = cameraManager.openCamera(CAMERA_ID)
            }

            override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
                Log.e(TAG, "surfaceChanged")
            }

            override fun surfaceDestroyed(p0: SurfaceHolder) {
                Log.e(TAG, "surfaceDestroyed")
            }
        })

SurfaceView + Camera展示图像的流程

1、监听SurfaceView的生命周期,在 surfaceCreated 方法中,说明SurfaceView创建完成

2、然后通过 Camera.open(cameraId) 获得Camera对象。

3、然后设置 Camera 的参数,比如展示的容器,设置预览的分辨率,设置对焦模式,设置旋转的角度,设置图像格式。

4、然后开始预览。

获得相机

fun openCamera(cameraId: Int): Camera? {
        this.cameraId = cameraId;
        if (camera == null) {
            try {
                camera = Camera.open(cameraId)
            } catch (e: Exception) {
            }
        }
        return camera
    }

设置相机展示的容器

camera?.setPreviewDisplay(surfaceHolder)

获得相机合适的预览分辨率

1、相机支持的分辨率有很多,比如有18个

(height:1080 width:1920、height:880 width:1920、height:720 width:1552、

height:864 width: 1536 .......)

特点:这些相机的预览分辨率都有一个特点,就是width > height

2、选择相机合适的预览分辨率的逻辑

(1)如果支持的分辨率集合为空,那么选择相机的预览分辨率。

(2)如果只有一个支持的分辨率,那么选择这一个分辨率,然后就是预览的分辨率。

(3)如果有多个支持的分辨率,首先选择分辨率相等的那个分辨率。

(4)如果分辨率不相等,那么选择宽高比最接近的那个分辨率。

具体逻辑如下:

package com.example.camerademo.utils

import android.graphics.Point
import android.hardware.Camera
import com.example.camerademo.CoreSetup

class CommonUtil {

    companion object {
        fun getCameraPreviewSize(parameters: Camera.Parameters): Point {
            // 获取所有支持的预览分辨率,支持的分辨率有18个(1920*1080)
            val rawSupportedSizes = parameters.supportedPreviewSizes

            // 选择合适支持的分辨率
            var supportedPreviewSize: Camera.Size?

            // 如果支持的分辨率为空
            if (rawSupportedSizes == null) {
                supportedPreviewSize = parameters.previewSize
                // TODO 这个值为啥要这么设置
                return Point(supportedPreviewSize.height, supportedPreviewSize.width)
            }

            // 如果只有一个支持的个数
            if (rawSupportedSizes.size == 1) {
                return Point(rawSupportedSizes.get(0).width, rawSupportedSizes.get(0).height)
            }

            // 如果有多个支持的个数
            // 首先判断是否有相等的,如果没有相等的,取出这个值
            supportedPreviewSize =
                rawSupportedSizes.find { it.width == CoreSetup.preWidth && it.height == CoreSetup.preHeight }
            // 如果 != null,说明有相同的分辨率,那么选择这个分辨率
            if (supportedPreviewSize != null) {
                return Point(supportedPreviewSize.width, supportedPreviewSize.height)
            }

            // 说明没有相同的分辨率,那么此时选择最近的分辨率
            return this.getClostPersize(parameters, CoreSetup.preWidth, CoreSetup.preHeight)
        }

        /**
         * 选择最近的分辨率
         */
        private fun getClostPersize(
            parameters: Camera.Parameters,
            width: Int,
            height: Int
        ): Point {
            var deltaRatioMin = 3.4028235E38f
            val surfaceViewWidth = width
            val surfaceViewHeight = height
            var realWidth = 0
            var realHeight = 0

            // 获得宽高比
            val surfaceViewRatio = surfaceViewWidth.toFloat() / surfaceViewHeight.toFloat()

            // 获得支持的分辨率
            val supportedPreviewSizes = parameters.supportedPreviewSizes

            // 遍历集合,选择一个最接近的分辨率
            var resultSize: Camera.Size? = null
            // 选择相等的分辨率
            for (temp in supportedPreviewSizes) {
                val realRatio = temp.width.toFloat() / temp.height.toFloat()
                if (realRatio == surfaceViewRatio) {
                    if (temp.width <= 1920) {
                        if (realWidth <= temp.width) {
                            realWidth = temp.width;
                            realHeight = temp.height;
                        }
                    }
                }
            }
            // 如果没有相等的的,那么选择一个最相近的
            if (realWidth == 0 || realHeight == 0) {
                for (temp in supportedPreviewSizes) {
                    val curRatio = temp.width.toFloat() / temp.height.toFloat()
                    val deltaRatio = Math.abs(surfaceViewRatio - curRatio)
                    if (deltaRatio < deltaRatioMin) {
                        deltaRatioMin = deltaRatio;
                        resultSize = temp
                        realWidth = temp.width;
                        realHeight = temp.height;
                    }
                }
            }

            if (null == resultSize) {
                resultSize = parameters.previewSize
                realWidth = resultSize.width
                realHeight = resultSize.height
            }

            return Point(realWidth, realHeight)
        }
    }

}

设置相机的对焦模式

        // 设置对焦模式
        val focusModes = parameters.supportedFocusModes
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }

设置相机的旋转角度

camera?.setDisplayOrientation(CameraUtil.getOrientation(activity,cameraId))

class CameraUtil {

    companion object {
        fun getOrientation(activity: Activity, cameraId: Int): Int {

            val info = Camera.CameraInfo()
            Camera.getCameraInfo(cameraId, info)
            // 获取页面旋转的角度类型
            val rotation = activity.windowManager.defaultDisplay.rotation
            // 根据页面旋转的角度类型,来获取角度
            var degrees = 0
            when (rotation) {
                Surface.ROTATION_0 -> degrees = 0
                Surface.ROTATION_90 -> degrees = 90
                Surface.ROTATION_180 -> degrees = 180
                Surface.ROTATION_270 -> degrees = 270
                else -> {
                }
            }
            
            // 根据相机的旋转角度 和 页面旋转的角度,生成相机需要旋转的角度。
            var result: Int
            //前置
            if (info.facing === Camera.CameraInfo.CAMERA_FACING_FRONT) {
                result = (info.orientation + degrees) % 360
                result = (360 - result) % 360
            } else {
                // 后置
                result = (info.orientation - degrees + 360) % 360
            }
            return result
        }
    }
}

定时对焦

    /**
     * 定时对焦
     */
    private fun recyclerAutoFocus() {
        val countDownTimer = object : CountDownTimer(Long.MAX_VALUE, 2000) {

            override fun onTick(p0: Long) {
                Log.e(TAG, "onTick执行")
                camera?.autoFocus(null)
            }

            override fun onFinish() {

            }
        }
        countDownTimer.start()
    }

开启闪光灯

    fun controlFlash() {
        if (camera != null) {
            val parameters = camera!!.parameters
            val flashMode = parameters.flashMode
            if (flashMode == null){
                Toast.makeText(activity,"不支持闪光灯",Toast.LENGTH_SHORT).show()
                return
            }

            // 正在亮灯情况,关闭灯
            if (flashMode.equals(Camera.Parameters.FLASH_MODE_TORCH)){
                parameters.flashMode = Camera.Parameters.FLASH_MODE_OFF
            } else {
                // 如果不是亮灯,打开灯
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                parameters.setExposureCompensation(-1);
            }

            try {
                camera!!.parameters = parameters
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

预览图片不变形逻辑处理

1、当SurfaceView创建完成的时候,获取SurfaceView的宽高,然后设置预览的分辨率。

2、然后根据此预览分辨率,同时根据相机支持的分辨率,选择相等的分辨率或者宽高比最接近的分辨率,然后更新预览的分辨率。

3、根据预览的分辨率的宽高比 + SurfaceView的宽高比,然后等比例的放大SurfaceView的宽高,这样预览的图像就不会变形。

private fun initSurfaceView(surfaceView: SurfaceView) {
        val holder = surfaceView.holder
        holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(p0: SurfaceHolder) {
                Log.e(TAG, "surfaceCreated")

                CoreSetup.preWidth = surfaceView.height
                CoreSetup.preHeight = surfaceView.width

                cameraManager.startPreview(holder)
            }

            override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
                Log.e(TAG, "surfaceChanged")
            }

            override fun surfaceDestroyed(p0: SurfaceHolder) {
                Log.e(TAG, "surfaceDestroyed")
            }
        })
    }

class PlateidSurfaceView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs) {

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        if (CoreSetup.preHeight == 0 || CoreSetup.preWidth == 0) {
            return
        }
        // 计算预览的宽高
        // 理想的宽
        val desiredWidth = CoreSetup.preHeight
        val desiredHeight = CoreSetup.preWidth
        // 理想的宽高比
        val desireRadio = desiredWidth.toFloat() / desiredHeight.toFloat()

        // 获取SurfaceView的宽高比
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        var layoutWidth = when (widthMode) {
            MeasureSpec.EXACTLY -> {
                widthSize
            }
            MeasureSpec.AT_MOST -> {
                Math.min(desiredWidth, widthSize)
            }
            else -> {
                desiredWidth
            }
        }

        var layoutHeight = if (heightMode == MeasureSpec.EXACTLY) {
            heightSize
        } else if (heightMode == MeasureSpec.AT_MOST) {
            Math.min(desiredHeight, heightSize)
        } else {
            desiredHeight
        }

        // 等比例的放大SurfaceView的宽高。
        val layoutRadio = layoutWidth.toFloat() / layoutHeight.toFloat()
        if (layoutRadio > desireRadio) {
            layoutHeight = (layoutWidth.toFloat() / desireRadio).toInt()
        } else {
            layoutWidth = (layoutHeight.toFloat() * desireRadio).toInt()
        }
        this.setMeasuredDimension(layoutWidth, layoutHeight)
    }

}

传感器用来判断是竖直、倒置、左侧状态、右侧状态

class ScreenRotation(val context: Context) {

    private val TAG: String = "ScreenRotation"
    private val sensorService: SensorManager? by lazy { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }

    // 这些参数默认为竖屏状态的参数
    /**向左旋转 */
    var rotateLeft = false

    /**正向旋转 */
    var rotateTop = true

    /**向右旋转 */
    var rotateRight = false

    /**倒置旋转 */
    var rotateBottom = false

    private val listener: SensorEventListener = object : SensorEventListener {

        // 传感器方向改变时会调用,及时手机放在那,也会持续调用
        override fun onSensorChanged(event: SensorEvent) {
            // IntervalLogUtil.e(TAG, "传感器改变了")
            val x = event.values[SensorManager.DATA_X]
            val y = event.values[SensorManager.DATA_Y]
            // IntervalLogUtil.e(TAG, "x:$x   y:$y")
            val standardValue = 7
            if (x > standardValue) {
                if (!rotateLeft) {
                    IntervalLogUtil.e(TAG, "手机在左侧状态")
                    rotateBottom = false
                    rotateRight = false
                    rotateTop = false
                    rotateLeft = true
                }
            } else if (x < -standardValue) {
                if (!rotateRight) {
                    IntervalLogUtil.e(TAG, "手机在右侧状态")
                    rotateBottom = false
                    rotateRight = true
                    rotateTop = false
                    rotateLeft = false
                }
            }else if (y < -standardValue) {
                if (!rotateBottom) {
                    IntervalLogUtil.e(TAG, "倒置状态")
                    rotateBottom = true
                    rotateRight = false
                    rotateTop = false
                    rotateLeft = false
                }
            } else if (y > standardValue) {
                if (!rotateTop) {
                    IntervalLogUtil.e(TAG, "竖屏状态")
                    rotateBottom = false
                    rotateRight = false
                    rotateTop = true
                    rotateLeft = false
                }
            }

        }

        // 当传感器的精度发生变化时会调用
        override fun onAccuracyChanged(event: Sensor, accuracy: Int) {
            IntervalLogUtil.e(TAG, "传感器的精度改变了")
        }

    }

    init {
        IntervalLogUtil.e(TAG, "init方法执行")
        val sensor = sensorService?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
        sensorService?.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI)
    }

}

Camera注意事项

1、SurfaceView的初始化放在onResume中,否则会出现:当锁屏,然后解锁以后,会出现黑屏的现象。

2、现象:当A的CameraActivity切换到后台,然后打开另外的App(B)的相机预览界面,然后返回A App,此时出现了A界面黑屏,看到提示显示:java.io.IOException: setPreviewTexture failed

解决办法:

(1)在Activity的onStop方法中,移除掉SurfaceView。

 override fun onStop() {
        super.onStop()
        (surfaceView?.parent as ViewGroup).removeView(surfaceView)
    }

(2)在 SurfaceView 的 surfaceDestroyed 方法中,需要释放Camera资源。

    fun removeCamera() {
        try {
            camera?.setPreviewCallback(null)
            camera?.stopPreview()
            camera?.release()
            camera = null
        } catch (e: Exception) {

        }
    }