前言
google推出Camera后,发现Camera功能简单,难以满足需求调用Camera各种效果,所以又推出了Camera2. Camera2功能强大但是使用十分麻烦,回调与冗余代码太多,而且特别容易在释放Camera上犯错导致activty的内存泄露. 所以google推出了更简单易用,但是功能也强大的CameraX.
因为CameraX的简单可以帮助我们高效率开发,所以也是有学习的必要性.(Camera2了解就行,没必要死磕浪费太多时间),CameraX有以下优势:
- CameraX与Liftcycle结合,与Activity或者Fragment的生命周期捆绑,不要考虑摄像头的释放问题,减少了代码的复杂度.
- CameraX兼容至 Android L (API 21)
- 依然支持Camera2的丰富摄像头功能
添加依赖
// CameraX 核心库使用 camera2 实现 implementation "androidx.camera:camera-camera2:1.0.0-beta03" // 可以使用CameraView implementation "androidx.camera:camera-view:1.0.0-alpha10" // 可以使用供应商扩展 implementation "androidx.camera:camera-extensions:1.0.0-alpha10" //camerax的生命周期库 implementation "androidx.camera:camera-lifecycle:1.0.0-beta03"
获取权限
跟以前一样,需要动态授权一些必要权限
<!-- 相机相关 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
预览摄像头画面
从最简单的预览摄像头图像开始,我们逐步了解使用方式,代码如下:
布局要求使用PreviewView,作为SurfaceProvider
<androidx.camera.view.PreviewView android:id="@+id/previewView" android:layout_width="match_parent" android:layout_height="match_parent"/>
代码:
class CameraXActivity : AppCompatActivity() { private val TAG = CameraXActivity::class.java.simpleName private lateinit var mPreviewView: PreviewView
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera_x2) mPreviewView = findViewById(R.id.previewView) startCameraPreview() } /** * 开始相机预览 */ private fun startCameraPreview() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { //用于将相机的生命周期绑定到生命周期所有者 val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() //创建预览 val preview = Preview.Builder().build() //选择后置摄像头 val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() try { //在重新绑定之前取消绑定 cameraProvider.unbindAll() //将生命周期,选择摄像头,预览,绑定到相机 val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview) //设置预览的View preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera.cameraInfo)) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } }
特别简单就完成了,而且无需考虑摄像头的释放
实现拍照
class CameraXActivity : AppCompatActivity() { private val TAG = CameraXActivity::class.java.simpleName private lateinit var mImageCapture: ImageCapture private lateinit var mImageAnalysis: ImageAnalysis private lateinit var mPreviewView: PreviewView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera_x2) mPreviewView = findViewById(R.id.previewView) startCameraPreview() takePhoto.setOnClickListener { //点击后拍照 takePhoto() } } /** * 开始相机预览 */ private fun startCameraPreview() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() val preview = Preview.Builder().build() //创建图像捕捉 mImageCapture = ImageCapture.Builder().build() val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() try { cameraProvider.unbindAll() //请注意,这里新增了一个ImageCapture val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, mImageCapture) preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera.cameraInfo)) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } /** * 拍照 */ private fun takePhoto() { //图像的保存路径与名称 val photoFile = File(applicationContext.externalCacheDir?.path , SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(System.currentTimeMillis()) + ".jpg") // 创建图像文件输出选项 val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() //拍照,并且注册拍照后的结果监听 mImageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), 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 = Uri.fromFile(photoFile) val msg = "Photo capture succeeded: $savedUri" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } }) } }
拍照图像旋转
代码的其他部分与上面的示例代码一致, 我们只需要关注ImageCapture的创建与添加setTargetRotation
private fun createImageCapture():ImageCapture{ //创建图像捕捉 mImageCapture = ImageCapture.Builder() .setTargetRotation(Surface.ROTATION_90)//设置旋转角度,并且只能有4个旋转方向属性ROTATION_0/ROTATION_90/ROTATION_180/ROTATION_270 .build() return mImageCapture }
设置执行IO线程
private fun createImageCapture():ImageCapture{ //创建图像捕捉 mImageCapture = ImageCapture.Builder() .setIoExecutor(Executors.newSingleThreadExecutor())//设置执行IO线程 .build() return mImageCapture }
设置捕捉模式
private fun createImageCapture():ImageCapture{ //创建图像捕捉 mImageCapture = ImageCapture.Builder() //CAPTURE_MODE_MAXIMIZE_QUALITY 高画质 //CAPTURE_MODE_MINIMIZE_LATENCY 低延迟 .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) .build() return mImageCapture }
设置闪光灯
private fun createImageCapture():ImageCapture{ //创建图像捕捉 mImageCapture = ImageCapture.Builder() //FLASH_MODE_ON 闪光灯开启 //FLASH_MODE_OFF 闪光灯关闭 //FLASH_MODE_AUTO 闪光灯自动 .setFlashMode(ImageCapture.FLASH_MODE_ON) .build() return mImageCapture }
设置宽高比
private fun createImageCapture():ImageCapture{ mImageCapture = ImageCapture.Builder() //RATIO_4_3 4比3 //RATIO_16_9 16比9 .setTargetAspectRatio(AspectRatio.RATIO_16_9) .build() return mImageCapture }
设置分辨率
下面还帖了一些注释,这注释的意思是,你可以随便设置分辨率大小,但是真正的分辨率并不一定是你设置的数值,而是在摄像头里获取的分辨率列表中去取最近似值.
为什么会有这种说明? 我这里给没有接触过摄像头开发的朋友说明一下:
手机的摄像头的分辨率并不是可以随便设置的,这需要取决于你开发的设备的摄像头驱动的分辨率列表. 在以往开发Camera1和Camera2的时候我们需要自己获取这份列表,从中选择我们需要的分辨率. 在使用CameraX的时候他们帮我们简化了这个筛选过程,你只需要设置目标分辨率,代码会自动选择近似分辨率
private fun createImageCapture(): ImageCapture { mImageCapture = ImageCapture.Builder() /* 目标分辨率尝试建立图像分辨率的最小界限。实际图像分辨率将是尺寸上最接近的可用分辨率,该分辨率不小于由相机实现确定的目标分辨率。 但是,如果不存在等于或大于目标分辨率的分辨率,则将选择最接近的小于目标分辨率的可用分辨率。 与提供的 {@link Size} 具有相同纵横比的分辨率将在不同纵横比的分辨率之前优先考虑。 */ .setTargetResolution(Size(1280, 720)) .build() return mImageCapture }
控制对焦
val factory = SurfaceOrientedMeteringPointFactory(width, height) val point = factory.createPoint(x, y) val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF) .addPoint(point2, FocusMeteringAction.FLAG_AE) // could have many // auto calling cancelFocusAndMetering in 5 seconds .setAutoCancelDuration(5, TimeUnit.SECONDS) .build() val future = cameraControl.startFocusAndMetering(action) future.addListener( Runnable { val result = future.get() // process the result } , executor)
实现录像
private void takeVideo() { VideoCapture videoCapture = new VideoCaptureConfig.Builder() //设置宽高 .setTargetAspectRatio(aspectRatio(width, height)) //设置旋转角度 .setTargetRotation(previewView.getDisplay().getRotation()) .build(); //录像前必须解绑 cameraProvider.unbindAll(); //开启相机预览 preview.setSurfaceProvider(previewView.createSurfaceProvider()); //绑定生命周期,这里如果没有参数preview,则只录像,不显示画面 cameraProvider.bindToLifecycle(this, cameraSelector,preview, videoCapture); //视频路径 File file = getFile(".mp4"); //开始录像 videoCapture.startRecording(file, ContextCompat.getMainExecutor(MainActivity.this), new VideoCapture.OnVideoSavedCallback() { @Override public void onVideoSaved(@NonNull File file) { Toast.makeText(MainActivity.this, file.getAbsolutePath(), Toast.LENGTH_SHORT).show(); } @Override public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) { Log.d(TAG, "onError: " + message); } }); //停止录像,并且回调OnVideoSavedCallback btn4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { videoCapture.stopRecording(); preview.clear(); } });
分析图像
val imageAnalysis = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), ImageAnalysis.Analyzer { image -> val rotationDegrees = image.imageInfo.rotationDegrees //旋转角度 val format = image.format //格式 val width = image.width //宽 val height = image.height //高 val plane = image.planes[0] //PlaneProxy数据 val buffer = plane.buffer Log.e("ytzn", "rotationDegrees = $rotationDegrees") Log.e("ytzn", "format = $format") Log.e("ytzn", "width = $width") Log.e("ytzn", "height = $height") // insert your code here. }) cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)
CameraX 会生成 YUV_420_888
格式的图片 在ImageFormat类里可以看到此格式
拓展功能
private fun setPreviewExtender(builder: Preview.Builder, cameraSelector: CameraSelector) { val extender = AutoPreviewExtender.create(builder) //自动模式 if (extender.isExtensionAvailable(cameraSelector)) { extender.enableExtension(cameraSelector) } //散景扩展 val bokehPreviewExtender = BokehPreviewExtender.create(builder) if (bokehPreviewExtender.isExtensionAvailable(cameraSelector)) { bokehPreviewExtender.enableExtension(cameraSelector) } //hdr扩展 val hdrPreviewExtender = HdrPreviewExtender.create(builder) if (hdrPreviewExtender.isExtensionAvailable(cameraSelector)) { hdrPreviewExtender.enableExtension(cameraSelector) } //美颜模式 val beautyPreviewExtender = BeautyPreviewExtender.create(builder) if (beautyPreviewExtender.isExtensionAvailable(cameraSelector)) { beautyPreviewExtender.enableExtension(cameraSelector) } //夜晚模式 val nightPreviewExtender = NightPreviewExtender.create(builder) if (nightPreviewExtender.isExtensionAvailable(cameraSelector)) { nightPreviewExtender.enableExtension(cameraSelector) } }
CameraX的一些问题
个人在开发过程中发现了一些问题,如下:
1.CameraX不支持外置摄像头
2.一直没找到跟Camera2一样配置摄像头拍照参数的方式
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/15132909.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步