• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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