Android开发(Jetpack) 学习CameraX 自定义相机实现

目录

一、CameraX和Camera2

二、CameraX的引入

三、自定义拍照

四、自定义视频录制

五、XML 配置


一、CameraX和Camera2

CameraX 是一个 Jetpack 库,旨在帮助您更轻松地开发相机应用。 对于新应用,Android官方建议从 CameraX 开始。它提供一致且易于使用的 API,适用于绝大多数 Android 设备,并向后兼容 Android 5.0(API 级别 21)。

Camera2是一个底层相机的实现,CameraX实则也是基于Camera2,所以Camera2非常底层,如果需要对纹理等操作,需要使用Camera2来实现。

Camera2出自jetpack库,它比自己粗糙封装的Camera2更加健壮,而且它的兼容性由官方维护更省心。

二、CameraX的引入

废话不多说,直接上依赖;
依赖引入:

// //使用camera2实现的CameraX核心库
def camerax_version = "1.1.0"
// 以下行是可选的,因为camera-camera2间接包含了核心库
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// 如果您想另外使用CameraX生命周期库
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// 如果您想额外使用CameraX VideoCapture库
implementation "androidx.camera:camera-video:${camerax_version}"
// 如果您想另外使用CameraX View类
implementation "androidx.camera:camera-view:${camerax_version}"
// 如果您想额外添加CameraX ML Kit Vision Integration
implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
// 如果您想另外使用CameraX Extensions库
implementation "androidx.camera:camera-extensions:${camerax_version}"

三、自定义拍照

在实际开发中经常可能碰到需要拍照的需求,但调用系统的照相机无法实现自己的自定义功能,自己封装Camera2有些麻烦,且没有处理好容易造成内存泄漏和闪退。使用CameraX虽然它属于再封装不够底层,但只是自定义相机的界面完全足够了。

以下是使用kotlin实现的实例代码,代码还有待优化。

/**
 * @author Ym
 * @version 1.0
 * @createTime 
 * @describe  自定义相机 拍照
 */
class CamareXActivity : AppCompatActivity(), View.OnClickListener {
    companion object {

        fun createIntent(mContext: Context): Intent {
            return Intent(mContext, CamareXActivity::class.java)
        }
    }

    private lateinit var imageCapture: ImageCapture
    private lateinit var cameraProviderFuture: ListenableFuture
    private var cameraProvider: ProcessCameraProvider? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camerax)
        initCameraX()
        tv_btn.setOnClickListener(this)
    }

    private fun initCameraX() {
        imageCapture = ImageCapture.Builder()
            .build()
        cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(Runnable {
            cameraProvider = cameraProviderFuture.get()
            cameraProvider?.let { bindPreview(it) }
        }, ContextCompat.getMainExecutor(this))
    }

    fun bindPreview(cameraProvider: ProcessCameraProvider) {
        var preview: Preview = Preview.Builder()
            .build()

        var cameraSelector: CameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()

        preview.setSurfaceProvider(pvCameraX.getSurfaceProvider())
        var camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
    }

    private var filepath: String? = null

    private fun takePicture() {
        if (filepath == null) {
            filepath = this.getExternalFilesDir(null)?.absolutePath + File.separator +
                    "camerax_image"
        }
        var imagePath = filepath + File.separator+ "img_${Calendar.getInstance().timeInMillis}.jpeg"
        val imageFile = File(imagePath)
        if (!imageFile.parentFile.exists()){
            imageFile.parentFile.mkdirs()
        }
        imageFile.createNewFile()
        Log.e("拍照地址", imagePath)
        val outputFileOptions = ImageCapture.OutputFileOptions.Builder(File(imagePath)).build()
        imageCapture.takePicture(outputFileOptions, ThreadUtil.getThread(),
            object : ImageCapture.OnImageSavedCallback {
                override fun onError(error: ImageCaptureException) {
                    Log.e("拍照失败", error.toString())
                }

                override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                    val imagePath = outputFileResults.savedUri
                    runOnUiThread {
                        Toast.makeText(
                            this@CamareXActivity, imagePath.toString(), Toast.LENGTH_SHORT
                        ).show()
                        Log.i("拍照结果", imagePath.toString())
                        Glide.with(this@CamareXActivity).load(imagePath).into(iv_image)
                    }
                }
            })
    }

    override fun onClick(p0: View?) {
        takePicture()
    }
}

四、自定义视频录制

自定义视频录像虽然实现了视频录像但它的缺陷如同它的出生是一个Jetpack 库,如果需要实现比较底层的图像处理,比较困难,如果只是实现录像和自定义界面CameraX绰绰有余。

以下是使用kotlin实现的实例代码,代码还有待优化。

/**
 * @author Ym
 * @version 1.0
 * @createTime 
 * @describe 自定义录制视频
 */
class VideoCameraXActivity : BaseActivity(), View.OnClickListener {
    companion object {

        fun createIntent(mContext: Context): Intent {
            return Intent(mContext, VideoCameraXActivity::class.java)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camerax_video)
        initView()
    }

    private lateinit var cameraProviderFuture:ListenableFuture
    private fun initView() {
        tv_start.setOnClickListener(this)
        tv_stop.setOnClickListener(this)
       initCameraX()
    }
    private var cameraProvider: ProcessCameraProvider? = null
    private lateinit var qualitySelector: QualitySelector

    private fun initCameraX() {
        qualitySelector = QualitySelector.from(Quality.HD)
        cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(Runnable {
            cameraProvider = cameraProviderFuture.get()
            cameraProvider?.let { bindPreview(it) }
        }, ContextCompat.getMainExecutor(this))

    }

    private lateinit var videoCapture: VideoCapture
    fun bindPreview(cameraProvider: ProcessCameraProvider) {
        var preview: Preview = Preview.Builder()
            .build()
        preview.setSurfaceProvider(pvCameraX.getSurfaceProvider())

        val recorder = Recorder.Builder()
            .setExecutor(ThreadUtil.getThread())
            .setQualitySelector(qualitySelector)
            .build()

        videoCapture = VideoCapture.withOutput(recorder)

        var cameraSelector: CameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()
        try {
            cameraProvider.bindToLifecycle(
              this, cameraSelector,videoCapture,preview)
        } catch (exc: Exception) {
            Log.e("video", "Use case binding failed", exc)
        }
    }

   private var currentRecording:Recording?= null
    private var filepath: String? = null
    fun start() {
        if (filepath == null) {
            filepath = this.getExternalFilesDir(null)?.absolutePath + File.separator +
                    "camerax_video"
        }
        var videoPath = filepath + File.separator+ "video_${Calendar.getInstance().timeInMillis}.mp4"
        val videoFile = File(videoPath)
        if (!videoFile.parentFile.exists()){
            videoFile.parentFile.mkdirs()
        }
        videoFile.createNewFile()
        Log.e("视频地址", videoPath)

        val mediaStoreOutput = FileOutputOptions.Builder(videoFile)
            .setFileSizeLimit(100*1024*1024)
            .build()

        var currentRecording = videoCapture.output
            .prepareRecording(this, mediaStoreOutput)
            .apply {
                if (ActivityCompat.checkSelfPermission(
                        this@VideoCameraXActivity,
                        Manifest.permission.RECORD_AUDIO
                    ) == PackageManager.PERMISSION_GRANTED
                ) {
                    withAudioEnabled()
                }
            }
            .start(ThreadUtil.getThread(), captureListener)

    }
    /**
     * CaptureEvent监听器。
     */
    private val captureListener = Consumer { event ->

        if (event is VideoRecordEvent.Finalize){
            val videoUrl = event.outputResults.outputUri
            Log.e("视频地址 Uri",videoUrl.toString())
        }
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_start -> {
                currentRecording?.stop()
                start()
            }
            R.id.tv_stop -> {
                currentRecording?.stop()
            }
        }
    }


}

五、XML 配置

xml代码省略了 布局配置,xxxlayout布局可以自行配置。

<...................>



................................./>

如果对您有一些意义,希望您给博主一些鼓励(点赞、关注、收藏),如果有错误欢迎大家评论。

你可能感兴趣的:(Android,开发,android,学习,kotlin)