blob: 0a13bd0e2731ad33e488ff0d99b90367f07d3b2c [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
package androidx.camera.camera2.pipe.integration.impl
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CaptureRequest
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.GraphState.GraphStateError
import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
import androidx.camera.camera2.pipe.RequestTemplate
import androidx.camera.camera2.pipe.core.Log.debug
import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
import androidx.camera.core.UseCase
import androidx.camera.core.impl.Config
import androidx.camera.core.impl.SessionConfig
import androidx.camera.core.impl.SessionProcessorSurface
import dagger.Binds
import dagger.Module
import javax.inject.Inject
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
internal val useCaseCameraIds = atomic(0)
internal val defaultOptionPriority = Config.OptionPriority.OPTIONAL
internal const val defaultTemplate = CameraDevice.TEMPLATE_PREVIEW
interface UseCaseCamera {
// UseCases
var runningUseCases: Set<UseCase>
interface RunningUseCasesChangeListener {
/**
* Invoked when value of [UseCaseCamera.runningUseCases] has been changed.
*/
fun onRunningUseCasesChanged()
}
// RequestControl of the UseCaseCamera
val requestControl: UseCaseCameraRequestControl
// Parameters
fun <T> setParameterAsync(
key: CaptureRequest.Key<T>,
value: T,
priority: Config.OptionPriority = defaultOptionPriority,
): Deferred<Unit>
fun setParametersAsync(
values: Map<CaptureRequest.Key<*>, Any>,
priority: Config.OptionPriority = defaultOptionPriority,
): Deferred<Unit>
fun setActiveResumeMode(enabled: Boolean) {}
// Lifecycle
fun close(): Job
}
/**
* API for interacting with a [CameraGraph] that has been configured with a set of [UseCase]'s
*/
@RequiresApi(21)
@UseCaseCameraScope
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") // Java version required for Dagger
class UseCaseCameraImpl @Inject constructor(
private val controls: java.util.Set<UseCaseCameraControl>,
private val useCaseGraphConfig: UseCaseGraphConfig,
private val useCases: java.util.ArrayList<UseCase>,
private val useCaseSurfaceManager: UseCaseSurfaceManager,
private val threads: UseCaseThreads,
private val sessionProcessorManager: SessionProcessorManager?,
private val sessionConfigAdapter: SessionConfigAdapter,
override val requestControl: UseCaseCameraRequestControl,
) : UseCaseCamera {
private val debugId = useCaseCameraIds.incrementAndGet()
private val closed = atomic(false)
override var runningUseCases = setOf<UseCase>()
set(value) {
field = value
// Note: This may be called with the same set of values that was previously set. This
// is used as a signal to indicate the properties of the UseCase may have changed.
SessionConfigAdapter(value).getValidSessionConfigOrNull()?.let {
requestControl.setSessionConfigAsync(it)
} ?: run {
debug { "Unable to reset the session due to invalid config" }
requestControl.setSessionConfigAsync(
SessionConfig.Builder().apply {
setTemplateType(defaultTemplate)
}.build()
)
}
controls.forEach { control ->
if (control is UseCaseCamera.RunningUseCasesChangeListener) {
control.onRunningUseCasesChanged()
}
}
}
init {
debug { "Configured $this for $useCases" }
useCaseGraphConfig.apply {
cameraStateAdapter.onGraphUpdated(graph)
}
threads.scope.launch {
useCaseGraphConfig.apply {
graph.graphState.collect {
cameraStateAdapter.onGraphStateUpdated(graph, it)
// Even if the UseCaseCamera is closed, we should still update the GraphState
// before cancelling the job, because it could be the last UseCaseCamera created
// (i.e., no new UseCaseCamera to update CameraStateAdapter that this one as
// stopped/closed).
if (closed.value &&
it is GraphStateStopped ||
it is GraphStateError
) {
this@launch.coroutineContext[Job]?.cancel()
}
// TODO: b/323614735: Technically our RequestProcessor implementation could be
// given to the SessionProcessor through onCaptureSessionStart after the
// new set of configurations (CameraGraph) is created. However, this seems to
// be causing occasional SIGBUS on the Android platform level. Delaying this
// seems to be mitigating the issue, but does result in overhead in startup
// latencies. Move this back to UseCaseManager once we understand more about
// the situation.
if (sessionProcessorManager != null && it is GraphStateStarted) {
val sessionProcessorSurfaces =
sessionConfigAdapter.deferrableSurfaces.map {
it as SessionProcessorSurface
}
val requestProcessorAdapter = RequestProcessorAdapter(
useCaseGraphConfig,
sessionProcessorSurfaces,
threads.scope,
)
sessionProcessorManager.onCaptureSessionStart(
requestProcessorAdapter
)
}
}
}
}
}
override fun close(): Job {
return if (closed.compareAndSet(expect = false, update = true)) {
threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
debug { "Closing $this" }
requestControl.close()
sessionProcessorManager?.prepareClose()
useCaseGraphConfig.graph.close()
if (sessionProcessorManager != null) {
useCaseGraphConfig.graph.graphState.first {
it is GraphStateStopped || it is GraphStateError
}
sessionProcessorManager.close()
}
useCaseSurfaceManager.stopAsync().await()
}
} else {
CompletableDeferred(Unit)
}
}
override fun <T> setParameterAsync(
key: CaptureRequest.Key<T>,
value: T,
priority: Config.OptionPriority,
): 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> = runIfNotClosed {
requestControl.addParametersAsync(
values = values,
optionPriority = priority
)
} ?: canceledResult
override fun setActiveResumeMode(enabled: Boolean) {
useCaseGraphConfig.graph.isForeground = enabled
}
private fun UseCaseCameraRequestControl.setSessionConfigAsync(
sessionConfig: SessionConfig
): 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
),
sessionConfig = sessionConfig,
)
} ?: canceledResult
private inline fun <R> runIfNotClosed(crossinline block: () -> R): R? {
return if (!closed.value) block() else null
}
override fun toString(): String = "UseCaseCamera-$debugId"
@Module
abstract class Bindings {
@UseCaseCameraScope
@Binds
abstract fun provideUseCaseCamera(useCaseCamera: UseCaseCameraImpl): UseCaseCamera
}
companion object {
private val canceledResult = CompletableDeferred<Unit>().apply { cancel() }
}
}