CameraX使用

之前写一个Camera2的使用,今天继续介绍视频采集的另一种方式使用CameraX采集。

CameraX 是什么?

CameraX 是一个用于开发 Android 摄像头应用程序的 Jetpack 组件库。它提供了一组简单易用的 API,用于实现摄像头功能,包括预览、拍照和录制视频等操作。通过使用 CameraX,开发者可以更轻松地访问和控制设备的摄像头,并实现各种自定义的相机特性。

CameraX 可以帮助开发者解决传统 Camera API 使用上的复杂性和兼容性问题。它提供了一种一致和简化的方式来管理摄像头和图像处理流程。CameraX 还支持多种设备和系统版本,帮助开发者更好地适配不同的 Android 设备。

CameraX 提供了一套强大的功能,如自动对焦、曝光控制、图像分析和图像捕获等。开发者可以根据自己的需求选择使用这些功能,并根据需要进行自定义扩展。CameraX 还提供了一种类似于生命周期的方式来管理相机资源,确保在应用程序的不同生命周期阶段正确地打开和释放相机。

总而言之,CameraX 为开发者提供了简洁、灵活和易用的方式来实现 Android 摄像头应用程序,帮助他们更高效地开发出功能丰富、稳定可靠的相机应用。

CameraX的优点有哪些?

  1. 简化的 API:CameraX 提供了一组简洁、易于使用的 API,使开发者能够更轻松地实现摄像头功能。相比传统的 Camera API,CameraX 的 API 更加直观和统一,减少了开发中的复杂性和学习曲线。

  2. 兼容性:CameraX 支持各种设备和系统版本。它提供了对多个 Android 设备的适配,包括前置摄像头、后置摄像头、多摄像头设备等,使开发者能够在不同的设备上稳定运行应用程序。

  3. 生命周期感知:CameraX 采用了一种类似于生命周期的方式来管理相机资源。它与 Android 生命周期组件(如 Activity 和 Fragment)无缝集成,自动处理相机的打开和释放,确保在应用程序的不同生命周期阶段正确地管理相机资源。

  4. 高级功能支持:CameraX 提供了许多高级功能,如自动对焦、曝光控制、图像分析和图像捕获等。开发者可以根据需求选择使用这些功能,并进行自定义扩展,以满足特定的应用需求。

  5. 强大的图像分析:CameraX 提供了用于图像分析的 API,使开发者能够实现实时的图像处理和计算功能。这为开发人员提供了许多机会,可以在相机应用中实现各种智能功能,如人脸检测、物体识别等。

  6. 社区支持和文档丰富:CameraX 是由 Google 推出的 Jetpack 组件库之一,拥有庞大的开发者社区和丰富的文档资源。开发者可以轻松地找到示例代码、教程和解决方案,以帮助他们更好地使用和理解 CameraX。

总体而言,CameraX 的优点在于其简化的 API、兼容性、生命周期感知、高级功能支持、强大的图像分析能力以及社区支持和文档丰富,使开发者能够更快速、高效地开发出功能丰富、稳定可靠的 Android 摄像头应用程序。

CameraX如何使用?

要使用 CameraX,需要按照以下步骤进行设置和编码:

  1. 添加依赖项:在您的项目中的 build.gradle 文件中添加以下依赖项:
groovy 复制代码
// CameraX core library
def camerax_version = '1.1.0-beta01'
implementation "androidx.camera:camera-core:$camerax_version"

// CameraX Camera2 extensions
implementation "androidx.camera:camera-camera2:$camerax_version"

// CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:$camerax_version"

// CameraX View class
implementation "androidx.camera:camera-view:$camerax_version"
  1. 设置权限:在您的 AndroidManifest.xml 文件中,确保添加了适当的摄像头权限,例如:
xml 复制代码
<uses-permission android:name="android.permission.CAMERA" />
  1. 创建预览布局:在您的布局文件中,创建一个用于显示相机预览的视图,例如:
xml 复制代码
<androidx.camera.view.PreviewView
    android:id="@+id/viewFinder"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
  1. 设置相机用例:在Activity 或 Fragment 中,创建一个相机用例,并将其与 PreviewView 绑定起来,例如:
kotlin 复制代码
typealias LumaListener = (luma: Double) -> Unit
class CameraXTakePhotoActivity : AppCompatActivity() {
    lateinit var viewFinder: PreviewView

    lateinit var cameraCaptureButton: Button
    lateinit var cameraSwitchButton: Button
    private val TAG = "CameraXTakePhotoActivity---"

    private var displayId: Int = -1
    private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
    private var preview: Preview? = null
    private var imageCapture: ImageCapture? = null
    private var imageAnalyzer: ImageAnalysis? = null
    private var camera: Camera? = null
    private var cameraProvider: ProcessCameraProvider? = null
    private lateinit var windowManager: WindowManager
    private lateinit var cameraExecutor: ExecutorService

    private val displayManager by lazy {
        application.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera_xtake_photo)
        viewFinder = findViewById(R.id.viewFinder)
        cameraCaptureButton = findViewById(R.id.cameraCaptureButton)
        cameraSwitchButton = findViewById(R.id.cameraSwitchButton)
        // 检查相机权限
        if (ContextCompat.checkSelfPermission(
                this, Manifest.permission.CAMERA
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            // 请求相机权限
            ActivityCompat.requestPermissions(
                this, arrayOf(Manifest.permission.CAMERA), 1005
            )
        } else {
            // 初始化线程池
            cameraExecutor = Executors.newSingleThreadExecutor()
            // 初始化WindowManager以检索显示指标
            windowManager = WindowManager(this)
            updateCameraUi()
            lifecycleScope.launch {
                setUpCamera()
            }
        }
    }

    /** 用于重新绘制相机UI控件,每次配置更改时调用。 */
    private fun updateCameraUi() {

    }

    /** 初始化CameraX,并准备绑定相机用例  */
    @SuppressLint("NewApi")
    private suspend fun setUpCamera() {
        cameraProvider = ProcessCameraProvider.getInstance(this).await()

        lensFacing = when {
            hasBackCamera() -> CameraSelector.LENS_FACING_BACK
            hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
            else -> throw IllegalStateException("Back and front camera are unavailable")
        }

        // 启用或禁用摄像头之间的切换
        updateCameraSwitchButton()

        // 构建并绑定相机用例
        bindCameraUseCases()

    }

    /**
     * 绑定Camera
     */
    @RequiresApi(Build.VERSION_CODES.R)
    private fun bindCameraUseCases() {
// 获取用于设置相机全屏分辨率的屏幕指标
        val metrics = windowManager.getCurrentWindowMetrics().bounds
        Log.d(TAG, "Screen metrics: ${metrics.width()} x ${metrics.height()}")
        // 计算宽高比
        val screenAspectRatio = aspectRatio(metrics.width(), metrics.height())
        Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")

        //获取 PreviewView方向
        val rotation = viewFinder.display.rotation

        // CameraProvider
        val cameraProvider = cameraProvider
            ?: throw IllegalStateException("Camera initialization failed.")

        // CameraSelector
        val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()

        // Preview
        preview = Preview.Builder()
            // 设置宽高比
            .setTargetAspectRatio(screenAspectRatio)
            // 设置方向
            .setTargetRotation(rotation)
            .build()

        //  ImageCapture.Metadata 设置输出文件名和其他相关参数
        val metadata = ImageCapture.Metadata().apply {
            // 使用前置摄像头时,处理镜像
            isReversedHorizontal =
                lensFacing == CameraSelector.LENS_FACING_FRONT
        }
        // imageCapture 用于拍摄照片
        imageCapture = ImageCapture.Builder()
            // CAPTURE_MODE_MINIMIZE_LATENCY:捕获模式为最小化延迟模式,适用于快速响应和低延迟的拍摄场景。
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            // 设定宽高比
            .setTargetAspectRatio(screenAspectRatio)
            //设置方向
            .setTargetRotation(rotation)
//            .setMetadata(metadata)
            .build()

        // ImageAnalysis:分析相机捕获的实时预览图像数据
        imageAnalyzer = ImageAnalysis.Builder()
            // 设定宽高比
            .setTargetAspectRatio(screenAspectRatio)
            // 设置方向
            .setTargetRotation(rotation)
            .build()
            // 将分析器分配给实例
            .also {
                // 在别的线程做分析 需要传入一个线程对象,和一个分析对象
                it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
                    Log.d(TAG, "Average luminosity: $luma")
                })
            }
        // 在重新绑定用例之前必须解除绑定
        cameraProvider.unbindAll()
        if (camera != null) {
            // 必须从之前的相机实例中移除观察者
            removeCameraStateObservers(camera!!.cameraInfo)
        }
        try {
            // 通过 cameraProvider 绑定生命周期得到Camera对象
            camera = cameraProvider.bindToLifecycle(
                this, cameraSelector, preview, imageCapture, imageAnalyzer)

            // 设置 surfaceProvider,这样数据就会源源不断流到 viewFinder(PreviewView)控件上了,这样就完成了预览了
            preview?.setSurfaceProvider(viewFinder.surfaceProvider)
            // 观察相机状态
            observeCameraState(camera?.cameraInfo!!)
        } catch (exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }
    }

    private fun aspectRatio(width: Int, height: Int): Int {
        val previewRatio = max(width, height).toDouble() / min(width, height)
        if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
            return AspectRatio.RATIO_4_3
        }
        return AspectRatio.RATIO_16_9
    }

    /**
     * 切换摄像头button
     */
    private fun updateCameraSwitchButton() {
        try {
            cameraSwitchButton.isEnabled = hasBackCamera() && hasFrontCamera()
        } catch (exception: CameraInfoUnavailableException) {
            cameraSwitchButton.isEnabled = false
        }
    }

    private fun hasBackCamera(): Boolean {
        return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
    }

    private fun hasFrontCamera(): Boolean {
        return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
    }

    private fun removeCameraStateObservers(cameraInfo: CameraInfo) {
        cameraInfo.cameraState.removeObservers(this)
    }

    private fun observeCameraState(cameraInfo: CameraInfo) {
        cameraInfo.cameraState.observe(this) { cameraState ->
            run {
                when (cameraState.type) {
                    // 即将打开
                    CameraState.Type.PENDING_OPEN -> {
                        Log.i(TAG,"CameraState: Pending Open")
                    }
                    // 正在打开 ,展示相机UI
                    CameraState.Type.OPENING -> {
                        // Show the Camera UI
                        Log.i(TAG,"CameraState: Opening")
                    }
                    //打开完成,设置相机资源并开始处理
                    CameraState.Type.OPEN -> {
                        Log.i(TAG,"CameraState: Open")
                    }
                    // 正在关闭,关闭CameraUI
                    CameraState.Type.CLOSING -> {
                        // Close camera UI
                        Log.i(TAG,"CameraState: Closing")

                    }
                    // 关闭完成,释放相机资源
                    CameraState.Type.CLOSED -> {

                          Log.i(TAG, "CameraState: Closed")
                    }
                }
            }

            cameraState.error?.let { error ->
                when (error.code) {
                    // 打开出错,确保正确地设置了用例
                    CameraState.ERROR_STREAM_CONFIG -> {
                        // Make sure to setup the use cases properly
                          Log.i(TAG, "Stream config error")
                    }
                    //相机正在使用
                    CameraState.ERROR_CAMERA_IN_USE -> {
                        // 关闭摄像头或要求用户关闭另一个正在使用的摄像头
                          Log.i(TAG, "Camera in use")
                    }
                    // 正在使用的最大摄像机
                    CameraState.ERROR_MAX_CAMERAS_IN_USE -> {
                        //关闭应用中另一个打开的摄像头,或者要求用户关闭正在使用该摄像头的另一个摄像头应用
                          Log.i(TAG, "Max cameras in use")
                    }
                    CameraState.ERROR_OTHER_RECOVERABLE_ERROR -> {
                          Log.i(TAG, "Other recoverable error")
                    }
                    // 关闭错误
                    CameraState.ERROR_CAMERA_DISABLED -> {
                        // 要求用户开启设备的摄像头
                          Log.i(TAG, "Camera disabled")
                    }
                    CameraState.ERROR_CAMERA_FATAL_ERROR -> {
                        // 要求用户重新启动设备以恢复相机功能
                          Log.i(TAG, "Fatal error")
                    }
                    // Closed errors
                    CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED -> {
                        // 请用户禁用"请勿打扰"模式,然后重新打开相机
                          Log.i(TAG, "Do not disturb mode enabled")
                    }
                }
            }
        }
    }


    private class LuminosityAnalyzer(listener: LumaListener? = null) : ImageAnalysis.Analyzer {
        private val frameRateWindow = 8
        private val frameTimestamps = ArrayDeque<Long>(5)
        private val listeners = ArrayList<LumaListener>().apply { listener?.let { add(it) } }
        private var lastAnalyzedTimestamp = 0L
        var framesPerSecond: Double = -1.0
            private set

        /**
         * Helper extension function used to extract a byte array from an image plane buffer
         */
        private fun ByteBuffer.toByteArray(): ByteArray {
            rewind()    // Rewind the buffer to zero
            val data = ByteArray(remaining())
            get(data)   // Copy the buffer into a byte array
            return data // Return the byte array
        }

        /**
         * Analyzes an image to produce a result.
         *
         * <p>The caller is responsible for ensuring this analysis method can be executed quickly
         * enough to prevent stalls in the image acquisition pipeline. Otherwise, newly available
         * images will not be acquired and analyzed.
         *
         * <p>The image passed to this method becomes invalid after this method returns. The caller
         * should not store external references to this image, as these references will become
         * invalid.
         *
         * @param image image being analyzed VERY IMPORTANT: Analyzer method implementation must
         * call image.close() on received images when finished using them. Otherwise, new images
         * may not be received or the camera may stall, depending on back pressure setting.
         *
         */
        override fun analyze(image: ImageProxy) {
            // If there are no listeners attached, we don't need to perform analysis
            if (listeners.isEmpty()) {
                image.close()
                return
            }

            // Keep track of frames analyzed
            val currentTime = System.currentTimeMillis()
            frameTimestamps.push(currentTime)

            // Compute the FPS using a moving average
            while (frameTimestamps.size >= frameRateWindow) frameTimestamps.removeLast()
            val timestampFirst = frameTimestamps.peekFirst() ?: currentTime
            val timestampLast = frameTimestamps.peekLast() ?: currentTime
            framesPerSecond = 1.0 / ((timestampFirst - timestampLast) /
                    frameTimestamps.size.coerceAtLeast(1).toDouble()) * 1000.0

            // Analysis could take an arbitrarily long amount of time
            // Since we are running in a different thread, it won't stall other use cases

            lastAnalyzedTimestamp = frameTimestamps.first

            // Since format in ImageAnalysis is YUV, image.planes[0] contains the luminance plane
            val buffer = image.planes[0].buffer

            // Extract image data from callback object
            val data = buffer.toByteArray()

            // Convert the data into an array of pixel values ranging 0-255
            val pixels = data.map { it.toInt() and 0xFF }

            // Compute average luminance for the image
            val luma = pixels.average()

            // Call all listeners with new value
            listeners.forEach { it(luma) }

            image.close()
        }
    }


    companion object {
        private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val PHOTO_TYPE = "image/jpeg"
        private const val RATIO_4_3_VALUE = 4.0 / 3.0
        private const val RATIO_16_9_VALUE = 16.0 / 9.0
    }
}

通过 cameraProvider 绑定生命周期得到Camera对象,设置 surfaceProvider,这样数据就会源源不断流到 viewFinder(PreviewView)控件上了,这样就完成了预览了。

拍照的实现

通过上面的实现,完成了预览功能,接下的拍照主要是通过 ImageCapture 来完成。

kotlin 复制代码
cameraCaptureButton.setOnClickListener {
    // ImageCapture 是用于拍摄照片的用例之一。它提供了一个简单、易于使用的 API,用于控制相机硬件以进行图像捕获操作。
    imageCapture?.let { imageCapture ->

        // 创建带有时间戳的名称和MediaStore
        val name = SimpleDateFormat(FILENAME, Locale.US)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, PHOTO_TYPE)
            if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                val appName = resources.getString(R.string.app_name)
                put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/${appName}")
            }
        }

        // Metadata:图像捕获配置和控制的属性
        val metadata = ImageCapture.Metadata().apply {
            // 使用到前摄像头 镜像值为true
            isReversedHorizontal =
                lensFacing == CameraSelector.LENS_FACING_FRONT
        }
        val outputOptions = ImageCapture.OutputFileOptions
            .Builder(this.contentResolver,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                contentValues)
            .setMetadata(metadata)
            .build()

        // imageCapture.takePicture() 方法是用于拍摄照片的方法。使用此方法,您可以执行图像捕获操作,并将结果传递给指定的 ImageCapture.OnImageSavedCallback。
        imageCapture.takePicture(
            outputOptions, cameraExecutor, 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 = output.savedUri
                    Log.d(TAG, "Photo capture succeeded: $savedUri")

                    // Android N 以下的发送一个广播更新相册
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                        @Suppress("DEPRECATION")
                        sendBroadcast(
                            //发送广播更新相册
                            Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri)
                        )
                    }
                }
            })


    }
}

这样就完成了拍照。

旋转摄像头

更换配置后,要重新初始化。

scss 复制代码
cameraSwitchButton.setOnClickListener {
    lensFacing = if (CameraSelector.LENS_FACING_FRONT == lensFacing) {
        CameraSelector.LENS_FACING_BACK
    } else {
        CameraSelector.LENS_FACING_FRONT
    }
    // 更新配置后,重新初始化
    bindCameraUseCases()
}

使用到的一些API说明:

ProcessCameraProvider

  1. 生命周期管理:ProcessCameraProvider 提供了与生命周期相关的方法,可以在适当的生命周期阶段,例如 onCreate()、onStart() 或 onDestroy() 中创建、启动或释放相机资源。这确保了相机资源的正确管理和释放,避免了内存泄漏和资源浪费。

  2. 相机绑定:ProcessCameraProvider 可以将相机绑定到指定的预览视图上,从而实现实时预览。可以通过调用 bindToLifecycle() 方法将相机绑定到指定的生命周期所有者(LifecycleOwner)上,并指定预览视图(如 TextureView、SurfaceView 等),从而在界面上显示相机预览。

  3. 相机配置:ProcessCameraProvider 提供了一组配置方法,用于设置相机的参数和选项。例如,可以指定要使用的摄像头(前置或后置)、预览分辨率、图像分辨率、闪光灯模式、对焦模式等。这使得您可以根据应用需求自定义相机的行为和功能。

  4. 组合多个用例:ProcessCameraProvider 支持多个用例的组合。可以同时使用预览、图像捕获、图像分析等用例,并将它们一起配置和绑定到相机提供者上。这样,您可以在一个应用程序中实现多种相机功能,并灵活地进行配置。

ImageCapture

ImageCapture 是 Android Jetpack CameraX 中的一个用例,用于拍摄照片。它提供了一个简单、易于使用的 API,用于控制相机硬件并执行图像捕获操作。

ImageCapture 用例的主要功能如下:

  1. 拍摄照片:使用 ImageCapture 可以轻松地拍摄照片。可以调用 takePicture() 方法来触发照片捕获操作,并指定保存图像的文件和其他参数。

  2. 配置捕获设置:ImageCapture 提供了一系列可配置的设置,例如图片格式、闪光灯模式、焦点模式等。可以根据需求来自定义这些设置,以满足特定的应用场景。

  3. 监听拍摄结果:ImageCapture 提供了一个回调接口,通过实现 ImageCapture.OnImageSavedCallback 接口,可以处理图像保存成功或失败时的结果。这样,可以根据需要对保存的图像进行进一步处理或处理可能发生的错误。

  4. 集成图像分析:ImageCapture 可以与图像分析用例结合使用,实现更复杂的图像处理和分析功能。例如,可以在拍摄照片后,使用图像分析来检测人脸或识别物体等。

ImageAnalysis

ImageAnalysis 是 Android Jetpack CameraX 库中的一个用例,用于实时分析和处理相机捕获的图像数据。它提供了一种方便的方式来执行实时图像处理、计算和分析的操作。

ImageAnalysis 的功能包括:

  1. 实时图像分析:ImageAnalysis 允许对相机捕获的实时预览图像数据进行实时分析。可以使用图像分析算法来处理和提取图像中的特征、对象或信息。

  2. 分析器回调:通过设置分析器(Analyzer),可以定义自己的图像分析逻辑,并在每次有新的图像可用时接收回调。在回调中,可以获取图像数据,并对其进行处理、计算和分析。这使得可以实时地获取和处理相机捕获的图像,并根据需要执行相应的操作。

  3. 图像处理和计算:ImageAnalysis 提供了一个用于处理和计算图像数据的接口。可以使用 OpenCV、TensorFlow 等库进行图像处理、计算和机器学习操作。例如,可以执行人脸检测、图像识别、条码扫描等操作,以实现各种应用场景。

  4. 背压策略和图像队列:ImageAnalysis 还提供了背压策略和图像队列的支持。背压策略定义了当图像处理速度慢于图像生成速度时的处理方式,而图像队列用于存储等待分析的图像数据。可以根据实际需求配置背压策略和图像队列深度,以平衡性能和系统资源的使用。

CameraState.Type

在 Android 相机开发中,CameraState.Type 是一个枚举类,表示相机的状态类型。以下是 CameraState.Type 的一些常见类型及其含义:

  1. OPENED:表示相机已经被打开并且处于活动状态。可以开始预览、拍照或录制视频。

  2. CLOSED:表示相机已经关闭。不再接收预览请求或拍照请求。

  3. UNINITIALIZED:表示相机未初始化。相机可能还没有被打开,或者在打开之前出现错误。

  4. SURFACE_READY:表示相机使用的输出 Surface 已经准备就绪,可以开始渲染预览画面。

  5. PREVIEW_STARTED:表示预览已经开始。相机正在输出实时预览画面。

  6. RECORDING_STARTED:表示录制视频已经开始。相机正在输出视频帧用于录制。

ImageCapture.Metadata

ImageCapture.Metadata 是用于捕获图像时附加元数据的类。它是 CameraX 相机库中的一部分。

ImageCapture.Metadata 类提供了一些可用于图像捕获配置和控制的属性,例如闪光灯模式、曝光补偿、对焦模式、白平衡等。可以通过创建一个 ImageCapture.Metadata 对象,并通过设置相应的属性来自定义图像捕获的行为。

以下是 ImageCapture.Metadata 类的一些常见属性:

  1. FlashMode:闪光灯模式,指定在图像捕获期间是否使用闪光灯。可选值有自动、关闭、打开等。

  2. ExposureCompensation:曝光补偿值,用于调整图像的亮度。可以通过设置一个浮点数值来进行曝光补偿。

  3. FocusMode:对焦模式,用于控制相机的对焦方式。可选值有自动对焦、连续对焦、固定对焦等。

  4. WhiteBalance:白平衡模式,用于校正图像中的色温。可选值包括自动、日光、阴影、白炽灯等。

除了上述属性外,还可以使用 ImageCapture.Metadata 类来设置其他一些属性,例如图像质量、图像方向等。

总结

  • 简单的介绍了 CameraX,和CameraX的优点;
  • 介绍了CameraX如何实现预览、拍照和转换摄像头功能;
  • 最后介绍了一些关键类,通过了解这些类后更好的实现一些功能的开发。
  • CameraX 的录制和视频编码放在下一篇文章。
相关推荐
码流怪侠19 小时前
3D视频技术全解析:从原理架构到产业应用的深度探索
unity3d·音视频开发
码流怪侠1 天前
视频HDR技术全解析:从原理到应用的深度探索
音视频开发·视频编码
keji16885 天前
视频压缩太慢了?这6款工具帮你开启高速世界的大门
音视频开发
keji16885 天前
视频压缩不得劲?那是因为你没遇见这6款满级神器!
音视频开发
keji16886 天前
这6个视频压缩免费工具,我真的哭死,含泪安利你使用!
音视频开发
keji16886 天前
5个效果超好的视频压缩工具,真的是绝绝子!
音视频开发
keji16887 天前
免费视频压缩软件在哪里?这6款宝贝软件真的值得你使用!
音视频开发
字节跳动视频云技术团队10 天前
ICME 2025 | 火山引擎在国际音频编码能力挑战赛中夺得冠军
llm·aigc·音视频开发