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