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:
parent
fabf019f66
commit
369cb4a043
@ -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
|
||||
var needsFlash: Boolean
|
||||
try {
|
||||
val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs)
|
||||
val skipIfPassivelyFocused = flash == Flash.OFF
|
||||
val options =
|
||||
PrecaptureOptions(
|
||||
val options = PrecaptureOptions(
|
||||
listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE, PrecaptureTrigger.AWB),
|
||||
flash,
|
||||
emptyList(),
|
||||
skipIfPassivelyFocused
|
||||
skipIfPassivelyFocused,
|
||||
PRECAPTURE_LOCK_TIMEOUT
|
||||
)
|
||||
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()
|
||||
|
@ -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,24 +96,42 @@ 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)
|
||||
}
|
||||
request.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START)
|
||||
} else {
|
||||
// AF is not supported on this device.
|
||||
precaptureModes.remove(PrecaptureTrigger.AF)
|
||||
}
|
||||
if (precaptureModes.contains(PrecaptureTrigger.AE) && deviceDetails.hardwareLevel.isAtLeast(HardwareLevel.LIMITED)) {
|
||||
}
|
||||
if (precaptureModes.contains(PrecaptureTrigger.AE)) {
|
||||
// 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_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)
|
||||
}
|
||||
request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
|
||||
} else {
|
||||
// AE is not supported on this device.
|
||||
precaptureModes.remove(PrecaptureTrigger.AE)
|
||||
}
|
||||
}
|
||||
if (precaptureModes.contains(PrecaptureTrigger.AWB)) {
|
||||
// AWB Precapture
|
||||
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()
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user