1 /*
2  * Copyright 2023 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.media
18 
19 import android.hardware.camera2.MultiResolutionImageReader
20 import android.hardware.camera2.params.MultiResolutionStreamInfo
21 import android.media.ImageReader
22 import android.os.Build
23 import android.os.Handler
24 import android.view.Surface
25 import androidx.annotation.RequiresApi
26 import androidx.camera.camera2.pipe.CameraStream
27 import androidx.camera.camera2.pipe.OutputId
28 import androidx.camera.camera2.pipe.StreamFormat
29 import androidx.camera.camera2.pipe.StreamId
30 import androidx.camera.camera2.pipe.compat.Api28Compat
31 import androidx.camera.camera2.pipe.compat.Api29Compat
32 import androidx.camera.camera2.pipe.compat.Api33Compat
33 import androidx.camera.camera2.pipe.core.Log
34 import java.util.concurrent.Executor
35 import kotlin.reflect.KClass
36 import kotlinx.atomicfu.atomic
37 
38 /** Implements an [ImageReaderWrapper] using an [ImageReader]. */
39 public class AndroidImageReader
40 private constructor(
41     private val imageReader: ImageReader,
42     override val capacity: Int,
43     private val streamId: StreamId,
44     private val outputId: OutputId
45 ) : ImageReaderWrapper, ImageReader.OnImageAvailableListener {
46     private val onImageListener = atomic<ImageReaderWrapper.OnImageListener?>(null)
47 
48     override val surface: Surface = imageReader.surface
49 
setOnImageListenernull50     override fun setOnImageListener(onImageListener: ImageReaderWrapper.OnImageListener) {
51         this.onImageListener.value = onImageListener
52     }
53 
onImageAvailablenull54     override fun onImageAvailable(reader: ImageReader?) {
55         val image = reader?.acquireNextImage()
56         if (image != null) {
57             val listener = onImageListener.value
58             if (listener == null) {
59                 image.close()
60                 return
61             }
62             listener.onImage(streamId, outputId, AndroidImage(image))
63         }
64     }
65 
closenull66     override fun close(): Unit = imageReader.close()
67 
68     override fun flush() {
69         // acquireLatestImage will acquire the most recent image and internally close any image that
70         // is older, this call ensures any pending images are closed before calling
71         // discardFreeBuffers to ensure we release as much memory as possible.
72         imageReader.acquireLatestImage()?.close()
73 
74         // ImageReaders are pools of shared memory that is not actively released until the
75         // ImageReader is closed. This method call actively frees these unused buffers from the
76         // internal buffer pool.
77         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
78             Api28Compat.discardFreeBuffers(imageReader)
79         }
80     }
81 
82     @Suppress("UNCHECKED_CAST")
unwrapAsnull83     override fun <T : Any> unwrapAs(type: KClass<T>): T? =
84         when (type) {
85             ImageReader::class -> imageReader as T?
86             else -> null
87         }
88 
toStringnull89     override fun toString(): String {
90         return "ImageReader@${super.hashCode().toString(16)}" +
91             "-${StreamFormat(imageReader.imageFormat).name}" +
92             "-w${imageReader.width}h${imageReader.height}"
93     }
94 
95     public companion object {
96         // See: b/172464059
97         //
98         // The ImageReader has an internal limit of 64 images by design, but depending on the device
99         // specific camera HAL (Which can be different per device) there is an additional number of
100         // images that are reserved by the Camera HAL which reduces this number. If, for example,
101         // the HAL reserves 8 images, you have a maximum of 56 (64 - 8).
102         //
103         // One of the worst cases observed is the HAL reserving 10 images, which gives a maximum
104         // capacity of 54 (64 - 10). For safety and compatibility reasons, set the maximum capacity
105         // to be 54, which leaves headroom for an app configured limit of 50.
106         internal const val IMAGEREADER_MAX_CAPACITY = 54
107 
108         /**
109          * Create and configure a new ImageReader instance as an [ImageReaderWrapper].
110          *
111          * See [ImageReader.newInstance] for details.
112          */
createnull113         public fun create(
114             width: Int,
115             height: Int,
116             format: Int,
117             capacity: Int,
118             usageFlags: Long?,
119             defaultDataSpace: Int?,
120             defaultHardwareBufferFormat: Int?,
121             streamId: StreamId,
122             outputId: OutputId,
123             handler: Handler
124         ): ImageReaderWrapper {
125             require(width > 0) { "Width ($width) must be > 0" }
126             require(height > 0) { "Height ($height) must be > 0" }
127             require(capacity > 0) { "Capacity ($capacity) must be > 0" }
128             require(capacity <= IMAGEREADER_MAX_CAPACITY) {
129                 "Capacity for creating new ImageSources is restricted to " +
130                     "$IMAGEREADER_MAX_CAPACITY. Android has undocumented internal limits that " +
131                     "are different depending on which device the ImageReader is created on."
132             }
133 
134             // Warnings for unsupported features:
135             if (usageFlags != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
136                 Log.warn {
137                     "Ignoring ImageReader usage ($usageFlags) " +
138                         "for $outputId. Android ${Build.VERSION.SDK_INT} does not " +
139                         "support creating ImageReaders with usage flags. " +
140                         "This may lead to unexpected behaviors."
141                 }
142             }
143             if (defaultDataSpace != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
144                 Log.warn {
145                     "Ignoring defaultDataSpace ($defaultDataSpace) " +
146                         "for $outputId. Android ${Build.VERSION.SDK_INT} does not " +
147                         "support creating ImageReaders with defaultDataSpace. " +
148                         "This may lead to unexpected behaviors."
149                 }
150             }
151             if (
152                 defaultHardwareBufferFormat != null &&
153                     Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
154             ) {
155                 Log.warn {
156                     "Ignoring defaultHardwareBufferFormat ($defaultHardwareBufferFormat) " +
157                         "for $outputId. Android ${Build.VERSION.SDK_INT} does not " +
158                         "support creating ImageReaders with defaultHardwareBufferFormat. " +
159                         "This may lead to unexpected behaviors."
160                 }
161             }
162 
163             // Create and configure a new ImageReader based on the current Android SDK
164             val imageReader =
165                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
166                     Api33Compat.newImageReaderFromImageReaderBuilder(
167                         width = width,
168                         height = height,
169                         imageFormat = format,
170                         maxImages = capacity,
171                         usage = usageFlags,
172                         defaultDataSpace = defaultDataSpace,
173                         defaultHardwareBufferFormat = defaultHardwareBufferFormat
174                     )
175                 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
176                     if (usageFlags != null) {
177                         Api29Compat.imageReaderNewInstance(
178                             width,
179                             height,
180                             format,
181                             capacity,
182                             usageFlags
183                         )
184                     } else {
185                         ImageReader.newInstance(width, height, format, capacity)
186                     }
187                 } else {
188                     ImageReader.newInstance(width, height, format, capacity)
189                 }
190 
191             // Create the ImageSource and wire it up the onImageAvailableListener
192             val androidImageReader = AndroidImageReader(imageReader, capacity, streamId, outputId)
193             imageReader.setOnImageAvailableListener(androidImageReader, handler)
194             return androidImageReader
195         }
196     }
197 }
198 
199 /** Implements an [ImageReaderWrapper] using a [MultiResolutionImageReader]. */
200 @RequiresApi(31)
201 public class AndroidMultiResolutionImageReader(
202     private val multiResolutionImageReader: MultiResolutionImageReader,
203     private val streamFormat: StreamFormat,
204     override val capacity: Int,
205     private val streamId: StreamId,
206     private val outputIdMap: Map<MultiResolutionStreamInfo, OutputId>
207 ) : ImageReaderWrapper, ImageReader.OnImageAvailableListener {
208     private val onImageListener = atomic<ImageReaderWrapper.OnImageListener?>(null)
209 
210     override val surface: Surface
211         get() = multiResolutionImageReader.surface
212 
setOnImageListenernull213     override fun setOnImageListener(onImageListener: ImageReaderWrapper.OnImageListener) {
214         this.onImageListener.value = onImageListener
215     }
216 
onImageAvailablenull217     override fun onImageAvailable(reader: ImageReader?) {
218         val image = reader?.acquireNextImage()
219         if (image != null) {
220             val listener = onImageListener.value
221             if (listener == null) {
222                 image.close()
223                 return
224             }
225 
226             // MultiResolutionImageReaders produce images from multiple sub-ImageReaders, in order
227             // to figure out which output the image is from, we have to first look up the
228             // StreamInfo from the MultiResolutionImageReader instance, and then use it to look it
229             // up in the outputMap that was used to create the MultiResolutionImageReader.
230             val streamInfo = multiResolutionImageReader.getStreamInfoForImageReader(reader)
231             val outputId =
232                 checkNotNull(outputIdMap[streamInfo]) {
233                     "$this: Failed to find OutputId for $reader based on streamInfo $streamInfo!"
234                 }
235 
236             // Note: During camera switches, MultiResolutionImageReaders does not guarantee that
237             // images will always be in monotonically increasing order. The primary reason for this
238             // is when a camera switches from one lens to another, which can cause the camera
239             // to produce overlapping images from each sensor and can be delivered out of order.
240             listener.onImage(streamId, outputId, AndroidImage(image))
241         }
242     }
243 
closenull244     override fun close(): Unit = multiResolutionImageReader.close()
245 
246     override fun flush() {
247         // ImageReaders are pools of shared memory that is not actively released until the
248         // ImageReader is closed. This method call actively frees these unused buffers from the
249         // internal buffer pool(s).
250         multiResolutionImageReader.flush()
251     }
252 
253     @Suppress("UNCHECKED_CAST")
unwrapAsnull254     override fun <T : Any> unwrapAs(type: KClass<T>): T? =
255         when (type) {
256             MultiResolutionImageReader::class -> multiResolutionImageReader as T?
257             else -> null
258         }
259 
toStringnull260     override fun toString(): String {
261         val sizeString =
262             outputIdMap.keys.joinToString(prefix = "[", postfix = "]") {
263                 "${it.physicalCameraId}:w${it.width}h${it.height}"
264             }
265         return "MultiResolutionImageReader@${super.hashCode().toString(16)}" +
266             "-${streamFormat.name}" +
267             "-$sizeString"
268     }
269 
270     public companion object {
271         @RequiresApi(31)
createnull272         public fun create(
273             outputFormat: Int,
274             streamId: StreamId,
275             outputIdMap: Map<MultiResolutionStreamInfo, OutputId>,
276             capacity: Int,
277             executor: Executor
278         ): ImageReaderWrapper {
279             require(capacity > 0) { "Capacity ($capacity) must be > 0" }
280             require(capacity <= AndroidImageReader.IMAGEREADER_MAX_CAPACITY) {
281                 "Capacity for creating new ImageSources is restricted to " +
282                     "${AndroidImageReader.IMAGEREADER_MAX_CAPACITY}. Android has undocumented " +
283                     "internal limits that are different depending on which device the " +
284                     "MultiResolutionImageReader is created on."
285             }
286 
287             // Create and configure a new MultiResolutionImageReader
288             val multiResolutionImageReader =
289                 MultiResolutionImageReader(outputIdMap.keys, outputFormat, capacity)
290 
291             val androidMultiResolutionImageReader =
292                 AndroidMultiResolutionImageReader(
293                     multiResolutionImageReader,
294                     StreamFormat(outputFormat),
295                     capacity,
296                     streamId,
297                     outputIdMap,
298                 )
299 
300             multiResolutionImageReader.setOnImageAvailableListener(
301                 androidMultiResolutionImageReader,
302                 executor
303             )
304 
305             return androidMultiResolutionImageReader
306         }
307 
308         @RequiresApi(31)
createnull309         public fun create(
310             cameraStream: CameraStream,
311             capacity: Int,
312             executor: Executor
313         ): ImageReaderWrapper {
314             require(cameraStream.outputs.isNotEmpty()) { "$cameraStream outputs cannot be empty!" }
315             val format = cameraStream.outputs.first().format
316             val outputMap =
317                 cameraStream.outputs.associate {
318                     MultiResolutionStreamInfo(it.size.width, it.size.height, it.camera.value) to
319                         it.id
320                 }
321             return create(format.value, cameraStream.id, outputMap, capacity, executor)
322         }
323     }
324 }
325