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:
parent
37398cc909
commit
a7701c8c9c
@ -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
|
||||
|
@ -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?
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user