这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战
Android自定义Camera2相机
自定义Camera2相机
关键API
CameraManager | CameraManager是一个负责查询和建立相机连接的系统服务,它的功能不多,这里列出几个CameraManager的关键功能: 1.将相机信息封装到CameraCharacteristics中,并提获取CameraCharacteristics实例的方式; 2.根据指定的相机ID连接相机设备; 3.提供将闪光灯设置成手电筒模式的快捷方式。 |
---|---|
CameraCharacteristics | CameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING ;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE ;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等。如果你对Camera1比较熟悉,那么CameraCharacteristics有点像Camera1的 Camera.CameraInfo 或者 Camera.Parameters 。 |
CameraDevice | CameraDevice 代表当前连接的相机设备,它的职责有以下四个: 1.根据指定的参数创建 CameraCaptureSession; 2.根据指定的模板创建 CaptureRequest; 3.关闭相机设备; 4.监听相机设备的状态,例如断开连接、开启成功和开启失败等。 熟悉Camera1的人可能会说CameraDevice就是Camera1的 Camera 类,实则不是,Camera 类几乎负责了所有相机的操作,而 CameraDevice 的功能则十分的单一,就是只负责建立相机连接的事务,而更加细化的相机操作则交给了稍后会介绍的CameraCaptureSession。 |
Surface | Surface 是一块用于填充图像数据的内存空间,例如你可以使用 SurfaceView 的 Surface 接收每一帧预览数据用于显示预览画面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 数据。每一个 Surface 都可以有自己的尺寸和数据格式,你可以从 CameraCharacteristics 获取某一个数据格式支持的尺寸列表。 |
CameraCaptureSession | CameraCaptureSession 实际上就是配置了目标 Surface 的 Pipeline 实例,我们在使用相机功能之前必须先创建 CameraCaptureSession 实例。一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等。 |
CaptureRequest | CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。 |
CaptureResult | CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。 |
ImageReader | 用于从相机打开的通道中读取需要的格式的原始图像数据,可以设置多个ImageReader。 |
开发流程
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.JPEG
,ImageFormat.YUV_420_888
等 - maxImages:最大Image个数,Image对象池的大小,指定了能从ImageReader获取Image对象的最大值,过多获取缓冲区可能导致OOM,所以最好按照最少的需要去设置这个值
ImageReader其他相关的方法和回调:
ImageReader.OnImageAvailableListener
:有新图像数据的回调acquireLatestImage()
:从ImageReader的队列里面,获取最新的Image,删除旧的,如果没有可用的Image,返回nullacquireNextImage()
:获取下一个最新的可用Image,没有则返回nullclose()
:释放与此ImageReader关联的所有资源getSurface()
:获取为当前ImageReader生成Image的Surface