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 {
private const val TAG = "PersistentCameraCaptureSession"
private const val FOCUS_RESET_TIMEOUT = 3000L
private const val PRECAPTURE_LOCK_TIMEOUT = 5000L
}
// Inputs/Dependencies
@ -178,21 +179,30 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
Log.i(TAG, "Locking AF/AE/AWB...")
// 1. Run precapture sequence
val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs)
val skipIfPassivelyFocused = flash == Flash.OFF
val options =
PrecaptureOptions(
var needsFlash: Boolean
try {
val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs)
val skipIfPassivelyFocused = flash == Flash.OFF
val options = PrecaptureOptions(
listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE, PrecaptureTrigger.AWB),
flash,
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 {
// 2. Once precapture AF/AE/AWB successfully locked, capture the actual photo
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.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.
focusJob = coroutineScope.launch {
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)
}
focusJob?.join()

View File

@ -18,7 +18,8 @@ data class PrecaptureOptions(
val modes: List<PrecaptureTrigger>,
val flash: Flash = Flash.OFF,
val pointsOfInterest: List<Point>,
val skipIfPassivelyFocused: Boolean
val skipIfPassivelyFocused: Boolean,
val timeoutMs: Long
)
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)
awbState = WhiteBalanceState.fromAWBState(result.get(CaptureResult.CONTROL_AWB_STATE) ?: CaptureResult.CONTROL_AWB_STATE_INACTIVE)
if (aeState == ExposureState.FlashRequired) {
Log.i(TAG, "Auto-Flash: Flash is required for photo capture, enabling flash...")
enableFlash = true
} else {
Log.i(TAG, "Auto-Flash: Flash is not required for photo capture.")
}
Log.i(TAG, "Precapture current states: AF: $afState, AE: $aeState, AWB: $awbState")
enableFlash = aeState == ExposureState.FlashRequired && options.flash == Flash.AUTO
} else {
// 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)
@ -99,23 +96,41 @@ suspend fun CameraCaptureSession.precapture(
// AF Precapture
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_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
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsExposureRegions) {
request.set(CaptureRequest.CONTROL_AE_REGIONS, meteringRectangles)
if (deviceDetails.aeModes.contains(CaptureRequest.CONTROL_AE_MODE_ON) && deviceDetails.hardwareLevel.isAtLeast(HardwareLevel.LIMITED)) {
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)) {
// AWB Precapture
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsWhiteBalanceRegions) {
request.set(CaptureRequest.CONTROL_AWB_REGIONS, meteringRectangles)
if (deviceDetails.awbModes.contains(CaptureRequest.CONTROL_AWB_MODE_AUTO)) {
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)
@ -125,7 +140,7 @@ suspend fun CameraCaptureSession.precapture(
// 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_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()

View File

@ -117,6 +117,7 @@ data class ResultState(val focusState: FocusState, val exposureState: ExposureSt
*/
suspend fun CameraCaptureSession.setRepeatingRequestAndWaitForPrecapture(
request: CaptureRequest,
timeoutMs: Long,
vararg precaptureTriggers: PrecaptureTrigger
): ResultState =
suspendCancellableCoroutine { continuation ->
@ -124,9 +125,9 @@ suspend fun CameraCaptureSession.setRepeatingRequestAndWaitForPrecapture(
val completed = precaptureTriggers.associateWith { false }.toMutableMap()
CoroutineScope(Dispatchers.Default).launch {
delay(5000) // after 5s, cancel capture
delay(timeoutMs) // after timeout, cancel capture
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())
try {
setRepeatingRequest(request, null, null)
@ -144,25 +145,25 @@ suspend fun CameraCaptureSession.setRepeatingRequestAndWaitForPrecapture(
super.onCaptureCompleted(session, request, result)
if (continuation.isActive) {
// AF Precapture
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(
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)) {
Log.i(TAG, "AF State: $afState (isCompleted: ${afState.isCompleted})")
completed[PrecaptureTrigger.AF] = afState.isCompleted
}
// AE Precapture
if (precaptureTriggers.contains(PrecaptureTrigger.AE)) {
Log.i(TAG, "AE State: $aeState (isCompleted: ${aeState.isCompleted})")
completed[PrecaptureTrigger.AE] = aeState.isCompleted
}
// AWB Precapture
if (precaptureTriggers.contains(PrecaptureTrigger.AWB)) {
Log.i(TAG, "AWB State: $awbState (isCompleted: ${awbState.isCompleted})")
completed[PrecaptureTrigger.AWB] = awbState.isCompleted
}