1 /*
2  * 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.compat
18 
19 import android.graphics.SurfaceTexture
20 import android.hardware.camera2.params.OutputConfiguration
21 import android.os.Build
22 import android.util.Size
23 import android.view.Surface
24 import android.view.SurfaceHolder
25 import androidx.annotation.RequiresApi
26 import androidx.camera.camera2.pipe.CameraId
27 import androidx.camera.camera2.pipe.OutputStream.DynamicRangeProfile
28 import androidx.camera.camera2.pipe.OutputStream.MirrorMode
29 import androidx.camera.camera2.pipe.OutputStream.OutputType
30 import androidx.camera.camera2.pipe.OutputStream.SensorPixelMode
31 import androidx.camera.camera2.pipe.OutputStream.StreamUseCase
32 import androidx.camera.camera2.pipe.OutputStream.TimestampBase
33 import androidx.camera.camera2.pipe.UnsafeWrapper
34 import androidx.camera.camera2.pipe.compat.OutputConfigurationWrapper.Companion.SURFACE_GROUP_ID_NONE
35 import androidx.camera.camera2.pipe.core.Log
36 import androidx.camera.camera2.pipe.core.checkNOrHigher
37 import androidx.camera.camera2.pipe.core.checkOOrHigher
38 import androidx.camera.camera2.pipe.core.checkPOrHigher
39 import java.util.concurrent.Executor
40 import kotlin.reflect.KClass
41 
42 /**
43  * A data class that mirrors the fields in [android.hardware.camera2.params.SessionConfiguration] so
44  * that a real instance can be constructed when creating a
45  * [android.hardware.camera2.CameraCaptureSession] on newer versions of the OS.
46  */
47 internal data class SessionConfigData(
48     val sessionType: Int,
49     val inputConfiguration: List<InputConfigData>?,
50     val outputConfigurations: List<OutputConfigurationWrapper>,
51     val executor: Executor,
52     val stateCallback: CameraCaptureSessionWrapper.StateCallback,
53     val sessionTemplateId: Int,
54     val sessionParameters: Map<*, Any?>,
55 )
56 
57 /**
58  * A data class that mirrors the fields in
59  * [android.hardware.camera2.params.ExtensionSessionConfiguration] so that a real instance can be
60  * constructed when creating a [android.hardware.camera2.CameraExtensionSession] on newer versions
61  * of the OS.
62  */
63 internal data class ExtensionSessionConfigData(
64     val sessionType: Int,
65     val outputConfigurations: List<OutputConfigurationWrapper>,
66     val executor: Executor,
67     val stateCallback: CameraCaptureSessionWrapper.StateCallback,
68     val sessionTemplateId: Int,
69     val sessionParameters: Map<*, Any?>,
70     val extensionMode: Int? = null,
71     val extensionStateCallback: CameraExtensionSessionWrapper.StateCallback? = null,
72     val postviewOutputConfiguration: OutputConfigurationWrapper? = null
73 )
74 
75 internal object Camera2SessionTypes {
76     const val SESSION_TYPE_REGULAR = 0
77     const val SESSION_TYPE_HIGH_SPEED = 1
78     const val SESSION_TYPE_EXTENSION = 2
79 }
80 
81 /**
82  * A data class that mirrors the fields in [android.hardware.camera2.params.InputConfiguration] so
83  * that a real instance can be constructed when creating a
84  * [android.hardware.camera2.CameraCaptureSession] on newer versions of the OS.
85  */
86 internal data class InputConfigData(val width: Int, val height: Int, val format: Int)
87 
88 /**
89  * An interface for [OutputConfiguration] with minor modifications.
90  *
91  * The primary modifications to this class are to make it harder to accidentally changes things that
92  * cannot be modified after the [android.hardware.camera2.CameraCaptureSession] has been created.
93  *
94  * [OutputConfiguration]'s are NOT immutable, and changing state of an [OutputConfiguration] may
95  * require the CameraCaptureSession to be finalized or updated.
96  */
97 internal interface OutputConfigurationWrapper : UnsafeWrapper {
98     /**
99      * This method will return null if the output configuration was created without a Surface, and
100      * until addSurface is called for the first time.
101      *
102      * @see OutputConfiguration.getSurface
103      */
104     val surface: Surface?
105 
106     /**
107      * This method returns the current list of surfaces for this [OutputConfiguration]. Since the
108      * [OutputConfiguration] is stateful, this value may change as a result of calling addSurface or
109      * removeSurface.
110      *
111      * @see OutputConfiguration.getSurfaces
112      */
113     val surfaces: List<Surface>
114 
115     /** @see OutputConfiguration.addSurface */
addSurfacenull116     fun addSurface(surface: Surface)
117 
118     /** @see OutputConfiguration.removeSurface */
119     fun removeSurface(surface: Surface)
120 
121     /** @see OutputConfiguration.setPhysicalCameraId */
122     val physicalCameraId: CameraId?
123 
124     /** @see OutputConfiguration.enableSurfaceSharing */
125     val surfaceSharing: Boolean
126 
127     /** @see OutputConfiguration.getMaxSharedSurfaceCount */
128     val maxSharedSurfaceCount: Int
129 
130     /** @see OutputConfiguration.getSurfaceGroupId */
131     val surfaceGroupId: Int
132 
133     companion object {
134         const val SURFACE_GROUP_ID_NONE = -1
135     }
136 }
137 
138 @RequiresApi(24)
139 internal class AndroidOutputConfiguration(
140     private val output: OutputConfiguration,
141     override val surfaceSharing: Boolean,
142     override val maxSharedSurfaceCount: Int,
143     override val physicalCameraId: CameraId?
144 ) : OutputConfigurationWrapper {
145 
146     @RequiresApi(24)
147     companion object {
148         /**
149          * Create and validate an OutputConfiguration for Camera2. null is returned when a
150          * non-exceptional error is encountered when creating the OutputConfiguration.
151          */
createnull152         fun create(
153             surface: Surface?,
154             outputType: OutputType = OutputType.SURFACE,
155             mirrorMode: MirrorMode? = null,
156             timestampBase: TimestampBase? = null,
157             dynamicRangeProfile: DynamicRangeProfile? = null,
158             streamUseCase: StreamUseCase? = null,
159             sensorPixelModes: List<SensorPixelMode> = emptyList(),
160             size: Size? = null,
161             surfaceSharing: Boolean = false,
162             surfaceGroupId: Int = SURFACE_GROUP_ID_NONE,
163             physicalCameraId: CameraId? = null,
164         ): OutputConfigurationWrapper? {
165             check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
166 
167             // Create the OutputConfiguration using the groupId via the constructor (if set)
168             val configuration: OutputConfiguration
169             if (outputType == OutputType.SURFACE) {
170                 check(surface != null) {
171                     "OutputConfigurations defined with ${OutputType.SURFACE} must provide a"
172                     "non-null surface!"
173                 }
174                 // OutputConfiguration will, on some OS versions, attempt to read the surface size
175                 // from the Surface object. If the Surface has been destroyed, this check will fail.
176                 // Because there's no way to cleanly synchronize and check the value, we catch the
177                 // exception for these cases.
178                 try {
179                     configuration =
180                         if (surfaceGroupId != SURFACE_GROUP_ID_NONE) {
181                             OutputConfiguration(surfaceGroupId, surface)
182                         } else {
183                             OutputConfiguration(surface)
184                         }
185                 } catch (e: Throwable) {
186                     Log.warn(e) { "Failed to create an OutputConfiguration for $surface!" }
187                     return null
188                 }
189             } else {
190                 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
191                     throw IllegalStateException(
192                         "Deferred OutputConfigurations are not supported on API " +
193                             "${Build.VERSION.SDK_INT} (requires API ${Build.VERSION_CODES.O})"
194                     )
195                 }
196 
197                 check(size != null) {
198                     "Size must defined when creating a deferred OutputConfiguration."
199                 }
200                 val outputKlass =
201                     when (outputType) {
202                         OutputType.SURFACE_TEXTURE -> SurfaceTexture::class.java
203                         OutputType.SURFACE_VIEW -> SurfaceHolder::class.java
204                         else -> throw IllegalStateException("Unsupported OutputType: $outputType")
205                     }
206                 configuration = Api26Compat.newOutputConfiguration(size, outputKlass)
207             }
208 
209             // Enable surface sharing, if set.
210             if (surfaceSharing) {
211                 configuration.enableSurfaceSharingCompat()
212             }
213 
214             // Pass along the physicalCameraId, if set.
215             if (physicalCameraId != null) {
216                 configuration.setPhysicalCameraIdCompat(physicalCameraId)
217             }
218 
219             if (mirrorMode != null) {
220                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
221                     Api33Compat.setMirrorMode(configuration, mirrorMode.value)
222                 } else {
223                     if (mirrorMode != MirrorMode.MIRROR_MODE_AUTO) {
224                         Log.warn {
225                             "Cannot set mirrorMode to a non-default value on " +
226                                 "API ${Build.VERSION.SDK_INT}. This may result in unexpected " +
227                                 "behavior. Requested $mirrorMode"
228                         }
229                     }
230                 }
231             }
232 
233             if (timestampBase != null) {
234                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
235                     Api33Compat.setTimestampBase(configuration, timestampBase.value)
236                 } else {
237                     if (timestampBase != TimestampBase.TIMESTAMP_BASE_DEFAULT) {
238                         Log.info {
239                             "The timestamp base on API ${Build.VERSION.SDK_INT} will " +
240                                 "default to TIMESTAMP_BASE_DEFAULT, with which the camera device" +
241                                 " adjusts timestamps based on the output target. " +
242                                 "Requested $timestampBase"
243                         }
244                     }
245                 }
246             }
247 
248             if (dynamicRangeProfile != null) {
249                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
250                     Api33Compat.setDynamicRangeProfile(configuration, dynamicRangeProfile.value)
251                 } else {
252                     if (dynamicRangeProfile != DynamicRangeProfile.STANDARD) {
253                         Log.warn {
254                             "Cannot set dynamicRangeProfile to a non-default value on API " +
255                                 "${Build.VERSION.SDK_INT}. This may result in unexpected " +
256                                 "behavior. Requested $dynamicRangeProfile"
257                         }
258                     }
259                 }
260             }
261 
262             if (streamUseCase != null) {
263                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
264                     Api33Compat.setStreamUseCase(configuration, streamUseCase.value)
265                 }
266             }
267 
268             if (sensorPixelModes.isNotEmpty()) {
269                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
270                     for (sensorPixelMode in sensorPixelModes) {
271                         Api31Compat.addSensorPixelModeUsed(configuration, sensorPixelMode.value)
272                     }
273                 } else {
274                     Log.warn {
275                         "Cannot add sensorPixelModeUsed value on API ${Build.VERSION.SDK_INT}. " +
276                             "This may result in unexpected behavior. Requested $sensorPixelModes"
277                     }
278                 }
279             }
280 
281             // Create and return the Android
282             return AndroidOutputConfiguration(
283                 configuration,
284                 surfaceSharing,
285                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
286                     Api28Compat.getMaxSharedSurfaceCount(configuration)
287                 } else {
288                     1
289                 },
290                 physicalCameraId
291             )
292         }
293 
OutputConfigurationnull294         private fun OutputConfiguration.enableSurfaceSharingCompat() {
295             checkNOrHigher("surfaceSharing")
296             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
297                 Api26Compat.enableSurfaceSharing(this)
298             }
299         }
300 
OutputConfigurationnull301         private fun OutputConfiguration.setPhysicalCameraIdCompat(physicalCameraId: CameraId) {
302             checkPOrHigher("physicalCameraId")
303             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
304                 Api28Compat.setPhysicalCameraId(this, physicalCameraId.value)
305             }
306         }
307     }
308 
309     override val surface: Surface? = output.surface
310     override val surfaces: List<Surface>
311         get() {
312             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
313                 return Api26Compat.getSurfaces(output)
314             }
315 
316             // On older versions of the OS, only one surface is allowed, and if an output
317             // configuration is in a deferred state it may not have a surface when it's first
318             // created.
319             return listOfNotNull(output.surface)
320         }
321 
addSurfacenull322     override fun addSurface(surface: Surface) {
323         checkOOrHigher("addSurface")
324         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
325             Api26Compat.addSurfaces(output, surface)
326         }
327     }
328 
removeSurfacenull329     override fun removeSurface(surface: Surface) {
330         checkPOrHigher("removeSurface")
331         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
332             Api28Compat.removeSurface(output, surface)
333         }
334     }
335 
336     override val surfaceGroupId: Int
337         get() = output.surfaceGroupId
338 
339     @Suppress("UNCHECKED_CAST")
unwrapAsnull340     override fun <T : Any> unwrapAs(type: KClass<T>): T? =
341         when (type) {
342             OutputConfiguration::class -> output as T
343             else -> null
344         }
345 
toStringnull346     override fun toString(): String = output.toString()
347 }
348