455 lines
22 KiB
Java
455 lines
22 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2021 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package android.hardware.camera2;
|
||
|
|
||
|
import static com.android.internal.util.Preconditions.checkNotNull;
|
||
|
|
||
|
import android.annotation.CallbackExecutor;
|
||
|
import android.annotation.FlaggedApi;
|
||
|
import android.annotation.IntRange;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.SuppressLint;
|
||
|
import android.graphics.ImageFormat;
|
||
|
import android.graphics.ImageFormat.Format;
|
||
|
import android.hardware.HardwareBuffer;
|
||
|
import android.hardware.HardwareBuffer.Usage;
|
||
|
import android.hardware.camera2.params.MultiResolutionStreamInfo;
|
||
|
import android.media.Image;
|
||
|
import android.media.ImageReader;
|
||
|
import android.util.Size;
|
||
|
import android.view.Surface;
|
||
|
|
||
|
import com.android.internal.camera.flags.Flags;
|
||
|
|
||
|
import java.util.Collection;
|
||
|
import java.util.concurrent.Executor;
|
||
|
|
||
|
/**
|
||
|
* <p>The MultiResolutionImageReader class wraps a group of {@link ImageReader ImageReaders} with
|
||
|
* the same format and different sizes, source camera Id, or camera sensor modes.</p>
|
||
|
*
|
||
|
* <p>The main use case of this class is for a
|
||
|
* {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical
|
||
|
* multi-camera} or an ultra high resolution sensor camera to output variable-size images. For a
|
||
|
* logical multi-camera which implements optical zoom, different physical cameras may have different
|
||
|
* maximum resolutions. As a result, when the camera device switches between physical cameras
|
||
|
* depending on zoom ratio, the maximum resolution for a particular format may change. For an
|
||
|
* ultra high resolution sensor camera, the camera device may deem it better or worse to run in
|
||
|
* maximum resolution mode / default mode depending on lighting conditions. So the application may
|
||
|
* choose to let the camera device decide on its behalf.</p>
|
||
|
*
|
||
|
* <p>MultiResolutionImageReader should be used for a camera device only if the camera device
|
||
|
* supports multi-resolution output stream by advertising the specified output format in {@link
|
||
|
* CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP}.</p>
|
||
|
*
|
||
|
* <p>To acquire images from the MultiResolutionImageReader, the application must use the
|
||
|
* {@link ImageReader} object passed by
|
||
|
* {@link ImageReader.OnImageAvailableListener#onImageAvailable} callback to call
|
||
|
* {@link ImageReader#acquireNextImage} or {@link ImageReader#acquireLatestImage}. The application
|
||
|
* must not use the {@link ImageReader} passed by an {@link
|
||
|
* ImageReader.OnImageAvailableListener#onImageAvailable} callback to acquire future images
|
||
|
* because future images may originate from a different {@link ImageReader} contained within the
|
||
|
* {@code MultiResolutionImageReader}.</p>
|
||
|
*
|
||
|
*
|
||
|
* @see ImageReader
|
||
|
* @see android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP
|
||
|
*/
|
||
|
public class MultiResolutionImageReader implements AutoCloseable {
|
||
|
|
||
|
private static final String TAG = "MultiResolutionImageReader";
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Create a new multi-resolution reader based on a group of camera stream properties returned
|
||
|
* by a camera device.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The valid size and formats depend on the camera characteristics.
|
||
|
* {@code MultiResolutionImageReader} for an image format is supported by the camera device if
|
||
|
* the format is in the supported multi-resolution output stream formats returned by
|
||
|
* {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}.
|
||
|
* If the image format is supported, the {@code MultiResolutionImageReader} object can be
|
||
|
* created with the {@code streams} objects returned by
|
||
|
* {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The {@code maxImages} parameter determines the maximum number of
|
||
|
* {@link Image} objects that can be acquired from each of the {@code ImageReader}
|
||
|
* within the {@code MultiResolutionImageReader}. However, requesting more buffers will
|
||
|
* use up more memory, so it is important to use only the minimum number necessary. The
|
||
|
* application is strongly recommended to acquire no more than {@code maxImages} images
|
||
|
* from all of the internal ImageReader objects combined. By keeping track of the number of
|
||
|
* acquired images for the MultiResolutionImageReader, the application doesn't need to do the
|
||
|
* bookkeeping for each internal ImageReader returned from {@link
|
||
|
* ImageReader.OnImageAvailableListener#onImageAvailable onImageAvailable} callback.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* Unlike the normal ImageReader, the MultiResolutionImageReader has a more complex
|
||
|
* configuration sequence. Instead of passing the same surface to OutputConfiguration and
|
||
|
* CaptureRequest, the
|
||
|
* {@link android.hardware.camera2.params.OutputConfiguration#createInstancesForMultiResolutionOutput}
|
||
|
* call needs to be used to create the OutputConfigurations for session creation, and then
|
||
|
* {@link #getSurface} is used to get {@link CaptureRequest.Builder#addTarget the target for
|
||
|
* CaptureRequest}.
|
||
|
* </p>
|
||
|
* @param streams The group of multi-resolution stream info, which is used to create
|
||
|
* a multi-resolution reader containing a number of ImageReader objects. Each
|
||
|
* ImageReader object represents a multi-resolution stream in the group.
|
||
|
* @param format The format of the Image that this multi-resolution reader will produce.
|
||
|
* This must be one of the {@link android.graphics.ImageFormat} or
|
||
|
* {@link android.graphics.PixelFormat} constants. Note that not all formats are
|
||
|
* supported, like ImageFormat.NV21. The supported multi-resolution
|
||
|
* reader format can be queried by {@link
|
||
|
* android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}.
|
||
|
* @param maxImages The maximum number of images the user will want to
|
||
|
* access simultaneously. This should be as small as possible to
|
||
|
* limit memory use. Once maxImages images are obtained by the
|
||
|
* user from any given internal ImageReader, one of them has to be released before
|
||
|
* a new Image will become available for access through the ImageReader's
|
||
|
* {@link ImageReader#acquireLatestImage()} or
|
||
|
* {@link ImageReader#acquireNextImage()}. Must be greater than 0.
|
||
|
* @see Image
|
||
|
* @see
|
||
|
* android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP
|
||
|
* @see
|
||
|
* android.hardware.camera2.params.MultiResolutionStreamConfigurationMap
|
||
|
*/
|
||
|
public MultiResolutionImageReader(
|
||
|
@NonNull Collection<MultiResolutionStreamInfo> streams,
|
||
|
@Format int format,
|
||
|
@IntRange(from = 1) int maxImages) {
|
||
|
mFormat = format;
|
||
|
mMaxImages = maxImages;
|
||
|
|
||
|
if (streams == null || streams.size() <= 1) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"The streams info collection must contain at least 2 entries");
|
||
|
}
|
||
|
if (mMaxImages < 1) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Maximum outstanding image count must be at least 1");
|
||
|
}
|
||
|
|
||
|
if (format == ImageFormat.NV21) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"NV21 format is not supported");
|
||
|
}
|
||
|
|
||
|
int numImageReaders = streams.size();
|
||
|
mReaders = new ImageReader[numImageReaders];
|
||
|
mStreamInfo = new MultiResolutionStreamInfo[numImageReaders];
|
||
|
int index = 0;
|
||
|
for (MultiResolutionStreamInfo streamInfo : streams) {
|
||
|
mReaders[index] = ImageReader.newInstance(streamInfo.getWidth(),
|
||
|
streamInfo.getHeight(), format, maxImages);
|
||
|
mStreamInfo[index] = streamInfo;
|
||
|
index++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Create a new multi-resolution reader based on a group of camera stream properties returned
|
||
|
* by a camera device, and the desired format, maximum buffer capacity and consumer usage flag.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The valid size and formats depend on the camera characteristics.
|
||
|
* {@code MultiResolutionImageReader} for an image format is supported by the camera device if
|
||
|
* the format is in the supported multi-resolution output stream formats returned by
|
||
|
* {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}.
|
||
|
* If the image format is supported, the {@code MultiResolutionImageReader} object can be
|
||
|
* created with the {@code streams} objects returned by
|
||
|
* {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The {@code maxImages} parameter determines the maximum number of
|
||
|
* {@link Image} objects that can be acquired from each of the {@code ImageReader}
|
||
|
* within the {@code MultiResolutionImageReader}. However, requesting more buffers will
|
||
|
* use up more memory, so it is important to use only the minimum number necessary. The
|
||
|
* application is strongly recommended to acquire no more than {@code maxImages} images
|
||
|
* from all of the internal ImageReader objects combined. By keeping track of the number of
|
||
|
* acquired images for the MultiResolutionImageReader, the application doesn't need to do the
|
||
|
* bookkeeping for each internal ImageReader returned from {@link
|
||
|
* ImageReader.OnImageAvailableListener#onImageAvailable onImageAvailable} callback.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* Unlike the normal ImageReader, the MultiResolutionImageReader has a more complex
|
||
|
* configuration sequence. Instead of passing the same surface to OutputConfiguration and
|
||
|
* CaptureRequest, the
|
||
|
* {@link android.hardware.camera2.params.OutputConfiguration#createInstancesForMultiResolutionOutput}
|
||
|
* call needs to be used to create the OutputConfigurations for session creation, and then
|
||
|
* {@link #getSurface} is used to get {@link CaptureRequest.Builder#addTarget the target for
|
||
|
* CaptureRequest}.
|
||
|
* </p>
|
||
|
* @param streams The group of multi-resolution stream info, which is used to create
|
||
|
* a multi-resolution reader containing a number of ImageReader objects. Each
|
||
|
* ImageReader object represents a multi-resolution stream in the group.
|
||
|
* @param format The format of the Image that this multi-resolution reader will produce.
|
||
|
* This must be one of the {@link android.graphics.ImageFormat} or
|
||
|
* {@link android.graphics.PixelFormat} constants. Note that not all formats are
|
||
|
* supported, like ImageFormat.NV21. The supported multi-resolution
|
||
|
* reader format can be queried by {@link
|
||
|
* android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}.
|
||
|
* @param maxImages The maximum number of images the user will want to
|
||
|
* access simultaneously. This should be as small as possible to
|
||
|
* limit memory use. Once maxImages images are obtained by the
|
||
|
* user from any given internal ImageReader, one of them has to be released before
|
||
|
* a new Image will become available for access through the ImageReader's
|
||
|
* {@link ImageReader#acquireLatestImage()} or
|
||
|
* {@link ImageReader#acquireNextImage()}. Must be greater than 0.
|
||
|
* @param usage The intended usage of the images produced by the internal ImageReader. See the usages
|
||
|
* on {@link HardwareBuffer} for a list of valid usage bits. See also
|
||
|
* {@link HardwareBuffer#isSupported(int, int, int, int, long)} for checking
|
||
|
* if a combination is supported. If it's not supported this will throw
|
||
|
* an {@link IllegalArgumentException}.
|
||
|
* @see Image
|
||
|
* @see
|
||
|
* android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP
|
||
|
* @see
|
||
|
* android.hardware.camera2.params.MultiResolutionStreamConfigurationMap
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@FlaggedApi(Flags.FLAG_MULTIRESOLUTION_IMAGEREADER_USAGE_CONFIG)
|
||
|
public MultiResolutionImageReader(
|
||
|
@NonNull Collection<MultiResolutionStreamInfo> streams,
|
||
|
@Format int format,
|
||
|
@IntRange(from = 1) int maxImages,
|
||
|
@Usage long usage) {
|
||
|
mFormat = format;
|
||
|
mMaxImages = maxImages;
|
||
|
|
||
|
if (streams == null || streams.size() <= 1) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"The streams info collection must contain at least 2 entries");
|
||
|
}
|
||
|
if (mMaxImages < 1) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Maximum outstanding image count must be at least 1");
|
||
|
}
|
||
|
|
||
|
if (format == ImageFormat.NV21) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"NV21 format is not supported");
|
||
|
}
|
||
|
|
||
|
int numImageReaders = streams.size();
|
||
|
mReaders = new ImageReader[numImageReaders];
|
||
|
mStreamInfo = new MultiResolutionStreamInfo[numImageReaders];
|
||
|
int index = 0;
|
||
|
for (MultiResolutionStreamInfo streamInfo : streams) {
|
||
|
mReaders[index] = ImageReader.newInstance(streamInfo.getWidth(),
|
||
|
streamInfo.getHeight(), format, maxImages, usage);
|
||
|
mStreamInfo[index] = streamInfo;
|
||
|
index++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set onImageAvailableListener callback.
|
||
|
*
|
||
|
* <p>This function sets the onImageAvailableListener for all the internal
|
||
|
* {@link ImageReader} objects.</p>
|
||
|
*
|
||
|
* <p>For a multi-resolution ImageReader, the timestamps of images acquired in
|
||
|
* onImageAvailable callback from different internal ImageReaders may become
|
||
|
* out-of-order due to the asynchronous callbacks between the different resolution
|
||
|
* image queues.</p>
|
||
|
*
|
||
|
* @param listener
|
||
|
* The listener that will be run.
|
||
|
* @param executor
|
||
|
* The executor which will be used when invoking the callback.
|
||
|
*/
|
||
|
@SuppressLint({"ExecutorRegistration", "SamShouldBeLast"})
|
||
|
public void setOnImageAvailableListener(
|
||
|
@Nullable ImageReader.OnImageAvailableListener listener,
|
||
|
@Nullable @CallbackExecutor Executor executor) {
|
||
|
for (int i = 0; i < mReaders.length; i++) {
|
||
|
mReaders[i].setOnImageAvailableListenerWithExecutor(listener, executor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void close() {
|
||
|
flush();
|
||
|
|
||
|
for (int i = 0; i < mReaders.length; i++) {
|
||
|
mReaders[i].close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void finalize() {
|
||
|
close();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Flush pending images from all internal ImageReaders
|
||
|
*
|
||
|
* <p>Acquire and close pending images from all internal ImageReaders. This has the same
|
||
|
* effect as calling acquireLatestImage() on all internal ImageReaders, and closing all
|
||
|
* latest images.</p>
|
||
|
*/
|
||
|
public void flush() {
|
||
|
flushOther(null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Flush pending images from other internal ImageReaders
|
||
|
*
|
||
|
* <p>Acquire and close pending images from all internal ImageReaders except for the
|
||
|
* one specified.</p>
|
||
|
*
|
||
|
* @param reader The ImageReader object that won't be flushed.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public void flushOther(ImageReader reader) {
|
||
|
for (int i = 0; i < mReaders.length; i++) {
|
||
|
if (reader != null && reader == mReaders[i]) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
while (true) {
|
||
|
Image image = mReaders[i].acquireNextImageNoThrowISE();
|
||
|
if (image == null) {
|
||
|
break;
|
||
|
} else {
|
||
|
image.close();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the internal ImageReader objects
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public @NonNull ImageReader[] getReaders() {
|
||
|
return mReaders;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the internal ImageReader surface based on configured size and physical camera Id.
|
||
|
*
|
||
|
* <p>The {@code configuredSize} and {@code physicalCameraId} parameters must match one of the
|
||
|
* MultiResolutionStreamInfo used to create this {@link MultiResolutionImageReader}.</p>
|
||
|
*
|
||
|
* <p>The Surface returned from this function isn't meant to be used directly as part of a
|
||
|
* {@link CaptureRequest}. It should instead be used for creating an OutputConfiguration
|
||
|
* before session creation. See {@link OutputConfiguration#setSurfacesForMultiResolutionOutput}
|
||
|
* for details. For {@link CaptureRequest}, use {@link #getSurface()} instead.</p>
|
||
|
*
|
||
|
* <p>Please note that holding on to the Surface objects returned by this method is not enough
|
||
|
* to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a
|
||
|
* Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the
|
||
|
* MultiResolutionImageReader that provides it.</p>
|
||
|
*
|
||
|
* @param configuredSize The configured size corresponding to one of the internal ImageReader.
|
||
|
* @param physicalCameraId The physical camera Id the internal ImageReader targets for. If
|
||
|
* the ImageReader is not targeting a physical camera of a logical
|
||
|
* multi-camera, this parameter is set to "".
|
||
|
*
|
||
|
* @return The {@link Surface} of the internal ImageReader corresponding to the provided
|
||
|
* configured size and physical camera Id.
|
||
|
*
|
||
|
* @throws IllegalArgumentException If {@code configuredSize} is {@code null}, or the ({@code
|
||
|
* configuredSize} and {@code physicalCameraId}) combo is not
|
||
|
* part of this {@code MultiResolutionImageReader}.
|
||
|
* @hide
|
||
|
*/
|
||
|
@FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
|
||
|
public @NonNull Surface getSurface(@NonNull Size configuredSize,
|
||
|
@NonNull String physicalCameraId) {
|
||
|
checkNotNull(configuredSize, "configuredSize must not be null");
|
||
|
checkNotNull(physicalCameraId, "physicalCameraId must not be null");
|
||
|
|
||
|
for (int i = 0; i < mStreamInfo.length; i++) {
|
||
|
if (mStreamInfo[i].getWidth() == configuredSize.getWidth()
|
||
|
&& mStreamInfo[i].getHeight() == configuredSize.getHeight()
|
||
|
&& physicalCameraId.equals(mStreamInfo[i].getPhysicalCameraId())) {
|
||
|
return mReaders[i].getSurface();
|
||
|
}
|
||
|
}
|
||
|
throw new IllegalArgumentException("configuredSize and physicalCameraId don't match with "
|
||
|
+ "this MultiResolutionImageReader");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the surface that is used as a target for {@link CaptureRequest}
|
||
|
*
|
||
|
* <p>The application must use the surface returned by this function as a target for
|
||
|
* {@link CaptureRequest}. The camera device makes the decision on which internal
|
||
|
* {@code ImageReader} will receive the output image.</p>
|
||
|
*
|
||
|
* <p>Please note that holding on to the Surface objects returned by this method is not enough
|
||
|
* to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a
|
||
|
* Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the
|
||
|
* MultiResolutionImageReader that provides it.</p>
|
||
|
*
|
||
|
* @return a {@link Surface} to use as the target for a capture request.
|
||
|
*/
|
||
|
public @NonNull Surface getSurface() {
|
||
|
// Pick the surface of smallest size. This is necessary for an ultra high resolution
|
||
|
// camera not to default to maximum resolution pixel mode.
|
||
|
int minReaderSize = mReaders[0].getWidth() * mReaders[0].getHeight();
|
||
|
Surface candidateSurface = mReaders[0].getSurface();
|
||
|
for (int i = 1; i < mReaders.length; i++) {
|
||
|
int readerSize = mReaders[i].getWidth() * mReaders[i].getHeight();
|
||
|
if (readerSize < minReaderSize) {
|
||
|
minReaderSize = readerSize;
|
||
|
candidateSurface = mReaders[i].getSurface();
|
||
|
}
|
||
|
}
|
||
|
return candidateSurface;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the MultiResolutionStreamInfo describing the ImageReader an image originates from
|
||
|
*
|
||
|
*<p>An image from a {@code MultiResolutionImageReader} is produced from one of the underlying
|
||
|
*{@code ImageReader}s. This function returns the {@link MultiResolutionStreamInfo} to describe
|
||
|
*the property for that {@code ImageReader}, such as width, height, and physical camera Id.</p>
|
||
|
*
|
||
|
* @param reader An internal ImageReader within {@code MultiResolutionImageReader}.
|
||
|
*
|
||
|
* @return The stream info describing the internal {@code ImageReader}.
|
||
|
*/
|
||
|
public @NonNull MultiResolutionStreamInfo getStreamInfoForImageReader(
|
||
|
@NonNull ImageReader reader) {
|
||
|
for (int i = 0; i < mReaders.length; i++) {
|
||
|
if (reader == mReaders[i]) {
|
||
|
return mStreamInfo[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
throw new IllegalArgumentException("ImageReader doesn't belong to this multi-resolution "
|
||
|
+ "imagereader");
|
||
|
}
|
||
|
|
||
|
// mReaders and mStreamInfo has the same length, and their entries are 1:1 mapped.
|
||
|
private final ImageReader[] mReaders;
|
||
|
private final MultiResolutionStreamInfo[] mStreamInfo;
|
||
|
|
||
|
private final int mFormat;
|
||
|
private final int mMaxImages;
|
||
|
}
|