perf: Allow skipping pre-capture sequence if already focused (#2561)

This PR speeds up photo capture on Android by skipping pre-capture sequences on modes that are already focused (either AF, AE or AWB)
This commit is contained in:
Marc Rousavy 2024-02-14 17:04:25 +01:00 committed by GitHub
parent 37398cc909
commit a7701c8c9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 17 deletions

View File

@ -172,7 +172,14 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
// 1. Run precapture sequence
val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs)
val options = PrecaptureOptions(listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE, PrecaptureTrigger.AWB), flash, emptyList())
val skipIfPassivelyFocused = flash == Flash.OFF
val options =
PrecaptureOptions(
listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE, PrecaptureTrigger.AWB),
flash,
emptyList(),
skipIfPassivelyFocused
)
val result = session.precapture(precaptureRequest, deviceDetails, options)
try {
@ -208,7 +215,7 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
// 1. Run a precapture sequence for AF, AE and AWB.
val request = repeatingRequest.createCaptureRequest(device, deviceDetails, outputs)
val options = PrecaptureOptions(listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE), Flash.OFF, listOf(point))
val options = PrecaptureOptions(listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE), Flash.OFF, listOf(point), false)
session.precapture(request, deviceDetails, options)
// 2. Wait 3 seconds

View File

@ -11,7 +11,12 @@ import com.mrousavy.camera.core.CameraDeviceDetails
import com.mrousavy.camera.types.Flash
import com.mrousavy.camera.types.HardwareLevel
data class PrecaptureOptions(val modes: List<PrecaptureTrigger>, val flash: Flash = Flash.OFF, val pointsOfInterest: List<Point>)
data class PrecaptureOptions(
val modes: List<PrecaptureTrigger>,
val flash: Flash = Flash.OFF,
val pointsOfInterest: List<Point>,
val skipIfPassivelyFocused: Boolean
)
data class PrecaptureResult(val needsFlash: Boolean)
@ -33,15 +38,23 @@ suspend fun CameraCaptureSession.precapture(
request.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO)
var enableFlash = options.flash == Flash.ON
var afState = FocusState.Inactive
var aeState = ExposureState.Inactive
var awbState = WhiteBalanceState.Inactive
val precaptureModes = options.modes.toMutableList()
// 1. Cancel any ongoing precapture sequences
request.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL)
request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL)
if (options.flash == Flash.AUTO) {
// we want Auto-Flash, so check the current lighting conditions if we need it.
if (options.flash == Flash.AUTO || options.skipIfPassivelyFocused) {
// We want to read the current AE/AF/AWB values to determine if we need flash or can skip AF/AE/AWB precapture
val result = this.capture(request.build(), false)
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) {
afState = FocusState.fromAFState(result.get(CaptureResult.CONTROL_AF_STATE) ?: CaptureResult.CONTROL_AF_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)
if (aeState == ExposureState.FlashRequired) {
Log.i(TAG, "Auto-Flash: Flash is required for photo capture, enabling flash...")
enableFlash = true
} else {
@ -57,11 +70,27 @@ suspend fun CameraCaptureSession.precapture(
MeteringRectangle(point, DEFAULT_METERING_SIZE, meteringWeight)
}.toTypedArray()
if (options.skipIfPassivelyFocused) {
// If user allows us to skip precapture for values that are already focused, remove them from the precapture modes.
if (afState.isPassivelyFocused) {
Log.i(TAG, "AF is already focused, skipping...")
precaptureModes.remove(PrecaptureTrigger.AF)
}
if (aeState.isPassivelyFocused) {
Log.i(TAG, "AE is already focused, skipping...")
precaptureModes.remove(PrecaptureTrigger.AE)
}
if (awbState.isPassivelyFocused) {
Log.i(TAG, "AWB is already focused, skipping...")
precaptureModes.remove(PrecaptureTrigger.AWB)
}
}
// 2. Submit a precapture start sequence
if (enableFlash && deviceDetails.hasFlash) {
request.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH)
}
if (options.modes.contains(PrecaptureTrigger.AF)) {
if (precaptureModes.contains(PrecaptureTrigger.AF)) {
// AF Precapture
if (deviceDetails.afModes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO)) {
request.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO)
@ -71,14 +100,14 @@ suspend fun CameraCaptureSession.precapture(
}
request.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START)
}
if (options.modes.contains(PrecaptureTrigger.AE) && deviceDetails.hardwareLevel.isAtLeast(HardwareLevel.LIMITED)) {
if (precaptureModes.contains(PrecaptureTrigger.AE) && deviceDetails.hardwareLevel.isAtLeast(HardwareLevel.LIMITED)) {
// AE Precapture
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsExposureRegions) {
request.set(CaptureRequest.CONTROL_AE_REGIONS, meteringRectangles)
}
request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
}
if (options.modes.contains(PrecaptureTrigger.AWB)) {
if (precaptureModes.contains(PrecaptureTrigger.AWB)) {
// AWB Precapture
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsWhiteBalanceRegions) {
request.set(CaptureRequest.CONTROL_AWB_REGIONS, meteringRectangles)
@ -89,7 +118,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(), *options.modes.toTypedArray())
val result = this.setRepeatingRequestAndWaitForPrecapture(request.build(), *precaptureModes.toTypedArray())
Log.i(TAG, "AF/AE/AWB successfully locked!")
// TODO: Set to idle again?

View File

@ -24,7 +24,12 @@ enum class PrecaptureTrigger {
AWB
}
enum class FocusState {
interface AutoState {
val isCompleted: Boolean
val isPassivelyFocused: Boolean
}
enum class FocusState : AutoState {
Inactive,
Scanning,
Focused,
@ -33,8 +38,10 @@ enum class FocusState {
PassiveFocused,
PassiveUnfocused;
val isCompleted: Boolean
override val isCompleted: Boolean
get() = this == Focused || this == Unfocused
override val isPassivelyFocused: Boolean
get() = this == PassiveFocused
companion object {
fun fromAFState(afState: Int): FocusState =
@ -50,7 +57,7 @@ enum class FocusState {
}
}
}
enum class ExposureState {
enum class ExposureState : AutoState {
Locked,
Inactive,
Precapture,
@ -58,8 +65,10 @@ enum class ExposureState {
Converged,
FlashRequired;
val isCompleted: Boolean
override val isCompleted: Boolean
get() = this == Converged || this == FlashRequired
override val isPassivelyFocused: Boolean
get() = this == Converged
companion object {
fun fromAEState(aeState: Int): ExposureState =
@ -75,13 +84,15 @@ enum class ExposureState {
}
}
enum class WhiteBalanceState {
enum class WhiteBalanceState : AutoState {
Inactive,
Locked,
Searching,
Converged;
val isCompleted: Boolean
override val isCompleted: Boolean
get() = this == Converged
override val isPassivelyFocused: Boolean
get() = this == Converged
companion object {