fix: Fix Precapture timed out after 5 seconds error (#2586)

* fix: Fix precapture timeout error on capture

* fix: Catch timeout errors

* Update PersistentCameraCaptureSession.kt

* Update PersistentCameraCaptureSession.kt

* fix: Remove unsupported AE/AF/AWB triggers

* fix: Only enable flash if it is really AUTO

* Update CameraCaptureSession+precapture.kt

* Update CameraCaptureSession+setRepeatingRequestAndWaitForPrecapture.kt

* Update PersistentCameraCaptureSession.kt
This commit is contained in:
Marc Rousavy 2024-02-20 18:35:55 +01:00 committed by GitHub
parent fabf019f66
commit 369cb4a043
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 33 deletions

View File

@ -42,6 +42,7 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
companion object { companion object {
private const val TAG = "PersistentCameraCaptureSession" private const val TAG = "PersistentCameraCaptureSession"
private const val FOCUS_RESET_TIMEOUT = 3000L private const val FOCUS_RESET_TIMEOUT = 3000L
private const val PRECAPTURE_LOCK_TIMEOUT = 5000L
} }
// Inputs/Dependencies // Inputs/Dependencies
@ -178,21 +179,30 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
Log.i(TAG, "Locking AF/AE/AWB...") Log.i(TAG, "Locking AF/AE/AWB...")
// 1. Run precapture sequence // 1. Run precapture sequence
val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs) var needsFlash: Boolean
val skipIfPassivelyFocused = flash == Flash.OFF try {
val options = val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs)
PrecaptureOptions( val skipIfPassivelyFocused = flash == Flash.OFF
val options = PrecaptureOptions(
listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE, PrecaptureTrigger.AWB), listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE, PrecaptureTrigger.AWB),
flash, flash,
emptyList(), emptyList(),
skipIfPassivelyFocused skipIfPassivelyFocused,
PRECAPTURE_LOCK_TIMEOUT
) )
val result = session.precapture(precaptureRequest, deviceDetails, options) val result = session.precapture(precaptureRequest, deviceDetails, options)
needsFlash = result.needsFlash
} catch (e: CaptureTimedOutError) {
// the precapture just timed out after 5 seconds, take picture anyways without focus.
needsFlash = false
} catch (e: FocusCanceledError) {
throw CaptureAbortedError(false)
}
try { try {
// 2. Once precapture AF/AE/AWB successfully locked, capture the actual photo // 2. Once precapture AF/AE/AWB successfully locked, capture the actual photo
val singleRequest = photoRequest.createCaptureRequest(device, deviceDetails, outputs) val singleRequest = photoRequest.createCaptureRequest(device, deviceDetails, outputs)
if (result.needsFlash) { if (needsFlash) {
singleRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) singleRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
singleRequest.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE) singleRequest.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE)
} }
@ -224,7 +234,8 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
// 1. Run a precapture sequence for AF, AE and AWB. // 1. Run a precapture sequence for AF, AE and AWB.
focusJob = coroutineScope.launch { focusJob = coroutineScope.launch {
val request = repeatingRequest.createCaptureRequest(device, deviceDetails, outputs) val request = repeatingRequest.createCaptureRequest(device, deviceDetails, outputs)
val options = PrecaptureOptions(listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE), Flash.OFF, listOf(point), false) val options =
PrecaptureOptions(listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE), Flash.OFF, listOf(point), false, FOCUS_RESET_TIMEOUT)
session.precapture(request, deviceDetails, options) session.precapture(request, deviceDetails, options)
} }
focusJob?.join() focusJob?.join()

View File

@ -18,7 +18,8 @@ data class PrecaptureOptions(
val modes: List<PrecaptureTrigger>, val modes: List<PrecaptureTrigger>,
val flash: Flash = Flash.OFF, val flash: Flash = Flash.OFF,
val pointsOfInterest: List<Point>, val pointsOfInterest: List<Point>,
val skipIfPassivelyFocused: Boolean val skipIfPassivelyFocused: Boolean,
val timeoutMs: Long
) )
data class PrecaptureResult(val needsFlash: Boolean) data class PrecaptureResult(val needsFlash: Boolean)
@ -57,12 +58,8 @@ suspend fun CameraCaptureSession.precapture(
aeState = ExposureState.fromAEState(result.get(CaptureResult.CONTROL_AE_STATE) ?: CaptureResult.CONTROL_AE_STATE_INACTIVE) aeState = ExposureState.fromAEState(result.get(CaptureResult.CONTROL_AE_STATE) ?: CaptureResult.CONTROL_AE_STATE_INACTIVE)
awbState = WhiteBalanceState.fromAWBState(result.get(CaptureResult.CONTROL_AWB_STATE) ?: CaptureResult.CONTROL_AWB_STATE_INACTIVE) awbState = WhiteBalanceState.fromAWBState(result.get(CaptureResult.CONTROL_AWB_STATE) ?: CaptureResult.CONTROL_AWB_STATE_INACTIVE)
if (aeState == ExposureState.FlashRequired) { Log.i(TAG, "Precapture current states: AF: $afState, AE: $aeState, AWB: $awbState")
Log.i(TAG, "Auto-Flash: Flash is required for photo capture, enabling flash...") enableFlash = aeState == ExposureState.FlashRequired && options.flash == Flash.AUTO
enableFlash = true
} else {
Log.i(TAG, "Auto-Flash: Flash is not required for photo capture.")
}
} else { } else {
// we either want Flash ON or OFF, so we don't care about lighting conditions - do a fast capture. // we either want Flash ON or OFF, so we don't care about lighting conditions - do a fast capture.
this.capture(request.build(), null, null) this.capture(request.build(), null, null)
@ -99,23 +96,41 @@ suspend fun CameraCaptureSession.precapture(
// AF Precapture // AF Precapture
if (deviceDetails.afModes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO)) { if (deviceDetails.afModes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO)) {
request.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO) request.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO)
request.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START)
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsFocusRegions) {
request.set(CaptureRequest.CONTROL_AF_REGIONS, meteringRectangles)
}
} else {
// AF is not supported on this device.
precaptureModes.remove(PrecaptureTrigger.AF)
} }
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsFocusRegions) {
request.set(CaptureRequest.CONTROL_AF_REGIONS, meteringRectangles)
}
request.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START)
} }
if (precaptureModes.contains(PrecaptureTrigger.AE) && deviceDetails.hardwareLevel.isAtLeast(HardwareLevel.LIMITED)) { if (precaptureModes.contains(PrecaptureTrigger.AE)) {
// AE Precapture // AE Precapture
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsExposureRegions) { if (deviceDetails.aeModes.contains(CaptureRequest.CONTROL_AE_MODE_ON) && deviceDetails.hardwareLevel.isAtLeast(HardwareLevel.LIMITED)) {
request.set(CaptureRequest.CONTROL_AE_REGIONS, meteringRectangles) request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
if (meteringRectangles.isNotEmpty() &&
deviceDetails.supportsExposureRegions &&
deviceDetails.hardwareLevel.isAtLeast(HardwareLevel.LIMITED)
) {
request.set(CaptureRequest.CONTROL_AE_REGIONS, meteringRectangles)
}
} else {
// AE is not supported on this device.
precaptureModes.remove(PrecaptureTrigger.AE)
} }
request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
} }
if (precaptureModes.contains(PrecaptureTrigger.AWB)) { if (precaptureModes.contains(PrecaptureTrigger.AWB)) {
// AWB Precapture // AWB Precapture
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsWhiteBalanceRegions) { if (deviceDetails.awbModes.contains(CaptureRequest.CONTROL_AWB_MODE_AUTO)) {
request.set(CaptureRequest.CONTROL_AWB_REGIONS, meteringRectangles) request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO)
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsWhiteBalanceRegions) {
request.set(CaptureRequest.CONTROL_AWB_REGIONS, meteringRectangles)
}
} else {
// AWB is not supported on this device.
precaptureModes.remove(PrecaptureTrigger.AWB)
} }
} }
this.capture(request.build(), null, null) this.capture(request.build(), null, null)
@ -125,7 +140,7 @@ suspend fun CameraCaptureSession.precapture(
// 3. Start a repeating request without the trigger and wait until AF/AE/AWB locks // 3. Start a repeating request without the trigger and wait until AF/AE/AWB locks
request.set(CaptureRequest.CONTROL_AF_TRIGGER, null) request.set(CaptureRequest.CONTROL_AF_TRIGGER, null)
request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, null) request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, null)
val result = this.setRepeatingRequestAndWaitForPrecapture(request.build(), *precaptureModes.toTypedArray()) val result = this.setRepeatingRequestAndWaitForPrecapture(request.build(), options.timeoutMs, *precaptureModes.toTypedArray())
if (!coroutineContext.isActive) throw FocusCanceledError() if (!coroutineContext.isActive) throw FocusCanceledError()

View File

@ -117,6 +117,7 @@ data class ResultState(val focusState: FocusState, val exposureState: ExposureSt
*/ */
suspend fun CameraCaptureSession.setRepeatingRequestAndWaitForPrecapture( suspend fun CameraCaptureSession.setRepeatingRequestAndWaitForPrecapture(
request: CaptureRequest, request: CaptureRequest,
timeoutMs: Long,
vararg precaptureTriggers: PrecaptureTrigger vararg precaptureTriggers: PrecaptureTrigger
): ResultState = ): ResultState =
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
@ -124,9 +125,9 @@ suspend fun CameraCaptureSession.setRepeatingRequestAndWaitForPrecapture(
val completed = precaptureTriggers.associateWith { false }.toMutableMap() val completed = precaptureTriggers.associateWith { false }.toMutableMap()
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
delay(5000) // after 5s, cancel capture delay(timeoutMs) // after timeout, cancel capture
if (continuation.isActive) { if (continuation.isActive) {
Log.e(TAG, "Precapture timed out after 5 seconds!") Log.e(TAG, "Precapture timed out after ${timeoutMs / 1000} seconds!")
continuation.resumeWithException(CaptureTimedOutError()) continuation.resumeWithException(CaptureTimedOutError())
try { try {
setRepeatingRequest(request, null, null) setRepeatingRequest(request, null, null)
@ -144,25 +145,25 @@ suspend fun CameraCaptureSession.setRepeatingRequestAndWaitForPrecapture(
super.onCaptureCompleted(session, request, result) super.onCaptureCompleted(session, request, result)
if (continuation.isActive) { if (continuation.isActive) {
// AF Precapture
val afState = FocusState.fromAFState(result.get(CaptureResult.CONTROL_AF_STATE) ?: CaptureResult.CONTROL_AF_STATE_INACTIVE) val afState = FocusState.fromAFState(result.get(CaptureResult.CONTROL_AF_STATE) ?: CaptureResult.CONTROL_AF_STATE_INACTIVE)
val aeState = ExposureState.fromAEState(result.get(CaptureResult.CONTROL_AE_STATE) ?: CaptureResult.CONTROL_AE_STATE_INACTIVE) val aeState = ExposureState.fromAEState(
result.get(CaptureResult.CONTROL_AE_STATE) ?: CaptureResult.CONTROL_AE_STATE_INACTIVE
)
val awbState = WhiteBalanceState.fromAWBState( val awbState = WhiteBalanceState.fromAWBState(
result.get(CaptureResult.CONTROL_AWB_STATE) ?: CaptureResult.CONTROL_AWB_STATE_INACTIVE result.get(CaptureResult.CONTROL_AWB_STATE) ?: CaptureResult.CONTROL_AWB_STATE_INACTIVE
) )
Log.i(TAG, "Precapture state: AF: $afState, AE: $aeState, AWB: $awbState")
// AF Precapture
if (precaptureTriggers.contains(PrecaptureTrigger.AF)) { if (precaptureTriggers.contains(PrecaptureTrigger.AF)) {
Log.i(TAG, "AF State: $afState (isCompleted: ${afState.isCompleted})")
completed[PrecaptureTrigger.AF] = afState.isCompleted completed[PrecaptureTrigger.AF] = afState.isCompleted
} }
// AE Precapture // AE Precapture
if (precaptureTriggers.contains(PrecaptureTrigger.AE)) { if (precaptureTriggers.contains(PrecaptureTrigger.AE)) {
Log.i(TAG, "AE State: $aeState (isCompleted: ${aeState.isCompleted})")
completed[PrecaptureTrigger.AE] = aeState.isCompleted completed[PrecaptureTrigger.AE] = aeState.isCompleted
} }
// AWB Precapture // AWB Precapture
if (precaptureTriggers.contains(PrecaptureTrigger.AWB)) { if (precaptureTriggers.contains(PrecaptureTrigger.AWB)) {
Log.i(TAG, "AWB State: $awbState (isCompleted: ${awbState.isCompleted})")
completed[PrecaptureTrigger.AWB] = awbState.isCompleted completed[PrecaptureTrigger.AWB] = awbState.isCompleted
} }