转载请标明出处:https://blog.csdn.net/ZhijunHong/article/details/115730693,谢谢~
Google从Android 5.0 L(API 21) 版本,开始引入Camera2(android.hardware.camera2)
以取代Camera1(android.hardware.Camera)
相机框架。
Camera2相比于之前的Camera1架构完全不同,使用起来比较复杂,与此同时功能也变得非常强大。
此篇博客,能够帮助你快速构建并理解自定义Camera2相机的关键步骤。
完整代码,请移步:https://github.com/zhijunhong/custom_camera/tree/master/camera2
通过设计框架的改造和优化,Camera2具备了以下优点:
Camera2的API模型被设计成一个 Pipeline(管道),它按顺序处理每一帧的请求并返回请求结果给客户端。下面这张来自官方的图展示了Pipeline的工作流程,我们会通过一个简单的例子详细解释这张图。
Pipeline示意图
为了解释上面的示意图,假设我们想要同时拍摄两张不同尺寸的图片,并且在拍摄的过程中闪光灯必须亮起来。整个拍摄流程如下:
一个新的CaptureRequest会被放入一个被称作Pending Request Queue的队列中等待被执行,当In-Flight Capture Queue队列空闲的时候就会从Pending Request Queue获取若干个待处理的CaptureRequest,并且根据每一个CaptureRequest 的配置进行Capture操作。最后我们从不同尺寸的Surface中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的CaptureResult,流程结束。
相机功能的强大与否和硬件息息相关,不同厂商对 Camera2 的支持程度也不同,所以Camera2定义了一个叫做Supported Hardware Level的重要概念。其作用是将不同设备上的Camera2根据功能的支持情况划分成多个不同级别以便开发者能够大概了解当前设备上Camera2的支持情况。截止到Android P为止,从低到高一共有LEGACY、LIMITED、FULL 和 LEVEL_3四个级别:
相机的所有操作和参数配置最终都是服务于图像捕获,例如对焦是为了让某一个区域的图像更加清晰,调节曝光补偿是为了调节图像的亮度等。因此,在Camera2 里面所有的相机操作和参数配置都被抽象成Capture(捕获),所以不要简单的把Capture直接理解成是拍照,因为Capture操作可能仅仅是为了让预览画面更清晰而进行对焦而已。如果你熟悉Camera,那你可能会问 setFlashMode()
在哪?setFocusMode()
在哪?takePicture()
在哪?告诉你,它们都是通过Capture 来实现的。
Capture从执行方式上又被细分为【单次模式】、【多次模式】和【重复模式】三种,我们来一一解释下:
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。 |
private val cameraManager: CameraManager by lazy {
getSystemService(CameraManager::class.java) }
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
)
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个参数:
ImageFormat.JPEG
,ImageFormat.YUV_420_888
等ImageReader其他相关的方法和回调:
ImageReader.OnImageAvailableListener
:有新图像数据的回调acquireLatestImage()
:从ImageReader的队列里面,获取最新的Image,删除旧的,如果没有可用的Image,返回nullacquireNextImage()
:获取下一个最新的可用Image,没有则返回nullclose()
:释放与此ImageReader关联的所有资源getSurface()
:获取为当前ImageReader生成Image的Surfaceval cameraStateCallback = CameraStateCallback()
cameraManager.openCamera(cameraId, cameraStateCallback, mainHandler)
......
private inner class CameraStateCallback : CameraDevice.StateCallback() {
@MainThread
override fun onOpened(camera: CameraDevice) {
cameraDeviceFuture!!.set(camera)
cameraCharacteristicsFuture!!.set(getCameraCharacteristics(camera.id))
}
@MainThread
override fun onClosed(camera: CameraDevice) {
}
@MainThread
override fun onDisconnected(camera: CameraDevice) {
cameraDeviceFuture!!.set(camera)
closeCamera()
}
@MainThread
override fun onError(camera: CameraDevice, error: Int) {
cameraDeviceFuture!!.set(camera)
closeCamera()
}
}
cameraManager.openCamera(@NonNull String cameraId,@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
的三个参数:
其中CameraStateCallback回调:
val sessionStateCallback = SessionStateCallback()
......
val cameraDevice = cameraDeviceFuture?.get()
cameraDevice?.createCaptureSession(outputs, sessionStateCallback, mainHandler)
......
private inner class SessionStateCallback : CameraCaptureSession.StateCallback() {
@MainThread
override fun onConfigureFailed(session: CameraCaptureSession) {
captureSessionFuture!!.set(session)
}
@MainThread
override fun onConfigured(session: CameraCaptureSession) {
captureSessionFuture!!.set(session)
}
@MainThread
override fun onClosed(session: CameraCaptureSession) {
}
}
这段的代码核心方法是mCameraDevice.createCaptureSession()
创建Capture会话,它接受了三个参数:
CaptureRequest是向CameraCaptureSession提交Capture请求时的信息载体,其内部包括了本次Capture的参数配置和接收图像数据的Surface
if (cameraDevice != null) {
previewImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
}
......
val cameraDevice = cameraDeviceFuture?.get()
val captureSession = captureSessionFuture?.get()
val previewImageRequestBuilder = previewImageRequestBuilder!!
val captureImageRequestBuilder = captureImageRequestBuilder!!
if (cameraDevice != null && captureSession != null) {
val previewSurface = previewSurface!!
val previewDataSurface = previewDataSurface
previewImageRequestBuilder.addTarget(previewSurface)
// Avoid missing preview frame while capturing image.
captureImageRequestBuilder.addTarget(previewSurface)
if (previewDataSurface != null) {
previewImageRequestBuilder.addTarget(previewDataSurface)
// Avoid missing preview data while capturing image.
captureImageRequestBuilder.addTarget(previewDataSurface)
}
val previewRequest = previewImageRequestBuilder.build()
captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler)
}
......
private inner class RepeatingCaptureStateCallback : CameraCaptureSession.CaptureCallback() {
@MainThread
override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
super.onCaptureStarted(session, request, timestamp, frameNumber)
}
@MainThread
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
super.onCaptureCompleted(session, request, result)
}
}
除了模式的配置,CaptureRequest还可以配置很多其他信息,例如图像格式、图像分辨率、传感器控制、闪光灯控制、3A(自动对焦-AF、自动曝光-AE和自动白平衡-AWB)控制等。在createCaptureSession的回调中可以进行设置,最后通过build()
方法生成CaptureRequest对象。
Camera2中,通过连续重复的Capture实现预览功能,每次Capture会把预览画面显示到对应的Surface上。连续重复的Capture操作通过
captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler)
实现,该方法有三个参数:
停止预览使用mCaptureSession.stopRepeating()
方法。
设置上面的request,session后,就可以真正的开始拍照操作
val captureImageRequest = captureImageRequestBuilder.build()
captureSession.capture(captureImageRequest, CaptureImageStateCallback(), mainHandler)
......
private inner class CaptureImageStateCallback : CameraCaptureSession.CaptureCallback() {
@MainThread
override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
super.onCaptureStarted(session, request, timestamp, frameNumber)
// Play the shutter click sound.
cameraHandler?.post {
mediaActionSound.play(MediaActionSound.SHUTTER_CLICK) }
}
@MainThread
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
super.onCaptureCompleted(session, request, result)
captureResults.put(result)
}
}
captureSession.capture()
方法也有三个参数,和mCaptureSession.setRepeatingRequest
一样:
和其他硬件资源的使用一样,当我们不再需要使用相机时记得调用 CameraDevice.close() 方法及时关闭相机回收资源。关闭相机的操作至关重要,因为如果你一直占用相机资源,其他基于相机开发的功能都会无法正常使用,严重情况下直接导致其他相机相关的 APP 无法正常使用,当相机被完全关闭的时候会通过 CameraStateCallback.onCllosed() 方法通知你相机已经被关闭。那么在什么时候关闭相机最合适呢?个人的建议是在 onPause() 的时候就一定要关闭相机,因为在这个时候相机页面已经不是用户关注的焦点,大部分情况下已经可以关闭相机了。
cameraDevice?.close()
previewDataImageReader?.close()
jpegImageReader?.close()
先后对CaptureSession,CameraDevice,ImageReader进行close操作,释放资源。
如果你的项目正在使用Camera1,并且打算从Camera1迁移到Camera2的话,希望以下几个建议可以对你有所帮助:
完整代码:https://github.com/zhijunhong/custom_camera/tree/master/camera2
最后,别忘了start哟~
Android Camera-Camera2使用
Android Camera2 教程