博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android Camera2 开发实践指南
阅读量:5931 次
发布时间:2019-06-19

本文共 7304 字,大约阅读时间需要 24 分钟。

hot3.png

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

 

使用 Camera2 开发会涉及到一些系统方法的调用,我们需要大概了解一下他们的作用。

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

CameraManager:相机管理类,可以获取相机个数,以及打开或关闭相机等操作。

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

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

CameraDevice:这个相当于是打开相机后当前摄像头的表示,相机开发后会传入一个CameraDevice,我们可以使用此类来创建与相机的连接。

CameraCaputreSession:由CameraDevice配置好后产生的session,用于处理相机预览或者是拍照等处理,就相当于是已经建立连接了,然后现在通过这个CameraCaptureSession处理与相机进行对话。

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

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

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
            }
            //得到相机支持的流配置(包括支持的图片分辨率等),不支持就返回
            val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                    ?: return
            //获取支持的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
     * iso 灵敏度
     * exposure 曝光时间
     * 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
        }
    }
 
 

转载于:https://my.oschina.net/u/920274/blog/3050120

你可能感兴趣的文章
Automatic Sql Server Backup Utility Using sqlserveragent
查看>>
Java是如何读取和写入浏览器Cookies的
查看>>
篇一、安装配置Android Studio
查看>>
C#代码安装、卸载、监控Windows服务
查看>>
2014年抢票总结
查看>>
zephir开发的扩展“wudimei框架”之模板词法扫描(三)完成代码切分
查看>>
ML 线性回归Linear Regression
查看>>
oracle如何用sql查看触发器?
查看>>
如何对HashMap按键值排序
查看>>
IIS负载均衡-Application Request Route详解第二篇:创建与配置Server Farm
查看>>
js/jquery/插件表单验证
查看>>
Bandwidth内存带宽測试工具
查看>>
为Node.js编写组件的几种方式
查看>>
(轉貼) Anders Hejlsberg談C#、Java和C++中的泛型 (.NET) (C#)
查看>>
30天敏捷结果(24):恢复你的精力
查看>>
JNI——访问数组
查看>>
C#开发和调用Web Service
查看>>
Android6.0机型上调用系统相机拍照返回的resultCode值始终等于0的问题
查看>>
全面理解Git
查看>>
JS敏感信息泄露:不容忽视的WEB漏洞
查看>>