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
|
// 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
|
||||||
|
@ -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?
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user