feat: Allow focus calls to be cancelable (#2567)
* feat: Allow focus calls to be cancelable * Cancelable
This commit is contained in:
parent
bcd12649e2
commit
4168d8f752
@ -104,6 +104,7 @@ class PhotoNotEnabledError :
|
||||
CameraError("capture", "photo-not-enabled", "Photo capture is disabled! Pass `photo={true}` to enable photo capture.")
|
||||
class CaptureAbortedError(wasImageCaptured: Boolean) :
|
||||
CameraError("capture", "aborted", "The image capture was aborted! Was Image captured: $wasImageCaptured")
|
||||
class FocusCanceledError : CameraError("capture", "focus-canceled", "The focus operation was canceled.")
|
||||
class CaptureTimedOutError : CameraError("capture", "timed-out", "The image capture was aborted because it timed out.")
|
||||
class UnknownCaptureError(wasImageCaptured: Boolean) :
|
||||
CameraError("capture", "unknown", "An unknown error occurred while trying to capture an Image! Was Image captured: $wasImageCaptured")
|
||||
|
@ -25,7 +25,7 @@ import com.mrousavy.camera.types.QualityPrioritization
|
||||
import java.io.Closeable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
@ -56,7 +56,7 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
|
||||
|
||||
private val mutex = Mutex()
|
||||
private var didDestroyFromOutside = false
|
||||
private var focusResetJob: Job? = null
|
||||
private var focusJob: Job? = null
|
||||
private val coroutineScope = CoroutineScope(CameraQueues.cameraQueue.coroutineDispatcher)
|
||||
|
||||
val isRunning: Boolean
|
||||
@ -74,6 +74,10 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
|
||||
}
|
||||
|
||||
suspend fun withConfiguration(block: suspend () -> Unit) {
|
||||
// Cancel any ongoing focus jobs
|
||||
focusJob?.cancel()
|
||||
focusJob = null
|
||||
|
||||
mutex.withLock {
|
||||
block()
|
||||
configure()
|
||||
@ -141,6 +145,10 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
|
||||
orientation: Orientation,
|
||||
enableShutterSound: Boolean
|
||||
): TotalCaptureResult {
|
||||
// Cancel any ongoing focus jobs
|
||||
focusJob?.cancel()
|
||||
focusJob = null
|
||||
|
||||
mutex.withLock {
|
||||
Log.i(TAG, "Capturing photo...")
|
||||
val session = session ?: throw CameraNotReadyError()
|
||||
@ -198,6 +206,10 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
|
||||
}
|
||||
|
||||
suspend fun focus(point: Point) {
|
||||
// Cancel any previous focus jobs
|
||||
focusJob?.cancel()
|
||||
focusJob = null
|
||||
|
||||
mutex.withLock {
|
||||
Log.i(TAG, "Focusing to $point...")
|
||||
val session = session ?: throw CameraNotReadyError()
|
||||
@ -209,17 +221,16 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
|
||||
}
|
||||
val outputs = outputs.filter { it.isRepeating }
|
||||
|
||||
// 0. Cancel the 3 second focus reset task
|
||||
focusResetJob?.cancelAndJoin()
|
||||
focusResetJob = null
|
||||
|
||||
// 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), false)
|
||||
session.precapture(request, deviceDetails, options)
|
||||
focusJob = coroutineScope.launch {
|
||||
val request = repeatingRequest.createCaptureRequest(device, deviceDetails, outputs)
|
||||
val options = PrecaptureOptions(listOf(PrecaptureTrigger.AF, PrecaptureTrigger.AE), Flash.OFF, listOf(point), false)
|
||||
session.precapture(request, deviceDetails, options)
|
||||
}
|
||||
focusJob?.join()
|
||||
|
||||
// 2. Wait 3 seconds
|
||||
focusResetJob = coroutineScope.launch {
|
||||
// 2. Reset AF/AE/AWB again after 3 seconds timeout
|
||||
focusJob = coroutineScope.launch {
|
||||
delay(FOCUS_RESET_TIMEOUT)
|
||||
if (!this.isActive) {
|
||||
// this job got canceled from the outside
|
||||
@ -230,7 +241,6 @@ class PersistentCameraCaptureSession(private val cameraManager: CameraManager, p
|
||||
return@launch
|
||||
}
|
||||
Log.i(TAG, "Resetting focus to auto-focus...")
|
||||
// 3. Reset AF/AE/AWB to continuous auto-focus again, which is the default here.
|
||||
repeatingRequest.createCaptureRequest(device, deviceDetails, outputs).also { request ->
|
||||
session.setRepeatingRequest(request.build(), null, null)
|
||||
}
|
||||
|
@ -8,8 +8,11 @@ import android.hardware.camera2.params.MeteringRectangle
|
||||
import android.util.Log
|
||||
import android.util.Size
|
||||
import com.mrousavy.camera.core.CameraDeviceDetails
|
||||
import com.mrousavy.camera.core.FocusCanceledError
|
||||
import com.mrousavy.camera.types.Flash
|
||||
import com.mrousavy.camera.types.HardwareLevel
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlinx.coroutines.isActive
|
||||
|
||||
data class PrecaptureOptions(
|
||||
val modes: List<PrecaptureTrigger>,
|
||||
@ -65,6 +68,8 @@ suspend fun CameraCaptureSession.precapture(
|
||||
this.capture(request.build(), null, null)
|
||||
}
|
||||
|
||||
if (!coroutineContext.isActive) throw FocusCanceledError()
|
||||
|
||||
val meteringWeight = MeteringRectangle.METERING_WEIGHT_MAX - 1
|
||||
val meteringRectangles = options.pointsOfInterest.map { point ->
|
||||
MeteringRectangle(point, DEFAULT_METERING_SIZE, meteringWeight)
|
||||
@ -115,13 +120,16 @@ suspend fun CameraCaptureSession.precapture(
|
||||
}
|
||||
this.capture(request.build(), null, null)
|
||||
|
||||
if (!coroutineContext.isActive) throw FocusCanceledError()
|
||||
|
||||
// 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(), *precaptureModes.toTypedArray())
|
||||
|
||||
if (!coroutineContext.isActive) throw FocusCanceledError()
|
||||
|
||||
Log.i(TAG, "AF/AE/AWB successfully locked!")
|
||||
// TODO: Set to idle again?
|
||||
|
||||
val needsFlash = result.exposureState == ExposureState.FlashRequired
|
||||
return PrecaptureResult(needsFlash)
|
||||
|
@ -43,6 +43,7 @@ export type CaptureError =
|
||||
| 'capture/photo-not-enabled'
|
||||
| 'capture/frame-invalid'
|
||||
| 'capture/aborted'
|
||||
| 'capture/focus-canceled'
|
||||
| 'capture/timed-out'
|
||||
| 'capture/unknown'
|
||||
export type SystemError =
|
||||
|
Loading…
Reference in New Issue
Block a user