Android-摄像头全解析之-自定义拍照界面

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010782846/article/details/89881890

前言

在实现拍照功能的时候,除了可以直接使用系统内置的拍照APP外,如果我们需要更美观的拍照功能界面,或者提供更多的操作功能,那么我们还可以选择在自己的应用程序对拍照功能进行自定义,自定义拍照界面我们仅仅需要特别关注两个类,一个是android.hardware.Camera负责调用摄像头进行操作,第二个就是android.view.SurfaceView,作用是用来预览拍照的效果。

自定义拍照流程

  1. 初始化摄像头
  2. 进行拍照(切换前后置摄像头功能、是否开启灯光功能等)
  3. 结束Activity.onDestroy()前,释放摄像头对象

初始化摄像头

初始化摄像头有分为很多流程,下面我们开始对摄像头进行初始化的流程梳理:

  1. 先判断Camera是否已经声明,如果Camera已经初始化声明过了,进行先开启预览
  2. 按照当前选择的前后置摄像头,获取到我们需要操作的Camera对象。(Android9以上才支持前后置摄像头的API)
  3. 获取到Camera对象的默认配置属性。
  4. 得到默认的Parameters后我们可以用一些默认配置进行修改,比如对焦类型、图片尺寸、调节手机方向和预览方向的问题、绑定SurfaceView.SurfaceHolder进行预览。
  5. 设置启动预览
  6. 为解决SurfaceView还没有初始化完成,添加监听,初始化完成后再次配置一次Camera对象

基于以上流程,我们开始进行编码:

初始开始启动预览:

    private fun initCamera() {
        if (mCamera != null) {
            mCamera?.startPreview()
            setPreviewLight()
        }
        .....
    }
        
    private fun setPreviewLight() {
        mCamera?.setPreviewCallback(object : Camera.PreviewCallback {
            override fun onPreviewFrame(data: ByteArray, camera: Camera) {

            }
        })
    }        

Camera.startPreview()表示启动摄像头的预览帧,启动后才能捕获帧显示到屏幕上,其中Camera.PreviewCallback 执行早于SurfaceHolder.setPreviewDisplay(),即可以使用Camera.PreviewCallback来做一些前置操作,比如通过获取到帧数据分析当前照片是否过暗,需要打开手电筒。

打开摄像头

    private fun safeCameraOpen(id: Int): Boolean {
        return try {
            //释放摄像头
            releaseCameraAndPreview()
            mCamera = Camera.open(id)
            true
        } catch (e: Exception) {
            Log.e("PreView", "failed to open Camera")
            e.printStackTrace()
            false
        }

打开摄像的API为Camera.open(id),适用于Android9以上的系统,可以传入Camera.CameraInfo.CAMERA_FACING_FRONT为前置摄像头,Camera.CameraInfo.CAMERA_FACING_BACK为后置摄像头。

获取到默认的配置对象:

    private fun initCamera() {
    	....
        val parameters = mCamera?.getParameters()
        ....
	}

Camera已经为我们设置一些默认的配置,我们可以在默认的配置对象上,具体的功能点我们下面再讲。

    private fun initCamera() {
    	....
        try {
            mCamera?.setPreviewDisplay(surfaceView.holder)
        } catch (e: IOException) {
            e.printStackTrace()
        }
          ....
	}

绑定SurfaceView.SurfaceHolder后,我们需要再调一次开始预览。
为了解决,有时候摄像头初始化结束后,SurfaceView还没有初始化的问题,我们使用以下代码监听SurfaceView的生命周期,并再次调用摄像头的初始化流程:

    private fun initCamera() {
    	....
        val holder = surfaceView.getHolder()
        if (holder != null) {
            if (mCallBack != null) {
                holder.removeCallback(mCallBack)
            }
            mCallBack = object : SurfaceHolder.Callback {
                override fun surfaceCreated(holder: SurfaceHolder) {
                    Log.e(TAG, "surfaceCreated$holder$this")
                    initCamera()
                }

                override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
                    Log.e(TAG, "surfaceChanged$holder$this")
                }

                override fun surfaceDestroyed(holder: SurfaceHolder) {
                    Log.e(TAG, "surfaceDestroyed$holder$this")
                }
            }
            holder.addCallback(mCallBack)
        }
            ....
}

进行拍照

Camera提供了Camera.takePicture(ShutterCallback, PictureCallback , PictureCallback)进行拍照操作,其中ShutterCallback是在拍照被触发前的一段很短的时间内被触发,常用于响起快门声或其他反馈,中间的PictureCallback传回的是源文件,没有经过压缩的数据源,最后一个PictureCallback传回jpeg格式数据源。

    private fun takePicture() {
        mCamera?.takePicture(object :Camera.ShutterCallback{
            override fun onShutter() {

            }

        },object :Camera.PictureCallback{
            override fun onPictureTaken(data: ByteArray?, camera: Camera?) {

            }

        },object:Camera.PictureCallback{
            override fun onPictureTaken(data: ByteArray?, camera: Camera?) {
                val bitmap=BitmapFactory.decodeByteArray(data,0,data?.size!!)
                var filePath:String?=null
                if (TextUtils.isEmpty(mPath)){
                    filePath=defaultFile().absolutePath
                }else{
                    filePath=mPath
                }
                val path=FileUtils.saveBitmap2File(bitmap,filePath)
                val intent = Intent()
                intent.putExtra("data", path)
                setResult(Activity.RESULT_OK,intent)
                finish()
            }

        })
    }

释放资源

为了防止内存泄漏,需要释放Camera对象,一般在onDestory()方法中调用:

    private fun releaseCameraAndPreview() {
        mCamera=null
        mCamera?.also { camera ->
            camera.release()
            mCamera = null
        }
    }

拍照属性配置功能点详解

设置摄像头的配置需要通过如下步骤:

  • 获取默认的配置对象Camera?.getParameters()
  • 设置配置前判断当前设备是否支持配置parameters?.getSupportedFocusModes()?,如果不支持会返回null
  • 设置完成后调用**Camera?.setParameters(parameters)**才回生效

摄像头的常用配置如下:

是否自动对焦

        if (parameters?.getSupportedFocusModes()?.contains(
                Camera.Parameters
                    .FOCUS_MODE_CONTINUOUS_PICTURE
            )!=null
        ) {
            //自动持续对焦
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)
        }

这段代码设置了应用于拍照的对焦模式,有一下几种模式进行参考:

  • FOCUS_MODE_AUTO 适用于任何操作的自动对焦模式
  • FOCUS_MODE_CONTINUOUS_PICTURE 适用于拍照的自动对焦模式
  • FOCUS_MODE_CONTINUOUS_VIDEO 适用于拍视频的自动对焦模式
  • FOCUS_MODE_EDOF 扩展景深模式
  • FOCUS_MODE_FIXED 固定焦点模式
  • FOCUS_MODE_INFINITY 焦点无限远
  • FOCUS_MODE_MACRO 特写模式

设置JPEG图片格式的GPS信息

当我们拍照或者摄影的时候,在JPEG格式的信息上,需要拍摄具有经纬度的图片,可以使用如下设置进行设置GPS信息:

  • parameters.setGpsLatitude 设置纬度
  • parameters.setGpsLongitude 设置经度
  • parameters.setGpsAltitude 设置海拔
  • parameters.setGpsTimestamp 设置时间戳
  • parameters.setGpsProcessingMethod 设置GPS处理方法
  • parameters.removeGpsData 清除之前设置的GPS信息

白平衡

  • WHITE_BALANCE_AUTO 自动白平衡
  • WHITE_BALANCE_INCANDESCENT 白织灯
  • WHITE_BALANCE_FLUORESCENT 荧光灯
  • WHITE_BALANCE_WARM_FLUORESCENT 温暖的荧光灯
  • WHITE_BALANCE_DAYLIGHT 充满阳光的天气
  • WHITE_BALANCE_CLOUDY_DAYLIGHT 阴天、日光
  • WHITE_BALANCE_TWILIGHT 暮光
  • WHITE_BALANCE_SHADE 阴凉处

设置预览的图片大小

不同的手机支持的摄像头图片大小不同,这就要求我们能够通过一定的算法,选出能够被设备支持并且合适的预览大小。

// 获取到设备支持的图片大小
val picSizes = parameters?.getSupportedPictureSizes()
private fun getPictureSize(picSizes: List<Camera.Size>?, width: Int, height: Int): Camera.Size? {
        // 对于存储最适合的最小
        var betterSize: Camera.Size? = null
        // 差值
        var diff = Integer.MAX_VALUE
        if (picSizes != null && picSizes.size > 0) {
            for (size in picSizes) {
                val newDiff = Math.abs(size.width - width) + Math.abs(size.height - height)
                // 如果没有任何差别、说明是最合适的,直接返回,减少遍历
                if (newDiff == 0) {
                    return size
                }
                if (newDiff < diff) {
                    betterSize = size
                    diff = newDiff
                }
            }
        }
        return betterSize
    }

通过以上对比遍历后,就能索引出最适合的图片预览大小,然后直接设置到Camera.Parameter]中即可:

parameters?.setPictureSize(picSize!!.width, picSize.height)

更多详情,请查阅Camera.Parameter

相机方向和预览方向的适配

猜你喜欢

转载自blog.csdn.net/u010782846/article/details/89881890