You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中集成Camera2 API调节ISO、快门速度与AWB

在Jetpack Compose中集成Camera2 API实现手动相机参数调节

1. Compose环境下初始化Camera2 API

Camera2的核心逻辑仍依赖Android原生组件(如CameraManager、CameraDevice),Compose仅负责UI层。建议将相机初始化、管理逻辑封装在ViewModel中,避免在Composable中处理复杂生命周期操作。

核心实现步骤

  • 在ViewModel中获取CameraManager实例,遍历并选择目标相机(优先后置)
  • 处理相机权限请求(Android 13+需额外申请POST_NOTIFICATIONS)
  • 打开相机设备,创建CameraCaptureSession并绑定预览Surface

ViewModel核心代码

class CameraViewModel : ViewModel() {
    private lateinit var cameraManager: CameraManager
    private var cameraDevice: CameraDevice? = null
    private var captureSession: CameraCaptureSession? = null
    private var previewRequestBuilder: CaptureRequest.Builder? = null
    private var previewSurface: Surface? = null

    fun initCameraManager(context: Context) {
        cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
    }

    fun openCamera(cameraId: String) {
        cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
            override fun onOpened(device: CameraDevice) {
                cameraDevice = device
                startPreview()
            }

            override fun onDisconnected(device: CameraDevice) {
                device.close()
                cameraDevice = null
            }

            override fun onError(device: CameraDevice, error: Int) {
                device.close()
                cameraDevice = null
            }
        }, Handler(Looper.getMainLooper()))
    }

    fun setPreviewSurface(surface: Surface) {
        previewSurface = surface
        cameraDevice?.let { startPreview() }
    }

    private fun startPreview() {
        val device = cameraDevice ?: return
        val surface = previewSurface ?: return

        try {
            previewRequestBuilder = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            previewRequestBuilder?.addTarget(surface)

            device.createCaptureSession(
                listOf(surface),
                object : CameraCaptureSession.StateCallback() {
                    override fun onConfigured(session: CameraCaptureSession) {
                        captureSession = session
                        updatePreview()
                    }

                    override fun onConfigureFailed(session: CameraCaptureSession) {
                        // 处理配置失败逻辑
                    }
                },
                Handler(Looper.getMainLooper())
            )
        } catch (e: CameraAccessException) {
            e.printStackTrace()
        }
    }

    private fun updatePreview() {
        val session = captureSession ?: return
        val request = previewRequestBuilder?.build() ?: return
        session.setRepeatingRequest(request, null, Handler(Looper.getMainLooper()))
    }

    override fun onCleared() {
        super.onCleared()
        captureSession?.close()
        cameraDevice?.close()
    }
}

Compose中绑定预览Surface

通过AndroidView桥接TextureView,为Camera2提供预览载体:

@Composable
fun CameraPreview(viewModel: CameraViewModel) {
    AndroidView(
        factory = { context ->
            TextureView(context).apply {
                surfaceTextureListener = object : TextureView.SurfaceTextureListener {
                    override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
                        viewModel.setPreviewSurface(Surface(surface))
                    }

                    override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
                    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
                        viewModel.setPreviewSurface(Surface(surface))
                        return true
                    }

                    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
                }
            }
        },
        modifier = Modifier.fillMaxSize()
    )
}

2. 编程方式调节ISO、快门速度与AWB

通过修改CaptureRequest.Builder的参数实现调节,每次修改后更新预览请求:

核心参数与调节方法(ViewModel中添加)

// 调节ISO(需在相机支持范围内)
fun setIso(iso: Int) {
    previewRequestBuilder?.set(CaptureRequest.SENSOR_SENSITIVITY, iso)
    updatePreview()
}

// 调节快门速度(单位:纳秒)
fun setExposureTime(exposureTime: Long) {
    previewRequestBuilder?.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposureTime)
    updatePreview()
}

// 锁定手动AWB
fun lockAwb() {
    previewRequestBuilder?.apply {
        set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF)
        set(CaptureRequest.CONTROL_AWB_LOCK, true)
    }
    updatePreview()
}

// 恢复自动AWB
fun unlockAwb() {
    previewRequestBuilder?.apply {
        set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO)
        set(CaptureRequest.CONTROL_AWB_LOCK, false)
    }
    updatePreview()
}

// 获取相机支持的参数范围
fun getCameraCharacteristics(cameraId: String): CameraCharacteristics? {
    return try {
        cameraManager.getCameraCharacteristics(cameraId)
    } catch (e: CameraAccessException) {
        null
    }
}

参数范围说明

  • ISO范围:从CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE获取
  • 快门速度范围:从CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE获取
  • AWB手动调节:需先关闭自动模式,再通过CONTROL_AWB_GAINS调节色温或直接锁定当前值

3. 参数调节的Compose UI最佳实践

布局原则

  • 全屏预览作为背景,参数控件置于底部/侧边
  • 使用LaunchedEffect处理权限请求与相机初始化
  • 通过rememberViewModel确保ViewModel生命周期与Composable一致

示例UI代码

@Composable
fun CameraControlScreen() {
    val viewModel: CameraViewModel = viewModel()
    val context = LocalContext.current
    val permissionState = rememberPermissionState(android.Manifest.permission.CAMERA)

    LaunchedEffect(Unit) {
        viewModel.initCameraManager(context)
        if (!permissionState.hasPermission) {
            permissionState.launchPermissionRequest()
        } else {
            // 获取后置摄像头ID
            val cameraId = cameraManager.cameraIdList.firstOrNull { id ->
                val characteristics = cameraManager.getCameraCharacteristics(id)
                characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK
            }
            cameraId?.let { viewModel.openCamera(it) }
        }
    }

    Box(modifier = Modifier.fillMaxSize()) {
        CameraPreview(viewModel = viewModel)

        // 参数调节面板
        Column(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .fillMaxWidth()
                .background(Color.Black.copy(alpha = 0.7f))
                .padding(16.dp)
        ) {
            // ISO调节滑块
            val cameraId = cameraManager.cameraIdList.firstOrNull() ?: ""
            val isoRange = viewModel.getCameraCharacteristics(cameraId)
                ?.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) ?: IntRange(100, 1600)
            var currentIso by remember { mutableStateOf(isoRange.first) }

            Text(text = "ISO: $currentIso", color = Color.White)
            Slider(
                value = currentIso.toFloat(),
                onValueChange = {
                    currentIso = it.toInt()
                    viewModel.setIso(currentIso)
                },
                valueRange = isoRange.first.toFloat()..isoRange.last.toFloat(),
                colors = SliderDefaults.colors(thumbColor = Color.White, activeTrackColor = Color.White)
            )

            // 快门速度调节滑块
            val exposureRange = viewModel.getCameraCharacteristics(cameraId)
                ?.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE) ?: LongRange(100000, 10000000)
            var currentExposure by remember { mutableStateOf(exposureRange.first) }
            val exposureMs = (currentExposure / 1000000).toFloat()

            Text(text = "快门速度: ${String.format("%.1f", exposureMs)}ms", color = Color.White)
            Slider(
                value = exposureMs,
                onValueChange = {
                    currentExposure = (it * 1000000).toLong()
                    viewModel.setExposureTime(currentExposure)
                },
                valueRange = (exposureRange.first / 1000000).toFloat()..(exposureRange.last / 1000000).toFloat(),
                colors = SliderDefaults.colors(thumbColor = Color.White, activeTrackColor = Color.White)
            )

            // AWB控制按钮
            Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
                Button(
                    onClick = { viewModel.lockAwb() },
                    colors = ButtonDefaults.buttonColors(backgroundColor = Color.White)
                ) {
                    Text(text = "锁定AWB")
                }
                Button(
                    onClick = { viewModel.unlockAwb() },
                    colors = ButtonDefaults.buttonColors(backgroundColor = Color.White)
                ) {
                    Text(text = "自动AWB")
                }
            }
        }
    }
}

关键注意事项

  • 权限校验:必须在获取相机权限后再初始化相机
  • 参数合法性:所有手动调节参数需落在相机支持范围内,避免抛出异常
  • 线程安全:Camera2操作需在主线程或指定Handler线程执行,示例中使用主线程回调
  • 内存泄漏:ViewModel销毁时必须关闭相机设备与捕获会话

内容的提问来源于stack exchange,提问作者Akshar Kalathiya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 20:27:04