• Android开发 CameraX开发


    前言

      google推出Camera后,发现Camera功能简单,难以满足需求调用Camera各种效果,所以又推出了Camera2. Camera2功能强大但是使用十分麻烦,回调与冗余代码太多,而且特别容易在释放Camera上犯错导致activty的内存泄露. 所以google推出了更简单易用,但是功能也强大的CameraX.

      因为CameraX的简单可以帮助我们高效率开发,所以也是有学习的必要性.(Camera2了解就行,没必要死磕浪费太多时间),CameraX有以下优势:

    1. CameraX与Liftcycle结合,与Activity或者Fragment的生命周期捆绑,不要考虑摄像头的释放问题,减少了代码的复杂度.
    2. CameraX兼容至 Android L (API 21)
    3. 依然支持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)
            }
    
        }

    End

  • 相关阅读:
    实现mypwd
    2019-2020-1 20175234 20175205 20175217 实验五 通讯协议设计
    2019-2020-1 20175205 20175234 20175217 实验四 外设驱动程序设计
    2019-2020-1 20175234 20175205 20175217 实验三 实时系统
    2019-2020-1 20175234 20175205 20175217 实验二 固件程序设计
    2018-2019-20175205 实验五《网络编程与安全》实验报告
    [HeadFrist-HTMLCSS学习笔记]第七、八章
    2018-2019-20175205实验四《Android程序设计》实验报告
    [HeadFrist-HTMLCSS学习笔记]第五章认识媒体:给网页添加图像
    [HeadFrist-HTMLCSS学习笔记]第三章构建模块:Web页面建设
  • 原文地址:https://www.cnblogs.com/guanxinjing/p/15132909.html
Copyright © 2020-2023  润新知