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 // 1. Run precapture sequence
val precaptureRequest = repeatingRequest.createCaptureRequest(device, deviceDetails, repeatingOutputs) 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) val result = session.precapture(precaptureRequest, deviceDetails, options)
try { try {
@ -208,7 +215,7 @@ 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.
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)) val options = PrecaptureOptions(listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE), Flash.OFF, listOf(point), false)
session.precapture(request, deviceDetails, options) session.precapture(request, deviceDetails, options)
// 2. Wait 3 seconds // 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.Flash
import com.mrousavy.camera.types.HardwareLevel 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) data class PrecaptureResult(val needsFlash: Boolean)
@ -33,15 +38,23 @@ suspend fun CameraCaptureSession.precapture(
request.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO) request.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO)
var enableFlash = options.flash == Flash.ON 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 // 1. Cancel any ongoing precapture sequences
request.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL) request.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL)
request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL) request.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL)
if (options.flash == Flash.AUTO) { if (options.flash == Flash.AUTO || options.skipIfPassivelyFocused) {
// we want Auto-Flash, so check the current lighting conditions if we need it. // 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 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...") Log.i(TAG, "Auto-Flash: Flash is required for photo capture, enabling flash...")
enableFlash = true enableFlash = true
} else { } else {
@ -57,11 +70,27 @@ suspend fun CameraCaptureSession.precapture(
MeteringRectangle(point, DEFAULT_METERING_SIZE, meteringWeight) MeteringRectangle(point, DEFAULT_METERING_SIZE, meteringWeight)
}.toTypedArray() }.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 // 2. Submit a precapture start sequence
if (enableFlash && deviceDetails.hasFlash) { if (enableFlash && deviceDetails.hasFlash) {
request.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH) request.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH)
} }
if (options.modes.contains(PrecaptureTrigger.AF)) { if (precaptureModes.contains(PrecaptureTrigger.AF)) {
// 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)
@ -71,14 +100,14 @@ suspend fun CameraCaptureSession.precapture(
} }
request.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START) 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 // AE Precapture
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsExposureRegions) { if (meteringRectangles.isNotEmpty() && deviceDetails.supportsExposureRegions) {
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) 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 // AWB Precapture
if (meteringRectangles.isNotEmpty() && deviceDetails.supportsWhiteBalanceRegions) { if (meteringRectangles.isNotEmpty() && deviceDetails.supportsWhiteBalanceRegions) {
request.set(CaptureRequest.CONTROL_AWB_REGIONS, meteringRectangles) 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 // 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(), *options.modes.toTypedArray()) val result = this.setRepeatingRequestAndWaitForPrecapture(request.build(), *precaptureModes.toTypedArray())
Log.i(TAG, "AF/AE/AWB successfully locked!") Log.i(TAG, "AF/AE/AWB successfully locked!")
// TODO: Set to idle again? // TODO: Set to idle again?

View File

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