Android Camera2相机使用 2

849 阅读4分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

Android自定义Camera2相机

自定义Camera2相机

关键API

CameraManagerCameraManager是一个负责查询和建立相机连接的系统服务,它的功能不多,这里列出几个CameraManager的关键功能:
1.将相机信息封装到CameraCharacteristics中,并提获取CameraCharacteristics实例的方式;
2.根据指定的相机ID连接相机设备;
3.提供将闪光灯设置成手电筒模式的快捷方式。
CameraCharacteristicsCameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等。如果你对Camera1比较熟悉,那么CameraCharacteristics有点像Camera1的 Camera.CameraInfo 或者 Camera.Parameters
CameraDeviceCameraDevice 代表当前连接的相机设备,它的职责有以下四个:
1.根据指定的参数创建 CameraCaptureSession;
2.根据指定的模板创建 CaptureRequest;
3.关闭相机设备;
4.监听相机设备的状态,例如断开连接、开启成功和开启失败等。
熟悉Camera1的人可能会说CameraDevice就是Camera1的 Camera 类,实则不是,Camera 类几乎负责了所有相机的操作,而 CameraDevice 的功能则十分的单一,就是只负责建立相机连接的事务,而更加细化的相机操作则交给了稍后会介绍的CameraCaptureSession。
SurfaceSurface 是一块用于填充图像数据的内存空间,例如你可以使用 SurfaceView 的 Surface 接收每一帧预览数据用于显示预览画面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 数据。每一个 Surface 都可以有自己的尺寸和数据格式,你可以从 CameraCharacteristics 获取某一个数据格式支持的尺寸列表。
CameraCaptureSessionCameraCaptureSession 实际上就是配置了目标 Surface 的 Pipeline 实例,我们在使用相机功能之前必须先创建 CameraCaptureSession 实例。一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等。
CaptureRequestCaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。
CaptureResultCaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。
ImageReader用于从相机打开的通道中读取需要的格式的原始图像数据,可以设置多个ImageReader。

开发流程

image.png

1.获取CameraManager

private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) }

2.获取相机信息

val cameraIdList = cameraManager.cameraIdList
cameraIdList.forEach { cameraId ->
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
    if (cameraCharacteristics.isHardwareLevelSupported(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)) {
        if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) {
            frontCameraId = cameraId
            frontCameraCharacteristics = cameraCharacteristics
        } else if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_BACK) {
            backCameraId = cameraId
            backCameraCharacteristics = cameraCharacteristics
        }
    }
}

通过CameraManager获取到所有摄像头cameraId,通过循环判断是前摄像头(CameraCharacteristics.LENS_FACING_FRONT)还是后摄像头(CameraCharacteristics.LENS_FACING_BACK

3.初始化ImageReader

private var jpegImageReader: ImageReader? = null
jpegImageReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 5)
jpegImageReader?.setOnImageAvailableListener(OnJpegImageAvailableListener(), cameraHandler)

......

private inner class OnJpegImageAvailableListener : ImageReader.OnImageAvailableListener {

  private val dateFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.getDefault())
  private val cameraDir: String = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)}/Camera"

  @WorkerThread
  override fun onImageAvailable(imageReader: ImageReader) {
    val image = imageReader.acquireNextImage()
    val captureResult = captureResults.take()
    if (image != null && captureResult != null) {
      image.use {
        val jpegByteBuffer = it.planes[0].buffer// Jpeg image data only occupy the planes[0].
        val jpegByteArray = ByteArray(jpegByteBuffer.remaining())
        jpegByteBuffer.get(jpegByteArray)
        val width = it.width
        val height = it.height
        saveImageExecutor.execute {
          val date = System.currentTimeMillis()
          val title = "IMG_${dateFormat.format(date)}"// e.g. IMG_20190211100833786
          val displayName = "$title.jpeg"// e.g. IMG_20190211100833786.jpeg
          val path = "$cameraDir/$displayName"// e.g. /sdcard/DCIM/Camera/IMG_20190211100833786.jpeg
          val orientation = captureResult[CaptureResult.JPEG_ORIENTATION]
          val location = captureResult[CaptureResult.JPEG_GPS_LOCATION]
          val longitude = location?.longitude ?: 0.0
          val latitude = location?.latitude ?: 0.0

          // Write the jpeg data into the specified file.
          File(path).writeBytes(jpegByteArray)

          // Insert the image information into the media store.
          val values = ContentValues()
          values.put(MediaStore.Images.ImageColumns.TITLE, title)
          values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, displayName)
          values.put(MediaStore.Images.ImageColumns.DATA, path)
          values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, date)
          values.put(MediaStore.Images.ImageColumns.WIDTH, width)
          values.put(MediaStore.Images.ImageColumns.HEIGHT, height)
          values.put(MediaStore.Images.ImageColumns.ORIENTATION, orientation)
          values.put(MediaStore.Images.ImageColumns.LONGITUDE, longitude)
          values.put(MediaStore.Images.ImageColumns.LATITUDE, latitude)
          contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

          // Refresh the thumbnail of image.
          val thumbnail = getThumbnail(path)
          if (thumbnail != null) {
            runOnUiThread {
              thumbnailView.setImageBitmap(thumbnail)
              thumbnailView.scaleX = 0.8F
              thumbnailView.scaleY = 0.8F
              thumbnailView.animate().setDuration(50).scaleX(1.0F).scaleY(1.0F).start()
            }
          }
        }
      }
    }
  }
}

ImageReader是获取图像数据的重要途径,通过它可以获取到不同格式的图像数据,例如JPEG、YUV、RAW等。通过ImageReader.newInstance(int width, int height, int format, int maxImages)创建ImageReader对象,有4个参数:

  • width:图像数据的宽度
  • height:图像数据的高度
  • format:图像数据的格式,例如ImageFormat.JPEGImageFormat.YUV_420_888
  • maxImages:最大Image个数,Image对象池的大小,指定了能从ImageReader获取Image对象的最大值,过多获取缓冲区可能导致OOM,所以最好按照最少的需要去设置这个值

ImageReader其他相关的方法和回调:

  • ImageReader.OnImageAvailableListener:有新图像数据的回调
  • acquireLatestImage():从ImageReader的队列里面,获取最新的Image,删除旧的,如果没有可用的Image,返回null
  • acquireNextImage():获取下一个最新的可用Image,没有则返回null
  • close():释放与此ImageReader关联的所有资源
  • getSurface():获取为当前ImageReader生成Image的Surface