Android CameraX 仿一甜相机(录像拍照可调节尺寸聚焦照明网格线),最全的CameraX教程

Posted 梦想改变生活

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android CameraX 仿一甜相机(录像拍照可调节尺寸聚焦照明网格线),最全的CameraX教程相关的知识,希望对你有一定的参考价值。

使用cameraX 仿一甜相机

前言

1、导入相关库
2、绑定LifeCycle生命周期并开启预览流
3、绑定ImageCapture图形捕捉以及VideoCapture视频帧捕捉
4、拍照
5、录像
6、聚焦
7、切换摄像头
8、缩放
9、闪光灯
10、照明、补光
11、Extensions 扩展程序使用

前言

CameraX 是jetpack 组件库中的一个非常重要的API,不同于Camera和Camera2,CameraX 在api解耦性上做出了非常大的调整。其中:

  • 1、新增了生命周期绑定管理,解决了老版本Camera的内存泄漏问题
  • 2、分辨率自动找寻最接近匹配问题,解决了开发者手动去查询支持分辨率列表(从中去找寻最匹配的分辨率)
  • 3、增加ImageAnalysis图像分析,可以在图像输出前对像素进行YUV 转换并且预处理等操作
  • 4、增加了 Extensions 扩展程序包括(AUTO、HDR、焦外成像(BOKEH)、夜景(NIGHT)、脸部照片修复(FACE_RETOUCH))等模式,当然,目前国内手机厂商都还没兼容当前的扩展模式,大部分还只有三星手机支持。期待
    手机厂商抓紧适配,减少不必要的算法融合。

以上的一些新增内容,足够我们去替换老板本的Camera或者Camera2 Api。下面就是CameraX 的基础用法和Extensions 用法。

CameraX仿一甜相机

  • 1、导入相关库
    def camerax_version = "1.2.0-alpha04"
    implementation "androidx.camera:camera-core:$camerax_version"
    implementation "androidx.camera:camera-camera2:$camerax_version"
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    implementation "androidx.camera:camera-video:$camerax_version"
    implementation "androidx.camera:camera-view:$camerax_version"
    implementation "androidx.camera:camera-extensions:$camerax_version"
  • 2、绑定LifeCycle生命周期并开启预览流

     fun openCamera()
     
     val cameraProviderFuture = ProcessCameraProvider.getInstance(mLifecycleOwner!!)

        cameraProviderFuture.addListener(
            // 绑定生命周期
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            // Preview 预览流
            val preview = Preview.Builder()
                .build()
                .also 
                    it.setSurfaceProvider(preview?.surfaceProvider)
                

            //选择后置摄像头
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try 
                //解绑所有摄像头使用
                cameraProvider.unbindAll()

                // 绑定输出
                camera = cameraProvider.bindToLifecycle(
                    mLifecycleOwner!!, cameraSelector,  preview
                )

             catch (exc: Exception) 
                Log.e(TAG, "Use case binding failed", exc)
            

        , ContextCompat.getMainExecutor(mLifecycleOwner!!))

  • 3、绑定ImageCapture图形捕捉以及VideoCapture视频帧捕捉
            //图像捕捉
            imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                .build()

        
         // 绑定输出
                camera = cameraProvider.bindToLifecycle(
                    mLifecycleOwner!!, cameraSelector, imageCapture, preview
                )


  • 4、拍照
 override fun takePhoto() 

        val imageCapture = imageCapture ?: return

        val name = SimpleDateFormat("yyyy-MM-dd", Locale.US)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply 
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) 
                put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
            
        
        val outputOptions = ImageCapture.OutputFileOptions
            .Builder(
                mLifecycleOwner?.contentResolver!!,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                contentValues
            )
            .build()
        imageCapture.takePicture(
            outputOptions,
            ContextCompat.getMainExecutor(mLifecycleOwner!!),
            object : ImageCapture.OnImageSavedCallback 
                override fun onError(exc: ImageCaptureException) 
                    Log.e(TAG, "Photo capture failed: $exc.message", exc)
                

                override fun onImageSaved(output: ImageCapture.OutputFileResults) 
                    val msg = "Photo capture succeeded: $output.savedUri"
                    Toast.makeText(mLifecycleOwner, msg, Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg)
                
            
        )
    
  • 5、录像

  @SuppressLint("CheckResult")
    override fun takeVideo() 
        val videoCapture = this.videoCapture ?: return


        //如果正在录制,则停止
        if (recording != null) 
            recording?.stop()
            recording = null
            return
        

        val name = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply 
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) 
                put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
            
        

        val mediaStoreOutputOptions = mLifecycleOwner?.contentResolver?.let 
            MediaStoreOutputOptions
                .Builder(it, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                .setContentValues(contentValues)
                .build()
        
        recording = videoCapture.output
            .prepareRecording(mLifecycleOwner!!, mediaStoreOutputOptions!!)
            .apply 
                if (ActivityCompat.checkSelfPermission(
                        mLifecycleOwner!!,
                        Manifest.permission.RECORD_AUDIO
                    ) != PackageManager.PERMISSION_GRANTED
                ) 
                    //启动音频
                    withAudioEnabled()
                

            
            .start(ContextCompat.getMainExecutor(mLifecycleOwner!!))  recordEvent ->
                when (recordEvent) 
                    is VideoRecordEvent.Start -> 
                        //录制开始
                        Toast.makeText(mLifecycleOwner, "开始录制", Toast.LENGTH_SHORT)
                            .show()
                    
                    is VideoRecordEvent.Finalize -> 
                        //录制结束
                        if (!recordEvent.hasError()) 
                            val msg = "Video capture succeeded: " +
                                    "$recordEvent.outputResults.outputUri"
                            Toast.makeText(mLifecycleOwner, msg, Toast.LENGTH_SHORT)
                                .show()
                            Log.d(TAG, msg)
                         else 
                            recording?.close()
                            recording = null
                            Log.e(
                                TAG, "Video capture ends with error: " +
                                        "$recordEvent.error"
                            )
                        

                    
                
            
    


  • 6、聚焦

聚焦分为三种模式

//使用PreviewView。用于自动聚焦,在previewView中创建一个坐标点
previewView.setOnTouchListener((view, motionEvent) ->  
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)

//使用DisplayOrientedMeteringPointFactory如果SurfaceView / TextureView用于
//预览。请注意,如果预览在视图中缩放或裁剪,
//正确转换坐标是应用程序的责任
//这样工厂的宽度和高度代表完整的预览FOV。
//和(x,y)传入创建MeteringPoint可能需要调整

val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

//使用SurfaceOrientedMeteringPointFactory如果点指定在
//图形分析ImageProxy。
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

基于以上的point 创建方式,可以封装为自动聚焦和手动聚焦


  /**
     * 聚焦
     * @param auto 聚焦模式
     */
    @SuppressLint("RestrictedApi")
    override fun focus(x: Float, y: Float, auto: Boolean) 
        cameraControl?.cancelFocusAndMetering()
        val createPoint: MeteringPoint = if (auto) 

            val meteringPointFactory = DisplayOrientedMeteringPointFactory(
                preview?.display!!,
                camera?.cameraInfo!!,
                preview?.width?.toFloat()!!,
                preview?.height?.toFloat()!!
            )
            meteringPointFactory.createPoint(x, y)
         else 
            val meteringPointFactory = preview?.meteringPointFactory
            meteringPointFactory?.createPoint(x, y)!!
        


        val build = FocusMeteringAction.Builder(createPoint, FLAG_AF)
            .setAutoCancelDuration(3, TimeUnit.SECONDS)
            .build()

        val future = cameraControl?.startFocusAndMetering(build)


        future?.addListener(
            try 

                if (future.get().isFocusSuccessful) 
                    //聚焦成功
                    Log.e(TAG, "聚焦成功")
                 else 
                    //聚焦失败
                    Log.e(TAG, "聚焦失败")
                
             catch (e: Exception) 
                Log.e(TAG, "异常" + e.message)
            

        , ContextCompat.getMainExecutor(mLifecycleOwner!!))


    
  • 7、切换摄像头

    /**
     * 切换镜头
     */
       override fun switchCamera() 


        mFacingFront = !mFacingFront

        // 解除绑定
        cameraProvider?.unbindAll()


        // 前后置摄像头选择器
        val cameraSelector = CameraSelector.Builder()
            .requireLensFacing(if (mFacingFront) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK)
            .build()
        imageCapture = ImageCapture.Builder()
            .setTargetAspectRatio(AspectRatio.RATIO_4_3)
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
            .build()


        // 绑定输出
        camera = cameraProvider?.bindToLifecycle(
            mLifecycleOwner!!,
            cameraSelector,
            imageCapture,
            videoCapture,
            mPreView

        )


    
  • 8、缩放

    /**
     * 缩放
     */
    override fun zoom(out: Boolean) 
        val zoomState = camera?.cameraInfo?.zoomState
        val zoomRatio: Float? = zoomState?.value?.zoomRatio        //当前值
        val maxZoomRatio: Float? = zoomState?.value?.maxZoomRatio//缩放最大值
        val minZoomRatio: Float? = zoomState?.value?.minZoomRatio //缩放最小值

        if (out) 
            //放大
            if (zoomRatio!! < maxZoomRatio!!) 
                cameraControl?.setZoomRatio((zoomRatio + zoomCoefficient))
            
         else 
            //缩小
            if (zoomRatio!! > minZoomRatio!!) 
                cameraControl?.setZoomRatio((zoomRatio - zoomCoefficient))
            
        
    
  • 9、闪光灯
       //闪光灯模式
        val flashMode =
            if (cameraParams?.mSplashOn == true && cameraParams?.mFacingFront == false) 
                ImageCapture.FLASH_MODE_ON
             else 
                ImageCapture.FLASH_MODE_OFF
            

        imageCapture?.flashMode = flashMode
  • 10、照明、补光
       cameraControl?.enableTorch(cameraParams?.torchSwitch!!)
  • 11、Extensions 扩展程序使用

以下是Extensions 支持的模式。

public final class ExtensionMode 
    /**正常无效果 */
    public static final int NONE = 0;
    /**
   焦外成像
     */
    public static final int BOKEH = 1;
    /**
     HDR
     */
    public static final int HDR = 2;
    /** 在光线较暗的情况下,尤其是在夜间,获得最好的静止图像 */
    public static final int NIGHT = 3;
    /**在拍摄静态图像时,修饰脸部皮肤色调,几何形状等 */
    public static final int FACE_RETOUCH = 4;
    /**
      自动
     */
    public static final int AUTO = 5;

使用方式


      //协程挂起是必须的
      mLifecycleOwner?.lifecycleScope?.launch 
      
                 //获取manager
                val extensionsManager = ExtensionsManager.getInstanceAsync(mLifecycleOwner!!, cameraProvider!!).await()


                //判断设备是否支持扩展程序
                if (extensionsManager.isExtensionAvailable(
                        cameraSelector,
                        cameraParams?.extensionMode!!
                    )
                ) 
                 //如果支持,则传入对应的mode 模式,如BOKEH 
                    Log.d(TAG, "支持" + cameraParams?.extensionMode)
                    val extensionId = extensionsManager.getExtensionEnabledCameraSelector(
                        cameraSelector,
                        cameraParams?.extensionMode!!
                    )
                    startPreView = true
                    bindCameraId(extensionId)

                 else 
                    startPreView = false
                    Log.d(TAG, "不支持" + cameraParams?.extensionMode)
                

            

    /**
     * 绑定相机id
     */
    private fun bindCameraId(cameraSelector: CameraSelector) 
        try 

            cameraProvider?.unbindAll()
            // 绑定输出
            camera = cameraProvider?.bindToLifecycle(
                mLifecycleOwner!!,
                cameraSelector,
                mPreView,
                imageCapture,
                videoCapture

            )
            if (!isInt) 
                isInt = true
                callBack?.ratioCallBack(cameraParams?.mRatioType)
            
            cameraControl = camera?.cameraControl

            focus(preview?.width?.div(2f)!!, preview?.height?.div(2f)!!, true)


         catch (exc: Exception) 
            Log.e(TAG, "Use case binding failed", exc)
        


    

由于本人的设备不支持扩展程序,所以就没有展示效果了,希望国能厂商能升级ROM 支持一下吧。
后续有支持的设备后,会更新效果图上来。
代码已上传:
https://github.com/ljlstudio/KtMvvm/tree/master/demo/src/main/java/com/kt/ktmvvm/jetpack/camerax

Android开发笔记(一百八十二)使用CameraX录像

通过CameraX实现录像功能的话,初始化相机的步骤与拍照时大同小异,区别在于增加了对视频捕捉器VideoCapture的处理。需要修改的代码主要有三个地方,分别说明如下。
第一个地方是在AndroidManifest.xml里补充声明录音权限,完整的权限声明配置如下所示:

<!-- 相机 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 录音 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

第二个地方是在重置相机的resetCamera方法中,构建完图像捕捉器对象,还要构建视频捕捉器对象,并设置视频的宽高比例、视频帧率、比特率(视频每秒录制的比特数)、旋转角度等录制参数。视频捕捉器的构建代码示例如下:

if (mCameraMode == MODE_RECORD)  // 录像
    // 构建一个视频捕捉器
    mVideoCapture = new VideoCapture.Builder()
            .setTargetAspectRatio(mAspectRatio) // 设置宽高比例
            .setVideoFrameRate(60) // 设置视频帧率
            .setBitRate(3 * 1024 * 1024) // 设置比特率
            .setTargetRotation(rotation) // 设置旋转角度
            .setAudioRecordSource(MediaRecorder.AudioSource.MIC)
            .build();

第三个地方是在绑定摄像头的bindCamera方法中,对于录像操作来说,需要把视频捕捉器绑定到相机提供器的生命周期,而非绑定图像捕捉器。绑定视频捕捉器的代码片段示例如下:

if (captureMode == MODE_RECORD)  // 录像
    // 把相机选择器、预览视图、视频捕捉器绑定到相机提供器的生命周期
    Camera camera = mCameraProvider.bindToLifecycle(
            mOwner, mCameraSelector, mPreview, mVideoCapture);

初始化相机之后,即可调用视频捕捉器的startRecording方法开始录像,或者调用stopRecording方法停止录像。录像代码示例如下:

private String mVideoPath; // 视频保存路径
private int MAX_RECORD_TIME = 15; // 最大录制时长,默认15秒
// 获取视频的保存路径
public String getVideoPath() 
    return mVideoPath;


// 开始录像
public void startRecord(int max_record_time) 
    MAX_RECORD_TIME = max_record_time;
    bindCamera(MODE_RECORD); // 绑定摄像头
    mVideoPath = String.format("%s/%s.mp4", mMediaDir, DateUtil.getNowDateTime());
    VideoCapture.Metadata metadata = new VideoCapture.Metadata();
    // 构建视频捕捉器的输出选项
    VideoCapture.OutputFileOptions options = new VideoCapture.OutputFileOptions.Builder(new File(mVideoPath))
            .setMetadata(metadata).build();
    // 开始录像动作
    mVideoCapture.startRecording(options, mExecutorService, new VideoCapture.OnVideoSavedCallback() 
        @Override
        public void onVideoSaved(VideoCapture.OutputFileResults outputFileResults) 
            mHandler.post(() -> bindCamera(MODE_PHOTO));
            mStopListener.onStop("录制完成的视频路径为"+mVideoPath);
        

        @Override
        public void onError(int videoCaptureError, String message, Throwable cause) 
            mHandler.post(() -> bindCamera(MODE_PHOTO));
            mStopListener.onStop("录制失败,错误信息为:"+cause.getMessage());
        
    );
    // 限定时长到达之后自动停止录像
    mHandler.postDelayed(() -> stopRecord(), MAX_RECORD_TIME*1000);


// 停止录像
public void stopRecord() 
    mVideoCapture.stopRecording(); // 视频捕捉器停止录像

当然,录像功能也要先在布局文件中添加CameraXView节点。为了方便观察当前已录制的时长,还可以在布局文件中添加计时器节点Chronometer。接着给Java代码补充CameraXView对象的初始化以及录像动作,其中关键代码示例如下:

private CameraXView cxv_preview; // 声明一个增强相机视图对象
private Chronometer chr_cost; // 声明一个计时器对象
private ImageView iv_record; // 声明一个图像视图对象
private boolean isRecording = false; // 是否正在录像

// 初始化相机
private void initCamera() 
    // 打开增强相机,并指定停止录像监听器
    cxv_preview.openCamera(this, CameraXView.MODE_RECORD, (result) -> 
        runOnUiThread(() -> 
            chr_cost.setVisibility(View.GONE);
            chr_cost.stop(); // 停止计时
            iv_record.setImageResource(R.drawable.record_start);
            iv_record.setEnabled(true);
            isRecording = false;
            Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
        );
    );


// 处理录像动作
private void dealRecord() 
    if (!isRecording) 
        iv_record.setImageResource(R.drawable.record_stop);
        cxv_preview.startRecord(15); // 开始录像
        chr_cost.setVisibility(View.VISIBLE);
        chr_cost.setBase(SystemClock.elapsedRealtime()); // 设置计时器的基准时间
        chr_cost.start(); // 开始计时
        isRecording = !isRecording;
     else 
        iv_record.setEnabled(false);
        cxv_preview.stopRecord(); // 停止录像
    

运行测试App,打开录像界面的初始效果如下图所示,此时除了预览画面外,界面下方还展示录制按钮。

 点击录制按钮开始录像,正在录像的界面如下图所示,此时录制按钮换成了暂停图标,其上方也跳动着已录制时长的数字。

 


点此查看Android开发笔记的完整目录

以上是关于Android CameraX 仿一甜相机(录像拍照可调节尺寸聚焦照明网格线),最全的CameraX教程的主要内容,如果未能解决你的问题,请参考以下文章

Android开发笔记(一百八十二)使用CameraX录像

Android学习笔记之CameraX实现拍照&录像功能

具有自定义表面的 Android CameraX

APP使用相机CameraX

APP使用相机CameraX

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