1 /*
<lambda>null2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.camera2.pipe.integration.adapter
18 
19 import android.hardware.camera2.CameraDevice
20 import android.media.MediaCodec
21 import android.util.Range
22 import androidx.annotation.VisibleForTesting
23 import androidx.camera.camera2.pipe.OutputStream
24 import androidx.camera.camera2.pipe.core.Log
25 import androidx.camera.camera2.pipe.core.Log.debug
26 import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig
27 import androidx.camera.camera2.pipe.integration.impl.STREAM_USE_HINT_OPTION
28 import androidx.camera.camera2.pipe.integration.internal.StreamUseCaseUtil
29 import androidx.camera.core.ImageAnalysis
30 import androidx.camera.core.ImageCapture
31 import androidx.camera.core.Preview
32 import androidx.camera.core.UseCase
33 import androidx.camera.core.impl.DeferrableSurface
34 import androidx.camera.core.impl.SessionConfig
35 import androidx.camera.core.impl.StreamSpec
36 import androidx.camera.core.impl.UseCaseConfig
37 import androidx.camera.core.streamsharing.StreamSharing
38 import java.util.Collections
39 import kotlinx.coroutines.CoroutineScope
40 import kotlinx.coroutines.Dispatchers
41 import kotlinx.coroutines.launch
42 
43 /**
44  * Aggregate the SessionConfig from a List of [UseCase]s, and provide a validated SessionConfig for
45  * operation.
46  */
47 public class SessionConfigAdapter(
48     private val useCases: Collection<UseCase>,
49     private val sessionProcessorConfig: SessionConfig? = null,
50     private val isPrimary: Boolean = true,
51 ) {
52     public val isSessionProcessorEnabled: Boolean = sessionProcessorConfig != null
53     public val surfaceToStreamUseCaseMap: Map<DeferrableSurface, Long> by lazy {
54         val sessionConfigs = mutableListOf<SessionConfig>()
55         val useCaseConfigs = mutableListOf<UseCaseConfig<*>>()
56         for (useCase in useCases) {
57             sessionConfigs.add(useCase.getSessionConfig(isPrimary))
58             useCaseConfigs.add(useCase.currentConfig)
59         }
60         getSurfaceToStreamUseCaseMapping(sessionConfigs, useCaseConfigs)
61     }
62     public val surfaceToStreamUseHintMap: Map<DeferrableSurface, Long> by lazy {
63         val sessionConfigs = useCases.map { it.getSessionConfig(isPrimary) }
64         getSurfaceToStreamUseHintMapping(sessionConfigs)
65     }
66     private val validatingBuilder: SessionConfig.ValidatingBuilder by lazy {
67         val validatingBuilder = SessionConfig.ValidatingBuilder()
68 
69         for (useCase in useCases) {
70             validatingBuilder.add(useCase.getSessionConfig(isPrimary))
71         }
72 
73         if (sessionProcessorConfig != null) {
74             validatingBuilder.clearSurfaces()
75             validatingBuilder.add(sessionProcessorConfig)
76         }
77 
78         validatingBuilder
79     }
80 
81     private val sessionConfig: SessionConfig by lazy {
82         check(validatingBuilder.isValid)
83 
84         validatingBuilder.build()
85     }
86 
87     public val deferrableSurfaces: List<DeferrableSurface> by lazy {
88         check(validatingBuilder.isValid)
89 
90         sessionConfig.postviewOutputConfig?.let {
91             Collections.unmodifiableList(
92                 mutableListOf<DeferrableSurface>().apply {
93                     addAll(sessionConfig.surfaces)
94                     add(it.surface)
95                 }
96             )
97         } ?: sessionConfig.surfaces
98     }
99 
100     public fun getValidSessionConfigOrNull(): SessionConfig? {
101         return if (isSessionConfigValid()) sessionConfig else null
102     }
103 
104     public fun isSessionConfigValid(): Boolean {
105         return validatingBuilder.isValid
106     }
107 
108     public fun reportSurfaceInvalid(deferrableSurface: DeferrableSurface) {
109         debug { "Unavailable $deferrableSurface, notify SessionConfig invalid" }
110 
111         // Only report error to one SessionConfig, CameraInternal#onUseCaseReset()
112         // will handle the other failed Surfaces if there are any.
113         val sessionConfig =
114             useCases
115                 .firstOrNull { useCase ->
116                     val sessionConfig = useCase.getSessionConfig(isPrimary)
117                     sessionConfig.surfaces.contains(deferrableSurface)
118                 }
119                 ?.sessionConfig
120 
121         CoroutineScope(Dispatchers.Main.immediate).launch {
122             // The error listener is used to notify the UseCase to recreate the pipeline,
123             // and the create pipeline task would be executed on the main thread.
124             sessionConfig?.errorListener?.apply {
125                 onError(sessionConfig, SessionConfig.SessionError.SESSION_ERROR_SURFACE_NEEDS_RESET)
126             }
127         }
128     }
129 
130     public fun getExpectedFrameRateRange(): Range<Int>? {
131         return if (
132             isSessionConfigValid() &&
133                 sessionConfig.expectedFrameRateRange != StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
134         )
135             sessionConfig.expectedFrameRateRange
136         else null
137     }
138 
139     /**
140      * Populates the mapping between surfaces of a capture session and the Stream Use Case of their
141      * associated stream.
142      *
143      * @param sessionConfigs collection of all session configs for this capture session
144      * @return the mapping between surfaces and Stream Use Case flag
145      */
146     @VisibleForTesting
147     public fun getSurfaceToStreamUseCaseMapping(
148         sessionConfigs: Collection<SessionConfig>,
149         useCaseConfigs: Collection<UseCaseConfig<*>>,
150     ): Map<DeferrableSurface, Long> {
151         if (sessionConfigs.any { it.templateType == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG }) {
152             // If is ZSL, do not populate anything.
153             Log.error { "ZSL in populateSurfaceToStreamUseCaseMapping()" }
154             return emptyMap()
155         }
156 
157         val mapping = mutableMapOf<DeferrableSurface, Long>()
158         StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
159             sessionConfigs,
160             useCaseConfigs,
161             mapping
162         )
163 
164         return mapping
165     }
166 
167     /**
168      * Populates the mapping between surfaces of a capture session and the Stream Use Hint of their
169      * associated stream.
170      *
171      * @param sessionConfigs collection of all session configs for this capture session
172      * @return the mapping between surfaces and Stream Use Hint flag
173      */
174     @VisibleForTesting
175     public fun getSurfaceToStreamUseHintMapping(
176         sessionConfigs: Collection<SessionConfig>
177     ): Map<DeferrableSurface, Long> {
178         val mapping = mutableMapOf<DeferrableSurface, Long>()
179         for (sessionConfig in sessionConfigs) {
180             for (surface in sessionConfig.surfaces) {
181                 if (
182                     sessionConfig.implementationOptions.containsOption(STREAM_USE_HINT_OPTION) &&
183                         sessionConfig.implementationOptions.retrieveOption(
184                             STREAM_USE_HINT_OPTION
185                         ) != null
186                 ) {
187                     mapping[surface] =
188                         sessionConfig.implementationOptions.retrieveOption(STREAM_USE_HINT_OPTION)!!
189                     continue
190                 }
191             }
192         }
193         return mapping
194     }
195 
196     private fun getStreamUseCaseForContainerClass(kClass: Class<*>?): Long {
197         return when (kClass) {
198             ImageAnalysis::class.java -> OutputStream.StreamUseCase.PREVIEW.value
199             Preview::class.java -> OutputStream.StreamUseCase.PREVIEW.value
200             ImageCapture::class.java -> OutputStream.StreamUseCase.STILL_CAPTURE.value
201             MediaCodec::class.java -> OutputStream.StreamUseCase.VIDEO_RECORD.value
202             StreamSharing::class.java -> OutputStream.StreamUseCase.VIDEO_RECORD.value
203             else -> OutputStream.StreamUseCase.DEFAULT.value
204         }
205     }
206 
207     private fun getStreamUseHintForContainerClass(kClass: Class<*>?): Long {
208         return when (kClass) {
209             MediaCodec::class.java -> OutputStream.StreamUseHint.VIDEO_RECORD.value
210             StreamSharing::class.java -> OutputStream.StreamUseHint.VIDEO_RECORD.value
211             else -> OutputStream.StreamUseHint.DEFAULT.value
212         }
213     }
214 
215     public companion object {
216         public fun SessionConfig.toCamera2ImplConfig(): Camera2ImplConfig {
217             return Camera2ImplConfig(implementationOptions)
218         }
219 
220         public fun UseCase.getSessionConfig(isPrimary: Boolean): SessionConfig {
221             return if (isPrimary) sessionConfig else secondarySessionConfig
222         }
223     }
224 }
225