目录
一、Camera2概述
1 Pipeline
2 CameraManager
3 CameraDevice
4 CameraCharacteristics
5 CameraCaptureSession
6 CaptureRequest
7 Surface
8 CaptureResult
三、Camera2的特性
1 Camera2 才支持的高级特性
2 Camera1 迁移到 Camera2
二、示例源码
Camera2 的 API 模型被设计成一个 Pipeline(管道),它按顺序处理每一帧的请求并返回请求结果给客户端。下面这张来自官方的图展示了 Pipeline 的工作流程,我们会通过一个简单的例子详细解释这张图
为了解释上面的示意图,假设我们想要同时拍摄两张不同尺寸的图片,并且在拍摄的过程中闪光灯必须亮起来。整个拍摄流程如下:
一个新的 CaptureRequest 会被放入一个被称作 Pending Request Queue 的队列中等待被执行,当 In-Flight Capture Queue 队列空闲的时候就会从 Pending Request Queue 获取若干个待处理的 CaptureRequest,并且根据每一个 CaptureRequest 的配置进行 Capture 操作。最后我们从不同尺寸的 Surface 中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的 CaptureResult,流程结束。
CameraManager 是一个负责查询和建立相机连接的系统服务,它的功能不多,这里列出几个 CameraManager 的关键功能:
提供将闪光灯设置成手电筒模式的快捷方式。
CameraDevice 代表当前连接的相机设备,它的职责有以下四个:
熟悉 Camera1 的人可能会说 CameraDevice 就是 Camera1 的 Camera 类,实则不是,Camera 类几乎负责了所有相机的操作,而 CameraDevice 的功能则十分的单一,就是只负责建立相机连接的事务,而更加细化的相机操作则交给了稍后会介绍的 CameraCaptureSession。
CameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING
;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE
;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES
等等。如果你对 Camera1 比较熟悉,那么 CameraCharacteristics 有点像 Camera1 的 Camera.CameraInfo
或者 Camera.Parameters
。
CameraCaptureSession 实际上就是配置了目标 Surface 的 Pipeline 实例,我们在使用相机功能之前必须先创建 CameraCaptureSession 实例。一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等等。
CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。
Surface 是一块用于填充图像数据的内存空间,例如你可以使用 SurfaceView 的 Surface 接收每一帧预览数据用于显示预览画面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 数据。每一个 Surface 都可以有自己的尺寸和数据格式,你可以从 CameraCharacteristics 获取某一个数据格式支持的尺寸列表。
CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。
如果要我给出强有力的理由解释为什么要使用 Camera2,那么通过 Camera2 提供的高级特性可以构建出更加高质量的相机应用程序应该是最佳理由了。
在开启相机之前检查相机信息
出于某些原因,你可能需要先检查相机信息再决定是否开启相机,例如检查闪光灯是否可用。在 Caemra1 上,你无法在开机相机之前检查详细的相机信息,因为这些信息都是通过一个已经开启的相机实例提供的。在 Camera2 上,我们有了和相机实例完全剥离的 CameraCharacteristics 实例专门提供相机信息,所以我们可以在不开启相机的前提下检查几乎所有的相机信息。
在不开启预览的情况下拍照
在 Camera1 上,开启预览是一个很重要的环节,因为只有在开启预览之后才能进行拍照,因此即使显示预览画面与实际业务需求相违背的时候,你也不得不开启预览。而 Camera2 则不强制要求你必须先开启预览才能拍照。
一次拍摄多张不同格式和尺寸的图片
在 Camera1 上,一次只能拍摄一张图片,更不同谈多张不同格式和尺寸的图片了。而 Camera2 则支持一次拍摄多张图片,甚至是多张格式和尺寸都不同的图片。例如你可以同时拍摄一张 1440x1080 的 JPEG 图片和一张全尺寸的 RAW 图片。
控制曝光时间
在暗环境下拍照的时候,如果能够适当延长曝光时间,就可以让图像画面的亮度得到提高。在 Camera2 上,你可以在规定的曝光时长范围内配置拍照的曝光时间,从而实现拍摄长曝光图片,你甚至可以延长每一帧预览画面的曝光时间让整个预览画面在暗环境下也能保证一定的亮度。而在 Camera1 上你只能 YY 一下。
连拍
连拍 30 张图片这样的功能在 Camera2 出现之前恐怕只有系统相机才能做到了(通过 OpenGL 截取预览画面的做法除外),也可能是出于这个原因,市面上的第三方相机无一例外都不支持连拍。有了 Camera2,你完全可以让你的相机应用程序支持连拍功能,甚至是连续拍 30 张使用不同曝光时间的图片。
灵活的 3A 控制
3A(AF、AE、AWB)的控制在 Camera2 上得到了最大化的放权,应用层可以根据业务需求灵活配置 3A 流程并且实时获取 3A 状态,而 Camera1 在 3A 的控制和监控方面提供的接口则要少了很多。例如你可以在拍照前进行 AE 操作,并且监听本这次拍照是否点亮闪光灯。
如果你熟悉 Camera1,并且打算从 Camera1 迁移到 Camera2 :
Camera1 严格区分了预览和拍照两个流程,而 Camera2 则把这两个流程都抽象成了 Capture 行为,只不过一个是不断重复的 Capture,一个是一次性的 Capture 而已,所以建议你不要带着过多的 Camera1 思维使用 Camera2,避免因为思维上的束缚而无法充分利用 Camera2 灵活的 API。
如同 Camera1 一样,Camera2 的一些 API 调用也会耗时,所以建议你使用独立的线程执行所有的相机操作,尽量避免直接在主线程调用 Camera2 的 API,HandlerThread 是一个不错的选择。
Camera2 所有的相机操作都可以注册相关的回调接口,然后在不同的回调方法里写业务逻辑,这可能会让你的代码因为不够线性而错综复杂,建议你可以尝试使用子线程的阻塞方式来尽可能地保证代码的线性执行(熟悉 Dart 的人一定很喜欢它的 async 和 await 操作)。例如在子线程阻塞等待 CaptureResult,然后继续执行后续的操作,而不是将代码拆分到到 CaptureCallback.onCaptureCompleted()
方法里。
你可以认为 Camera1 是 Camera2 的一个子集,也就是说 Camera1 能完成的事情 Camera2 一定能做,反过来则不一定能实现。
如果你的应用程序需要同时兼容 Camera1 和 Camera2,建议分开维护,因为 Camera1 的 API 设计很可能让 Camera2 灵活的 API 无法得到充分的发挥,另外将两个设计上完全不兼容的东西搅和在一起带来的痛苦可能远大于其带来便利性。
官方说 Camera2 的性能会更好,起码在较早期的一些机器上运行 Camera2 的性能并没有比 Camera1 好。
当设备的 Supported Hardware Level 低于 FULL 的时候,建议还是使用 Camera1,因为 FULL 级别以下的 Camera2 能提供的功能几乎和 Camera1 一样,所以倒不如选择更加稳定的 Camera1。
class Camera2Helper private constructor(val mContext: Context) {
companion object {
const val DEFAULT_HEIGHT = 1920
const val DEFAULT_WIDTH = 1080
const val CAMERA_ID_FRONT = "1"
const val CAMERA_ID_BACK = "0"
fun build(mContext: Context): Camera2Helper {
return Camera2Helper(mContext)
}
}
var currentHeight = DEFAULT_HEIGHT //默认设置的预览高度
var currentWidth = DEFAULT_WIDTH //默认设置的预览宽度
var cameraID = CAMERA_ID_FRONT //默认设置的前置摄像头
//相机设备
private var mCameraDevice: CameraDevice? = null
//预览纹理
private var mSurfaceTexture: SurfaceTexture? = null
private var mCameraHandler: Handler? = null
private var mCameraThread: HandlerThread? = null
private var mCameraSession: CameraCaptureSession? = null
private lateinit var mCaptureRequestBuild: CaptureRequest.Builder
/**
* 开始
*/
fun onStart(mSurfaceTexture: SurfaceTexture) {
this.mSurfaceTexture = mSurfaceTexture
val mCameraThread = HandlerThread("CameraHandler")
this.mCameraThread = mCameraThread
mCameraThread.start()
mCameraHandler = Handler(mCameraThread.looper)
openCamera(mContext)
}
/**
* 重新开始
*/
fun onRestart() {
//避免误调用
onStop()
val mCameraThread = HandlerThread("CameraHandler")
this.mCameraThread = mCameraThread
mCameraThread.start()
mCameraHandler = Handler(mCameraThread.looper)
openCamera(mContext)
}
/**
* 暂停释放
*/
fun onStop() {
mCameraSession?.stopRepeating()
mCameraDevice?.close()
mCameraThread?.quitSafely()
try {
mCameraThread?.join()
mCameraSession = null
mCameraDevice = null
mCameraHandler = null
mCameraThread = null;
} catch (e: InterruptedException) {
Log.e("Camera2Helper", e.toString())
}
}
/**
* 切换摄像头
*/
fun switchCamera() {
cameraID = if (cameraID == CAMERA_ID_FRONT) CAMERA_ID_BACK else CAMERA_ID_FRONT
onStop()
onRestart()
}
/**
* 开启摄像头
*/
private fun openCamera(mContext: Context) {
try {
val cameraManager =
mContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
if (ActivityCompat.checkSelfPermission(
mContext, Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
cameraManager.openCamera(cameraID, mDeviceStateCallback, mCameraHandler)
}
} catch (e: CameraAccessException) {
Log.e("Camera2Helper", e.toString())
}
}
/**
* 开启摄像头 回调
*/
private val mDeviceStateCallback = object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
mCameraDevice = camera
createCameraSession(camera)
}
override fun onDisconnected(camera: CameraDevice) {
Log.e("Camera2Helper", " onDisconnected")
onStop()
}
override fun onError(camera: CameraDevice, error: Int) {
Log.e("Camera2Helper", "CameraDevice.StateCallback(): $error")
}
}
/**
* 创建CameraSession
*/
private fun createCameraSession(mCameraDevice: CameraDevice) {
try {
// 我们将默认缓冲区的大小配置为我们想要的相机预览的大小。
mSurfaceTexture?.let {
it.setDefaultBufferSize(currentWidth, currentHeight)
// 输出surface。
val surface = Surface(it)
//构建请求
mCaptureRequestBuild =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) //有其他配置自行看源码
mCaptureRequestBuild.addTarget(surface) //必须添加 OutputConfiguration中的surface
//版本兼容
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val outConfig = OutputConfiguration(surface)
// 输出配置
val config = SessionConfiguration(
SessionConfiguration.SESSION_REGULAR, //有其他配置自行看源码
listOf(outConfig),
ThreadPoolUtil.executor, //线程池
mCaptureStateCallback
)
mCameraDevice.createCaptureSession(config)
} else {
mCameraDevice.createCaptureSession(
mutableListOf(surface),
mCaptureStateCallback,
mCameraHandler
)
}
}
} catch (e: CameraAccessException) {
Log.d("Camera2Helper", e.toString())
}
}
/**
* 会话状态回调
*/
private val mCaptureStateCallback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
mCameraSession = session
//设置请求
session.setRepeatingRequest(
mCaptureRequestBuild.build(),
mCaptureCallback,
mCameraHandler
)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
}
}
//请求回调 可以不是实现任何回调
private val mCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
}
}
如果对您有一些意义,希望您给博主一些鼓励(点赞、关注、收藏),如果有错误欢迎大家评论。