美文网首页
Camera2使用(1)相机预览及拍照

Camera2使用(1)相机预览及拍照

作者: 非典型程序猿 | 来源:发表于2021-10-10 20:53 被阅读0次

概述

前几天看了Camera2的API,因此今天花了一天写了小demo来学习Camera2相关的API,因此今天写个文章记录一下

重要类

1.CameraManager类

  • 通过getSystemService(Context.CAMERA_SERVICE)方法获取,使用相机的入口,同时可以通过getCameraIdList()来获取camerId,在使用摄像头之前是必须要确定一个cameraId然后才能进行接下来的操作

2.CameraCharacteristics类

  • 通过CameraManager类中的如下方法获取,这个类含有很多关于camera的参数
getCameraCharacteristics(@NonNull String cameraId)

因为还没有学习过所有参数,这里我们拿可用的预览尺寸参数作为例子,获取可用的预览的尺寸可采用下面的方式

  /**
     * 获取当前camera的支持预览大小
     */
    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)
    }

这里获取到的就是一个Size的数组了

3.CameraDevice类

这个类就是具体的Camera设备了,通过CameraManager中的openCamera()来打开设备,在其第二参数的CameraDevice.StateCallback回调中可以获取,我们可以通过这类来和Camera通信,并获取Camera的数据

4.CameraCaptureSession

CameraCaptureSession主要就是负责建立一路和相机进行数据交互的会话,需要通过CameraDevice来创建

cameraDevice!!.createCaptureSession(
            listOf(
                Surface(captureTexture),
                (imageReader!!.surface)
            ), captureSessionCallback, handler
        )

CameraCaptureSession的获取也是在第二个参数CameraCaptureSession.StateCallback的回调方法中获取

5.CaptureRequest

这个类主要就针对CameraCaptureSession来进行一些具体的操作,每个CaptureRequest就可以看作是对这个CameraCaptureSession的一次操作,而CaptureRequest有个内部类Builder,这个类可以通过CameraDevice创建,而CaptureRequest.Builder通过build()方法即可完成CaptureRequest的创建

实现相机预览

利用TextureView相机预览的步骤如下

  1. 获取CameraManager
  2. 获取camerId
  3. 通过获取到的cameraId来打开一个camera设备,得到一个CameraDevice实例
  4. 利用TextureView获取一个SurfaceTexture,并用CameraDevice创建一个CameraCaptureSession
  5. 创建一个预览类型的CaptureRequest

实现拍照

利用ImageReader来读取相机返回的数据,步骤如下

  1. 在创建CameraCaptureSession的时候需要再传一个ImageReader的SurfaceTexture
  2. 创建一个拍照类型的CaptureRequest
  3. 在ImageReader的ImageReader.OnImageAvailableListener来获取相机返回的Image对象

更改预览尺寸

  1. 获取当前相机的所有可用预览尺寸
  2. 在创建CameraCaptureSession之前设置SurfaceTexture的默认buffer尺寸
  3. 然后关闭并重新创建一个CameraCaptureSession和ImageReader,因为这两个的尺寸最好保持一致

切换摄像头

关闭当前的所有CameraCaptureSession和CameraDevice,并用新的cameraId重新进行一次所有类的创建

源码

我对上述的所有功能进行了一个简单的封装

package com.tx.openglcamera.camera

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.*
import android.hardware.camera2.*
import android.media.ImageReader
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import android.util.Size
import android.view.Surface
import android.widget.Toast
import androidx.core.app.ActivityCompat
import java.lang.Exception

/**
 * create by xu.tian
 * @date 2021/10/9
 */
class CameraProvider(var context: Context, var captureTexture: SurfaceTexture) {
    private val tag = CameraProvider::class.java.simpleName
    private var handlerThread: HandlerThread = HandlerThread("CameraProvider")
    private var handler: Handler
    private var mCameraId = "0"
    private var cameraManager: CameraManager? = null
    private var captureSession: CameraCaptureSession? = null
    private var imageReader: ImageReader? = null
    var previewWidth = 0
    var previewHeight = 0
    private var cameraDevice: CameraDevice? = null
    private var captureCallBack: CaptureCallBack? = null

    init {
        handlerThread.start()
        handler = Handler(handlerThread.looper)
        cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        mCameraId = getCamera()[0]
        initDefaultPreviewSize()
    }

    /**
     * 获取CameraId
     */
    fun getCamera(): Array<out String> {
        return cameraManager!!.cameraIdList
    }

    /**
     * 获取当前camera的支持预览大小
     */
    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的状态回调
     */
    var cameraStateCallback = object : CameraDevice.StateCallback() {
        override fun onOpened(p0: CameraDevice) {
            log("cameraStateCallback onOpened")
            cameraDevice = p0
            createCaptureSession()
        }

        override fun onDisconnected(p0: CameraDevice) {
            log("cameraStateCallback onDisconnected")
        }

        override fun onError(p0: CameraDevice, p1: Int) {
            log("cameraStateCallback onError")
        }
    }

    /**
     * 创建CameraCaptureSession的状态回调
     */
    var captureSessionCallback = object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(p0: CameraCaptureSession) {
            captureSession = p0
            var captureSessionRequest =
                cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            captureSessionRequest.addTarget(Surface(captureTexture))
            var request = captureSessionRequest.build()
            captureSession!!.setRepeatingRequest(request, null, handler)
        }

        override fun onConfigureFailed(p0: CameraCaptureSession) {
            log("captureSessionCallback onConfigureFailed")
        }
    }

    /**
     * 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()
            matrix.postRotate(90f)
            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()
        }
        mCameraId = cameraId
        initDefaultPreviewSize()
        openCamera()
    }
    /**
     * 获取默认的预览尺寸
     */
    private fun initDefaultPreviewSize() {
        var size = getPreviewSize()!![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))
            }
            Toast.makeText(context, "${e.localizedMessage}", Toast.LENGTH_SHORT).show()
        }

    }
    /**
     * 释放相机
     */
    fun releaseCamera() {
        captureSession!!.close()
        cameraDevice!!.close()
    }
    /**
     * 释放handlerThread
     */
    fun destroy() {
        handlerThread.quit()
    }

    private fun log(string: String) {
        Log.d(tag, string)
    }
}

预览的使用也很简单,只需要在TextureView创建完成时,调用如下代码

        override fun onSurfaceTextureAvailable(p0: SurfaceTexture, p1: Int, p2: Int) {
            cameraProvider = CameraProvider(mContext!!,p0)
            cameraProvider.openCamera()
   }

拍照时这样

       cameraProvider.capturePic(object : CaptureCallBack{
            override fun onSucceed(bitmap: Bitmap) {
                   // 处理得到的图片
            }
            override fun onFailed(e: Throwable) {
           }
       })

切换摄像头时这样

      cameraProvider.openCamera(cameraId)

切换预览分辨率时这样

      cameraProvider.changePreviewSize(size.width,size.height)

使用起来还是比较简单的,我写了一个简单的demo,运行效果如下

切换摄像头

切换摄像头.gif
哈哈哈哈租的地方有点破旧~不要在意这些细节
快门按钮的实现在这 Android自定义View(14) 《手写一个MIUI的相机快门按钮》

切换分辨率

切换分辨率.gif

Github地址

总结

  • 注意点
    对Camera的大多操作都是耗时的,建议创建一个HandlerThread来统一进行对相机的操作
    Camera2的使用也是刚开始学习,如果有误的地方希望大家指出

相关文章

网友评论

      本文标题:Camera2使用(1)相机预览及拍照

      本文链接:https://www.haomeiwen.com/subject/hheholtx.html