Merge "[Camera-pipe] Fix image capture no response" into androidx-main
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestControl.kt
index e4dec65..3a7e196 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestControl.kt
@@ -19,7 +19,6 @@
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.core.Log.debug
-import androidx.camera.camera2.pipe.core.Log.warn
import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
import androidx.camera.camera2.pipe.integration.adapter.propagateOnceTo
import androidx.camera.camera2.pipe.integration.config.CameraScope
@@ -40,7 +39,6 @@
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withTimeoutOrNull
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@CameraScope
@@ -144,12 +142,7 @@
// completed. On some devices, AE preCapture triggered in submitStillCaptures may not
// work properly if the repeating request to change the flash mode is not completed.
debug { "StillCaptureRequestControl: Waiting for flash control" }
- withTimeoutOrNull(1_000L) {
- flashControl.updateSignal.join()
- } ?: {
- warn { "StillCaptureRequestControl: Waiting for flash control timed out" }
- }
- debug { "StillCaptureRequestControl: Waiting for flash control done" }
+ flashControl.updateSignal.join()
debug { "StillCaptureRequestControl: Issuing single capture" }
val deferredList = camera.requestControl.issueSingleCaptureAsync(
request.captureConfigs,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
index 32c7488..1f31a5a 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -139,6 +139,7 @@
return if (closed.compareAndSet(expect = false, update = true)) {
threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
debug { "Closing $this" }
+ requestControl.close()
useCaseGraphConfig.graph.close()
useCaseSurfaceManager.stopAsync().await()
}
@@ -151,15 +152,19 @@
key: CaptureRequest.Key<T>,
value: T,
priority: Config.OptionPriority,
- ): Deferred<Unit> = setParametersAsync(mapOf(key to (value as Any)), priority)
+ ): Deferred<Unit> = runIfNotClosed {
+ setParametersAsync(mapOf(key to (value as Any)), priority)
+ } ?: canceledResult
override fun setParametersAsync(
values: Map<CaptureRequest.Key<*>, Any>,
priority: Config.OptionPriority,
- ): Deferred<Unit> = requestControl.addParametersAsync(
- values = values,
- optionPriority = priority
- )
+ ): Deferred<Unit> = runIfNotClosed {
+ requestControl.addParametersAsync(
+ values = values,
+ optionPriority = priority
+ )
+ } ?: canceledResult
override fun setActiveResumeMode(enabled: Boolean) {
useCaseGraphConfig.graph.isForeground = enabled
@@ -167,21 +172,27 @@
private fun UseCaseCameraRequestControl.setSessionConfigAsync(
sessionConfig: SessionConfig
- ): Deferred<Unit> = setConfigAsync(
- type = UseCaseCameraRequestControl.Type.SESSION_CONFIG,
- config = sessionConfig.implementationOptions,
- tags = sessionConfig.repeatingCaptureConfig.tagBundle.toMap(),
- listeners = setOf(
- CameraCallbackMap.createFor(
- sessionConfig.repeatingCameraCaptureCallbacks,
- threads.backgroundExecutor
- )
- ),
- template = RequestTemplate(sessionConfig.repeatingCaptureConfig.templateType),
- streams = useCaseGraphConfig.getStreamIdsFromSurfaces(
- sessionConfig.repeatingCaptureConfig.surfaces
- ),
- )
+ ): Deferred<Unit> = runIfNotClosed {
+ setConfigAsync(
+ type = UseCaseCameraRequestControl.Type.SESSION_CONFIG,
+ config = sessionConfig.implementationOptions,
+ tags = sessionConfig.repeatingCaptureConfig.tagBundle.toMap(),
+ listeners = setOf(
+ CameraCallbackMap.createFor(
+ sessionConfig.repeatingCameraCaptureCallbacks,
+ threads.backgroundExecutor
+ )
+ ),
+ template = RequestTemplate(sessionConfig.repeatingCaptureConfig.templateType),
+ streams = useCaseGraphConfig.getStreamIdsFromSurfaces(
+ sessionConfig.repeatingCaptureConfig.surfaces
+ ),
+ )
+ } ?: canceledResult
+
+ private inline fun <R> runIfNotClosed(crossinline block: () -> R): R? {
+ return if (!closed.value) block() else null
+ }
override fun toString(): String = "UseCaseCamera-$debugId"
@@ -191,4 +202,8 @@
@Binds
abstract fun provideUseCaseCamera(useCaseCamera: UseCaseCameraImpl): UseCaseCamera
}
+
+ companion object {
+ private val canceledResult = CompletableDeferred<Unit>().apply { cancel() }
+ }
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index 6ce070e..13ac999 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -46,6 +46,7 @@
import dagger.Binds
import dagger.Module
import javax.inject.Inject
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
@@ -150,6 +151,8 @@
flashType: Int,
flashMode: Int,
): List<Deferred<Void?>>
+
+ fun close()
}
@UseCaseCameraScope
@@ -161,6 +164,9 @@
) : UseCaseCameraRequestControl {
private val graph = useCaseGraphConfig.graph
+ @Volatile
+ private var closed = false
+
private data class InfoBundle(
val options: Camera2ImplConfig.Builder = Camera2ImplConfig.Builder(),
val tags: MutableMap<String, Any> = mutableMapOf(),
@@ -178,15 +184,17 @@
optionPriority: Config.OptionPriority,
tags: Map<String, Any>,
listeners: Set<Request.Listener>
- ): Deferred<Unit> = synchronized(lock) {
- debug { "[$type] Add request option: $values" }
- infoBundleMap.getOrPut(type) { InfoBundle() }.let {
- it.options.addAllCaptureRequestOptionsWithPriority(values, optionPriority)
- it.tags.putAll(tags)
- it.listeners.addAll(listeners)
- }
- infoBundleMap.merge()
- }.updateCameraStateAsync()
+ ): Deferred<Unit> = runIfNotClosed {
+ synchronized(lock) {
+ debug { "[$type] Add request option: $values" }
+ infoBundleMap.getOrPut(type) { InfoBundle() }.let {
+ it.options.addAllCaptureRequestOptionsWithPriority(values, optionPriority)
+ it.tags.putAll(tags)
+ it.listeners.addAll(listeners)
+ }
+ infoBundleMap.merge()
+ }.updateCameraStateAsync()
+ } ?: canceledResult
override fun setConfigAsync(
type: UseCaseCameraRequestControl.Type,
@@ -195,25 +203,27 @@
streams: Set<StreamId>?,
template: RequestTemplate?,
listeners: Set<Request.Listener>
- ): Deferred<Unit> = synchronized(lock) {
- debug { "[$type] Set config: ${config?.toParameters()}" }
- infoBundleMap[type] = InfoBundle(
- Camera2ImplConfig.Builder().apply {
- config?.let {
- insertAllOptions(it)
- }
- },
- tags.toMutableMap(),
- listeners.toMutableSet(),
- template,
+ ): Deferred<Unit> = runIfNotClosed {
+ synchronized(lock) {
+ debug { "[$type] Set config: ${config?.toParameters()}" }
+ infoBundleMap[type] = InfoBundle(
+ Camera2ImplConfig.Builder().apply {
+ config?.let {
+ insertAllOptions(it)
+ }
+ },
+ tags.toMutableMap(),
+ listeners.toMutableSet(),
+ template,
+ )
+ infoBundleMap.merge()
+ }.updateCameraStateAsync(
+ streams = streams,
)
- infoBundleMap.merge()
- }.updateCameraStateAsync(
- streams = streams,
- )
+ } ?: canceledResult
- override suspend fun setTorchAsync(enabled: Boolean): Deferred<Result3A> =
- graph.acquireSession().use {
+ override suspend fun setTorchAsync(enabled: Boolean): Deferred<Result3A> = runIfNotClosed {
+ useGraphSessionOrFailed {
it.setTorch(
when (enabled) {
true -> TorchState.ON
@@ -221,6 +231,7 @@
}
)
}
+ } ?: submitFailedResult
override suspend fun startFocusAndMeteringAsync(
aeRegions: List<MeteringRectangle>?,
@@ -231,54 +242,49 @@
awbLockBehavior: Lock3ABehavior?,
afTriggerStartAeMode: AeMode?,
timeLimitNs: Long,
- ): Deferred<Result3A> = graph.acquireSession().use {
- it.lock3A(
- aeRegions = aeRegions,
- afRegions = afRegions,
- awbRegions = awbRegions,
- aeLockBehavior = aeLockBehavior,
- afLockBehavior = afLockBehavior,
- awbLockBehavior = awbLockBehavior,
- afTriggerStartAeMode = afTriggerStartAeMode,
- timeLimitNs = timeLimitNs,
- )
- }
+ ): Deferred<Result3A> = runIfNotClosed {
+ useGraphSessionOrFailed {
+ it.lock3A(
+ aeRegions = aeRegions,
+ afRegions = afRegions,
+ awbRegions = awbRegions,
+ aeLockBehavior = aeLockBehavior,
+ afLockBehavior = afLockBehavior,
+ awbLockBehavior = awbLockBehavior,
+ afTriggerStartAeMode = afTriggerStartAeMode,
+ timeLimitNs = timeLimitNs,
+ )
+ }
+ } ?: submitFailedResult
- override suspend fun cancelFocusAndMeteringAsync(): Deferred<Result3A> {
- graph.acquireSession().use {
+ override suspend fun cancelFocusAndMeteringAsync() = runIfNotClosed {
+ useGraphSessionOrFailed {
it.unlock3A(ae = true, af = true, awb = true)
}.await()
- return graph.acquireSession().use {
+ useGraphSessionOrFailed {
it.update3A(
aeRegions = METERING_REGIONS_DEFAULT.asList(),
afRegions = METERING_REGIONS_DEFAULT.asList(),
awbRegions = METERING_REGIONS_DEFAULT.asList()
)
}
- }
+ } ?: submitFailedResult
override suspend fun issueSingleCaptureAsync(
captureSequence: List<CaptureConfig>,
captureMode: Int,
flashType: Int,
flashMode: Int,
- ): List<Deferred<Void?>> {
+ ) = runIfNotClosed {
if (captureSequence.hasInvalidSurface()) {
- return List(captureSequence.size) {
- CompletableDeferred<Void?>().apply {
- completeExceptionally(
- ImageCaptureException(
- ImageCapture.ERROR_CAPTURE_FAILED,
- "Capture request failed due to invalid surface",
- null
- )
- )
- }
- }
+ failedResults(
+ captureSequence.size,
+ "Capture request failed due to invalid surface"
+ )
}
- return synchronized(lock) {
+ synchronized(lock) {
infoBundleMap.merge()
}.let { infoBundle ->
debug { "UseCaseCameraRequestControl: Submitting still captures to capture pipeline" }
@@ -295,8 +301,21 @@
flashMode = flashMode,
)
}
+ } ?: failedResults(captureSequence.size, "Capture request is cancelled on closed CameraGraph")
+
+ override fun close() {
+ closed = true
}
+ private fun failedResults(count: Int, message: String): List<Deferred<Void?>> =
+ List(count) {
+ CompletableDeferred<Void>().apply {
+ completeExceptionally(
+ ImageCaptureException(ImageCapture.ERROR_CAPTURE_FAILED, message, null)
+ )
+ }
+ }
+
private fun List<CaptureConfig>.hasInvalidSurface(): Boolean {
forEach { captureConfig ->
if (captureConfig.surfaces.isEmpty()) {
@@ -339,23 +358,37 @@
}
}
- private fun InfoBundle.updateCameraStateAsync(streams: Set<StreamId>? = null): Deferred<Unit> {
- capturePipeline.template =
- if (template != null && template!!.value != TEMPLATE_TYPE_NONE) {
- template!!.value
- } else {
- DEFAULT_REQUEST_TEMPLATE
- }
+ private fun InfoBundle.updateCameraStateAsync(streams: Set<StreamId>? = null): Deferred<Unit> =
+ runIfNotClosed {
+ capturePipeline.template =
+ if (template != null && template!!.value != TEMPLATE_TYPE_NONE) {
+ template!!.value
+ } else {
+ DEFAULT_REQUEST_TEMPLATE
+ }
- return state.updateAsync(
- parameters = options.build().toParameters(),
- appendParameters = false,
- internalParameters = mapOf(CAMERAX_TAG_BUNDLE to toTagBundle()),
- appendInternalParameters = false,
- streams = streams,
- template = template,
- listeners = listeners,
- )
+ state.updateAsync(
+ parameters = options.build().toParameters(),
+ appendParameters = false,
+ internalParameters = mapOf(CAMERAX_TAG_BUNDLE to toTagBundle()),
+ appendInternalParameters = false,
+ streams = streams,
+ template = template,
+ listeners = listeners,
+ )
+ } ?: canceledResult
+
+ private inline fun <R> runIfNotClosed(block: () -> R): R? {
+ return if (!closed) block() else null
+ }
+
+ private suspend inline fun useGraphSessionOrFailed(
+ crossinline block: suspend (CameraGraph.Session) -> Deferred<Result3A>
+ ): Deferred<Result3A> = try {
+ graph.acquireSession().use { block(it) }
+ } catch (e: CancellationException) {
+ debug(e) { "Cannot acquire the CameraGraph.Session" }
+ submitFailedResult
}
@Module
@@ -366,6 +399,12 @@
requestControl: UseCaseCameraRequestControlImpl
): UseCaseCameraRequestControl
}
+
+ companion object {
+ private val submitFailedResult =
+ CompletableDeferred(Result3A(Result3A.Status.SUBMIT_FAILED))
+ private val canceledResult = CompletableDeferred<Unit>().apply { cancel() }
+ }
}
fun TagBundle.toMap(): Map<String, Any> = mutableMapOf<String, Any>().also {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
index 75a210e..b67a339 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
@@ -38,6 +38,7 @@
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
import javax.inject.Inject
import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
@@ -241,9 +242,14 @@
threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
val result: CompletableDeferred<Unit>?
val request: Request?
- cameraGraph.acquireSession().use {
+ try {
+ cameraGraph.acquireSession()
+ } catch (e: CancellationException) {
+ Log.debug(e) { "Cannot acquire session at ${this@UseCaseCameraState}" }
+ null
+ }.let { session ->
synchronized(lock) {
- request = if (currentStreams.isEmpty()) {
+ request = if (currentStreams.isEmpty() || session == null) {
null
} else {
Request(
@@ -263,18 +269,21 @@
updating = false
updateSignal = null
}
-
- if (request == null) {
- it.stopRepeating()
- } else {
- result?.let { result ->
- synchronized(lock) {
- updateSignals.add(RequestSignal(submittedRequestCounter.value, result))
+ session?.use {
+ if (request == null) {
+ it.stopRepeating()
+ } else {
+ result?.let { result ->
+ synchronized(lock) {
+ updateSignals.add(
+ RequestSignal(submittedRequestCounter.value, result)
+ )
+ }
}
+ Log.debug { "Update RepeatingRequest: $request" }
+ it.startRepeating(request)
+ it.update3A(request.parameters)
}
- Log.debug { "Update RepeatingRequest: $request" }
- it.startRepeating(request)
- it.update3A(request.parameters)
}
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index d2d439e..5fd0c64 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -160,6 +160,9 @@
}
}
+ override fun close() {
+ }
+
data class FocusMeteringParams(
val aeRegions: List<MeteringRectangle>? = null,
val afRegions: List<MeteringRectangle>? = null,