1520 lines
60 KiB
Java
1520 lines
60 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2013 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.media;
|
||
|
|
||
|
import android.annotation.IntRange;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.SuppressLint;
|
||
|
import android.app.compat.CompatChanges;
|
||
|
import android.compat.annotation.ChangeId;
|
||
|
import android.compat.annotation.EnabledAfter;
|
||
|
import android.graphics.GraphicBuffer;
|
||
|
import android.graphics.ImageFormat;
|
||
|
import android.graphics.ImageFormat.Format;
|
||
|
import android.graphics.Rect;
|
||
|
import android.hardware.DataSpace;
|
||
|
import android.hardware.DataSpace.NamedDataSpace;
|
||
|
import android.hardware.HardwareBuffer;
|
||
|
import android.hardware.HardwareBuffer.Usage;
|
||
|
import android.hardware.SyncFence;
|
||
|
import android.hardware.camera2.MultiResolutionImageReader;
|
||
|
import android.os.Build;
|
||
|
import android.os.Handler;
|
||
|
import android.os.Looper;
|
||
|
import android.os.ParcelFileDescriptor;
|
||
|
import android.os.Trace;
|
||
|
import android.view.Surface;
|
||
|
|
||
|
import dalvik.system.VMRuntime;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.lang.ref.WeakReference;
|
||
|
import java.nio.ByteBuffer;
|
||
|
import java.nio.ByteOrder;
|
||
|
import java.nio.NioUtils;
|
||
|
import java.util.List;
|
||
|
import java.util.Objects;
|
||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||
|
import java.util.concurrent.Executor;
|
||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||
|
|
||
|
/**
|
||
|
* <p>The ImageReader class allows direct application access to image data
|
||
|
* rendered into a {@link android.view.Surface}</p>
|
||
|
*
|
||
|
* <p>Several Android media API classes accept Surface objects as targets to
|
||
|
* render to, including {@link MediaPlayer}, {@link MediaCodec},
|
||
|
* {@link android.hardware.camera2.CameraDevice}, {@link ImageWriter} and
|
||
|
* {@link android.renderscript.Allocation RenderScript Allocations}. The image
|
||
|
* sizes and formats that can be used with each source vary, and should be
|
||
|
* checked in the documentation for the specific API.</p>
|
||
|
*
|
||
|
* <p>The image data is encapsulated in {@link Image} objects, and multiple such
|
||
|
* objects can be accessed at the same time, up to the number specified by the
|
||
|
* {@code maxImages} constructor parameter. New images sent to an ImageReader
|
||
|
* through its {@link Surface} are queued until accessed through the {@link #acquireLatestImage}
|
||
|
* or {@link #acquireNextImage} call. Due to memory limits, an image source will
|
||
|
* eventually stall or drop Images in trying to render to the Surface if the
|
||
|
* ImageReader does not obtain and release Images at a rate equal to the
|
||
|
* production rate.</p>
|
||
|
*/
|
||
|
public class ImageReader implements AutoCloseable {
|
||
|
|
||
|
/**
|
||
|
* Returned by nativeImageSetup when acquiring the image was successful.
|
||
|
*/
|
||
|
private static final int ACQUIRE_SUCCESS = 0;
|
||
|
/**
|
||
|
* Returned by nativeImageSetup when we couldn't acquire the buffer,
|
||
|
* because there were no buffers available to acquire.
|
||
|
*/
|
||
|
private static final int ACQUIRE_NO_BUFS = 1;
|
||
|
/**
|
||
|
* Returned by nativeImageSetup when we couldn't acquire the buffer
|
||
|
* because the consumer has already acquired {@maxImages} and cannot
|
||
|
* acquire more than that.
|
||
|
*/
|
||
|
private static final int ACQUIRE_MAX_IMAGES = 2;
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Flag to gate correct exception thrown by {@code #detachImage}.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* {@code #detachImage} is documented as throwing {@link java.lang.IllegalStateException} in
|
||
|
* the event of an error; a native helper method to this threw
|
||
|
* {@link java.lang.RuntimeException} if the surface was abandoned while detaching the
|
||
|
* {@code Image}.
|
||
|
* <p>
|
||
|
* This previously undocumented exception behavior continues through Android T.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* After Android T, the native helper method only throws {@code IllegalStateExceptions} in
|
||
|
* accordance with the documentation.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* {@code #detachImage} will now throw only ISEs if it runs into errors while detaching
|
||
|
* the image. Behavior on apps targeting API levels <= T remains unchanged.
|
||
|
* </p>
|
||
|
*/
|
||
|
@ChangeId
|
||
|
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
|
||
|
private static final long DETACH_THROWS_ISE_ONLY = 236825255L;
|
||
|
|
||
|
/**
|
||
|
* Cached value of {@link #DETACH_THROWS_ISE_ONLY} flag to prevent repeated calls when
|
||
|
* detaching image.
|
||
|
*/
|
||
|
private final boolean mDetachThrowsIseOnly =
|
||
|
CompatChanges.isChangeEnabled(DETACH_THROWS_ISE_ONLY);
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Create a new reader for images of the desired size and format.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The {@code maxImages} parameter determines the maximum number of
|
||
|
* {@link Image} objects that can be be acquired from the
|
||
|
* {@code ImageReader} simultaneously. Requesting more buffers will use up
|
||
|
* more memory, so it is important to use only the minimum number necessary
|
||
|
* for the use case.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The valid sizes and formats depend on the source of the image data.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* If the {@code format} is {@link ImageFormat#PRIVATE PRIVATE}, the created
|
||
|
* {@link ImageReader} will produce images that are not directly accessible
|
||
|
* by the application. The application can still acquire images from this
|
||
|
* {@link ImageReader}, and send them to the
|
||
|
* {@link android.hardware.camera2.CameraDevice camera} for reprocessing via
|
||
|
* {@link ImageWriter} interface. However, the {@link Image#getPlanes()
|
||
|
* getPlanes()} will return an empty array for {@link ImageFormat#PRIVATE
|
||
|
* PRIVATE} format images. The application can check if an existing reader's
|
||
|
* format by calling {@link #getImageFormat()}.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* {@link ImageFormat#PRIVATE PRIVATE} format {@link ImageReader
|
||
|
* ImageReaders} are more efficient to use when application access to image
|
||
|
* data is not necessary, compared to ImageReaders using other format such
|
||
|
* as {@link ImageFormat#YUV_420_888 YUV_420_888}.
|
||
|
* </p>
|
||
|
*
|
||
|
* @param width The default width in pixels of the Images that this reader
|
||
|
* will produce.
|
||
|
* @param height The default height in pixels of the Images that this reader
|
||
|
* will produce.
|
||
|
* @param format The format of the Image that this 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.
|
||
|
* @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, one of them has to be released before a new Image will
|
||
|
* become available for access through
|
||
|
* {@link #acquireLatestImage()} or {@link #acquireNextImage()}.
|
||
|
* Must be greater than 0.
|
||
|
* @see Image
|
||
|
*/
|
||
|
public static @NonNull ImageReader newInstance(
|
||
|
@IntRange(from = 1) int width,
|
||
|
@IntRange(from = 1) int height,
|
||
|
@Format int format,
|
||
|
@IntRange(from = 1) int maxImages) {
|
||
|
// If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not
|
||
|
// work, and is inscrutable anyway
|
||
|
return new ImageReader(width, height, format, maxImages,
|
||
|
format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Create a new reader for images of the desired size, format and consumer usage flag.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The {@code maxImages} parameter determines the maximum number of {@link Image} objects that
|
||
|
* can be be acquired from the {@code ImageReader} simultaneously. Requesting more buffers will
|
||
|
* use up more memory, so it is important to use only the minimum number necessary for the use
|
||
|
* case.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The valid sizes and formats depend on the source of the image data.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* The format and usage flag combination describes how the buffer will be used by
|
||
|
* consumer end-points. For example, if the application intends to send the images to
|
||
|
* {@link android.media.MediaCodec} or {@link android.media.MediaRecorder} for hardware video
|
||
|
* encoding, the format and usage flag combination needs to be
|
||
|
* {@link ImageFormat#PRIVATE PRIVATE} and {@link HardwareBuffer#USAGE_VIDEO_ENCODE}. When an
|
||
|
* {@link ImageReader} object is created with a valid size and such format/usage flag
|
||
|
* combination, the application can send the {@link Image images} to an {@link ImageWriter} that
|
||
|
* is created with the input {@link android.view.Surface} provided by the
|
||
|
* {@link android.media.MediaCodec} or {@link android.media.MediaRecorder}.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* If the {@code format} is {@link ImageFormat#PRIVATE PRIVATE}, the created {@link ImageReader}
|
||
|
* will produce images that are not directly accessible by the application. The application can
|
||
|
* still acquire images from this {@link ImageReader}, and send them to the
|
||
|
* {@link android.hardware.camera2.CameraDevice camera} for reprocessing, or to the
|
||
|
* {@link android.media.MediaCodec} / {@link android.media.MediaRecorder} for hardware video
|
||
|
* encoding via {@link ImageWriter} interface. However, the {@link Image#getPlanes()
|
||
|
* getPlanes()} will return an empty array for {@link ImageFormat#PRIVATE PRIVATE} format
|
||
|
* images. The application can check if an existing reader's format by calling
|
||
|
* {@link #getImageFormat()}.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* {@link ImageFormat#PRIVATE PRIVATE} format {@link ImageReader ImageReaders} are more
|
||
|
* efficient to use when application access to image data is not necessary, compared to
|
||
|
* ImageReaders using other format such as {@link ImageFormat#YUV_420_888 YUV_420_888}.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* Note that not all format and usage flag combinations are supported by the
|
||
|
* {@link ImageReader}. Below are the supported combinations by the {@link ImageReader}
|
||
|
* (assuming the consumer end-points support the such image consumption, e.g., hardware video
|
||
|
* encoding).
|
||
|
* <table>
|
||
|
* <tr>
|
||
|
* <th>Format</th>
|
||
|
* <th>Compatible usage flags</th>
|
||
|
* </tr>
|
||
|
* <tr>
|
||
|
* <td>non-{@link android.graphics.ImageFormat#PRIVATE PRIVATE} formats defined by
|
||
|
* {@link android.graphics.ImageFormat ImageFormat} or
|
||
|
* {@link android.graphics.PixelFormat PixelFormat}</td>
|
||
|
* <td>{@link HardwareBuffer#USAGE_CPU_READ_RARELY} or
|
||
|
* {@link HardwareBuffer#USAGE_CPU_READ_OFTEN}</td>
|
||
|
* </tr>
|
||
|
* <tr>
|
||
|
* <td>{@link android.graphics.ImageFormat#PRIVATE}</td>
|
||
|
* <td>{@link HardwareBuffer#USAGE_VIDEO_ENCODE} or
|
||
|
* {@link HardwareBuffer#USAGE_GPU_SAMPLED_IMAGE}, or combined</td>
|
||
|
* </tr>
|
||
|
* </table>
|
||
|
* Using other combinations may result in {@link IllegalArgumentException}. Additionally,
|
||
|
* specifying {@link HardwareBuffer#USAGE_CPU_WRITE_RARELY} or
|
||
|
* {@link HardwareBuffer#USAGE_CPU_WRITE_OFTEN} and writing to the ImageReader's buffers
|
||
|
* might break assumptions made by some producers, and should be used with caution.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* If the {@link ImageReader} is used as an output target for a {@link
|
||
|
* android.hardware.camera2.CameraDevice}, and if the usage flag contains
|
||
|
* {@link HardwareBuffer#USAGE_VIDEO_ENCODE}, the timestamps of the
|
||
|
* {@link Image images} produced by the {@link ImageReader} won't be in the same timebase as
|
||
|
* {@link android.os.SystemClock#elapsedRealtimeNanos}, even if
|
||
|
* {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE} is
|
||
|
* {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME}.
|
||
|
* Instead, the timestamps will be roughly in the same timebase as in
|
||
|
* {@link android.os.SystemClock#uptimeMillis}, so that A/V synchronization could work for
|
||
|
* video recording. In this case, the timestamps from the {@link ImageReader} with
|
||
|
* {@link HardwareBuffer#USAGE_VIDEO_ENCODE} usage flag may not be directly comparable with
|
||
|
* timestamps of other streams or capture result metadata.
|
||
|
* </p>
|
||
|
* @param width The default width in pixels of the Images that this reader will produce.
|
||
|
* @param height The default height in pixels of the Images that this reader will produce.
|
||
|
* @param format The format of the Image that this 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.
|
||
|
* @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, one of them has to be released before a new Image will
|
||
|
* become available for access through {@link #acquireLatestImage()} or
|
||
|
* {@link #acquireNextImage()}. Must be greater than 0.
|
||
|
* @param usage The intended usage of the images produced by this 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 HardwareBuffer
|
||
|
*/
|
||
|
public static @NonNull ImageReader newInstance(
|
||
|
@IntRange(from = 1) int width,
|
||
|
@IntRange(from = 1) int height,
|
||
|
@Format int format,
|
||
|
@IntRange(from = 1) int maxImages,
|
||
|
@Usage long usage) {
|
||
|
// TODO: Check this - can't do it just yet because format support is different
|
||
|
// Unify formats! The only reliable way to validate usage is to just try it and see.
|
||
|
|
||
|
// if (!HardwareBuffer.isSupported(width, height, format, 1, usage)) {
|
||
|
// throw new IllegalArgumentException("The given format=" + Integer.toHexString(format)
|
||
|
// + " & usage=" + Long.toHexString(usage) + " is not supported");
|
||
|
// }
|
||
|
return new ImageReader(width, height, format, maxImages, usage, /*parent*/ null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static @NonNull ImageReader newInstance(
|
||
|
@IntRange(from = 1) int width,
|
||
|
@IntRange(from = 1) int height,
|
||
|
@Format int format,
|
||
|
@IntRange(from = 1) int maxImages,
|
||
|
@NonNull MultiResolutionImageReader parent) {
|
||
|
// If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not
|
||
|
// work, and is inscrutable anyway
|
||
|
return new ImageReader(width, height, format, maxImages,
|
||
|
format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, parent);
|
||
|
}
|
||
|
|
||
|
private void initializeImageReader(int width, int height, int imageFormat, int maxImages,
|
||
|
long usage, int hardwareBufferFormat, int dataSpace) {
|
||
|
if (width < 1 || height < 1) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"The image dimensions must be positive");
|
||
|
}
|
||
|
|
||
|
if (maxImages < 1) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Maximum outstanding image count must be at least 1");
|
||
|
}
|
||
|
|
||
|
if (imageFormat == ImageFormat.NV21) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"NV21 format is not supported");
|
||
|
}
|
||
|
|
||
|
nativeInit(new WeakReference<>(this), width, height, maxImages, usage,
|
||
|
hardwareBufferFormat, dataSpace);
|
||
|
|
||
|
mIsReaderValid = true;
|
||
|
|
||
|
mSurface = nativeGetSurface();
|
||
|
// Estimate the native buffer allocation size and register it so it gets accounted for
|
||
|
// during GC. Note that this doesn't include the buffers required by the buffer queue
|
||
|
// itself and the buffers requested by the producer.
|
||
|
// Only include memory for 1 buffer, since actually accounting for the memory used is
|
||
|
// complex, and 1 buffer is enough for the VM to treat the ImageReader as being of some
|
||
|
// size.
|
||
|
mEstimatedNativeAllocBytes = ImageUtils.getEstimatedNativeAllocBytes(
|
||
|
width, height, imageFormat, /*buffer count*/ 1);
|
||
|
VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
|
||
|
}
|
||
|
|
||
|
private ImageReader(int width, int height, int imageFormat, int maxImages, long usage,
|
||
|
MultiResolutionImageReader parent) {
|
||
|
mWidth = width;
|
||
|
mHeight = height;
|
||
|
mFormat = imageFormat;
|
||
|
mUsage = usage;
|
||
|
mMaxImages = maxImages;
|
||
|
mParent = parent;
|
||
|
// retrieve hal Format and hal dataspace from imageFormat
|
||
|
mHardwareBufferFormat = PublicFormatUtils.getHalFormat(mFormat);
|
||
|
mDataSpace = PublicFormatUtils.getHalDataspace(mFormat);
|
||
|
mNumPlanes = ImageUtils.getNumPlanesForFormat(mFormat);
|
||
|
|
||
|
initializeImageReader(width, height, imageFormat, maxImages, usage, mHardwareBufferFormat,
|
||
|
mDataSpace);
|
||
|
}
|
||
|
|
||
|
private ImageReader(int width, int height, int maxImages, long usage,
|
||
|
MultiResolutionImageReader parent, int hardwareBufferFormat, int dataSpace) {
|
||
|
mWidth = width;
|
||
|
mHeight = height;
|
||
|
mUsage = usage;
|
||
|
mMaxImages = maxImages;
|
||
|
mParent = parent;
|
||
|
mHardwareBufferFormat = hardwareBufferFormat;
|
||
|
mDataSpace = dataSpace;
|
||
|
mNumPlanes = ImageUtils.getNumPlanesForHardwareBufferFormat(mHardwareBufferFormat);
|
||
|
mFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace);
|
||
|
|
||
|
initializeImageReader(width, height, mFormat, maxImages, usage, hardwareBufferFormat,
|
||
|
dataSpace);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The default width of {@link Image Images}, in pixels.
|
||
|
*
|
||
|
* <p>The width may be overridden by the producer sending buffers to this
|
||
|
* ImageReader's Surface. If so, the actual width of the images can be
|
||
|
* found using {@link Image#getWidth}.</p>
|
||
|
*
|
||
|
* @return the expected width of an Image
|
||
|
*/
|
||
|
public int getWidth() {
|
||
|
return mWidth;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The default height of {@link Image Images}, in pixels.
|
||
|
*
|
||
|
* <p>The height may be overridden by the producer sending buffers to this
|
||
|
* ImageReader's Surface. If so, the actual height of the images can be
|
||
|
* found using {@link Image#getHeight}.</p>
|
||
|
*
|
||
|
* @return the expected height of an Image
|
||
|
*/
|
||
|
public int getHeight() {
|
||
|
return mHeight;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The default {@link ImageFormat image format} of {@link Image Images}.
|
||
|
*
|
||
|
* <p>Some color formats may be overridden by the producer sending buffers to
|
||
|
* this ImageReader's Surface if the default color format allows. ImageReader
|
||
|
* guarantees that all {@link Image Images} acquired from ImageReader
|
||
|
* (for example, with {@link #acquireNextImage}) will have a "compatible"
|
||
|
* format to what was specified in {@link #newInstance}.
|
||
|
* As of now, each format is only compatible to itself.
|
||
|
* The actual format of the images can be found using {@link Image#getFormat}.</p>
|
||
|
*
|
||
|
* <p>Use this function if the ImageReader instance is created by factory method
|
||
|
* {@code newInstance} function or by builder pattern {@code ImageReader.Builder} and using
|
||
|
* {@link Builder#setImageFormat}.</p>
|
||
|
*
|
||
|
* @return the expected format of an Image
|
||
|
*
|
||
|
* @see ImageFormat
|
||
|
*/
|
||
|
public int getImageFormat() {
|
||
|
return mFormat;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The default {@link HardwareBuffer} format of {@link Image Images}.
|
||
|
*
|
||
|
* <p>Use this function if the ImageReader instance is created by builder pattern
|
||
|
* {@code ImageReader.Builder} and using {@link Builder#setDefaultHardwareBufferFormat} and
|
||
|
* {@link Builder#setDefaultDataSpace}.</p>
|
||
|
*
|
||
|
* @return the expected {@link HardwareBuffer} format of an Image.
|
||
|
*/
|
||
|
public @HardwareBuffer.Format int getHardwareBufferFormat() {
|
||
|
return mHardwareBufferFormat;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The default dataspace of {@link Image Images}.
|
||
|
*
|
||
|
* <p>Use this function if the ImageReader instance is created by builder pattern
|
||
|
* {@code ImageReader.Builder} and {@link Builder#setDefaultDataSpace}.</p>
|
||
|
*
|
||
|
* @return the expected dataspace of an Image.
|
||
|
*/
|
||
|
@SuppressLint("MethodNameUnits")
|
||
|
public @NamedDataSpace int getDataSpace() {
|
||
|
return mDataSpace;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Maximum number of images that can be acquired from the ImageReader by any time (for example,
|
||
|
* with {@link #acquireNextImage}).
|
||
|
*
|
||
|
* <p>An image is considered acquired after it's returned by a function from ImageReader, and
|
||
|
* until the Image is {@link Image#close closed} to release the image back to the ImageReader.
|
||
|
* </p>
|
||
|
*
|
||
|
* <p>Attempting to acquire more than {@code maxImages} concurrently will result in the
|
||
|
* acquire function throwing a {@link IllegalStateException}. Furthermore,
|
||
|
* while the max number of images have been acquired by the ImageReader user, the producer
|
||
|
* enqueueing additional images may stall until at least one image has been released. </p>
|
||
|
*
|
||
|
* @return Maximum number of images for this ImageReader.
|
||
|
*
|
||
|
* @see Image#close
|
||
|
*/
|
||
|
public int getMaxImages() {
|
||
|
return mMaxImages;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The usage flag of images that can be produced by the ImageReader.
|
||
|
*
|
||
|
* @return The usage flag of the images for this ImageReader.
|
||
|
*/
|
||
|
public @Usage long getUsage() {
|
||
|
return mUsage;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this
|
||
|
* {@code ImageReader}.</p>
|
||
|
*
|
||
|
* <p>Until valid image data is rendered into this {@link Surface}, the
|
||
|
* {@link #acquireNextImage} method will return {@code null}. Only one source
|
||
|
* can be producing data into this Surface at the same time, although the
|
||
|
* same {@link Surface} can be reused with a different API once the first source is
|
||
|
* disconnected from the {@link Surface}.</p>
|
||
|
*
|
||
|
* <p>Please note that holding on to the Surface object returned by this method is not enough
|
||
|
* to keep its parent ImageReader from being reclaimed. In that sense, a Surface acts like a
|
||
|
* {@link java.lang.ref.WeakReference weak reference} to the ImageReader that provides it.</p>
|
||
|
*
|
||
|
* @return A {@link Surface} to use for a drawing target for various APIs.
|
||
|
*/
|
||
|
public Surface getSurface() {
|
||
|
return mSurface;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Acquire the latest {@link Image} from the ImageReader's queue, dropping older
|
||
|
* {@link Image images}. Returns {@code null} if no new image is available.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* This operation will acquire all the images possible from the ImageReader,
|
||
|
* but {@link #close} all images that aren't the latest. This function is
|
||
|
* recommended to use over {@link #acquireNextImage} for most use-cases, as it's
|
||
|
* more suited for real-time processing.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* Note that {@link #getMaxImages maxImages} should be at least 2 for
|
||
|
* {@link #acquireLatestImage} to be any different than {@link #acquireNextImage} -
|
||
|
* discarding all-but-the-newest {@link Image} requires temporarily acquiring two
|
||
|
* {@link Image Images} at once. Or more generally, calling {@link #acquireLatestImage}
|
||
|
* with less than two images of margin, that is
|
||
|
* {@code (maxImages - currentAcquiredImages < 2)} will not discard as expected.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* This operation will fail by throwing an {@link IllegalStateException} if
|
||
|
* {@code maxImages} have been acquired with {@link #acquireLatestImage} or
|
||
|
* {@link #acquireNextImage}. In particular a sequence of {@link #acquireLatestImage}
|
||
|
* calls greater than {@link #getMaxImages} without calling {@link Image#close} in-between
|
||
|
* will exhaust the underlying queue. At such a time, {@link IllegalStateException}
|
||
|
* will be thrown until more images are
|
||
|
* released with {@link Image#close}.
|
||
|
* </p>
|
||
|
*
|
||
|
* @return latest frame of image data, or {@code null} if no image data is available.
|
||
|
* @throws IllegalStateException if too many images are currently acquired
|
||
|
*/
|
||
|
public Image acquireLatestImage() {
|
||
|
Image image = acquireNextImage();
|
||
|
if (image == null) {
|
||
|
return null;
|
||
|
}
|
||
|
try {
|
||
|
for (;;) {
|
||
|
Image next = acquireNextImageNoThrowISE();
|
||
|
if (next == null) {
|
||
|
Image result = image;
|
||
|
image = null;
|
||
|
return result;
|
||
|
}
|
||
|
image.close();
|
||
|
image = next;
|
||
|
}
|
||
|
} finally {
|
||
|
if (image != null) {
|
||
|
image.close();
|
||
|
}
|
||
|
if (mParent != null) {
|
||
|
mParent.flushOther(this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Don't throw IllegalStateException if there are too many images acquired.
|
||
|
*
|
||
|
* @return Image if acquiring succeeded, or null otherwise.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public Image acquireNextImageNoThrowISE() {
|
||
|
SurfaceImage si = new SurfaceImage(mFormat);
|
||
|
return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempts to acquire the next image from the underlying native implementation.
|
||
|
*
|
||
|
* <p>
|
||
|
* Note that unexpected failures will throw at the JNI level.
|
||
|
* </p>
|
||
|
*
|
||
|
* @param si A blank SurfaceImage.
|
||
|
* @return One of the {@code ACQUIRE_*} codes that determine success or failure.
|
||
|
*
|
||
|
* @see #ACQUIRE_MAX_IMAGES
|
||
|
* @see #ACQUIRE_NO_BUFS
|
||
|
* @see #ACQUIRE_SUCCESS
|
||
|
*/
|
||
|
private int acquireNextSurfaceImage(SurfaceImage si) {
|
||
|
synchronized (mCloseLock) {
|
||
|
// A null image will eventually be returned if ImageReader is already closed.
|
||
|
int status = ACQUIRE_NO_BUFS;
|
||
|
if (mIsReaderValid) {
|
||
|
status = nativeImageSetup(si);
|
||
|
}
|
||
|
|
||
|
switch (status) {
|
||
|
case ACQUIRE_SUCCESS:
|
||
|
si.mIsImageValid = true;
|
||
|
case ACQUIRE_NO_BUFS:
|
||
|
case ACQUIRE_MAX_IMAGES:
|
||
|
break;
|
||
|
default:
|
||
|
throw new AssertionError("Unknown nativeImageSetup return code " + status);
|
||
|
}
|
||
|
|
||
|
// Only keep track the successfully acquired image, as the native buffer is only mapped
|
||
|
// for such case.
|
||
|
if (status == ACQUIRE_SUCCESS) {
|
||
|
mAcquiredImages.add(si);
|
||
|
}
|
||
|
return status;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Acquire the next Image from the ImageReader's queue. Returns {@code null} if
|
||
|
* no new image is available.
|
||
|
* </p>
|
||
|
*
|
||
|
* <p><i>Warning:</i> Consider using {@link #acquireLatestImage()} instead, as it will
|
||
|
* automatically release older images, and allow slower-running processing routines to catch
|
||
|
* up to the newest frame. Usage of {@link #acquireNextImage} is recommended for
|
||
|
* batch/background processing. Incorrectly using this function can cause images to appear
|
||
|
* with an ever-increasing delay, followed by a complete stall where no new images seem to
|
||
|
* appear.
|
||
|
* </p>
|
||
|
*
|
||
|
* <p>
|
||
|
* This operation will fail by throwing an {@link IllegalStateException} if
|
||
|
* {@code maxImages} have been acquired with {@link #acquireNextImage} or
|
||
|
* {@link #acquireLatestImage}. In particular a sequence of {@link #acquireNextImage} or
|
||
|
* {@link #acquireLatestImage} calls greater than {@link #getMaxImages maxImages} without
|
||
|
* calling {@link Image#close} in-between will exhaust the underlying queue. At such a time,
|
||
|
* {@link IllegalStateException} will be thrown until more images are released with
|
||
|
* {@link Image#close}.
|
||
|
* </p>
|
||
|
*
|
||
|
* @return a new frame of image data, or {@code null} if no image data is available.
|
||
|
* @throws IllegalStateException if {@code maxImages} images are currently acquired
|
||
|
* @see #acquireLatestImage
|
||
|
*/
|
||
|
public Image acquireNextImage() {
|
||
|
// Initialize with reader format, but can be overwritten by native if the image
|
||
|
// format is different from the reader format.
|
||
|
SurfaceImage si;
|
||
|
si = new SurfaceImage(mFormat);
|
||
|
int status = acquireNextSurfaceImage(si);
|
||
|
|
||
|
switch (status) {
|
||
|
case ACQUIRE_SUCCESS:
|
||
|
return si;
|
||
|
case ACQUIRE_NO_BUFS:
|
||
|
return null;
|
||
|
case ACQUIRE_MAX_IMAGES:
|
||
|
throw new IllegalStateException(
|
||
|
String.format(
|
||
|
"maxImages (%d) has already been acquired, " +
|
||
|
"call #close before acquiring more.", mMaxImages));
|
||
|
default:
|
||
|
throw new AssertionError("Unknown nativeImageSetup return code " + status);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>Return the frame to the ImageReader for reuse.</p>
|
||
|
*
|
||
|
* This method should only be called via {@link SurfaceImage#close} which ensures that image
|
||
|
* closing is atomic.
|
||
|
*/
|
||
|
private void releaseImage(Image i) {
|
||
|
if (! (i instanceof SurfaceImage) ) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"This image was not produced by an ImageReader");
|
||
|
}
|
||
|
SurfaceImage si = (SurfaceImage) i;
|
||
|
if (si.mIsImageValid == false) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (si.getReader() != this || !mAcquiredImages.contains(i)) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"This image was not produced by this ImageReader");
|
||
|
}
|
||
|
|
||
|
si.clearSurfacePlanes();
|
||
|
nativeReleaseImage(i);
|
||
|
si.mIsImageValid = false;
|
||
|
mAcquiredImages.remove(i);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register a listener to be invoked when a new image becomes available
|
||
|
* from the ImageReader.
|
||
|
*
|
||
|
* @param listener
|
||
|
* The listener that will be run.
|
||
|
* @param handler
|
||
|
* The handler on which the listener should be invoked, or null
|
||
|
* if the listener should be invoked on the calling thread's looper.
|
||
|
* @throws IllegalArgumentException
|
||
|
* If no handler specified and the calling thread has no looper.
|
||
|
*/
|
||
|
public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) {
|
||
|
synchronized (mListenerLock) {
|
||
|
if (listener != null) {
|
||
|
Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
|
||
|
if (looper == null) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"handler is null but the current thread is not a looper");
|
||
|
}
|
||
|
if (mListenerHandler == null || mListenerHandler.getLooper() != looper) {
|
||
|
mListenerHandler = new ListenerHandler(looper);
|
||
|
mListenerExecutor = new HandlerExecutor(mListenerHandler);
|
||
|
}
|
||
|
} else {
|
||
|
mListenerHandler = null;
|
||
|
mListenerExecutor = null;
|
||
|
}
|
||
|
mListener = listener;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register a listener to be invoked when a new image becomes available
|
||
|
* from the ImageReader.
|
||
|
*
|
||
|
* @param listener
|
||
|
* The listener that will be run.
|
||
|
* @param executor
|
||
|
* The executor which will be used to invoke the listener.
|
||
|
* @throws IllegalArgumentException
|
||
|
* If no handler specified and the calling thread has no looper.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public void setOnImageAvailableListenerWithExecutor(@NonNull OnImageAvailableListener listener,
|
||
|
@NonNull Executor executor) {
|
||
|
if (executor == null) {
|
||
|
throw new IllegalArgumentException("executor must not be null");
|
||
|
}
|
||
|
|
||
|
synchronized (mListenerLock) {
|
||
|
mListenerExecutor = executor;
|
||
|
mListener = listener;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Callback interface for being notified that a new image is available.
|
||
|
*
|
||
|
* <p>
|
||
|
* The onImageAvailable is called per image basis, that is, callback fires for every new frame
|
||
|
* available from ImageReader.
|
||
|
* </p>
|
||
|
*/
|
||
|
public interface OnImageAvailableListener {
|
||
|
/**
|
||
|
* Callback that is called when a new image is available from ImageReader.
|
||
|
*
|
||
|
* @param reader the ImageReader the callback is associated with.
|
||
|
* @see ImageReader
|
||
|
* @see Image
|
||
|
*/
|
||
|
void onImageAvailable(ImageReader reader);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Free up all the resources associated with this ImageReader.
|
||
|
*
|
||
|
* <p>
|
||
|
* After calling this method, this ImageReader can not be used. Calling
|
||
|
* any methods on this ImageReader and Images previously provided by
|
||
|
* {@link #acquireNextImage} or {@link #acquireLatestImage}
|
||
|
* will result in an {@link IllegalStateException}, and attempting to read from
|
||
|
* {@link ByteBuffer ByteBuffers} returned by an earlier
|
||
|
* {@link Image.Plane#getBuffer Plane#getBuffer} call will
|
||
|
* have undefined behavior.
|
||
|
* </p>
|
||
|
*/
|
||
|
@Override
|
||
|
public void close() {
|
||
|
setOnImageAvailableListener(null, null);
|
||
|
if (mSurface != null) mSurface.release();
|
||
|
|
||
|
/**
|
||
|
* Close all outstanding acquired images before closing the ImageReader. It is a good
|
||
|
* practice to close all the images as soon as it is not used to reduce system instantaneous
|
||
|
* memory pressure. CopyOnWrite list will use a copy of current list content. For the images
|
||
|
* being closed by other thread (e.g., GC thread), doubling the close call is harmless. For
|
||
|
* the image being acquired by other threads, mCloseLock is used to synchronize close and
|
||
|
* acquire operations.
|
||
|
*/
|
||
|
synchronized (mCloseLock) {
|
||
|
mIsReaderValid = false;
|
||
|
for (Image image : mAcquiredImages) {
|
||
|
image.close();
|
||
|
}
|
||
|
mAcquiredImages.clear();
|
||
|
|
||
|
nativeClose();
|
||
|
|
||
|
if (mEstimatedNativeAllocBytes > 0) {
|
||
|
VMRuntime.getRuntime().registerNativeFree(mEstimatedNativeAllocBytes);
|
||
|
mEstimatedNativeAllocBytes = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Discard any free buffers owned by this ImageReader.
|
||
|
*
|
||
|
* <p>
|
||
|
* Generally, the ImageReader caches buffers for reuse once they have been
|
||
|
* allocated, for best performance. However, sometimes it may be important to
|
||
|
* release all the cached, unused buffers to save on memory.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* Calling this method will discard all free cached buffers. This does not include any buffers
|
||
|
* associated with Images acquired from the ImageReader, any filled buffers waiting to be
|
||
|
* acquired, and any buffers currently in use by the source rendering buffers into the
|
||
|
* ImageReader's Surface.
|
||
|
* <p>
|
||
|
* The ImageReader continues to be usable after this call, but may need to reallocate buffers
|
||
|
* when more buffers are needed for rendering.
|
||
|
* </p>
|
||
|
*/
|
||
|
public void discardFreeBuffers() {
|
||
|
synchronized (mCloseLock) {
|
||
|
nativeDiscardFreeBuffers();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void finalize() throws Throwable {
|
||
|
try {
|
||
|
close();
|
||
|
} finally {
|
||
|
super.finalize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <p>
|
||
|
* Remove the ownership of this image from the ImageReader.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* After this call, the ImageReader no longer owns this image, and the image
|
||
|
* ownership can be transferred to another entity like {@link ImageWriter}
|
||
|
* via {@link ImageWriter#queueInputImage}. It's up to the new owner to
|
||
|
* release the resources held by this image. For example, if the ownership
|
||
|
* of this image is transferred to an {@link ImageWriter}, the image will be
|
||
|
* freed by the ImageWriter after the image data consumption is done.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* This method can be used to achieve zero buffer copy for use cases like
|
||
|
* {@link android.hardware.camera2.CameraDevice Camera2 API} PRIVATE and YUV
|
||
|
* reprocessing, where the application can select an output image from
|
||
|
* {@link ImageReader} and transfer this image directly to
|
||
|
* {@link ImageWriter}, where this image can be consumed by camera directly.
|
||
|
* For PRIVATE reprocessing, this is the only way to send input buffers to
|
||
|
* the {@link android.hardware.camera2.CameraDevice camera} for
|
||
|
* reprocessing.
|
||
|
* </p>
|
||
|
* <p>
|
||
|
* This is a package private method that is only used internally.
|
||
|
* </p>
|
||
|
*
|
||
|
* @param image The image to be detached from this ImageReader.
|
||
|
* @throws IllegalStateException If the ImageReader or image have been
|
||
|
* closed, or the has been detached, or has not yet been
|
||
|
* acquired.
|
||
|
* @throws RuntimeException If there is an error detaching {@code Image} from {@code Surface}.
|
||
|
* {@code RuntimeException} is only thrown for applications targeting SDK <=
|
||
|
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
|
||
|
* For applications targeting SDK >
|
||
|
* {@link android.os.Build.VERSION_CODES#TIRAMISU},
|
||
|
* this method only throws {@code IllegalStateException}.
|
||
|
* @hide
|
||
|
*/
|
||
|
public void detachImage(@Nullable Image image) {
|
||
|
if (image == null) {
|
||
|
throw new IllegalArgumentException("input image must not be null");
|
||
|
}
|
||
|
if (!isImageOwnedbyMe(image)) {
|
||
|
throw new IllegalArgumentException("Trying to detach an image that is not owned by"
|
||
|
+ " this ImageReader");
|
||
|
}
|
||
|
|
||
|
SurfaceImage si = (SurfaceImage) image;
|
||
|
si.throwISEIfImageIsInvalid();
|
||
|
|
||
|
if (si.isAttachable()) {
|
||
|
throw new IllegalStateException("Image was already detached from this ImageReader");
|
||
|
}
|
||
|
|
||
|
nativeDetachImage(image, mDetachThrowsIseOnly);
|
||
|
si.clearSurfacePlanes();
|
||
|
si.mPlanes = null;
|
||
|
si.setDetached(true);
|
||
|
}
|
||
|
|
||
|
private boolean isImageOwnedbyMe(Image image) {
|
||
|
if (!(image instanceof SurfaceImage)) {
|
||
|
return false;
|
||
|
}
|
||
|
SurfaceImage si = (SurfaceImage) image;
|
||
|
return si.getReader() == this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called from Native code when an Event happens.
|
||
|
*
|
||
|
* This may be called from an arbitrary Binder thread, so access to the ImageReader must be
|
||
|
* synchronized appropriately.
|
||
|
*/
|
||
|
private static void postEventFromNative(Object selfRef) {
|
||
|
@SuppressWarnings("unchecked")
|
||
|
WeakReference<ImageReader> weakSelf = (WeakReference<ImageReader>)selfRef;
|
||
|
final ImageReader ir = weakSelf.get();
|
||
|
if (ir == null) {
|
||
|
return;
|
||
|
}
|
||
|
Trace.beginSection("android.media.ImageReader#postEventFromNative");
|
||
|
|
||
|
final Executor executor;
|
||
|
final OnImageAvailableListener listener;
|
||
|
synchronized (ir.mListenerLock) {
|
||
|
executor = ir.mListenerExecutor;
|
||
|
listener = ir.mListener;
|
||
|
}
|
||
|
final boolean isReaderValid;
|
||
|
synchronized (ir.mCloseLock) {
|
||
|
isReaderValid = ir.mIsReaderValid;
|
||
|
}
|
||
|
|
||
|
// It's dangerous to fire onImageAvailable() callback when the ImageReader
|
||
|
// is being closed, as application could acquire next image in the
|
||
|
// onImageAvailable() callback.
|
||
|
if (executor != null && listener != null && isReaderValid) {
|
||
|
executor.execute(new Runnable() {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
listener.onImageAvailable(ir);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
Trace.endSection();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Builder class for {@link ImageReader} objects.
|
||
|
*/
|
||
|
public static final class Builder {
|
||
|
private int mWidth;
|
||
|
private int mHeight;
|
||
|
private int mMaxImages = 1;
|
||
|
private int mImageFormat = ImageFormat.UNKNOWN;
|
||
|
private int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
|
||
|
private int mDataSpace = DataSpace.DATASPACE_UNKNOWN;
|
||
|
private long mUsage = HardwareBuffer.USAGE_CPU_READ_OFTEN;
|
||
|
private boolean mUseLegacyImageFormat = false;
|
||
|
|
||
|
/**
|
||
|
* Constructs a new builder for {@link ImageReader}.
|
||
|
*
|
||
|
* @param width The default width in pixels that will be passed to the producer.
|
||
|
* May be overridden by the producer.
|
||
|
* @param height The default height in pixels that will be passed to the producer.
|
||
|
* May be overridden by the producer.
|
||
|
* @see Image
|
||
|
*/
|
||
|
public Builder(@IntRange(from = 1) int width, @IntRange(from = 1) int height) {
|
||
|
mWidth = width;
|
||
|
mHeight = height;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the maximal number of images.
|
||
|
*
|
||
|
* @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. Default value is 1.
|
||
|
* @return the Builder instance with customized usage value.
|
||
|
*/
|
||
|
public @NonNull Builder setMaxImages(int maxImages) {
|
||
|
mMaxImages = maxImages;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the consumer usage flag.
|
||
|
*
|
||
|
* @param usage The intended usage of the images consumed by this ImageReader.
|
||
|
* See the usages on {@link HardwareBuffer} for a list of valid usage bits.
|
||
|
* Default value is {@link HardwareBuffer#USAGE_CPU_READ_OFTEN}.
|
||
|
* @return the Builder instance with customized usage value.
|
||
|
*
|
||
|
* @see HardwareBuffer
|
||
|
*/
|
||
|
public @NonNull Builder setUsage(@Usage long usage) {
|
||
|
mUsage = usage;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the default image format passed by the producer. May be overridden by the producer.
|
||
|
*
|
||
|
* <p>{@link #setImageFormat} function replaces the combination of
|
||
|
* {@link #setDefaultHardwareBufferFormat} and {@link #setDefaultDataSpace} functions.
|
||
|
* Either this or these two functions must be called to initialize an {@code ImageReader}
|
||
|
* instance.</p>
|
||
|
*
|
||
|
* @param imageFormat The format of the image that this 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 default value is
|
||
|
* {@link ImageFormat#UNKNOWN}.
|
||
|
* @return the builder instance with customized image format value.
|
||
|
*
|
||
|
* @see #setDefaultHardwareBufferFormat
|
||
|
* @see #setDefaultDataSpace
|
||
|
*/
|
||
|
public @NonNull Builder setImageFormat(@Format int imageFormat) {
|
||
|
mImageFormat = imageFormat;
|
||
|
mUseLegacyImageFormat = true;
|
||
|
mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
|
||
|
mDataSpace = DataSpace.DATASPACE_UNKNOWN;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the default hardwareBuffer format passed by the producer.
|
||
|
* May be overridden by the producer.
|
||
|
*
|
||
|
* <p>This function works together with {@link #setDefaultDataSpace} for an
|
||
|
* {@link ImageReader} instance. Setting at least one of these two replaces
|
||
|
* {@link #setImageFormat} function.</p>
|
||
|
*
|
||
|
* <p>The format of the Image can be overridden after {@link #setImageFormat} by calling
|
||
|
* this function and then {@link #setDefaultDataSpace} functions.
|
||
|
* <i>Warning:</i> Missing one of callings for initializing or overriding the format may
|
||
|
* involve undefined behaviors.</p>
|
||
|
*
|
||
|
* @param hardwareBufferFormat The HardwareBuffer format of the image that this reader
|
||
|
* will produce. The default value is
|
||
|
* {@link HardwareBuffer#RGBA_8888 HardwareBuffer.RGBA_8888}.
|
||
|
* @return the builder instance with customized hardwareBuffer value.
|
||
|
*
|
||
|
* @see #setDefaultDataSpace
|
||
|
* @see #setImageFormat
|
||
|
*/
|
||
|
@SuppressLint("MissingGetterMatchingBuilder")
|
||
|
public @NonNull Builder setDefaultHardwareBufferFormat(
|
||
|
@HardwareBuffer.Format int hardwareBufferFormat) {
|
||
|
mHardwareBufferFormat = hardwareBufferFormat;
|
||
|
mUseLegacyImageFormat = false;
|
||
|
mImageFormat = ImageFormat.UNKNOWN;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the default dataspace passed by the producer.
|
||
|
* May be overridden by the producer.
|
||
|
*
|
||
|
* <p>This function works together with {@link #setDefaultHardwareBufferFormat} for an
|
||
|
* {@link ImageReader} instance. Setting at least one of these two replaces
|
||
|
* {@link #setImageFormat} function.</p>
|
||
|
*
|
||
|
* @param dataSpace The dataspace of the image that this reader will produce.
|
||
|
* The default value is {@link DataSpace#DATASPACE_UNKNOWN}.
|
||
|
* @return the builder instance with customized dataspace value.
|
||
|
*
|
||
|
* @see #setDefaultHardwareBufferFormat
|
||
|
*/
|
||
|
@SuppressLint("MissingGetterMatchingBuilder")
|
||
|
public @NonNull Builder setDefaultDataSpace(@NamedDataSpace int dataSpace) {
|
||
|
mDataSpace = dataSpace;
|
||
|
mUseLegacyImageFormat = false;
|
||
|
mImageFormat = ImageFormat.UNKNOWN;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Builds a new ImageReader object.
|
||
|
*
|
||
|
* @return The new ImageReader object.
|
||
|
*/
|
||
|
public @NonNull ImageReader build() {
|
||
|
if (mUseLegacyImageFormat) {
|
||
|
return new ImageReader(mWidth, mHeight, mImageFormat, mMaxImages, mUsage, null);
|
||
|
} else {
|
||
|
return new ImageReader(mWidth, mHeight, mMaxImages, mUsage, null,
|
||
|
mHardwareBufferFormat, mDataSpace);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private final int mWidth;
|
||
|
private final int mHeight;
|
||
|
private final int mFormat;
|
||
|
private final long mUsage;
|
||
|
private final int mMaxImages;
|
||
|
private final int mNumPlanes;
|
||
|
private Surface mSurface;
|
||
|
private int mEstimatedNativeAllocBytes;
|
||
|
|
||
|
private final Object mListenerLock = new Object();
|
||
|
private final Object mCloseLock = new Object();
|
||
|
private boolean mIsReaderValid = false;
|
||
|
private OnImageAvailableListener mListener;
|
||
|
private Executor mListenerExecutor;
|
||
|
private ListenerHandler mListenerHandler;
|
||
|
// Keep track of the successfully acquired Images. This need to be thread safe as the images
|
||
|
// could be closed by different threads (e.g., application thread and GC thread).
|
||
|
private List<Image> mAcquiredImages = new CopyOnWriteArrayList<>();
|
||
|
|
||
|
// Applicable if this isn't a standalone ImageReader, but belongs to a
|
||
|
// MultiResolutionImageReader.
|
||
|
private final MultiResolutionImageReader mParent;
|
||
|
|
||
|
private final int mHardwareBufferFormat;
|
||
|
|
||
|
private final @NamedDataSpace int mDataSpace;
|
||
|
|
||
|
/**
|
||
|
* This field is used by native code, do not access or modify.
|
||
|
*/
|
||
|
private long mNativeContext;
|
||
|
|
||
|
/**
|
||
|
* This custom handler runs asynchronously so callbacks don't get queued behind UI messages.
|
||
|
*/
|
||
|
private final class ListenerHandler extends Handler {
|
||
|
public ListenerHandler(Looper looper) {
|
||
|
super(looper, null, true /*async*/);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An adapter {@link Executor} that posts all executed tasks onto the
|
||
|
* given {@link Handler}.
|
||
|
**/
|
||
|
private final class HandlerExecutor implements Executor {
|
||
|
private final Handler mHandler;
|
||
|
|
||
|
public HandlerExecutor(@NonNull Handler handler) {
|
||
|
mHandler = Objects.requireNonNull(handler);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void execute(Runnable command) {
|
||
|
mHandler.post(command);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class SurfaceImage extends android.media.Image {
|
||
|
private final Object mCloseLock = new Object();
|
||
|
|
||
|
public SurfaceImage(int format) {
|
||
|
mFormat = format;
|
||
|
mHardwareBufferFormat = ImageReader.this.mHardwareBufferFormat;
|
||
|
mDataSpace = ImageReader.this.mDataSpace;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void close() {
|
||
|
synchronized (this.mCloseLock) {
|
||
|
ImageReader.this.releaseImage(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public ImageReader getReader() {
|
||
|
return ImageReader.this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getFormat() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
// update mFormat only if ImageReader is initialized by factory pattern.
|
||
|
// if using builder pattern, mFormat has been updated upon initialization.
|
||
|
// no need update here.
|
||
|
int readerFormat = ImageReader.this.getImageFormat();
|
||
|
// Assume opaque reader always produce opaque images.
|
||
|
mFormat = (readerFormat == ImageFormat.PRIVATE) ? readerFormat :
|
||
|
nativeGetFormat(readerFormat);
|
||
|
return mFormat;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getWidth() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
int width;
|
||
|
switch(getFormat()) {
|
||
|
case ImageFormat.JPEG:
|
||
|
case ImageFormat.DEPTH_POINT_CLOUD:
|
||
|
case ImageFormat.RAW_PRIVATE:
|
||
|
case ImageFormat.DEPTH_JPEG:
|
||
|
case ImageFormat.HEIC:
|
||
|
case ImageFormat.JPEG_R:
|
||
|
width = ImageReader.this.getWidth();
|
||
|
break;
|
||
|
default:
|
||
|
width = nativeGetWidth();
|
||
|
}
|
||
|
return width;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getHeight() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
int height;
|
||
|
switch(getFormat()) {
|
||
|
case ImageFormat.JPEG:
|
||
|
case ImageFormat.DEPTH_POINT_CLOUD:
|
||
|
case ImageFormat.RAW_PRIVATE:
|
||
|
case ImageFormat.DEPTH_JPEG:
|
||
|
case ImageFormat.HEIC:
|
||
|
case ImageFormat.JPEG_R:
|
||
|
height = ImageReader.this.getHeight();
|
||
|
break;
|
||
|
default:
|
||
|
height = nativeGetHeight();
|
||
|
}
|
||
|
return height;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public long getTimestamp() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return mTimestamp;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getTransform() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return mTransform;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getScalingMode() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return mScalingMode;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getPlaneCount() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return ImageReader.this.mNumPlanes;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public SyncFence getFence() throws IOException {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
// duplicate ParcelFileDescriptor because native still retains the fence ownership.
|
||
|
int fence = nativeGetFenceFd();
|
||
|
if (fence != -1) {
|
||
|
return SyncFence.create(ParcelFileDescriptor.fromFd(nativeGetFenceFd()));
|
||
|
} else {
|
||
|
return SyncFence.createEmpty();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public HardwareBuffer getHardwareBuffer() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return nativeGetHardwareBuffer();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public @NamedDataSpace int getDataSpace() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return mDataSpace;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setTimestamp(long timestampNs) {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
mTimestamp = timestampNs;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Plane[] getPlanes() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
|
||
|
if (mPlanes == null) {
|
||
|
mPlanes = nativeCreatePlanes(ImageReader.this.mNumPlanes,
|
||
|
ImageReader.this.mHardwareBufferFormat, ImageReader.this.mUsage);
|
||
|
}
|
||
|
// Shallow copy is fine.
|
||
|
return mPlanes.clone();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected final void finalize() throws Throwable {
|
||
|
try {
|
||
|
close();
|
||
|
} finally {
|
||
|
super.finalize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isAttachable() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return mIsDetached.get();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
ImageReader getOwner() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return ImageReader.this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
long getNativeContext() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return mNativeBuffer;
|
||
|
}
|
||
|
|
||
|
private void setDetached(boolean detached) {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
mIsDetached.getAndSet(detached);
|
||
|
}
|
||
|
|
||
|
private void clearSurfacePlanes() {
|
||
|
// Image#getPlanes may not be called before the image is closed.
|
||
|
if (mIsImageValid && mPlanes != null) {
|
||
|
for (int i = 0; i < mPlanes.length; i++) {
|
||
|
if (mPlanes[i] != null) {
|
||
|
mPlanes[i].clearBuffer();
|
||
|
mPlanes[i] = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class SurfacePlane extends android.media.Image.Plane {
|
||
|
// SurfacePlane instance is created by native code when SurfaceImage#getPlanes() is
|
||
|
// called
|
||
|
private SurfacePlane(int rowStride, int pixelStride, ByteBuffer buffer) {
|
||
|
mRowStride = rowStride;
|
||
|
mPixelStride = pixelStride;
|
||
|
mBuffer = buffer;
|
||
|
/**
|
||
|
* Set the byteBuffer order according to host endianness (native
|
||
|
* order), otherwise, the byteBuffer order defaults to
|
||
|
* ByteOrder.BIG_ENDIAN.
|
||
|
*/
|
||
|
mBuffer.order(ByteOrder.nativeOrder());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ByteBuffer getBuffer() {
|
||
|
throwISEIfImageIsInvalid();
|
||
|
return mBuffer;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getPixelStride() {
|
||
|
SurfaceImage.this.throwISEIfImageIsInvalid();
|
||
|
if (ImageReader.this.mFormat == ImageFormat.RAW_PRIVATE) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"getPixelStride is not supported for RAW_PRIVATE plane");
|
||
|
}
|
||
|
return mPixelStride;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getRowStride() {
|
||
|
SurfaceImage.this.throwISEIfImageIsInvalid();
|
||
|
if (ImageReader.this.mFormat == ImageFormat.RAW_PRIVATE) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"getRowStride is not supported for RAW_PRIVATE plane");
|
||
|
}
|
||
|
return mRowStride;
|
||
|
}
|
||
|
|
||
|
private void clearBuffer() {
|
||
|
// Need null check first, as the getBuffer() may not be called before an image
|
||
|
// is closed.
|
||
|
if (mBuffer == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (mBuffer.isDirect()) {
|
||
|
NioUtils.freeDirectBuffer(mBuffer);
|
||
|
}
|
||
|
mBuffer = null;
|
||
|
}
|
||
|
|
||
|
final private int mPixelStride;
|
||
|
final private int mRowStride;
|
||
|
|
||
|
private ByteBuffer mBuffer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This field is used to keep track of native object and used by native code only.
|
||
|
* Don't modify.
|
||
|
*/
|
||
|
private long mNativeBuffer;
|
||
|
|
||
|
/**
|
||
|
* These fields are set by native code during nativeImageSetup().
|
||
|
*/
|
||
|
private long mTimestamp;
|
||
|
private int mTransform;
|
||
|
private int mScalingMode;
|
||
|
|
||
|
private SurfacePlane[] mPlanes;
|
||
|
private int mFormat = ImageFormat.UNKNOWN;
|
||
|
private int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
|
||
|
private int mDataSpace = DataSpace.DATASPACE_UNKNOWN;
|
||
|
// If this image is detached from the ImageReader.
|
||
|
private AtomicBoolean mIsDetached = new AtomicBoolean(false);
|
||
|
|
||
|
private synchronized native SurfacePlane[] nativeCreatePlanes(int numPlanes,
|
||
|
int hardwareBufferFormat, long readerUsage);
|
||
|
private synchronized native int nativeGetWidth();
|
||
|
private synchronized native int nativeGetHeight();
|
||
|
private synchronized native int nativeGetFormat(int readerFormat);
|
||
|
private synchronized native int nativeGetFenceFd();
|
||
|
private synchronized native HardwareBuffer nativeGetHardwareBuffer();
|
||
|
}
|
||
|
|
||
|
private synchronized native void nativeInit(Object weakSelf, int w, int h, int maxImgs,
|
||
|
long consumerUsage, int hardwareBufferFormat, int dataSpace);
|
||
|
private synchronized native void nativeClose();
|
||
|
private synchronized native void nativeReleaseImage(Image i);
|
||
|
private synchronized native Surface nativeGetSurface();
|
||
|
private synchronized native int nativeDetachImage(Image i, boolean throwISEOnly);
|
||
|
private synchronized native void nativeDiscardFreeBuffers();
|
||
|
|
||
|
/**
|
||
|
* @return A return code {@code ACQUIRE_*}
|
||
|
*
|
||
|
* @see #ACQUIRE_SUCCESS
|
||
|
* @see #ACQUIRE_NO_BUFS
|
||
|
* @see #ACQUIRE_MAX_IMAGES
|
||
|
*/
|
||
|
private synchronized native int nativeImageSetup(Image i);
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static class ImagePlane extends android.media.Image.Plane {
|
||
|
private ImagePlane(int rowStride, int pixelStride, ByteBuffer buffer) {
|
||
|
mRowStride = rowStride;
|
||
|
mPixelStride = pixelStride;
|
||
|
mBuffer = buffer;
|
||
|
/**
|
||
|
* Set the byteBuffer order according to host endianness (native
|
||
|
* order), otherwise, the byteBuffer order defaults to
|
||
|
* ByteOrder.BIG_ENDIAN.
|
||
|
*/
|
||
|
mBuffer.order(ByteOrder.nativeOrder());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ByteBuffer getBuffer() {
|
||
|
return mBuffer;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getPixelStride() {
|
||
|
return mPixelStride;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getRowStride() {
|
||
|
return mRowStride;
|
||
|
}
|
||
|
|
||
|
final private int mPixelStride;
|
||
|
final private int mRowStride;
|
||
|
|
||
|
private ByteBuffer mBuffer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static ImagePlane[] initializeImagePlanes(int numPlanes,
|
||
|
GraphicBuffer buffer, int fenceFd, int format, long timestamp, int transform,
|
||
|
int scalingMode, Rect crop) {
|
||
|
|
||
|
return nativeCreateImagePlanes(numPlanes, buffer, fenceFd, format, crop.left, crop.top,
|
||
|
crop.right, crop.bottom);
|
||
|
}
|
||
|
|
||
|
private synchronized static native ImagePlane[] nativeCreateImagePlanes(int numPlanes,
|
||
|
GraphicBuffer buffer, int fenceFd, int format, int cropLeft, int cropTop,
|
||
|
int cropRight, int cropBottom);
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static void unlockGraphicBuffer(GraphicBuffer buffer) {
|
||
|
nativeUnlockGraphicBuffer(buffer);
|
||
|
}
|
||
|
|
||
|
private synchronized static native void nativeUnlockGraphicBuffer(GraphicBuffer buffer);
|
||
|
|
||
|
/**
|
||
|
* We use a class initializer to allow the native code to cache some
|
||
|
* field offsets.
|
||
|
*/
|
||
|
private static native void nativeClassInit();
|
||
|
static {
|
||
|
System.loadLibrary("media_jni");
|
||
|
nativeClassInit();
|
||
|
}
|
||
|
}
|