You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何在Android Camera2中手动控制快门?含预览场景操作方法

Android Camera2 手动快门控制全攻略

嘿,我之前帮不少开发者解决过Camera2手动快门的问题,其实核心逻辑就是通过自定义CaptureRequest来设置快门相关参数,替代系统自动模式的默认值。下面一步步给你拆解完整的实现方案:

一、核心原理

Camera2采用基于请求的架构,所有相机操作(预览、拍照)都通过提交CaptureRequest来完成。手动控制快门,本质就是关闭自动曝光(AE)模式,手动指定曝光时间感光度(ISO)这两个核心参数,然后将配置好的请求提交给相机会话。

二、具体实现步骤

1. 搭建相机基础环境

首先得完成Camera2的基础初始化:获取相机管理器、打开相机、创建捕获会话。注意要先申请CAMERA动态权限(Android 6及以上)。

private lateinit var cameraManager: CameraManager
private var cameraDevice: CameraDevice? = null
private var captureSession: CameraCaptureSession? = null
private lateinit var previewTexture: TextureView

// 初始化相机管理器
cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager

// 打开相机(这里假设已经获取了相机ID,比如后置相机)
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
    override fun onOpened(camera: CameraDevice) {
        cameraDevice = camera
        // 相机打开后创建捕获会话
        createCaptureSession()
    }

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

    override fun onError(camera: CameraDevice, error: Int) {
        camera.close()
        cameraDevice = null
        Toast.makeText(this@MainActivity, "相机打开失败", Toast.LENGTH_SHORT).show()
    }
}, Handler(Looper.getMainLooper()))

2. 创建支持预览+拍照的捕获会话

要同时支持预览和手动快门拍照,需要把预览的TextureView.Surface和用于接收拍照数据的ImageReader.Surface都添加到捕获会话中:

private fun createCaptureSession() {
    val previewSurface = previewTexture.surface
    // 创建ImageReader用于接收拍照的JPEG数据
    val imageReader = ImageReader.newInstance(
        previewTexture.width,
        previewTexture.height,
        ImageFormat.JPEG,
        1 // 最多缓存1张图片
    )

    // 设置ImageReader的回调,用于处理拍照后的图片
    imageReader.setOnImageAvailableListener({ reader ->
        val image = reader.acquireLatestImage()
        // 这里可以处理图片,比如保存到本地
        saveImageToLocal(image)
        image.close()
    }, Handler(Looper.getMainLooper()))

    // 收集所有需要的Surface
    val surfaces = listOf(previewSurface, imageReader.surface)

    cameraDevice?.createCaptureSession(surfaces, object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(session: CameraCaptureSession) {
            captureSession = session
            // 会话配置完成后启动预览
            startManualPreview()
        }

        override fun onConfigureFailed(session: CameraCaptureSession) {
            Toast.makeText(this@MainActivity, "相机会话配置失败", Toast.LENGTH_SHORT).show()
        }
    }, Handler(Looper.getMainLooper()))
}

3. 配置手动参数启动预览

如果希望预览阶段就使用手动快门参数(而不是自动曝光),可以在创建预览请求时关闭自动曝光,手动设置曝光时间和ISO:

private fun startManualPreview() {
    val previewRequestBuilder = cameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) ?: return
    previewRequestBuilder.addTarget(previewTexture.surface)

    // 获取相机支持的参数范围(必须,避免设置超出硬件支持的参数)
    val characteristics = cameraManager.getCameraCharacteristics(cameraId)
    val exposureRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE)!!
    val isoRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE)!!

    // 设置手动参数:这里用中间值举例,你可以根据需求调整
    val targetExposureTime = (exposureRange.lower + exposureRange.upper) / 2 // 纳秒为单位
    val targetIso = (isoRange.lower + isoRange.upper) / 2

    // 关闭自动曝光模式
    previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)
    // 设置曝光时间
    previewRequestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, targetExposureTime)
    // 设置ISO
    previewRequestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, targetIso)

    // 启动重复预览请求
    captureSession?.setRepeatingRequest(
        previewRequestBuilder.build(),
        null, // 预览回调可选,不需要可以传null
        Handler(Looper.getMainLooper())
    )
}

4. 触发手动快门拍照

当需要拍照时,创建一个静态捕获请求,同样设置手动参数,然后提交单次捕获请求:

fun takeManualPhoto() {
    val captureRequestBuilder = cameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) ?: return
    val imageReader = // 这里引用之前创建的ImageReader实例
    captureRequestBuilder.addTarget(imageReader.surface)

    // 同样获取相机支持的参数范围
    val characteristics = cameraManager.getCameraCharacteristics(cameraId)
    val exposureRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE)!!
    val isoRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE)!!

    // 设置拍照的手动参数(可以和预览一致,也可以单独调整)
    val captureExposureTime = exposureRange.upper / 4 // 比预览短一点,避免过曝
    val captureIso = isoRange.lower // 低ISO减少噪点

    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)
    captureRequestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, captureExposureTime)
    captureRequestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, captureIso)

    // 可选:设置自动对焦模式,保证拍照清晰
    captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)

    // 触发单次捕获
    captureSession?.capture(
        captureRequestBuilder.build(),
        object : CameraCaptureSession.CaptureCallback() {
            override fun onCaptureCompleted(
                session: CameraCaptureSession,
                request: CaptureRequest,
                result: TotalCaptureResult
            ) {
                super.onCaptureCompleted(session, request, result)
                Toast.makeText(this@MainActivity, "拍照完成", Toast.LENGTH_SHORT).show()
            }
        },
        Handler(Looper.getMainLooper())
    )
}

三、关键注意事项

  • 参数合法性:不同设备的相机硬件支持的曝光时间和ISO范围差异很大,一定要通过CameraCharacteristics获取允许的范围,否则设置无效甚至导致相机崩溃。
  • 权限问题:除了CAMERA权限,Android 10及以上保存图片时建议使用MediaStore,不需要额外的存储权限;Android 9及以下需要WRITE_EXTERNAL_STORAGE权限。
  • 线程处理:Camera2的回调默认运行在指定的Handler线程,建议使用后台线程处理图片保存等耗时操作,避免阻塞UI线程。
  • 预览与拍照同步:如果希望拍照参数和预览完全一致,可以直接复用预览的CaptureRequest.Builder,只修改目标Surface为ImageReader的Surface即可。

四、常见问题排查

  • 预览黑屏/异常:检查是否关闭了自动曝光模式,或者参数超出了相机支持的范围。
  • 图片过暗/过亮:调整曝光时间和ISO的组合——曝光时间越长画面越亮,ISO越高画面越亮但噪点越多,根据场景平衡两者。
  • 拍照不清晰:确保设置了合适的自动对焦模式,或者在拍照前触发一次对焦锁定。

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

火山引擎 最新活动