blob: 162e92bafbc000d14da0d525012d62ad3f157527 [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.
*/
package androidx.camera.camera2.pipe.integration.impl
import android.content.Context
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration.SESSION_HIGH_SPEED
import android.hardware.camera2.params.SessionConfiguration.SESSION_REGULAR
import android.media.MediaCodec
import android.os.Build
import androidx.annotation.GuardedBy
import androidx.annotation.VisibleForTesting
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraph.OperatingMode
import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.CameraStream
import androidx.camera.camera2.pipe.InputStream
import androidx.camera.camera2.pipe.OutputStream
import androidx.camera.camera2.pipe.StreamFormat
import androidx.camera.camera2.pipe.compat.CameraPipeKeys
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.EncoderProfilesProviderAdapter
import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
import androidx.camera.camera2.pipe.integration.adapter.SupportedSurfaceCombination
import androidx.camera.camera2.pipe.integration.adapter.ZslControl
import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCameraDeviceOnCameraGraphCloseQuirk
import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCaptureSessionOnDisconnectQuirk
import androidx.camera.camera2.pipe.integration.compat.quirk.CloseCaptureSessionOnVideoQuirk
import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
import androidx.camera.camera2.pipe.integration.compat.quirk.DisableAbortCapturesOnStopWithSessionProcessorQuirk
import androidx.camera.camera2.pipe.integration.config.CameraConfig
import androidx.camera.camera2.pipe.integration.config.CameraScope
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
import androidx.camera.core.DynamicRange
import androidx.camera.core.MirrorMode
import androidx.camera.core.UseCase
import androidx.camera.core.impl.CameraControlInternal
import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.core.impl.CameraInternal
import androidx.camera.core.impl.CameraMode
import androidx.camera.core.impl.DeferrableSurface
import androidx.camera.core.impl.PreviewConfig
import androidx.camera.core.impl.SessionConfig
import androidx.camera.core.impl.SessionConfig.OutputConfig.SURFACE_GROUP_ID_NONE
import androidx.camera.core.impl.SessionConfig.ValidatingBuilder
import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.stabilization.StabilizationMode
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.runBlocking
/**
* This class keeps track of the currently attached and active [UseCase]'s for a specific camera.
* A [UseCase] during its lifetime, can be:
*
* - Attached: This happens when a use case is bound to a CameraX Lifecycle, and signals that the
* camera should be opened, and a camera capture session should be created to include the
* stream corresponding to the use case. In the integration layer here, we'll recreate a
* CameraGraph when a use case is attached.
* - Detached: This happens when a use case is unbound from a CameraX Lifecycle, and signals that
* we no longer need this specific use case and therefore its corresponding stream in our
* current capture session. In the integration layer, we'll also recreate a CameraGraph when
* a use case is detached, though it might not be strictly necessary.
* - Active: This happens when the use case is considered "ready", meaning that the use case is
* ready to have frames delivered to it. In the case of the integration layer, this means we
* can start submitting the capture requests corresponding to the use case. An important note
* here is that a use case can actually become "active" before it is "attached", and thus we
* should only take action when a use case is both "attached" and "active".
* - Inactive: This happens when use case no longer needs frames delivered to it. This is can be
* seen as an optimization signal, as we technically are allowed to continue submitting
* capture requests, but we no longer need to. An example of this is when you clear the
* analyzer during ImageAnalysis.
*
* In this class, we also define a new term - "Running". A use case is considered running when it's
* both "attached" and "active". This means we should have a camera opened, a capture session with
* the streams created and have capture requests submitting.
*/
@OptIn(ExperimentalCamera2Interop::class)
@CameraScope
class UseCaseManager @Inject constructor(
private val cameraPipe: CameraPipe,
private val callbackMap: CameraCallbackMap,
private val requestListener: ComboRequestListener,
private val cameraConfig: CameraConfig,
private val builder: UseCaseCameraComponent.Builder,
private val cameraControl: CameraControlInternal,
private val zslControl: ZslControl,
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") // Java version required for Dagger
private val controls: java.util.Set<UseCaseCameraControl>,
private val camera2CameraControl: Camera2CameraControl,
private val cameraStateAdapter: CameraStateAdapter,
private val cameraQuirks: CameraQuirks,
private val cameraGraphFlags: CameraGraph.Flags,
private val cameraInternal: Provider<CameraInternal>,
private val useCaseThreads: Provider<UseCaseThreads>,
private val cameraInfoInternal: Provider<CameraInfoInternal>,
context: Context,
cameraProperties: CameraProperties,
displayInfoManager: DisplayInfoManager,
) {
private val lock = Any()
internal var sessionProcessor: SessionProcessor? = null
get() = synchronized(lock) {
return field
}
set(value) = synchronized(lock) {
field = value
}
@GuardedBy("lock")
private var sessionProcessorManager: SessionProcessorManager? = null
@GuardedBy("lock")
private val attachedUseCases = mutableSetOf<UseCase>()
@GuardedBy("lock")
private val activeUseCases = mutableSetOf<UseCase>()
@GuardedBy("lock")
private var activeResumeEnabled = false
@GuardedBy("lock")
private var shouldCreateCameraGraphImmediately = true
@GuardedBy("lock")
private var deferredUseCaseManagerConfig: UseCaseManagerConfig? = null
@GuardedBy("lock")
private var pendingSessionProcessorInitialization = false
@GuardedBy("lock")
private val pendingUseCasesToNotifyCameraControlReady = mutableSetOf<UseCase>()
private val meteringRepeating by lazy {
MeteringRepeating.Builder(
cameraProperties,
displayInfoManager
).build()
}
private val supportedSurfaceCombination by lazy {
SupportedSurfaceCombination(
context,
cameraProperties.metadata,
EncoderProfilesProviderAdapter(cameraConfig.cameraId.value, cameraQuirks.quirks)
)
}
@Volatile
private var _activeComponent: UseCaseCameraComponent? = null
val camera: UseCaseCamera?
get() = _activeComponent?.getUseCaseCamera()
val useCaseGraphConfig: UseCaseGraphConfig?
get() = _activeComponent?.getUseCaseGraphConfig()
private val closingCameraJobs = mutableListOf<Job>()
private val allControls = controls.toMutableSet().apply { add(camera2CameraControl) }
internal fun setCameraGraphCreationMode(createImmediately: Boolean) = synchronized(lock) {
shouldCreateCameraGraphImmediately = createImmediately
if (shouldCreateCameraGraphImmediately) {
// Clear the UseCaseManager configuration that haven't been "resumed" when we return
// to single camera operating mode early.
deferredUseCaseManagerConfig = null
}
}
internal fun getDeferredCameraGraphConfig() = synchronized(lock) {
deferredUseCaseManagerConfig?.cameraGraphConfig
}
/**
* This attaches the specified [useCases] to the current set of attached use cases. When any
* changes are identified (i.e., a new use case is added), the subsequent actions would trigger
* a recreation of the current CameraGraph if there is one.
*/
fun attach(useCases: List<UseCase>): Unit = synchronized(lock) {
if (useCases.isEmpty()) {
Log.warn { "Attach [] from $this (Ignored)" }
return
}
Log.debug { "Attaching $useCases from $this" }
val unattachedUseCases = useCases.filter { useCase ->
!attachedUseCases.contains(useCase)
}
// Notify state attached to use cases
for (useCase in unattachedUseCases) {
useCase.onStateAttached()
}
if (attachedUseCases.addAll(useCases)) {
if (!addOrRemoveRepeatingUseCase(getRunningUseCases())) {
updateZslDisabledByUseCaseConfigStatus()
refreshAttachedUseCases(attachedUseCases)
}
}
if (sessionProcessor != null || !shouldCreateCameraGraphImmediately) {
pendingUseCasesToNotifyCameraControlReady.addAll(unattachedUseCases)
} else {
unattachedUseCases.forEach { useCase ->
// Notify CameraControl is ready after the UseCaseCamera is created
useCase.onCameraControlReady()
}
}
}
/**
* This detaches the specified [useCases] from the current set of attached use cases. When any
* changes are identified (i.e., an existing use case is removed), the subsequent actions would
* trigger a recreation of the current CameraGraph.
*/
fun detach(useCases: List<UseCase>): Unit = synchronized(lock) {
if (useCases.isEmpty()) {
Log.warn { "Detaching [] from $this (Ignored)" }
return
}
Log.debug { "Detaching $useCases from $this" }
// When use cases are detached, they should be considered inactive as well. Also note that
// we remove the use cases from our set directly because the subsequent cleanup actions from
// detaching the use cases should suffice here.
activeUseCases.removeAll(useCases)
// Notify state detached to use cases
for (useCase in useCases) {
if (attachedUseCases.contains(useCase)) {
useCase.onStateDetached()
}
}
// TODO: We might only want to tear down when the number of attached use cases goes to
// zero. If a single UseCase is removed, we could deactivate it?
if (attachedUseCases.removeAll(useCases)) {
if (addOrRemoveRepeatingUseCase(getRunningUseCases())) {
return
}
if (attachedUseCases.isEmpty()) {
cameraControl.setZslDisabledByUserCaseConfig(false)
} else {
updateZslDisabledByUseCaseConfigStatus()
}
refreshAttachedUseCases(attachedUseCases)
}
pendingUseCasesToNotifyCameraControlReady.removeAll(useCases)
}
/**
* This marks the specified [useCase] as active ("activate"). This refreshes the current set of
* active use cases, and if any changes are identified, we update [UseCaseCamera] with the
* latest set of "running" (attached and active) use cases, which will in turn trigger actions
* for SessionConfig updates.
*/
fun activate(useCase: UseCase) = synchronized(lock) {
if (activeUseCases.add(useCase)) {
refreshRunningUseCases()
}
}
/**
* This marks the specified [useCase] as inactive ("deactivate"). This refreshes the current set
* of active use cases, and if any changes are identified, we update [UseCaseCamera] with the
* latest set of "running" (attached and active) use cases, which will in turn trigger actions
* for SessionConfig updates.
*/
fun deactivate(useCase: UseCase) = synchronized(lock) {
if (activeUseCases.remove(useCase)) {
refreshRunningUseCases()
}
}
fun update(useCase: UseCase) = synchronized(lock) {
if (attachedUseCases.contains(useCase)) {
refreshRunningUseCases()
}
}
fun reset(useCase: UseCase) = synchronized(lock) {
if (attachedUseCases.contains(useCase)) {
refreshAttachedUseCases(attachedUseCases)
}
}
fun setActiveResumeMode(enabled: Boolean) = synchronized(lock) {
activeResumeEnabled = enabled
camera?.setActiveResumeMode(enabled)
}
suspend fun close() {
val closingJobs = synchronized(lock) {
if (attachedUseCases.isNotEmpty()) {
detach(attachedUseCases.toList())
}
meteringRepeating.onUnbind()
closingCameraJobs.toList()
}
closingJobs.joinAll()
}
override fun toString(): String = "UseCaseManager<${cameraConfig.cameraId}>"
@GuardedBy("lock")
private fun refreshRunningUseCases() {
// refreshRunningUseCases() is called after we activate, deactivate, update or have finished
// attaching use cases. If the SessionProcessor is still being initialized, we cannot
// refresh the current set of running use cases (we don't have a UseCaseCamera), but we
// can safely abort here, because once the SessionProcessor is initialized, we'll resume
// the process of creating UseCaseCamera components, finish attaching use cases and finally
// invoke refreshingRunningUseCases().
if (pendingSessionProcessorInitialization) return
val runningUseCases = getRunningUseCases()
when {
shouldAddRepeatingUseCase(runningUseCases) -> addRepeatingUseCase()
shouldRemoveRepeatingUseCase(runningUseCases) -> removeRepeatingUseCase()
else -> camera?.runningUseCases = runningUseCases
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@GuardedBy("lock")
private fun refreshAttachedUseCases(newUseCases: Set<UseCase>) {
val useCases = newUseCases.toList()
// Close prior camera graph
camera.let { useCaseCamera ->
_activeComponent = null
useCaseCamera?.close()?.let { closingJob ->
if (sessionProcessorManager != null) {
// If the current session was created for extensions. We need to make sure
// the closing procedures are done. This is needed because the same
// SessionProcessor instance may be reused in the next extensions session, and
// we need to make sure we de-initialize the current SessionProcessor session.
runBlocking { closingJob.join() }
} else {
closingCameraJobs.add(closingJob)
closingJob.invokeOnCompletion {
synchronized(lock) {
closingCameraJobs.remove(closingJob)
}
}
}
}
}
sessionProcessorManager?.let {
it.close()
sessionProcessorManager = null
pendingSessionProcessorInitialization = false
}
// Update list of active useCases
if (useCases.isEmpty()) {
for (control in allControls) {
control.useCaseCamera = null
control.reset()
}
return
}
if (sessionProcessor != null || !shouldCreateCameraGraphImmediately) {
// We will need to set the UseCaseCamera to null since the new UseCaseCamera along with
// its respective CameraGraph configurations won't be ready until:
//
// - SessionProcessorManager finishes the initialization, _acquires the lock_, and
// resume UseCaseManager successfully
// - And/or, the UseCaseManager is ready to be resumed under concurrent camera settings.
for (control in allControls) {
control.useCaseCamera = null
}
}
if (sessionProcessor != null) {
Log.debug { "Setting up UseCaseManager with SessionProcessorManager" }
sessionProcessorManager = SessionProcessorManager(
sessionProcessor!!,
cameraInfoInternal.get(),
useCaseThreads.get().scope,
).also { manager ->
pendingSessionProcessorInitialization = true
manager.initialize(this, useCases) { config ->
synchronized(lock) {
if (manager.isClosed()) {
// We've been cancelled by other use case transactions. This means the
// attached set of use cases have been updated in the meantime, and the
// UseCaseManagerConfig we have here is obsolete, so we can simply abort
// here.
return@initialize
}
if (config == null) {
Log.error { "Failed to initialize SessionProcessor" }
manager.close()
sessionProcessorManager = null
return@initialize
}
pendingSessionProcessorInitialization = false
this@UseCaseManager.tryResumeUseCaseManager(config)
}
}
}
return
} else {
val sessionConfigAdapter = SessionConfigAdapter(useCases)
val streamConfigMap = mutableMapOf<CameraStream.Config, DeferrableSurface>()
val graphConfig = createCameraGraphConfig(sessionConfigAdapter, streamConfigMap)
val useCaseManagerConfig = UseCaseManagerConfig(
useCases,
sessionConfigAdapter,
graphConfig,
streamConfigMap
)
this.tryResumeUseCaseManager(useCaseManagerConfig)
}
}
@VisibleForTesting
@GuardedBy("lock")
internal fun tryResumeUseCaseManager(useCaseManagerConfig: UseCaseManagerConfig) {
if (!shouldCreateCameraGraphImmediately) {
deferredUseCaseManagerConfig = useCaseManagerConfig
return
}
val cameraGraph = cameraPipe.create(useCaseManagerConfig.cameraGraphConfig)
beginComponentCreation(useCaseManagerConfig, cameraGraph)
}
internal fun resumeDeferredComponentCreation(cameraGraph: CameraGraph) = synchronized(lock) {
beginComponentCreation(checkNotNull(deferredUseCaseManagerConfig), cameraGraph)
}
@GuardedBy("lock")
private fun beginComponentCreation(
useCaseManagerConfig: UseCaseManagerConfig,
cameraGraph: CameraGraph
) {
val sessionProcessorEnabled =
useCaseManagerConfig.sessionConfigAdapter.isSessionProcessorEnabled
with(useCaseManagerConfig) {
if (sessionProcessorEnabled) {
for ((streamConfig, deferrableSurface) in streamConfigMap) {
cameraGraph.streams[streamConfig]?.let {
cameraGraph.setSurface(it.id, deferrableSurface.surface.get())
}
}
}
// Create and configure the new camera component.
_activeComponent =
builder.config(
UseCaseCameraConfig(
useCases,
sessionConfigAdapter,
cameraStateAdapter,
cameraGraph,
streamConfigMap,
sessionProcessorManager,
)
).build()
for (control in allControls) {
control.useCaseCamera = camera
}
camera?.setActiveResumeMode(activeResumeEnabled)
refreshRunningUseCases()
}
Log.debug { "Notifying $pendingUseCasesToNotifyCameraControlReady camera control ready" }
for (useCase in pendingUseCasesToNotifyCameraControlReady) {
useCase.onCameraControlReady()
}
pendingUseCasesToNotifyCameraControlReady.clear()
}
@GuardedBy("lock")
private fun getRunningUseCases(): Set<UseCase> {
return attachedUseCases.intersect(activeUseCases)
}
/**
* Adds or removes repeating use case if needed.
*
* @param runningUseCases the set of currently running use cases
* @return true if repeating use cases is added or removed, false otherwise
*/
@GuardedBy("lock")
private fun addOrRemoveRepeatingUseCase(runningUseCases: Set<UseCase>): Boolean {
if (shouldAddRepeatingUseCase(runningUseCases)) {
addRepeatingUseCase()
return true
}
if (shouldRemoveRepeatingUseCase(runningUseCases)) {
removeRepeatingUseCase()
return true
}
return false
}
@GuardedBy("lock")
private fun shouldAddRepeatingUseCase(runningUseCases: Set<UseCase>): Boolean {
val meteringRepeatingEnabled = attachedUseCases.contains(meteringRepeating)
if (!meteringRepeatingEnabled) {
val activeSurfaces = runningUseCases.withoutMetering().surfaceCount()
return activeSurfaces > 0 && with(attachedUseCases.withoutMetering()) {
(onlyVideoCapture() || requireMeteringRepeating()) && supportMeteringCombination()
}
}
return false
}
@GuardedBy("lock")
private fun addRepeatingUseCase() {
meteringRepeating.bindToCamera(cameraInternal.get(), null, null)
meteringRepeating.setupSession()
attach(listOf(meteringRepeating))
activate(meteringRepeating)
}
@GuardedBy("lock")
private fun shouldRemoveRepeatingUseCase(runningUseCases: Set<UseCase>): Boolean {
val meteringRepeatingEnabled = runningUseCases.contains(meteringRepeating)
if (meteringRepeatingEnabled) {
val activeSurfaces = runningUseCases.withoutMetering().surfaceCount()
return activeSurfaces == 0 || with(attachedUseCases.withoutMetering()) {
!(onlyVideoCapture() || requireMeteringRepeating()) || !supportMeteringCombination()
}
}
return false
}
@GuardedBy("lock")
private fun removeRepeatingUseCase() {
deactivate(meteringRepeating)
detach(listOf(meteringRepeating))
meteringRepeating.unbindFromCamera(cameraInternal.get())
}
internal fun createCameraGraphConfig(
sessionConfigAdapter: SessionConfigAdapter,
streamConfigMap: MutableMap<CameraStream.Config, DeferrableSurface>,
isExtensions: Boolean = false,
): CameraGraph.Config {
return Companion.createCameraGraphConfig(
sessionConfigAdapter,
streamConfigMap,
callbackMap,
requestListener,
cameraConfig,
cameraQuirks,
cameraGraphFlags,
zslControl,
isExtensions,
)
}
private fun Collection<UseCase>.onlyVideoCapture(): Boolean {
return isNotEmpty() && checkSurfaces { _, sessionSurfaces ->
sessionSurfaces.isNotEmpty() && sessionSurfaces.all {
it.containerClass == MediaCodec::class.java
}
}
}
private fun Collection<UseCase>.supportMeteringCombination(): Boolean {
val useCases = this.toMutableList().apply { add(meteringRepeating) }
if (meteringRepeating.attachedSurfaceResolution == null) {
meteringRepeating.setupSession()
}
return isCombinationSupported(useCases).also {
Log.debug { "Combination of $useCases is supported: $it" }
}
}
private fun isCombinationSupported(currentUseCases: Collection<UseCase>): Boolean {
val surfaceConfigs = currentUseCases.map { useCase ->
// TODO: Test with correct Camera Mode when concurrent mode / ultra high resolution is
// implemented.
supportedSurfaceCombination.transformSurfaceConfig(
CameraMode.DEFAULT,
useCase.imageFormat,
useCase.attachedSurfaceResolution!!
)
}
var isPreviewStabilizationOn = false
for (useCase in currentUseCases) {
if (useCase.currentConfig is PreviewConfig) {
isPreviewStabilizationOn =
useCase.currentConfig.previewStabilizationMode == StabilizationMode.ON
}
}
return supportedSurfaceCombination.checkSupported(
SupportedSurfaceCombination.FeatureSettings(
CameraMode.DEFAULT,
DynamicRange.BIT_DEPTH_8_BIT,
isPreviewStabilizationOn
), surfaceConfigs
)
}
private fun Collection<UseCase>.surfaceCount(): Int =
ValidatingBuilder().let { validatingBuilder ->
forEach { useCase -> validatingBuilder.add(useCase.sessionConfig) }
return validatingBuilder.build().surfaces.size
}
private fun Collection<UseCase>.withoutMetering(): Collection<UseCase> =
filterNot { it is MeteringRepeating }
private fun Collection<UseCase>.requireMeteringRepeating(): Boolean {
return isNotEmpty() && checkSurfaces { repeatingSurfaces, sessionSurfaces ->
// There is no repeating UseCases
sessionSurfaces.isNotEmpty() && repeatingSurfaces.isEmpty()
}
}
private fun Collection<UseCase>.checkSurfaces(
predicate: (
repeatingSurfaces: List<DeferrableSurface>,
sessionSurfaces: List<DeferrableSurface>
) -> Boolean
): Boolean = ValidatingBuilder().let { validatingBuilder ->
forEach { useCase -> validatingBuilder.add(useCase.sessionConfig) }
val sessionConfig = validatingBuilder.build()
val captureConfig = sessionConfig.repeatingCaptureConfig
return predicate(captureConfig.surfaces, sessionConfig.surfaces)
}
private fun updateZslDisabledByUseCaseConfigStatus() {
val disableZsl = attachedUseCases.any { it.currentConfig.isZslDisabled(false) }
cameraControl.setZslDisabledByUserCaseConfig(disableZsl)
}
companion object {
internal data class UseCaseManagerConfig(
val useCases: List<UseCase>,
val sessionConfigAdapter: SessionConfigAdapter,
val cameraGraphConfig: CameraGraph.Config,
val streamConfigMap: MutableMap<CameraStream.Config, DeferrableSurface>
)
fun SessionConfig.toCamera2ImplConfig(): Camera2ImplConfig {
return Camera2ImplConfig(implementationOptions)
}
fun createCameraGraphConfig(
sessionConfigAdapter: SessionConfigAdapter,
streamConfigMap: MutableMap<CameraStream.Config, DeferrableSurface>,
callbackMap: CameraCallbackMap,
requestListener: ComboRequestListener,
cameraConfig: CameraConfig,
cameraQuirks: CameraQuirks,
cameraGraphFlags: CameraGraph.Flags?,
zslControl: ZslControl,
isExtensions: Boolean = false,
): CameraGraph.Config {
var containsVideo = false
var operatingMode = OperatingMode.NORMAL
val streamGroupMap = mutableMapOf<Int, MutableList<CameraStream.Config>>()
val inputStreams = mutableListOf<InputStream.Config>()
sessionConfigAdapter.getValidSessionConfigOrNull()?.let { sessionConfig ->
operatingMode = when (sessionConfig.sessionType) {
SESSION_REGULAR -> OperatingMode.NORMAL
SESSION_HIGH_SPEED -> OperatingMode.HIGH_SPEED
else -> OperatingMode.custom(sessionConfig.sessionType)
}
val physicalCameraIdForAllStreams =
sessionConfig.toCamera2ImplConfig().getPhysicalCameraId(null)
var zslStream: CameraStream.Config? = null
for (outputConfig in sessionConfig.outputConfigs) {
val deferrableSurface = outputConfig.surface
val physicalCameraId =
physicalCameraIdForAllStreams ?: outputConfig.physicalCameraId
val mirrorMode = outputConfig.mirrorMode
val outputStreamConfig = OutputStream.Config.create(
size = deferrableSurface.prescribedSize,
format = StreamFormat(deferrableSurface.prescribedStreamFormat),
camera = if (physicalCameraId == null) {
null
} else {
CameraId.fromCamera2Id(physicalCameraId)
},
// No need to map MIRROR_MODE_ON_FRONT_ONLY to MIRROR_MODE_AUTO
// since its default value in framework
mirrorMode = when (mirrorMode) {
MirrorMode.MIRROR_MODE_OFF -> OutputStream.MirrorMode(
OutputConfiguration.MIRROR_MODE_NONE)
MirrorMode.MIRROR_MODE_ON -> OutputStream.MirrorMode(
OutputConfiguration.MIRROR_MODE_H)
else -> null
},
streamUseCase = getStreamUseCase(
deferrableSurface,
sessionConfigAdapter.surfaceToStreamUseCaseMap
),
streamUseHint = getStreamUseHint(
deferrableSurface,
sessionConfigAdapter.surfaceToStreamUseHintMap
),
)
val surfaces = outputConfig.sharedSurfaces + deferrableSurface
for (surface in surfaces) {
val stream = CameraStream.Config.create(outputStreamConfig)
streamConfigMap[stream] = surface
if (outputConfig.surfaceGroupId != SURFACE_GROUP_ID_NONE) {
val streamList = streamGroupMap[outputConfig.surfaceGroupId]
if (streamList == null) {
streamGroupMap[outputConfig.surfaceGroupId] =
mutableListOf(stream)
} else {
streamList.add(stream)
}
}
if (surface.containerClass == MediaCodec::class.java) {
containsVideo = true
}
if (surface != deferrableSurface) continue
if (zslControl.isZslSurface(surface, sessionConfig)) {
zslStream = stream
}
}
}
if (sessionConfig.inputConfiguration != null) {
zslStream?.let {
inputStreams.add(
InputStream.Config(
stream = it,
maxImages = 1,
streamFormat = it.outputs.single().format,
)
)
}
}
}
val shouldCloseCaptureSessionOnDisconnect =
if (isExtensions) {
true
} else if (CameraQuirks.isImmediateSurfaceReleaseAllowed()) {
// If we can release Surfaces immediately, we'll finalize the session when the
// camera graph is closed (through FinalizeSessionOnCloseQuirk), and thus we won't
// need to explicitly close the capture session.
false
} else {
if (cameraQuirks.quirks.contains(CloseCaptureSessionOnVideoQuirk::class.java) &&
containsVideo
) {
true
} else {
DeviceQuirks[CloseCaptureSessionOnDisconnectQuirk::class.java] != null
}
}
val shouldCloseCameraDeviceOnClose =
DeviceQuirks[CloseCameraDeviceOnCameraGraphCloseQuirk::class.java] != null
val shouldAbortCapturesOnStop =
if (isExtensions &&
DeviceQuirks[DisableAbortCapturesOnStopWithSessionProcessorQuirk::class.java] !=
null
) {
false
} else {
/**
* @see [CameraGraph.Flags.abortCapturesOnStop]
*/
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
}
val combinedFlags = cameraGraphFlags?.copy(
abortCapturesOnStop = shouldAbortCapturesOnStop,
quirkCloseCaptureSessionOnDisconnect = shouldCloseCaptureSessionOnDisconnect,
quirkCloseCameraDeviceOnClose = shouldCloseCameraDeviceOnClose,
)
?: CameraGraph.Flags(
abortCapturesOnStop = shouldAbortCapturesOnStop,
quirkCloseCaptureSessionOnDisconnect = shouldCloseCaptureSessionOnDisconnect,
quirkCloseCameraDeviceOnClose = shouldCloseCameraDeviceOnClose,
)
// Set video stabilization mode to capture request
var videoStabilizationMode = CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_OFF
if (sessionConfigAdapter.getValidSessionConfigOrNull() != null) {
val config = sessionConfigAdapter
.getValidSessionConfigOrNull()!!
.repeatingCaptureConfig
val isPreviewStabilizationMode = config.previewStabilizationMode
val isVideoStabilizationMode = config.videoStabilizationMode
if (isPreviewStabilizationMode == StabilizationMode.OFF ||
isVideoStabilizationMode == StabilizationMode.OFF
) {
videoStabilizationMode = CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF
} else if (isPreviewStabilizationMode == StabilizationMode.ON) {
videoStabilizationMode =
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION
} else if (isVideoStabilizationMode == StabilizationMode.ON) {
videoStabilizationMode = CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON
}
}
val defaultParameters = buildMap<Any, Any?> {
if (isExtensions) {
set(CameraPipeKeys.ignore3ARequiredParameters, true)
}
set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, videoStabilizationMode)
set(
CameraPipeKeys.camera2CaptureRequestTag,
"android.hardware.camera2.CaptureRequest.setTag.CX"
)
}
// TODO: b/327517884 - Add a quirk to not abort captures on stop for certain OEMs during
// extension sessions.
// Build up a config (using TEMPLATE_PREVIEW by default)
return CameraGraph.Config(
camera = cameraConfig.cameraId,
streams = streamConfigMap.keys.toList(),
exclusiveStreamGroups = streamGroupMap.values.toList(),
input = if (inputStreams.isEmpty()) null else inputStreams,
sessionMode = operatingMode,
defaultListeners = listOf(callbackMap, requestListener),
defaultParameters = defaultParameters,
flags = combinedFlags,
)
}
private fun getStreamUseCase(
deferrableSurface: DeferrableSurface,
mapping: Map<DeferrableSurface, Long>
): OutputStream.StreamUseCase? {
return mapping[deferrableSurface]?.let { OutputStream.StreamUseCase(it) }
}
private fun getStreamUseHint(
deferrableSurface: DeferrableSurface,
mapping: Map<DeferrableSurface, Long>
): OutputStream.StreamUseHint? {
return mapping[deferrableSurface]?.let { OutputStream.StreamUseHint(it) }
}
}
}