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 {
|
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
|
||||||
|
var needsFlash: Boolean
|
||||||
|
try {
|
||||||
val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs)
|
val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs)
|
||||||
val skipIfPassivelyFocused = flash == Flash.OFF
|
val skipIfPassivelyFocused = flash == Flash.OFF
|
||||||
val options =
|
val options = PrecaptureOptions(
|
||||||
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()
|
||||||
|
@ -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,24 +96,42 @@ 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) {
|
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsFocusRegions) {
|
||||||
request.set(CaptureRequest.CONTROL_AF_REGIONS, meteringRectangles)
|
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
|
// 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_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)) {
|
if (precaptureModes.contains(PrecaptureTrigger.AWB)) {
|
||||||
// AWB Precapture
|
// 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) {
|
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsWhiteBalanceRegions) {
|
||||||
request.set(CaptureRequest.CONTROL_AWB_REGIONS, meteringRectangles)
|
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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user