Camerax 入门教程

程序员Android

共 10515字,需浏览 22分钟

 · 2021-08-20

和你一起终身学习,这里是程序员Android

经典好文推荐,通过阅读本文,您将收获以下知识点:

一、Camera的预备知识
二、CameraX是什么,能解决什么问题
三、 CameraX如何使用
四、CameraX的一些思考

基于官网demo,增加了对焦、手势缩放、手电、闪光灯等操作

一、Camera的预备知识

熟悉的大佬可以跳过

Surface、SurfaceView、SurfaceHolder这三个是啥
Surface是什么?一句话来说Surface 是一块用于填充图像数据的内存空间。

可以再深入一点,了解下它包含的东西:咱后面可以重点关注下Buffer转纹理的细节实际上用openGL修改流我们下一章分析


SurfaceView是什么?一句话来描述的话那就是:它一个可以显示surface的view!在App端它仍在View hierachy中,但在WMS中(可以理解为Server端),它与宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独线程去做,不会影响主线程对事件的响应。缺点也很明显,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换(7.0之前)。

SurfaceHolder是什么?它是一个接口,给持有surface的对象使用,可以控制surface的大小和格式,编辑surface中的像素,以及监听surface的变化,这个接口通过SurfaceView获得。下面这张图可以说明一切

二、CameraX是什么,能解决什么问题

Jetpack的一个支持库,最低版本要求Android5.0
默认的相机功能还是Camera2的能力,当然API都变了,同时提供CameraX Extensions拓展库可以添加各种特效,例如人像、HDR、夜间和美颜模式(从上图也可以看出,这是依赖OEM的)
绑定生命周期,所以Camera本身无需在生命周期中调用什么onPause onResume之类的样板代码,且忘记后会造成各种问题
抹平设备兼容性问题,无需在代码库中添加设备专属代码

三、 CameraX如何使用

第一步

引入依赖

    // CameraX core library
def camerax_version = '1.0.0-rc03'
// def camerax_version = '1.1.0-alpha03'目前最新版,但是为了稳定我们还是选择rc版
implementation "androidx.camera:camera-core:$camerax_version"
// CameraX Camera2 extensions[可选]拓展库可实现人像、HDR、夜间和美颜、滤镜但依赖于OEM
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle library[可选]避免手动在生命周期释放和销毁数据
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class[可选]最佳实践,最好用里面的PreviewView,它会自行判断用SurfaceView还是TextureView来实现
implementation 'androidx.camera:camera-view:1.0.0-alpha23'

第二步

添加布局控件

<androidx.camera.view.PreviewView
android:id="@+id/view_finder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

第三步

初始化cameraProviderFuture并得到cameraProvider

    /** Initialize CameraX, and prepare to bind the camera use cases  */
//UseCase实际上是一个抽象类,相机中最核心的几个类的父类ImageAnalysis/ImageCapture/Preview/VideoCapture
private fun setUpCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
// CameraProvider
cameraProvider = cameraProviderFuture.get()
// 选择默认摄像头给后续使用
lensFacing = when {
hasBackCamera() -> CameraSelector.LENS_FACING_BACK
hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
else -> throw IllegalStateException("Back and front camera are unavailable")
}
// Enable or disable switching between cameras
updateCameraSwitchButton()
// Build and bind the camera use cases
bindCameraUseCases()
}, ContextCompat.getMainExecutor(requireContext()))
}

得到cameraProvider后初始化Preview、ImageCapture、ImageAnalysis可以看到他们3个是单独配置的,所以完全解耦。最后可以根据需要设置0个或多个到camera中


/** Declare and bind preview, capture and analysis use cases */
private fun bindCameraUseCases() {
// CameraSelector
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
// Preview
preview = Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build()
// ImageCapture
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build()
// ImageAnalysis
imageAnalyzer = ImageAnalysis.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setImageQueueDepth(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetRotation(rotation)
.build()
.also {
it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
//这里拿到字节数组可以为 所 欲 为 !
val buffer = image.planes[0].buffer
val data = buffer.toByteArray()
})
}
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try {
//这里拿到camera对象可以取出两个重要的对象来用
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
//用来聚焦、手势、闪光灯、手电等操作
mCameraInfo = camera?.cameraInfo
mCameraControl = camera?.cameraControl
initCameraListener()
preview?.setSurfaceProvider(viewFinder.surfaceProvider)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}

然后是拍照

// Get a stable reference of the modifiable image capture use case
//takePicture有重载方法,源码的注释写的比较清楚了:
/*
* We need to chain the following callbacks to save the image to disk:
*
* +-----------------------+
* | |
* |ImageCapture. |
* |OnImageCapturedCallback|
* | |
* +-----------+-----------+
* |
* |
* +-----------v-----------+ +----------------------+
* | | | |
* | ImageSaver. | | ImageCapture. |
* | OnImageSavedCallback +------> OnImageSavedCallback |
* | | | |
* +-----------------------+ +----------------------+
*/

imageCapture?.let { imageCapture ->
imageCapture.takePicture(
outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
Log.d(TAG, "Photo capture succeeded: $savedUri")
}
})
}

第四步

手电筒、闪光灯、前后摄像头、聚焦、手势

手电筒:

//通过拿到的Camera接口的两个方法拿到后进行设置即可
cameraControl = camera.cameraControl
cameraInfo = camera.cameraInfo
....
private fun toggleTorch() {
if (cameraInfo?.torchState?.value == TorchState.ON) {
cameraControl?.enableTorch(false)
} else {
cameraControl?.enableTorch(true)
}
}

闪光灯模式

mImageCapture.flashMode = ImageCapture.FLASH_MODE_ON

切换摄像头

lensFacing = when {
hasBackCamera() -> CameraSelector.LENS_FACING_BACK
hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
else -> throw IllegalStateException("Back and front camera are unavailable")
}
// CameraSelector
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()

聚焦和手势,由于新版的PreviewView已经被final修饰,无法被继承然后重写方法了,所以只能通过setOnTouchListener进行手势监听

val zoomState = mCameraInfo?.zoomState
val cameraXPreviewViewTouchListener = CameraXPreviewViewTouchListener(requireContext())
cameraXPreviewViewTouchListener.setCustomTouchListener(object : CameraXPreviewViewTouchListener.CustomTouchListener {
override fun zoom(delta: Float) {
zoomState?.value?.let {
val currentZoomRatio = it.zoomRatio
mCameraControl?.setZoomRatio(currentZoomRatio * delta)
}
}
override fun click(x: Float, y: Float) {
val factory = viewFinder.meteringPointFactory
val point = factory.createPoint(x, y)
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
.setAutoCancelDuration(3, TimeUnit.SECONDS)
.build()
focus_view.startFocus(Point(x.toInt(), y.toInt()))
val future = mCameraControl?.startFocusAndMetering(action)
future?.let {
it.addListener({
try {
val result = it.get() as FocusMeteringResult
if (result.isFocusSuccessful) {
focus_view.onFocusSuccess()
} else {
focus_view.onFocusFailed()
}
} catch (e: Exception) {

}
}, cameraExecutor)
}
}

override fun doubleClick(x: Float, y: Float) {
// 双击放大缩小
zoomState?.value?.let {
val currentZoomRatio = it.zoomRatio
if (currentZoomRatio > it.minZoomRatio) {
mCameraControl?.setLinearZoom(0f)
} else {
mCameraControl?.setLinearZoom(0.5f)
}
}
}

override fun longClick(x: Float, y: Float) = Unit
})
view_finder.setOnTouchListener(cameraXPreviewViewTouchListener)


四、CameraX的一些思考

优点
下面我们项目代码中兼容性问题以及相应的special code可以去掉了
代码在解耦层面做的优秀,以及链式的数据解析都更容易开发者使用
可拓展的美颜、滤镜、人像、HDR可以较为轻量的实现
缺点
目前还在rc3中,稳定性难说
虽然使用的能力是基于camera2的,但是API都变掉了(估计是为了他们内部解耦),这意味着对本身项目的侵入性是较大的。

        //这种代码可以消失了
if (forceUseCamera1 || CameraHelper.getInstance(getContext()).shouldUseCamera1()) {//只使用Camera1的方案
CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera1 (for previous experience)", Build.VERSION.SDK_INT);
return new Camera1(callbackBridge, preview, context, ratio);
} else {//根据版本可能使用Camera2的方案
if (Build.VERSION.SDK_INT < 21) {
CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera1", Build.VERSION.SDK_INT);
return new Camera1(callbackBridge, preview, context, ratio);
} else if (Build.VERSION.SDK_INT < 23) {
CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera2", Build.VERSION.SDK_INT);
return new Camera2(callbackBridge, preview, context, ratio);
} else {
CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera2Api23", Build.VERSION.SDK_INT);
return new Camera2Api23(callbackBridge, preview, context, ratio);
}
}

参考链接:https://blog.csdn.net/u012346890/article/details/116020542

友情推荐:

Android 开发干货集锦

至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

点击阅读原文,为大佬点赞!

浏览 60
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报