ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android进阶宝典 -- CameraX与Camera2的使用比对

Android进阶宝典 -- CameraX与Camera2的使用比对 相机作为手机最重要的一个多媒体工具被应用于众多app软件中如果整个项目中涉及到拍照、直播、录视频、扫码那么相机就必须要用到。传统的相机app一般使用到Camera或者Camera2比较多但是Google的JectPack框架中引入了CameraX组件作为官方推荐相机架构既然推出此框架那么一定是有它自身的优势之处在的本文将会从CameraX和Camera2的框架机制出发分析两者的不同以及性能差异。1 Google相机元老 Camera2其实在早期开发相机app的时候有一部分会使用Camera有一部分会使用Camera2但是用起来真的是苦不堪言往往在相机配置时为了调出预览页面至少要写1000行代码而且仅仅是一个预览页面后面拍照、录视频等等还需要做额外的开发说这么多我们先看下Camera2是如何使用的吧。1.1 Camera2的使用首先Camera2是Google原生的相机框架所以不需要引任何框架进来。第一步创建承载相机的容器?xml version1.0 encodingutf-8? FrameLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent TextureView android:idid/camera_view_container android:layout_widthmatch_parent android:layout_heightmatch_parent/ /FrameLayout一般来说承载相机预览页面的就是TextureView当TextureView创建完成之后就可以配置相机参数展示预览页面。第二步打开摄像头的时机Camera2和Camera不同的是Camera2是将应用层与相机内核层做了完全的解耦需要通过Camera Service来获取到CameraManager以此调用相机内核层提供的能力。那么在什么时机才能打开摄像头呢就是在TextureView的onSurfaceTextureAvailable回调的时候去初始化相机参数开启摄像头。/** * 初始化相机 * 触发时机 TextureView 的 onSurfaceTextureAvailable回调 */ private fun initCamera(){ } override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { initCamera() } override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { } override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { return false } override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { }第三步搭建应用层与相机内核的桥梁/** * 开启摄像头 */ SuppressLint(MissingPermission) Synchronized fun start(textureView: TextureView) { val cameraManager context.getSystemService(Context.CAMERA_SERVICE) as CameraManager //建立数据传输的桥梁 imageReader ImageReader.newInstance( previewWidth, previewHeight, ImageFormat.YUV_420_888, 2 ) val handlerThread HandlerThread(Camera2Manager) handlerThread.start() handler Handler(handlerThread.looper) imageReader?.setOnImageAvailableListener(this, handler) //真正地开启摄像头 cameraManager.openCamera(0, this, handler) } // setOnImageAvailableListener的回调 override fun onImageAvailable(reader: ImageReader?) { } // Device StateCallback override fun onOpened(camera: CameraDevice) { } override fun onDisconnected(camera: CameraDevice) { } override fun onError(camera: CameraDevice, error: Int) { }因为应用层和相机内核层已经完全解耦所以两者想要进行数据传递必须要建立桥梁那么通过ImageReader就可以完成可以通过传入一些参数预览尺寸、图像格式等设置setOnImageAvailableListener有数据发送过来之后通过onImageAvailable获取到数据显示即可。然后调用CameraManager的openCamera方法才是真正打开摄像头那么成功还是失败呢就需要通过 CameraDevice.StateCallback的回调来判断。第四步建立会话创建预览请求override fun onOpened(camera: CameraDevice) { this.cameraDevice camera //建立会话 createPreviewSession() } override fun onDisconnected(camera: CameraDevice) { cameraDevice?.close() cameraDevice null } override fun onError(camera: CameraDevice, error: Int) { cameraDevice?.close() cameraDevice null }当打开Camera之后如何判断是否成功开启摄像头呢就是通过onOpened这个回调来判断当成功开启摄像头之后就需要与相机内核建立会话发起预览请求createCaptureRequest。private fun createPreviewSession() { //创建Surface所有的画面渲染都是由Surface处理 val texture textureView?.surfaceTexture texture?.setDefaultBufferSize(previewWidth, previewHeight) val surface Surface(texture) //开启预览会话这样就可以预览数据 val builder cameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) builder?.addTarget(surface) builder?.set( CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE ) //想要获取每一帧的数据需要给ImageReader设置Target imageReader?.surface?.let { builder?.addTarget(it) } //建立连接开启会话 cameraDevice?.createCaptureSession( mutableListOf(surface, imageReader?.surface), CaptureSessionCallback(), handler )我们知道如果使用TextureView那么所有画面的渲染都是通过Surface来渲染因此在创建Surface之后就可以添加到cameraDevice中此时页面中就会有图像了如果想要分析每一帧的数据那么也需要给ImageReader设置Surface.完成一系列配置之后调用CameraDevice的createCaptureSession方法开启会话。inner class CaptureSessionCallback : CameraCaptureSession.StateCallback() { override fun onConfigured(session: CameraCaptureSession) { mSession session if (cameraDevice null) return session.setRepeatingRequest(requestBuilder?.build()!!, null, handler) } override fun onConfigureFailed(session: CameraCaptureSession) { } }因为需要实时的数据流一帧一帧地传递过来因此需要发起重复请求setRepeatingRequest以此将实时的图像帧发送到手机页面上进行渲染。1.2 小结至此我们先做一个小小的总结我们知道如果做一个相机app最主要的两个功能预览和分析图片帧。我们在打开摄像头之前首先会通过ImageReader来完成应用层和相机内核层的建立通过监听回调获取每一帧的图像数据然后开启摄像头之后又完成的一个工作就是配置Surface将其挂载到CameraDevice上然后如果想要请求获取图像帧那么就需要开启session重复发起预览的请求来获取到每一帧的数据也正是下图所展示的。1.3 问题分析如果我们做到了上图的配置我们运行之后发现只拿到了一帧数据紧接着抛出了一个异常这个是在onError中回调的。2023-01-14 14:56:14.220 29697-29803/com.lay.highavailablecamera E/HighAvailableCamera: onOpened 2023-01-14 14:56:14.574 29697-29803/com.lay.highavailablecamera E/HighAvailableCamera: onImageAvailable android.media.ImageReaderf04668 2023-01-14 14:59:14.122 29697-29803/com.lay.highavailablecamera E/HighAvailableCamera: onError 3如果做过相机应用的伙伴应该也见到过这个情况主要原因就是一帧一帧地数据传递过来之后我们没有处理导致数据一直阻塞没有关闭从而无法处理下一帧数据因此在拿到最新的数据之后需要主动调用close方法以便下一帧数据进入到屏幕中。override fun onImageAvailable(reader: ImageReader?) { Log.e(Constants.TAG, onImageAvailable $reader) val latestImage reader?.acquireNextImage() //NOTE 做数据处理 latestImage?.close() }2 CameraX的便捷之处前面我们用Camera2完成了页面的预览伙伴们如果有需要源码的可以直接找我。通过前面对于Camera2的使用我们发现太麻烦了就为了完成一个页面预览写了很多配置好处就是这些配置是通用的不需要频繁地改动但是我们想的就是能够一键式搭建一个相机应用那么CameraX就是我们需要的”傻瓜相机“。2.1 UseCase分类其实CameraX对于每个功能都做了详细的划分例如预览的PreView、图像分析的ImageAnalysis等因此在做Camera配置的时候可以选择自己想要的UseCase进行初始化。private fun initCameraConfig(context: Context) { val cameraProviderFuture ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener({ // 预览配置 val preview Preview.Builder() .build() .also { it.setSurfaceProvider(binding.basePreview.surfaceProvider) } //设置摄像头是前置还是后置 val cameraSelector CameraSelector.DEFAULT_BACK_CAMERA if (cameraProvider null) { cameraProvider cameraProviderFuture.get() //拍照useCase配置 if (imageCapture null) { imageCapture ImageCapture .Builder() .setTargetRotation(Surface.ROTATION_90) .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) .setFlashMode(ImageCapture.FLASH_MODE_AUTO) .setTargetAspectRatio(AspectRatio.RATIO_16_9) .build() } //图像分析useCase配置 if (imageAnalysis null) { imageAnalysis ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .setTargetAspectRatio(AspectRatio.RATIO_16_9) .build() } //图像数据处 try { cameraProvider?.unbindAll() cameraProvider?.bindToLifecycle( lifecycleOwner!!, cameraSelector, preview, imageCapture, imageAnalysis ) } catch (exc: Exception) { } } }, ContextCompat.getMainExecutor(context)) }其实我们可以看到这里我们是将拍照的userCase以及图像分析的useCase都做了配置如果只需要做一个预览配置也就几十行代码就能够完成单就使用上比Camera2要简单的多的多。androidx.camera.view.PreviewView android:idid/base_preview android:layout_widthmatch_parent android:layout_heightmatch_parent/对于预览页面CameraX中也提供了PreviewView容器它其实是继承自FrameLayout。其实对于CameraX这块仅仅是讲了如何使用具体的核心实现等下篇文章细细道来。作者Vector7链接https://juejin.cn/post/7188444727904043067最后如果想要成为架构师或想突破20~30K薪资范畴那就不要局限在编码业务要会选型、扩展提升编程思维。此外良好的职业规划也很重要学习的习惯很重要但是最重要的还是要能持之以恒任何不能坚持落实的计划都是空谈。如果你没有方向这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》帮大家将杂乱、零散、碎片化的知识进行体系化的整理让大家系统而高效地掌握Android开发的各个知识点。相对于我们平时看的碎片化内容这份笔记的知识点更系统化更容易理解和记忆是严格按照知识体系编排的。全套视频资料一、面试合集二、源码解析合集三、开源框架合集欢迎大家一键三连支持若需要文中资料直接点击文末CSDN官方认证微信卡片免费领取↓↓↓
返回列表