1 /* 2 * Copyright (C) 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 android.hardware.camera2; 18 19 import static com.android.internal.util.Preconditions.checkNotNull; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.FlaggedApi; 23 import android.annotation.IntRange; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SuppressLint; 27 import android.graphics.ImageFormat; 28 import android.graphics.ImageFormat.Format; 29 import android.hardware.HardwareBuffer; 30 import android.hardware.HardwareBuffer.Usage; 31 import android.hardware.camera2.params.MultiResolutionStreamInfo; 32 import android.media.Image; 33 import android.media.ImageReader; 34 import android.util.Size; 35 import android.view.Surface; 36 37 import com.android.internal.camera.flags.Flags; 38 39 import java.util.Collection; 40 import java.util.concurrent.Executor; 41 42 /** 43 * <p>The MultiResolutionImageReader class wraps a group of {@link ImageReader ImageReaders} with 44 * the same format and different sizes, source camera Id, or camera sensor modes.</p> 45 * 46 * <p>The main use case of this class is for a 47 * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical 48 * multi-camera} or an ultra high resolution sensor camera to output variable-size images. For a 49 * logical multi-camera which implements optical zoom, different physical cameras may have different 50 * maximum resolutions. As a result, when the camera device switches between physical cameras 51 * depending on zoom ratio, the maximum resolution for a particular format may change. For an 52 * ultra high resolution sensor camera, the camera device may deem it better or worse to run in 53 * maximum resolution mode / default mode depending on lighting conditions. So the application may 54 * choose to let the camera device decide on its behalf.</p> 55 * 56 * <p>MultiResolutionImageReader should be used for a camera device only if the camera device 57 * supports multi-resolution output stream by advertising the specified output format in {@link 58 * CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP}.</p> 59 * 60 * <p>To acquire images from the MultiResolutionImageReader, the application must use the 61 * {@link ImageReader} object passed by 62 * {@link ImageReader.OnImageAvailableListener#onImageAvailable} callback to call 63 * {@link ImageReader#acquireNextImage} or {@link ImageReader#acquireLatestImage}. The application 64 * must not use the {@link ImageReader} passed by an {@link 65 * ImageReader.OnImageAvailableListener#onImageAvailable} callback to acquire future images 66 * because future images may originate from a different {@link ImageReader} contained within the 67 * {@code MultiResolutionImageReader}.</p> 68 * 69 * 70 * @see ImageReader 71 * @see android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP 72 */ 73 public class MultiResolutionImageReader implements AutoCloseable { 74 75 private static final String TAG = "MultiResolutionImageReader"; 76 77 /** 78 * <p> 79 * Create a new multi-resolution reader based on a group of camera stream properties returned 80 * by a camera device. 81 * </p> 82 * <p> 83 * The valid size and formats depend on the camera characteristics. 84 * {@code MultiResolutionImageReader} for an image format is supported by the camera device if 85 * the format is in the supported multi-resolution output stream formats returned by 86 * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. 87 * If the image format is supported, the {@code MultiResolutionImageReader} object can be 88 * created with the {@code streams} objects returned by 89 * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}. 90 * </p> 91 * <p> 92 * The {@code maxImages} parameter determines the maximum number of 93 * {@link Image} objects that can be acquired from each of the {@code ImageReader} 94 * within the {@code MultiResolutionImageReader}. However, requesting more buffers will 95 * use up more memory, so it is important to use only the minimum number necessary. The 96 * application is strongly recommended to acquire no more than {@code maxImages} images 97 * from all of the internal ImageReader objects combined. By keeping track of the number of 98 * acquired images for the MultiResolutionImageReader, the application doesn't need to do the 99 * bookkeeping for each internal ImageReader returned from {@link 100 * ImageReader.OnImageAvailableListener#onImageAvailable onImageAvailable} callback. 101 * </p> 102 * <p> 103 * Unlike the normal ImageReader, the MultiResolutionImageReader has a more complex 104 * configuration sequence. Instead of passing the same surface to OutputConfiguration and 105 * CaptureRequest, the 106 * {@link android.hardware.camera2.params.OutputConfiguration#createInstancesForMultiResolutionOutput} 107 * call needs to be used to create the OutputConfigurations for session creation, and then 108 * {@link #getSurface} is used to get {@link CaptureRequest.Builder#addTarget the target for 109 * CaptureRequest}. 110 * </p> 111 * @param streams The group of multi-resolution stream info, which is used to create 112 * a multi-resolution reader containing a number of ImageReader objects. Each 113 * ImageReader object represents a multi-resolution stream in the group. 114 * @param format The format of the Image that this multi-resolution reader will produce. 115 * This must be one of the {@link android.graphics.ImageFormat} or 116 * {@link android.graphics.PixelFormat} constants. Note that not all formats are 117 * supported, like ImageFormat.NV21. The supported multi-resolution 118 * reader format can be queried by {@link 119 * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. 120 * @param maxImages The maximum number of images the user will want to 121 * access simultaneously. This should be as small as possible to 122 * limit memory use. Once maxImages images are obtained by the 123 * user from any given internal ImageReader, one of them has to be released before 124 * a new Image will become available for access through the ImageReader's 125 * {@link ImageReader#acquireLatestImage()} or 126 * {@link ImageReader#acquireNextImage()}. Must be greater than 0. 127 * @see Image 128 * @see 129 * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP 130 * @see 131 * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap 132 */ MultiResolutionImageReader( @onNull Collection<MultiResolutionStreamInfo> streams, @Format int format, @IntRange(from = 1) int maxImages)133 public MultiResolutionImageReader( 134 @NonNull Collection<MultiResolutionStreamInfo> streams, 135 @Format int format, 136 @IntRange(from = 1) int maxImages) { 137 mFormat = format; 138 mMaxImages = maxImages; 139 140 if (streams == null || streams.size() <= 1) { 141 throw new IllegalArgumentException( 142 "The streams info collection must contain at least 2 entries"); 143 } 144 if (mMaxImages < 1) { 145 throw new IllegalArgumentException( 146 "Maximum outstanding image count must be at least 1"); 147 } 148 149 if (format == ImageFormat.NV21) { 150 throw new IllegalArgumentException( 151 "NV21 format is not supported"); 152 } 153 154 int numImageReaders = streams.size(); 155 mReaders = new ImageReader[numImageReaders]; 156 mStreamInfo = new MultiResolutionStreamInfo[numImageReaders]; 157 int index = 0; 158 for (MultiResolutionStreamInfo streamInfo : streams) { 159 mReaders[index] = ImageReader.newInstance(streamInfo.getWidth(), 160 streamInfo.getHeight(), format, maxImages); 161 mStreamInfo[index] = streamInfo; 162 index++; 163 } 164 } 165 166 /** 167 * <p> 168 * Create a new multi-resolution reader based on a group of camera stream properties returned 169 * by a camera device, and the desired format, maximum buffer capacity and consumer usage flag. 170 * </p> 171 * <p> 172 * The valid size and formats depend on the camera characteristics. 173 * {@code MultiResolutionImageReader} for an image format is supported by the camera device if 174 * the format is in the supported multi-resolution output stream formats returned by 175 * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. 176 * If the image format is supported, the {@code MultiResolutionImageReader} object can be 177 * created with the {@code streams} objects returned by 178 * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}. 179 * </p> 180 * <p> 181 * The {@code maxImages} parameter determines the maximum number of 182 * {@link Image} objects that can be acquired from each of the {@code ImageReader} 183 * within the {@code MultiResolutionImageReader}. However, requesting more buffers will 184 * use up more memory, so it is important to use only the minimum number necessary. The 185 * application is strongly recommended to acquire no more than {@code maxImages} images 186 * from all of the internal ImageReader objects combined. By keeping track of the number of 187 * acquired images for the MultiResolutionImageReader, the application doesn't need to do the 188 * bookkeeping for each internal ImageReader returned from {@link 189 * ImageReader.OnImageAvailableListener#onImageAvailable onImageAvailable} callback. 190 * </p> 191 * <p> 192 * Unlike the normal ImageReader, the MultiResolutionImageReader has a more complex 193 * configuration sequence. Instead of passing the same surface to OutputConfiguration and 194 * CaptureRequest, the 195 * {@link android.hardware.camera2.params.OutputConfiguration#createInstancesForMultiResolutionOutput} 196 * call needs to be used to create the OutputConfigurations for session creation, and then 197 * {@link #getSurface} is used to get {@link CaptureRequest.Builder#addTarget the target for 198 * CaptureRequest}. 199 * </p> 200 * @param streams The group of multi-resolution stream info, which is used to create 201 * a multi-resolution reader containing a number of ImageReader objects. Each 202 * ImageReader object represents a multi-resolution stream in the group. 203 * @param format The format of the Image that this multi-resolution reader will produce. 204 * This must be one of the {@link android.graphics.ImageFormat} or 205 * {@link android.graphics.PixelFormat} constants. Note that not all formats are 206 * supported, like ImageFormat.NV21. The supported multi-resolution 207 * reader format can be queried by {@link 208 * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. 209 * @param maxImages The maximum number of images the user will want to 210 * access simultaneously. This should be as small as possible to 211 * limit memory use. Once maxImages images are obtained by the 212 * user from any given internal ImageReader, one of them has to be released before 213 * a new Image will become available for access through the ImageReader's 214 * {@link ImageReader#acquireLatestImage()} or 215 * {@link ImageReader#acquireNextImage()}. Must be greater than 0. 216 * @param usage The intended usage of the images produced by the internal ImageReader. See the usages 217 * on {@link HardwareBuffer} for a list of valid usage bits. See also 218 * {@link HardwareBuffer#isSupported(int, int, int, int, long)} for checking 219 * if a combination is supported. If it's not supported this will throw 220 * an {@link IllegalArgumentException}. 221 * @see Image 222 * @see 223 * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP 224 * @see 225 * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap 226 * 227 */ 228 @FlaggedApi(Flags.FLAG_MULTIRESOLUTION_IMAGEREADER_USAGE_PUBLIC) MultiResolutionImageReader( @onNull Collection<MultiResolutionStreamInfo> streams, @Format int format, @IntRange(from = 1) int maxImages, @Usage long usage)229 public MultiResolutionImageReader( 230 @NonNull Collection<MultiResolutionStreamInfo> streams, 231 @Format int format, 232 @IntRange(from = 1) int maxImages, 233 @Usage long usage) { 234 mFormat = format; 235 mMaxImages = maxImages; 236 237 if (streams == null || streams.size() <= 1) { 238 throw new IllegalArgumentException( 239 "The streams info collection must contain at least 2 entries"); 240 } 241 if (mMaxImages < 1) { 242 throw new IllegalArgumentException( 243 "Maximum outstanding image count must be at least 1"); 244 } 245 246 if (format == ImageFormat.NV21) { 247 throw new IllegalArgumentException( 248 "NV21 format is not supported"); 249 } 250 251 int numImageReaders = streams.size(); 252 mReaders = new ImageReader[numImageReaders]; 253 mStreamInfo = new MultiResolutionStreamInfo[numImageReaders]; 254 int index = 0; 255 for (MultiResolutionStreamInfo streamInfo : streams) { 256 mReaders[index] = ImageReader.newInstance(streamInfo.getWidth(), 257 streamInfo.getHeight(), format, maxImages, usage); 258 mStreamInfo[index] = streamInfo; 259 index++; 260 } 261 } 262 263 /** 264 * Set onImageAvailableListener callback. 265 * 266 * <p>This function sets the onImageAvailableListener for all the internal 267 * {@link ImageReader} objects.</p> 268 * 269 * <p>For a multi-resolution ImageReader, the timestamps of images acquired in 270 * onImageAvailable callback from different internal ImageReaders may become 271 * out-of-order due to the asynchronous callbacks between the different resolution 272 * image queues.</p> 273 * 274 * @param listener 275 * The listener that will be run. 276 * @param executor 277 * The executor which will be used when invoking the callback. 278 */ 279 @SuppressLint({"ExecutorRegistration", "SamShouldBeLast"}) setOnImageAvailableListener( @ullable ImageReader.OnImageAvailableListener listener, @Nullable @CallbackExecutor Executor executor)280 public void setOnImageAvailableListener( 281 @Nullable ImageReader.OnImageAvailableListener listener, 282 @Nullable @CallbackExecutor Executor executor) { 283 for (int i = 0; i < mReaders.length; i++) { 284 mReaders[i].setOnImageAvailableListenerWithExecutor(listener, executor); 285 } 286 } 287 288 @Override close()289 public void close() { 290 flush(); 291 292 for (int i = 0; i < mReaders.length; i++) { 293 mReaders[i].close(); 294 } 295 } 296 297 @Override finalize()298 protected void finalize() { 299 close(); 300 } 301 302 /** 303 * Flush pending images from all internal ImageReaders 304 * 305 * <p>Acquire and close pending images from all internal ImageReaders. This has the same 306 * effect as calling acquireLatestImage() on all internal ImageReaders, and closing all 307 * latest images.</p> 308 */ flush()309 public void flush() { 310 flushOther(null); 311 } 312 313 /** 314 * Flush pending images from other internal ImageReaders 315 * 316 * <p>Acquire and close pending images from all internal ImageReaders except for the 317 * one specified.</p> 318 * 319 * @param reader The ImageReader object that won't be flushed. 320 * 321 * @hide 322 */ flushOther(ImageReader reader)323 public void flushOther(ImageReader reader) { 324 for (int i = 0; i < mReaders.length; i++) { 325 if (reader != null && reader == mReaders[i]) { 326 continue; 327 } 328 329 while (true) { 330 Image image = mReaders[i].acquireNextImageNoThrowISE(); 331 if (image == null) { 332 break; 333 } else { 334 image.close(); 335 } 336 } 337 } 338 } 339 340 /** 341 * Get the internal ImageReader objects 342 * 343 * @hide 344 */ getReaders()345 public @NonNull ImageReader[] getReaders() { 346 return mReaders; 347 } 348 349 /** 350 * Get the internal ImageReader surface based on configured size and physical camera Id. 351 * 352 * <p>The {@code configuredSize} and {@code physicalCameraId} parameters must match one of the 353 * MultiResolutionStreamInfo used to create this {@link MultiResolutionImageReader}.</p> 354 * 355 * <p>The Surface returned from this function isn't meant to be used directly as part of a 356 * {@link CaptureRequest}. It should instead be used for creating an OutputConfiguration 357 * before session creation. See {@link OutputConfiguration#setSurfacesForMultiResolutionOutput} 358 * for details. For {@link CaptureRequest}, use {@link #getSurface()} instead.</p> 359 * 360 * <p>Please note that holding on to the Surface objects returned by this method is not enough 361 * to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a 362 * Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the 363 * MultiResolutionImageReader that provides it.</p> 364 * 365 * @param configuredSize The configured size corresponding to one of the internal ImageReader. 366 * @param physicalCameraId The physical camera Id the internal ImageReader targets for. If 367 * the ImageReader is not targeting a physical camera of a logical 368 * multi-camera, this parameter is set to "". 369 * 370 * @return The {@link Surface} of the internal ImageReader corresponding to the provided 371 * configured size and physical camera Id. 372 * 373 * @throws IllegalArgumentException If {@code configuredSize} is {@code null}, or the ({@code 374 * configuredSize} and {@code physicalCameraId}) combo is not 375 * part of this {@code MultiResolutionImageReader}. 376 * @hide 377 */ 378 @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) getSurface(@onNull Size configuredSize, @NonNull String physicalCameraId)379 public @NonNull Surface getSurface(@NonNull Size configuredSize, 380 @NonNull String physicalCameraId) { 381 checkNotNull(configuredSize, "configuredSize must not be null"); 382 checkNotNull(physicalCameraId, "physicalCameraId must not be null"); 383 384 for (int i = 0; i < mStreamInfo.length; i++) { 385 if (mStreamInfo[i].getWidth() == configuredSize.getWidth() 386 && mStreamInfo[i].getHeight() == configuredSize.getHeight() 387 && physicalCameraId.equals(mStreamInfo[i].getPhysicalCameraId())) { 388 return mReaders[i].getSurface(); 389 } 390 } 391 throw new IllegalArgumentException("configuredSize and physicalCameraId don't match with " 392 + "this MultiResolutionImageReader"); 393 } 394 395 /** 396 * Get the surface that is used as a target for {@link CaptureRequest} 397 * 398 * <p>The application must use the surface returned by this function as a target for 399 * {@link CaptureRequest}. The camera device makes the decision on which internal 400 * {@code ImageReader} will receive the output image.</p> 401 * 402 * <p>Please note that holding on to the Surface objects returned by this method is not enough 403 * to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a 404 * Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the 405 * MultiResolutionImageReader that provides it.</p> 406 * 407 * @return a {@link Surface} to use as the target for a capture request. 408 */ getSurface()409 public @NonNull Surface getSurface() { 410 // Pick the surface of smallest size. This is necessary for an ultra high resolution 411 // camera not to default to maximum resolution pixel mode. 412 int minReaderSize = mReaders[0].getWidth() * mReaders[0].getHeight(); 413 Surface candidateSurface = mReaders[0].getSurface(); 414 for (int i = 1; i < mReaders.length; i++) { 415 int readerSize = mReaders[i].getWidth() * mReaders[i].getHeight(); 416 if (readerSize < minReaderSize) { 417 minReaderSize = readerSize; 418 candidateSurface = mReaders[i].getSurface(); 419 } 420 } 421 return candidateSurface; 422 } 423 424 /** 425 * Get the MultiResolutionStreamInfo describing the ImageReader an image originates from 426 * 427 *<p>An image from a {@code MultiResolutionImageReader} is produced from one of the underlying 428 *{@code ImageReader}s. This function returns the {@link MultiResolutionStreamInfo} to describe 429 *the property for that {@code ImageReader}, such as width, height, and physical camera Id.</p> 430 * 431 * @param reader An internal ImageReader within {@code MultiResolutionImageReader}. 432 * 433 * @return The stream info describing the internal {@code ImageReader}. 434 */ getStreamInfoForImageReader( @onNull ImageReader reader)435 public @NonNull MultiResolutionStreamInfo getStreamInfoForImageReader( 436 @NonNull ImageReader reader) { 437 for (int i = 0; i < mReaders.length; i++) { 438 if (reader == mReaders[i]) { 439 return mStreamInfo[i]; 440 } 441 } 442 443 throw new IllegalArgumentException("ImageReader doesn't belong to this multi-resolution " 444 + "imagereader"); 445 } 446 447 // mReaders and mStreamInfo has the same length, and their entries are 1:1 mapped. 448 private final ImageReader[] mReaders; 449 private final MultiResolutionStreamInfo[] mStreamInfo; 450 451 private final int mFormat; 452 private final int mMaxImages; 453 } 454