作者:星陨
来源:音视频开发进阶
CameraX介绍
官方有提供一个示例的工程,我fork了之后,加入使用OpenGL黑白滤镜渲染的操作,具体地址如下:
官方并没有提到CameraX库具体如何进行OpenGL线程渲染的,继续往下看,你会找到答案的~~~
关于CameraX更多的介绍,建议看看Google I / O大会上的视频记录,比看文档能了解更多内容~~~
在视频中提到,目前有很多应用都开始加入了CameraX,例如Camera360,Tik Tok等。
简述Camera开发
关于Camera的开发,之前也有写过相关的文章🤔
Android相机开发中的尺寸和方向问题
https://glumes.com/post/android/android-camera-aspect-ratio-and-orientation/
Android相机模型及API接口演变
https://glumes.com/post/android/android-camrea-api-evolution/
对于一个简单能用的Camera应用(演示等级)来说,关注两个方面就好了:预览和拍摄。
相机最基本的功能就是能针对预览和拍摄提供两套分辨率,因此就得区分场景去设置。
对于拍摄还好说一点,要获得最好的图像质量,就选择同比例中分辨率最大的吧。
而预览的图像最终要呈现到Android的Surface上,因此选择分辨率的时候要考虑Surface的宽高比例,不要出现比例不匹配导致图像拉伸的现象。
另外,如果要做美颜,滤镜类的应用,就要把Camera预览的图像放到OpenGL渲染的线程上去,然后由OpenGL去做图像相关的操作,也就没Camera什么事了。等到拍摄图片时,可以由OpenGL去获取图像内容,也可以由Camera获得图像内容,然后通过OpenGL做离屏处理~~~
以Camera开发的其他功能,缩小对焦,曝光,白平衡,HDR等操作,不一定所有的Camera都能够支持,而且也可以在上面的基础上当做Camera的一个功能去拓展开发,而不是算难事,这也是一个Camera开发工程师进阶所要掌握的内容~~
CameraX开发实践
CameraX目前的版本是1.0.0-alpha01
,在使用时要添加以下的依赖:
1 // CameraX 2 def camerax_version = "1.0.0-alpha01" 3 implementation "androidx.camera:camera-core:${camerax_version}" 4 implementation "androidx.camera:camera-camera2:${camerax_version}"
CameraX向后兼容到Android 5.0(API级别21),并且它是基于Camera 2.0的API进行封装的,解决了城市表面绝大部分手机的兼容性问题~~~
概述Camera 2.0复杂的调用流程,CameraX就简化了很多,只关心我们需要的内容就好了,不像前者得自己维护CameraSession会话等状态,并且CameraX和Jetpack主打的生命周期绑定在一起了,什么时候该打开相机,什么时候该释放相机,都交给Lifecycle生命周期去管理吧
上手CameraX主要关注三个方面:
- 图像预览(Image Preview)
- 图像分析(Image analysis)
- 图像拍摄(Image capture)
预览
不论是预览还是图像分析,图像拍摄,CameraX都是通过一个构造器模式来构造参数。配置类,再由配置类创建预览,分析器,拍摄的类,并在绑定生命周期时将其传递过去。
1// // Apply declared configs to CameraX using the same lifecycle owner 2CameraX.bindToLifecycle( 3 lifecycleOwner: this, preview, imageCapture, imageAnalyzer)
既可以绑定Activity的生命周期,也可以绑定Fragment的。
当需要解除绑定时:
1// Unbinds all use cases from the lifecycle and removes them from CameraX. 2 CameraX.unbindAll()
关于预览的参数配置,如果你有看过之前的文章:Android相机开发中的尺寸和方向问题想必就会很了解了。
提供我们的目标参数,由CameraX去判断当前Camera是否支持,并选择最符合的。
1fun buildPreviewUseCase(): Preview { 2 val previewConfig = PreviewConfig.Builder() 3 // 宽高比 4 .setTargetAspectRatio(aspectRatio) 5 // 旋转 6 .setTargetRotation(rotation) 7 // 分辨率 8 .setTargetResolution(resolution) 9 // 前后摄像头 10 .setLensFacing(lensFacing) 11 .build() 12 13 // 创建 Preview 对象 14 val preview = Preview(previewConfig) 15 // 设置监听 16 preview.setOnPreviewOutputUpdateListener { previewOutput -> 17 // PreviewOutput 会返回一个 SurfaceTexture 18 cameraTextureView.surfaceTexture = previewOutput.surfaceTexture 19 } 20 21 return preview 22}
通过建造者模式创建Preview
对象,并且一定要给Preview对象设置OnPreviewOutputUpdateListener
接口替代。
相机预览的图像流是通过SurfaceTexture来返回的,而在项目示例中,是通过把TextureView的SurfaceTexture替换成CameraX返回的SurfaceTexture,从而实现了TextureView控件的显示。
另外,还需要考虑到设备的选择方向,当设备横屏变成竖屏了,TextureView也要相应的做旋转。
1preview.setOnPreviewOutputUpdateListener { previewOutput -> 2 cameraTextureView.surfaceTexture = previewOutput.surfaceTexture 3 4 // Compute the center of preview (TextureView) 5 val centerX = cameraTextureView.width.toFloat() / 2 6 val centerY = cameraTextureView.height.toFloat() / 2 7 8 // Correct preview output to account for display rotation 9 val rotationDegrees = when (cameraTextureView.display.rotation) { 10 Surface.ROTATION_0 -> 0 11 Surface.ROTATION_90 -> 90 12 Surface.ROTATION_180 -> 180 13 Surface.ROTATION_270 -> 270 14 else -> return@setOnPreviewOutputUpdateListener 15 } 16 17 val matrix = Matrix() 18 matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY) 19 20 // Finally, apply transformations to TextureView 21 cameraTextureView.setTransform(matrix) 22}
TextureView旋转的设置同样在OnPreviewOutputUpdateListener
接口中去完成。
图像分析
在bindToLifecycle
方法中,imageAnalyzer
参数并不是必需的。
ImageAnalysis
可以帮助我们做一些图像质量的分析,需要我们去实现ImageAnalysis.Analyzer
接口的analyze
方法。
1fun buildImageAnalysisUseCase(): ImageAnalysis { 2 // 分析器配置 Config 的建造者 3 val analysisConfig = ImageAnalysisConfig.Builder() 4 // 宽高比例 5 .setTargetAspectRatio(aspectRatio) 6 // 旋转 7 .setTargetRotation(rotation) 8 // 分辨率 9 .setTargetResolution(resolution) 10 // 图像渲染模式 11 .setImageReaderMode(readerMode) 12 // 图像队列深度 13 .setImageQueueDepth(queueDepth) 14 // 设置回调的线程 15 .setCallbackHandler(handler) 16 .build() 17 18 // 创建分析器 ImageAnalysis 对象 19 val analysis = ImageAnalysis(analysisConfig) 20 21 // setAnalyzer 传入实现了 analyze 接口的类 22 analysis.setAnalyzer { image, rotationDegrees -> 23 // 可以得到的一些图像信息,参见 ImageProxy 类相关方法 24 val rect = image.cropRect 25 val format = image.format 26 val width = image.width 27 val height = image.height 28 val planes = image.planes 29 } 30 31 return analysis 32}
在图像分析器的相关配置中,有个ImageReaderMode
和ImageQueueDepth
的设置。
ImageQueueDepth会指定相机管线中图像的个数,提高ImageQueueDepth的数量相机的性能和内存的使用造成影响
其中,ImageReaderMode有两种模式:
- ACQUIRE_LATEST_IMAGE
- 该模式下,获得图像像素中最新的图片,并且会清空副本已有的旧的图像。
- ACQUIRE_NEXT_IMAGE
- 该模式下,获得下一张图像。
在图像分析的analyze
方法中,能通过ImageProxy类拿到一些图像信息,并基于这些信息做分析。
拍摄
拍摄同样有一个Config参数内置者类,而且设置的参数和预览相差不大,也是图像宽高比例,旋转方向,分辨率,还有闪光灯等配置项。
1fun buildImageCaptureUseCase(): ImageCapture { 2 val captureConfig = ImageCaptureConfig.Builder() 3 .setTargetAspectRatio(aspectRatio) 4 .setTargetRotation(rotation) 5 .setTargetResolution(resolution) 6 .setFlashMode(flashMode) 7 // 拍摄模式 8 .setCaptureMode(captureMode) 9 .build() 10 11 // 创建 ImageCapture 对象 12 val capture = ImageCapture(captureConfig) 13 cameraCaptureImageButton.setOnClickListener { 14 // Create temporary file 15 val fileName = System.currentTimeMillis().toString() 16 val fileFormat = ".jpg" 17 val imageFile = createTempFile(fileName, fileFormat) 18 19 // Store captured image in the temporary file 20 capture.takePicture(imageFile, object : ImageCapture.OnImageSavedListener { 21 override fun onImageSaved(file: File) { 22 // You may display the image for example using its path file.absolutePath 23 } 24 25 override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) { 26 // Display error message 27 } 28 }) 29 } 30 31 return capture 32}
在图像拍摄的相关配置中,也有个CaptureMode
的设置。
它有两种选择:
- MIN_LATENCY
- 该模式下,拍摄速度会相对快一点,但图像质量会打折扣
- MAX_QUALITY
- 该模式下,拍摄速度会慢一点,但图像质量好
OpenGL渲染
以上是关于CameraX的简单应用方面的内容,更关心的是如何用CameraX去做OpenGL渲染实现美颜。滤镜等效果。
还记得在图像预览Preview的setOnPreviewOutputUpdateListener方法中,会返回一个SurfaceTexture
,相机的图像流就是通过它返回的。
那么要实现OpenGL线程的渲染,首先就要基于EGL去创建OpenGL布局环境,然后利用SurfaceTexture的attachToGLContext
方法,将SurfaceTexture添加到OpenGL线程去。
attachToGLContext的参数是一个纹理ID,这个纹理就必须是OES类型的纹理。
然后再把这一个纹理ID移到OpenGL对应的Surface上,这可以看成是两个不同的线程在允许,一个Camera预览线程,一个OpenGL布局线程。
如果你不是很理解的话,建议还是看看上面提供的代码地址:
也可以关注我的微信公众号【纸上浅谈】,里面有一些关于OpenGL学习和实践的文章~~~
CameraX的拓展
如果您看了Google I / O大会的视频,那肯定了解CameraX的扩展属性。
在视频中提到Google也正在和华为,三星,LG,摩托摩拉等厂商进行合作,以便获得厂商系统相机的一些能力,例如HDR等。
不过考虑到目前的预期,可能和华为的合作难以继续下去了吧...
但还是期待CameraX能给带来更多的新特性吧~~~
参考
- https://www.youtube.com/watch?v=kuv8uK-5CLY
- https://proandroiddev.com/android-camerax-preview-analyze-capture-1b3f403a9395
「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。