如何在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




