Camera2学习总结

一、简述

1、camera2的发展是由于手机厂商在添加过多的摄像头,而google的camera1只是针对单摄像头的所以对于多摄像头的处理不是很友好。
2、由于手机主板需要占据手机大部分的空间,而手机的摄像头也是长方形的,所以手机厂商的后置摄像头的放置一般都是逆时针旋转90度的。

二、学习camera2需要了解哪些api的使用?

CameraManager//获取相机的一些参数信息和打开相机的管理类
CameraCaptureSession//用来向相机设备发送获取图像的请求
ImageReader//获取屏幕渲染数据 可以搭配MediaProjectionManager录屏一起使用

三、学习camera2遇到的一些问题

1:用户在使用手机在拍照的时候不会是完全竖直使用的,那在用户点击拍照的时候怎么处理手机旋转带来的问题?
2:前置摄像头就像是镜子,所以拍照出来的数据是镜像的我们需要怎么处理?
3:用户切换前后摄像头代码应该怎么做?
     带着以上3个问题开始代码之旅

四、问题处理
  1. 手机旋转我们可以做监听处理,系统提供了OrientationEventListener
val mOrEventListener: OrientationEventListener =
            object : OrientationEventListener(context) {
                override fun onOrientationChanged(orientation: Int) {
                    if (((orientation >= 0) && (orientation <= 45)) || (orientation > 315) && (orientation <= 360)) {
                        phoneDegree = 0;
                    } else if ((orientation > 45) && (orientation <= 135)) {
                        phoneDegree = 90;
                    } else if ((orientation > 135) && (orientation <= 225)) {
                        phoneDegree = 180;
                    } else if ((orientation > 225) && (orientation <= 315)) {
                        phoneDegree = 270;
                    }
                }
            }
mOrEventListener.enable()

我们将获取的旋转角度保存为全局变量,此时只是获取了旋转角度,还有摄像头对应得角度我们还需要再处理,上代码

val cameraCharacteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
var mOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)

mCameraId是对应得前置还是后置的摄像头,mOrientation 就是相机需要的旋转角度

  • 对应得前置摄像头的处理方案
(mOrientation-phoneDegree + 360)% 360
  • 对应得后置摄像头的处理方案
(mOrientation+phoneDegree )% 360

至此第一个问题可以解决了!!!

  1. 镜像处理应该大家都做过很简单的一个沿着x翻转就可以了,这里我们用的是Matrix
 matrix.postScale(-1F, 1F);//利用matrix 对矩阵进行转换,x轴镜像

至此第二个问题可以解决了!!!

  1. 第三个不是技术问题,是摄像头在使用过程中不支持动态更改配置,如果需要更改的话必须重新初始化所有配置操作。所以封装好代码按步骤初始化吧。这类需要提的几点就是,对于camera如果已经打开过了我们需要release
 captureSession!!.close()
 cameraDevice!!.close()

对于ImageReader类我们已经创建过的需要close

imageReader!!.close()

至此以上遇到的一些问题都解决了!!!没有可以难倒我们的了。接下来就是看看代码如何处理吧
(代码主要是kotlin写的,初学者,希望写的不对的大家可以指教!!!!)
下面贴一下完整的代码。

class Camera2Manager(var context: Context, var captureTexture: SurfaceTexture) {
    private val tag = Camera2Manager::class.java.simpleName
    private var handlerThread = HandlerThread("Camera2Manager")
    private var handler: Handler? = null
    private var mCameraId = "0"//初始为后置摄像头
    private var cameraManager: CameraManager? = null//获取相机的一些参数信息和打开相机的管理类
    private var captureSession: CameraCaptureSession? = null//用来向相机设备发送获取图像的请求
    private var imageReader: ImageReader? = null//获取屏幕渲染数据 可以搭配MediaProjectionManager录屏一起使用
    private var previewWidth = 0//图片预览宽
    private var previewHeight = 0//图片预览高
    private var cameraDevice: CameraDevice? = null
    private var captureCallBack: CaptureCallBack? = null
    private var isFrontCamera: Boolean = false//前置摄像头的判断
    private var phoneDegree = 0;//手机的旋转角度
    init {
        handlerThread.start()//启动工作线程
        handler=Handler(handlerThread.looper)
        cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        mCameraId = getCamera()[0]
        initDefaultPreviewSize()
        startOrientationListener()
    }
    /**
     * 主要是监听手机旋转角度来保证所拍摄的照片始终是正向的
     */
    private fun startOrientationListener() {
        val mOrEventListener: OrientationEventListener =
            object : OrientationEventListener(context) {
                override fun onOrientationChanged(orientation: Int) {
                    if (((orientation >= 0) && (orientation <= 45)) || (orientation > 315) && (orientation <= 360)) {
                        phoneDegree = 0;
                    } else if ((orientation > 45) && (orientation <= 135)) {
                        phoneDegree = 90;
                    } else if ((orientation > 135) && (orientation <= 225)) {
                        phoneDegree = 180;
                    } else if ((orientation > 225) && (orientation <= 315)) {
                        phoneDegree = 270;
                    }
                }
            }
        mOrEventListener.enable()
    }

    /**
     * 获取CameraId 一台设备会有多个摄像头
     */
    private fun getCamera(): Array<out String> {
        return cameraManager!!.cameraIdList
    }

    /**
     * 获取当前camera的支持预览大小 可以供用户选择分辨率不过一般给与最大分辨率拍摄
     */
    private fun getPreviewSize(): Array<out Size>? {
        var characteristics = cameraManager?.getCameraCharacteristics(mCameraId)
        val configs = characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
        return configs!!.getOutputSizes(SurfaceTexture::class.java)
    }

    /**
     * 根据cameraId打开指定的摄像头
     */
    fun openCamera() {
        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.CAMERA
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        createImageReader()
        cameraManager?.openCamera(mCameraId, cameraStateCallback, handler)
    }

    /**
     *  打开Camera的状态回调
     */
    private var cameraStateCallback = object : CameraDevice.StateCallback() {
        override fun onOpened(device: CameraDevice) {
            cameraDevice = device
            createCaptureSession()
        }
        override fun onDisconnected(device: CameraDevice) {
        }
        override fun onError(device: CameraDevice, p1: Int) {
        }
    }

    /**
     * 创建CameraCaptureSession的状态回调
     */
    private var captureSessionCallback = object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(session: CameraCaptureSession) {
            captureSession = session
            var captureSessionRequest =
                cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            captureSessionRequest.addTarget(Surface(captureTexture))
            // 设置自动对焦模式
            captureSessionRequest?.set(
                CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 设置自动曝光模式
            captureSessionRequest?.set(CaptureRequest.CONTROL_AE_MODE,
                CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            var request = captureSessionRequest.build()
            captureSession!!.setRepeatingRequest(request, null, handler)
        }
        override fun onConfigureFailed(session: CameraCaptureSession) {
        }
    }
    /**
     * ImageReader获取到数据时的回调
     */
    private var imageAvailableListener = ImageReader.OnImageAvailableListener {
        var image = imageReader!!.acquireLatestImage()//点击拍照的时候会获取当前的一帧数据
        var buffer = image.planes[0].buffer
        var length = buffer.remaining()
        var bytes = ByteArray(length)
        buffer.get(bytes)
        image.close()
        try {
            var bmp = BitmapFactory.decodeByteArray(bytes, 0, length, null)
            var matrix = Matrix()

            val cameraCharacteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
            var mOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
            if (isFrontCamera) {
                matrix.postRotate((mOrientation-phoneDegree + 360)% 360 * 1.0F)
                matrix.postScale(-1F, 1F);//利用matrix 对矩阵进行转换,x轴镜像
            } else {
                matrix.postRotate((mOrientation+phoneDegree )% 360 * 1.0F)
            }
            var bitmap = Bitmap.createBitmap(bmp, 0, 0, bmp.width, bmp.height, matrix, false)
            if (captureCallBack != null) {
                captureCallBack!!.onSucceed(bitmap)
            }
        } catch (e: Exception) {
            if (captureCallBack != null) {
                captureCallBack!!.onFailed(Throwable(e.localizedMessage))
            }
        }

    }

    /**
     * 更改预览,本质上是重新创建CameraCaptureSession和ImageReader
     */
    fun changePreviewSize(width: Int, height: Int) {
        previewWidth = width
        previewHeight = height
        if (cameraDevice != null) {
            createImageReader()
            createCaptureSession()
        }
    }

    /**
     * 创建CameraCaptureSession
     */
    private fun createCaptureSession() {
        captureTexture!!.setDefaultBufferSize(previewWidth, previewHeight);//设置SurfaceTexture缓冲区大小
        if (captureSession != null) {
            captureSession!!.close()
            captureSession = null
        }
        cameraDevice!!.createCaptureSession(
            listOf(
                Surface(captureTexture),
                (imageReader!!.surface)
            ), captureSessionCallback, handler
        )
    }

    /**
     * 创建ImageReader
     */
    private fun createImageReader() {
        if (imageReader != null) {
            imageReader!!.close()
            imageReader = null
        }
        imageReader = ImageReader.newInstance(previewWidth, previewHeight, ImageFormat.JPEG, 2)
        imageReader!!.setOnImageAvailableListener(imageAvailableListener, handler)
    }

    /**
     * 重新打开一个相机 打开之前要先做一下清理
     * 每次切换摄像头都需要重新创建
     */
    fun openCamera(cameraId: String) {
        if (cameraDevice != null) {
            releaseCamera()
        }

        //可以根据下面的代码判断是否是前置摄像头或者后置
//        val characteristics = cameraManager!!.getCameraCharacteristics(mCameraId)
//        if(characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)//前置
//        if(characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK)//后置
        mCameraId = cameraId
        isFrontCamera = when (cameraId) {
            "0" -> false
            else -> true
        }
        initDefaultPreviewSize()
        openCamera()
    }

    /**
     * 获取默认的预览尺寸 取第一个尺寸的默认最大
     */
    private fun initDefaultPreviewSize() {
        var previewSize = getPreviewSize()
        for (i in 0 until previewSize?.size!!) {
            var width = previewSize[i].width
            var height = previewSize[i].height
            Log.e("我的预览尺寸$i", "$width  ----  $height")
        }
        var size = previewSize!![0]
        previewWidth = size.width
        previewHeight = size.height
    }

    /**
     * 拍照
     */
    fun capturePic(captureCallBack: CaptureCallBack) {
        if (cameraDevice == null) {
            return
        }
        this.captureCallBack = captureCallBack
        try {
            var requestBuilder =
                cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
            requestBuilder.addTarget(imageReader!!.surface)
            var request = requestBuilder.build()
            captureSession!!.capture(request, null, handler)
        } catch (e: Exception) {
            if (captureCallBack != null) {
                captureCallBack.onFailed(Throwable(e.localizedMessage))
            }
        }
    }
    /**
     * 释放相机
     */
    fun releaseCamera() {
        captureSession!!.close()
        cameraDevice!!.close()
    }
    /**
     * 释放handlerThread
     */
    fun destroy() {
        handlerThread.quit()
    }
}

至此暂时结束简单的处理,接下来有时间会写更详细的处理,还有camerax的使用之类的。
简书不一定会常用,以后也会不定期更新一些关于音视频方面的东西,希望跟志同道合的朋友一起学习进步。
下一篇会写android端p2p的投屏软件,喜欢的朋友可以关注下一起进步。
追加一些自动对焦的处理,引用下别人的文章Android Camera2之 手动点击区域对焦

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容