Android Camera2 开发实践指南

咱们知道 Android 中相机开发是有两套 API 可使用的,一个是 Camera,这个适用于 Android 5.0 如下,另一个是 Camera2,这个适用于 Android 5.0 以上。可是这仅仅是系统的建议,其实开发中因为国内厂商对 Camera2 的支持程度各不相同,即使是 5.0 以上的手机,也可能对 Camera2 支持很是差的状况,咱们可能还得降级使用 Camera 来开发。android

 

使用 Camera2 开发会涉及到一些系统方法的调用,咱们须要大概了解一下他们的做用。ios

1.相机的管理主要由如下两个类提供:session

CameraManager:相机管理类,能够获取相机个数,以及打开或关闭相机等操做。ide

CameraCharacteristics:获取相机的配置参数,好比获取相机支持的拍摄分辨率大小、ISO范围、曝光时间等,系统提供了大概78个配置选项。性能

2.相机的预览和拍摄主要由下面的类管理:动画

CameraDevice:这个至关因而打开相机后当前摄像头的表示,相机开发后会传入一个CameraDevice,咱们可使用此类来建立与相机的链接。ui

CameraCaputreSession:由CameraDevice配置好后产生的session,用于处理相机预览或者是拍照等处理,就至关因而已经创建链接了,而后如今经过这个CameraCaptureSession处理与相机进行对话。this

CaptureRequest:控制本次获取图像的配置,好比配置图片的ISO,对焦方式和曝光时间等。google

CaptureResult:描述拍照完成后的结果。.net

ImageReader:能够用这个类来作简单的捕获图像处理。

3.展现预览图像可使用 SurfaceView 或 TextureView:

SurfaceView:界面渲染能够放在单独线程,自身不能支持使用动画。

TextureView:只能在拥有硬件加速层层的Window绘制,性能不如SurfaceView,而且可能丢帧,可是能够作一些动画效果。

二者详细差异分析能够参考:https://groups.google.com/a/chromium.org/forum/#!topic/graphics-dev/Z0yE-PWQXc4

 

Camera2 开发的整个流程就上面介绍的那样:

先请求相机权限
使用 CameraManager找到你想要的摄像头,前置或是后置。
经过 CameraCharacteristics 获取相机的配置信息,方便以后调整相机的各类参数。
经过 CameraManager 打开相机,获得当前的 CameraDevice。
经过 CameraDevice 建立本次会话,获得本次会话的 CameraCaptureSession。
使用 CaptureRequest 设置获取图片的参数信息,设置到 CameraCaptureSession 中。
在 ImageReader 或 CaptureResult 处理获得的图片。
关闭相机(不关闭有可能会致使相机资源占用,致使别的相机没法正常打开)
 

开发以前先来了解一些相机的配置介绍:

AE:Automatic Exposure(自动曝光)。

AF:Auto Focus(自动对焦)。

ISO:International Orization for Standardization,这个是国际标准化组织,因为照相机的感光度最终由这个组织发布,因此称为感光度ISO值。

EV:Exposure value(曝光值)

F:F-number(光圈)

咱们经过本身设置这些参数能够拍出很是有意思的照片,下面说一下 Android 为咱们提供的与摄像头交互的一些类,以及如何配置本身的相机参数来开发相机。

下面将使用 TextureView 结合 Camera2 API 制做一个简单的相机预览

1. 在TextureView可用状态时打开相机。

    //监听view的事件
    mTextureView.surfaceTextureListener = this
    
    override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
        //可用的时候会回调当前方法,width是此view的宽,height是高。
        openCamera(width,height)
    }
2.首先在 openCamera 中检查是否有相机权限

    private fun openCamera(width : Int,height : Int) {
        //判断是否有相机权限
        if (ContextCompat.checkSelfPermission(activity!!,         Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            requestCameraPermission()
            return
        }
        //设置要使用的摄像头以及获取摄像头的配置
        setUpCameraOutputs(width, height)
        //打开摄像头
        waitOpen()
    }
 
    //申请权限
    private fun requestCameraPermission() {
        requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
    }
3.配置相关属性

    private fun setUpCameraOutputs(width: Int, height: Int) {
        val activity = act
        val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        manager.cameraIdList.forEach { cameraId ->
            val characteristics = manager.getCameraCharacteristics(cameraId)
 
            val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
            //这里咱们不使用前置摄像头
            if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                return@forEach
            }
            //获得相机支持的流配置(包括支持的图片分辨率等),不支持就返回
            val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                    ?: return@forEach
            //获取支持的iso范围
            val isoRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE)
            //获得最高和最低值
            if (isoRange != null) {
                val isoMin = isoRange.lower
                val isoMax = isoRange.upper
            }
            //获取支持的图像曝光时间范围,单位纳秒
            val timeRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE)
            //获得最大最小值
            if (timeRange != null) {
                val timeMin = timeRange.lower
                val timeMax = timeRange.upper
            }
 
            //为了方便,这里咱们获取支持摄像头支持的最大尺寸来存储
            //咱们直接使用了 JPEG 的图片格式,android 支持的图片格式能够查看ImageFormat这个类
            val largest = Collections.max(
                    Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)),
                    CompareSizesByArea())
            //建立一个用于获取摄像头图片的 ImageReader,最多接受 2 个
            mImageReader = ImageReader.newInstance(largest.width, largest.height, ImageFormat.JPEG, 2)
            //监听接受到图片的事件
            mImageReader?.setOnImageAvailableListener(mOnImageAvailableListener, mHandler)
            //检查是否支持 flash
            mFlashSupported = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
            //获得当前的摄像头id
            mCameraId = cameraId
        }
    }
4.根据 id 打开相机

    private fun waitOpen() {
        val activity = act
        val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        if (mCameraId == null){
            //没有找到符合的摄像头
            return
        }
        manager.openCamera(mCameraId,mStateCallback,mHandler)
    }
 
    //在这里获得打开相机的各类回调
    private val mStateCallback : CameraDevice.StateCallback = object : CameraDevice.StateCallback(){
        override fun onOpened(camera: CameraDevice) {
            //获得当前摄像头
            mCameraDevice = camera
            //建立预览请求
            createCameraPreviewSession()
        }
 
        override fun onDisconnected(camera: CameraDevice) {
        }
 
        override fun onError(camera: CameraDevice, error: Int) {
        }
    }
5.建立预览请求

    private fun createCameraPreviewSession(){
        //获取view的surface,这里的宽高应该是获取适合摄像头当前的宽高,这里为了方便就直接使用屏幕宽高了
        val texture = mTextureView.surfaceTexture
        texture.setDefaultBufferSize(act.getWidth(),act.getHeight())
        val surface = Surface(texture)
        //构建预览请求
        mPreviewRequestBuilder = mCameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
        mPreviewRequestBuilder?.addTarget(mImageReader?.surface)
        mPreviewRequestBuilder?.addTarget(surface)
        //建立预览会话
        mCameraDevice?.createCaptureSession(listOf(surface, mImageReader?.surface),mSessionCallBack,null)
    }
   //会在mSessionCallBack获得咱们本次的session
    private val mSessionCallBack : CameraCaptureSession.StateCallback = object : CameraCaptureSession.StateCallback(){
        override fun onConfigureFailed(session: CameraCaptureSession) {}
 
        override fun onConfigured(session: CameraCaptureSession) {
            if (mCameraDevice == null){
                return
            }
            //获得本次的会话类
            mCaptureSession= session
            //设置为自动对焦的会话预览
           mPreviewRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
            //将imageReader添加进来便可在此类中获得预览的图片
            mPreviewRequestBuilder?.addTarget(mImageReader?.surface)
            mPreviewRequest = mPreviewRequestBuilder?.build()
            //设置为连续请求
            mCaptureSession?.setRepeatingRequest(mPreviewRequest,mCaptureCallback,mHandler)
        }
    }
这里还能够设置别的模式,好比设置能够本身调节ISO大小,值的大小能够根据从相机配置读取的参数范围设置

    /**
     * 设置ios感光度以及曝光时间
     * ae 自动曝光 Automatic Exposure
     * af 自动对焦 Auto Focus
     * @param iso 灵敏度
     * @param exposure 曝光时间
     * @param frame 帧持续时间
     */
    private fun setIsoMode(iso: Int, exposure: Long, frame: Long) {
            //禁用全部自动设置
            //            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
            //只是禁用曝光,白平衡继续开启,本身设置iso等值,必须禁用曝光
            mPreviewRequestBuilder?.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)
            mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposure)
            mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_SENSITIVITY, iso)
            mPreviewRequestBuilder?.set(CaptureRequest.SENSOR_FRAME_DURATION, frame)
            mPreviewRequestBuilder?.addTarget(mImageReader?.surface)
            //须要预览的话改为这个而且添加到下面的createCaptureSession里
            mPreviewRequest = mPreviewRequestBuilder?.build()
            mCaptureSession?.setRepeatingRequest(mPreviewRequest,
                    mCaptureCallback, mHandler)
    }
6.在 ImageReader 中处理图片结果

    //这个是刚刚为mImageReader添加的回调接口
    private val mOnImageAvailableListener = (ImageReader.OnImageAvailableListener { reader ->
        //获取下一张图片
        val image = reader.acquireNextImage()
        //这里的planes大小和所选的图片格式有关,好比YUV_444就有三个通道
        val buffer = image.planes[0].buffer
        val bytes = ByteArray(buffer.remaining())
        val bitmap = BitmapFactory.decodeByteArray(bytes,0,bytes.size)
        //使用完记得回收
        image.close()
    })
7.操做完以后就记得释放相机资源

    private fun closeCamera(){         if (mCaptureSession != null) {             mCaptureSession?.close()             mCaptureSession = null         }         if (mCameraDevice != null) {             mCameraDevice?.close()             mCameraDevice = null         }         if (mImageReader != null) {             mImageReader?.close()             mImageReader = null         }     }    

相关文章
相关标签/搜索