背景
Camera2已经发布很长时间了,但是一直没有用它替换老的Camera接口。没有替换的原因是新接口使用比较复杂并且与老接口的调用逻辑相差较大。正是因为Camera2接口使用比较复杂,学习成本比较高,所以google提供了Camera2的封装组件CameraX。使用CameraX来开发相机功能轻松了许多,但是要将原有的项目迁移到CameraX组件又要面对很多的代码适配问题。总之是一言难尽。
开启相机流程
这个流程图简单列举了Camera2开启预览的流程,单从开启预览的步骤看就要比老接口复杂的多。更让人烦心的是这里的多个步骤都是异步的操作,所以camera开启的状态维护也变得更复杂。如果我们再考虑surface的设置,我相信大多数人的头都要炸了。因为通常surface的创建也是异步的操作。surface的异步与camera2操作的异步混合在一起,Camera2的状态就是一个迷。
封装目的
封装的基本原则就是保证封装类接口的调用没有时序的要求,没有camera状态的检查。
1.将Camera2的异步操作转变成同步操作。
2.虽然surface的创建时异步的,但是只要surface被创建了,我们就可以随时通过封装的接口进行设置。
3.封装类的内部维护camera状态,用户调用封装类的时候不用检查camera状态并且保证camera状态的正确性。
实现思路
最初想通过状态机的方式来实现,但是我发现这样实现的时候有很多的重复代码,因为每个定义的状态下都可以切换到多个状态。同时camera异步操作仍然没有很好的解决。后来受到GLSurfaceView实现的启发,我采用了类似GLSurfaceView的状态管理方式配合协程对Camera2进行了封装。
封装代码
invalidate函数就是最核心的部分。首先这个函数启动了协程来执行操作。这样做的目的是通过协程把camera的异步操作切换成同步调用。我把开启camera、开启preview的操作都封装成可挂起的操作。
fun invalidate() = runInCameraThread {
GlobalScope.async {
try {
Log.d(TAG, "invalidate acquire")
invalidateBlockSemaphore.acquire()
Log.d(TAG, "invalidate run")
//没有请求preview或是请求重新开启preview、重新开启camera等的时候关闭session
if (cameraCaptureSession != null && (!requestPreview || requestRestartPreview || !requestOpen || requestRestartOpen)) {
cameraCaptureSession?.close()
cameraCaptureSession = null
Log.d(TAG, "invalidate cameraCaptureSession?.close()")
}
//没有请求开启camera或是请求重新开启camera的时候关闭camera
if (cameraDevice != null && (!requestOpen || requestRestartOpen || requestOpen)) {
cameraDevice?.close()
cameraDevice = null
Log.d(TAG, "invalidate cameraDevice?.close()")
}
//请求开启camera
if (cameraDevice == null && requestOpen) {
cameraDevice = try {
openCamera(cameraId)
} catch (e: Exception) {
null
}
Log.d(TAG, "invalidate openCamera() $cameraDevice")
}
//请求开启preview
if (cameraDevice != null && cameraCaptureSession == null && requestPreview) {
cameraCaptureSession = try {
startPreview(cameraDevice!!, surfaces)
} catch (e: Exception) {
null
}
Log.d(TAG, "invalidate startPreview() $cameraCaptureSession")
}
requestRestartPreview = false
requestRestartOpen = false
} finally {
Log.d(TAG, "invalidate release")
invalidateBlockSemaphore.release()
}
}
}
定义openCamera可挂起函数,通过协程将异步操作切换成同步。
private suspend fun openCamera(cameraId: String) = suspendCoroutine<CameraDevice> {
//检查权限
if (!checkPermission()) {
handler.post { it.resumeWithException(RuntimeException("openCamera camera permission error")) }
return@suspendCoroutine
}
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
Log.d(TAG, "onOpened $camera")
//调用resume唤醒协程执行。
it.resume(camera)
}
override fun onDisconnected(camera: CameraDevice) {
Log.d(TAG, "onDisconnected $camera")
camera.close()
it.resumeWithException(RuntimeException("onDisconnected"))
}
override fun onError(camera: CameraDevice, error: Int) {
Log.d(TAG, "onError $camera")
camera.close()
it.resumeWithException(RuntimeException("onError"))
}
override fun onClosed(camera: CameraDevice) {
super.onClosed(camera)
if (requestRelease) {
handlerThread.quitSafely()
}
}
}, handler)
}
定义startPreview可挂起函数。
private suspend fun startPreview(cameraDevice: CameraDevice, surfaces: MutableList<Surface>) =
suspendCoroutine<CameraCaptureSession> {
val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
surfaces.forEach { surface ->
builder.addTarget(surface)
}
cameraDevice.createCaptureSession(
surfaces,
object : CameraCaptureSession.StateCallback() {
override fun onConfigureFailed(session: CameraCaptureSession) {
Log.d(TAG, "onConfigureFailed $session")
session.close()
it.resumeWithException(RuntimeException("onConfigureFailed"))
}
override fun onConfigured(session: CameraCaptureSession) {
Log.d(TAG, "onConfigured $session")
session.setRepeatingRequest(builder.build(), null, handler)
it.resume(session)
}
},
handler
)
}
下面的代码是封装的主要对外接口,这些对外接口都是设置了请求状态flag。当设置状态flag后,我们需要调用invalidate() 方法使状态有效。
fun setSurface(vararg vararg: Surface) = runInCameraThread {
if (surfaces.size > 0) {
requestRestartPreview = true
}
surfaces.clear()
vararg.forEach {
surfaces.add(it)
}
}
fun open() = runInCameraThread {
requestOpen = true
}
fun startPreview() = runInCameraThread {
requestOpen = true
requestPreview = true
}
fun stopPreview() = runInCameraThread {
requestPreview = false
}
fun release() = runInCameraThread {
requestPreview = false
requestOpen = false
requestRelease = true
}
如何使用封装的camera2
1.选择camera并打开
cameraHolder = CameraHolder(this)
cameraHolder?.let {
it.selectCamera(cameraId)//CameraHolder.CAMERA_FRONT/CameraHolder.CAMERA_REAR
it.open()
}
2.开启和关闭preview
textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureSizeChanged(
surface: SurfaceTexture?,
width: Int,
height: Int
) {
Log.d("TextureView", "onSurfaceTextureSizeChanged ")
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
Log.d("TextureView", "onSurfaceTextureDestroyed $surface")
//关闭preview
cameraHolder?.let {
it.stopPreview().invalidate()
}
return true
}
override fun onSurfaceTextureAvailable(
surface: SurfaceTexture?,
width: Int,
height: Int
) {
Log.d("TextureView", "onSurfaceTextureAvailable $surface")
cameraHolder?.let {
it.getPreviewSize { sizes ->
val size = sizes?.get(0)
size?.let { size ->
surface?.setDefaultBufferSize(size.width, size.height)
//更新preview显示比例
updatePreview(size.width, size.height)
//设置surface并启动preview
it.setSurface(Surface(surface)).startPreview().invalidate()
}
}
}
}
}
3.前后摄像头切换
//切换cameraId,不需要关心camera是否打开,是否正在进行preview
cameraId = (cameraId.toInt() + 1).rem(2).toString()
cameraHolder?.selectCamera(cameraId)?.invalidate()
4.切换preview分辨率
val size = sizesString[index].split("*")
val width = size[0].toInt()
val height = size[1].toInt()
textureView.surfaceTexture.setDefaultBufferSize(width, height)
updatePreview(width, height)
//切换分辨率的时候也不需要考虑camera的当前状态
it.setSurface(Surface(textureView.surfaceTexture)).invalidate()
总结
通过简单的封装后,通过CameraHolder类操作camera2的时候逻辑比较清晰。因为我们在调用CameraHolder的方法时,我们不需要检查camera的当前状态。CameraHolder提供的方法在调用时序上没有严格的要求。surface什么时候被创建好就在什么时候设置给camera。同时在前后摄像头切换和preview size更新的时候,操作也很简单,没有camera状态的检查。可以认为这次的封装基本达到了封装的目的。这个封装是比较简单的封装,它的意义在于对camera封装有新的思考。在思考如何封装的过程中,我尝试过多种方案,但是他们都没有达到我满意的效果。直到偶然间看到了GLSurfaceView的实现,我才豁然开朗。希望这次的简单封装能够对大家有所帮助。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。