1325 lines
52 KiB
Java
1325 lines
52 KiB
Java
/*
|
|
* Copyright 2015 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.SuppressLint;
|
|
import android.graphics.GraphicBuffer;
|
|
import android.graphics.ImageFormat;
|
|
import android.graphics.ImageFormat.Format;
|
|
import android.graphics.PixelFormat;
|
|
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.utils.SurfaceUtils;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.util.Size;
|
|
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.concurrent.CopyOnWriteArrayList;
|
|
|
|
/**
|
|
* <p>
|
|
* The ImageWriter class allows an application to produce Image data into a
|
|
* {@link android.view.Surface}, and have it be consumed by another component
|
|
* like {@link android.hardware.camera2.CameraDevice CameraDevice}.
|
|
* </p>
|
|
* <p>
|
|
* Several Android API classes can provide input {@link android.view.Surface
|
|
* Surface} objects for ImageWriter to produce data into, including
|
|
* {@link MediaCodec MediaCodec} (encoder),
|
|
* {@link android.hardware.camera2.CameraCaptureSession CameraCaptureSession}
|
|
* (reprocessing input), {@link ImageReader}, etc.
|
|
* </p>
|
|
* <p>
|
|
* The input Image data is encapsulated in {@link Image} objects. To produce
|
|
* Image data into a destination {@link android.view.Surface Surface}, the
|
|
* application can get an input Image via {@link #dequeueInputImage} then write
|
|
* Image data into it. Multiple such {@link Image} objects can be dequeued at
|
|
* the same time and queued back in any order, up to the number specified by the
|
|
* {@code maxImages} constructor parameter.
|
|
* </p>
|
|
* <p>
|
|
* If the application already has an Image from {@link ImageReader}, the
|
|
* application can directly queue this Image into the ImageWriter (via
|
|
* {@link #queueInputImage}), potentially with zero buffer copies. This
|
|
* even works if the image format of the ImageWriter is
|
|
* {@link ImageFormat#PRIVATE PRIVATE}, and prior to Android P is the only
|
|
* way to enqueue images into such an ImageWriter. Starting in Android P
|
|
* private images may also be accessed through their hardware buffers
|
|
* (when available) through the {@link Image#getHardwareBuffer()} method.
|
|
* Attempting to access the planes of a private image, will return an
|
|
* empty array.
|
|
* </p>
|
|
* <p>
|
|
* Once new input Images are queued into an ImageWriter, it's up to the
|
|
* downstream components (e.g. {@link ImageReader} or
|
|
* {@link android.hardware.camera2.CameraDevice}) to consume the Images. If the
|
|
* downstream components cannot consume the Images at least as fast as the
|
|
* ImageWriter production rate, the {@link #dequeueInputImage} call will
|
|
* eventually block and the application will have to drop input frames.
|
|
* </p>
|
|
* <p>
|
|
* If the consumer component that provided the input {@link android.view.Surface Surface}
|
|
* abandons the {@link android.view.Surface Surface}, {@link #queueInputImage queueing}
|
|
* or {@link #dequeueInputImage dequeueing} an {@link Image} will throw an
|
|
* {@link IllegalStateException}.
|
|
* </p>
|
|
*/
|
|
public class ImageWriter implements AutoCloseable {
|
|
private final Object mListenerLock = new Object();
|
|
private OnImageReleasedListener mListener;
|
|
private ListenerHandler mListenerHandler;
|
|
private final Object mCloseLock = new Object();
|
|
private boolean mIsWriterValid = false;
|
|
private long mNativeContext;
|
|
|
|
private int mWidth;
|
|
private int mHeight;
|
|
private final int mMaxImages;
|
|
private long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN;
|
|
private @HardwareBuffer.Format int mHardwareBufferFormat;
|
|
private @NamedDataSpace int mDataSpace;
|
|
|
|
// Field below is used by native code, do not access or modify.
|
|
private int mWriterFormat;
|
|
|
|
// Keep track of the currently dequeued Image. 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> mDequeuedImages = new CopyOnWriteArrayList<>();
|
|
private int mEstimatedNativeAllocBytes;
|
|
|
|
/**
|
|
* <p>
|
|
* Create a new ImageWriter.
|
|
* </p>
|
|
* <p>
|
|
* The {@code maxImages} parameter determines the maximum number of
|
|
* {@link Image} objects that can be be dequeued from the
|
|
* {@code ImageWriter} simultaneously. Requesting more buffers will use up
|
|
* more memory, so it is important to use only the minimum number necessary.
|
|
* </p>
|
|
* <p>
|
|
* The input Image size and format depend on the Surface that is provided by
|
|
* the downstream consumer end-point.
|
|
* </p>
|
|
*
|
|
* @param surface The destination Surface this writer produces Image data
|
|
* into.
|
|
* @param maxImages The maximum number of Images the user will want to
|
|
* access simultaneously for producing Image data. This should be
|
|
* as small as possible to limit memory use. Once maxImages
|
|
* Images are dequeued by the user, one of them has to be queued
|
|
* back before a new Image can be dequeued for access via
|
|
* {@link #dequeueInputImage()}.
|
|
* @return a new ImageWriter instance.
|
|
*/
|
|
public static @NonNull ImageWriter newInstance(@NonNull Surface surface,
|
|
@IntRange(from = 1) int maxImages) {
|
|
return new ImageWriter(surface, maxImages, true, ImageFormat.UNKNOWN, -1 /*width*/,
|
|
-1 /*height*/);
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Create a new ImageWriter with given number of max Images, format and producer dimension.
|
|
* </p>
|
|
* <p>
|
|
* The {@code maxImages} parameter determines the maximum number of
|
|
* {@link Image} objects that can be be dequeued from the
|
|
* {@code ImageWriter} simultaneously. Requesting more buffers will use up
|
|
* more memory, so it is important to use only the minimum number necessary.
|
|
* </p>
|
|
* <p>
|
|
* The format specifies the image format of this ImageWriter. The format
|
|
* from the {@code surface} will be overridden with this format. For example,
|
|
* if the surface is obtained from a {@link android.graphics.SurfaceTexture}, the default
|
|
* format may be {@link PixelFormat#RGBA_8888}. If the application creates an ImageWriter
|
|
* with this surface and {@link ImageFormat#PRIVATE}, this ImageWriter will be able to operate
|
|
* with {@link ImageFormat#PRIVATE} Images.
|
|
* </p>
|
|
* <p>
|
|
* Note that the consumer end-point may or may not be able to support Images with different
|
|
* format, for such case, the application should only use this method if the consumer is able
|
|
* to consume such images.
|
|
* </p>
|
|
* <p> The input Image size can also be set by the client. </p>
|
|
*
|
|
* @param surface The destination Surface this writer produces Image data
|
|
* into.
|
|
* @param maxImages The maximum number of Images the user will want to
|
|
* access simultaneously for producing Image data. This should be
|
|
* as small as possible to limit memory use. Once maxImages
|
|
* Images are dequeued by the user, one of them has to be queued
|
|
* back before a new Image can be dequeued for access via
|
|
* {@link #dequeueInputImage()}.
|
|
* @param format The format of this ImageWriter. It can be any valid format specified by
|
|
* {@link ImageFormat} or {@link PixelFormat}.
|
|
*
|
|
* @param width Input size width.
|
|
* @param height Input size height.
|
|
*
|
|
* @return a new ImageWriter instance.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static @NonNull ImageWriter newInstance(@NonNull Surface surface,
|
|
@IntRange(from = 1) int maxImages, @Format int format, int width, int height) {
|
|
if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
|
|
throw new IllegalArgumentException("Invalid format is specified: " + format);
|
|
}
|
|
return new ImageWriter(surface, maxImages, false, format, width, height);
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Create a new ImageWriter with given number of max Images and format.
|
|
* </p>
|
|
* <p>
|
|
* The {@code maxImages} parameter determines the maximum number of
|
|
* {@link Image} objects that can be be dequeued from the
|
|
* {@code ImageWriter} simultaneously. Requesting more buffers will use up
|
|
* more memory, so it is important to use only the minimum number necessary.
|
|
* </p>
|
|
* <p>
|
|
* The format specifies the image format of this ImageWriter. The format
|
|
* from the {@code surface} will be overridden with this format. For example,
|
|
* if the surface is obtained from a {@link android.graphics.SurfaceTexture}, the default
|
|
* format may be {@link PixelFormat#RGBA_8888}. If the application creates an ImageWriter
|
|
* with this surface and {@link ImageFormat#PRIVATE}, this ImageWriter will be able to operate
|
|
* with {@link ImageFormat#PRIVATE} Images.
|
|
* </p>
|
|
* <p>
|
|
* Note that the consumer end-point may or may not be able to support Images with different
|
|
* format, for such case, the application should only use this method if the consumer is able
|
|
* to consume such images.
|
|
* </p>
|
|
* <p>
|
|
* The input Image size depends on the Surface that is provided by
|
|
* the downstream consumer end-point.
|
|
* </p>
|
|
*
|
|
* @param surface The destination Surface this writer produces Image data
|
|
* into.
|
|
* @param maxImages The maximum number of Images the user will want to
|
|
* access simultaneously for producing Image data. This should be
|
|
* as small as possible to limit memory use. Once maxImages
|
|
* Images are dequeued by the user, one of them has to be queued
|
|
* back before a new Image can be dequeued for access via
|
|
* {@link #dequeueInputImage()}.
|
|
* @param format The format of this ImageWriter. It can be any valid format specified by
|
|
* {@link ImageFormat} or {@link PixelFormat}.
|
|
*
|
|
* @return a new ImageWriter instance.
|
|
*/
|
|
public static @NonNull ImageWriter newInstance(@NonNull Surface surface,
|
|
@IntRange(from = 1) int maxImages, @Format int format) {
|
|
if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
|
|
throw new IllegalArgumentException("Invalid format is specified: " + format);
|
|
}
|
|
return new ImageWriter(surface, maxImages, false, format, -1 /*width*/, -1 /*height*/);
|
|
}
|
|
|
|
private void initializeImageWriter(Surface surface, int maxImages,
|
|
boolean useSurfaceImageFormatInfo, int imageFormat,
|
|
int hardwareBufferFormat, int dataSpace, int width, int height, long usage) {
|
|
if (surface == null || maxImages < 1) {
|
|
throw new IllegalArgumentException("Illegal input argument: surface " + surface
|
|
+ ", maxImages: " + maxImages);
|
|
}
|
|
|
|
// Note that the underlying BufferQueue is working in synchronous mode
|
|
// to avoid dropping any buffers.
|
|
mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, width, height,
|
|
useSurfaceImageFormatInfo, hardwareBufferFormat, dataSpace, usage);
|
|
|
|
// if useSurfaceImageFormatInfo is true, imageformat should be read from the surface.
|
|
if (useSurfaceImageFormatInfo) {
|
|
// nativeInit internally overrides UNKNOWN format. So does surface format query after
|
|
// nativeInit and before getEstimatedNativeAllocBytes().
|
|
mHardwareBufferFormat = hardwareBufferFormat = SurfaceUtils.getSurfaceFormat(surface);
|
|
mDataSpace = dataSpace = SurfaceUtils.getSurfaceDataspace(surface);
|
|
imageFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace);
|
|
}
|
|
|
|
// 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 ImageWriter as being of some
|
|
// size.
|
|
Size surfSize = SurfaceUtils.getSurfaceSize(surface);
|
|
mWidth = width == -1 ? surfSize.getWidth() : width;
|
|
mHeight = height == -1 ? surfSize.getHeight() : height;
|
|
|
|
mEstimatedNativeAllocBytes = ImageUtils.getEstimatedNativeAllocBytes(mWidth, mHeight,
|
|
imageFormat, /*buffer count*/ 1);
|
|
VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
|
|
|
|
mIsWriterValid = true;
|
|
}
|
|
|
|
private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo,
|
|
int imageFormat, int width, int height) {
|
|
mMaxImages = maxImages;
|
|
if (!useSurfaceImageFormatInfo) {
|
|
mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat);
|
|
mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat);
|
|
}
|
|
|
|
initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo,
|
|
imageFormat, mHardwareBufferFormat, mDataSpace, width, height, mUsage);
|
|
}
|
|
|
|
private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo,
|
|
int imageFormat, int width, int height, long usage) {
|
|
mMaxImages = maxImages;
|
|
mUsage = usage;
|
|
if (!useSurfaceImageFormatInfo) {
|
|
mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat);
|
|
mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat);
|
|
}
|
|
|
|
initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo,
|
|
imageFormat, mHardwareBufferFormat, mDataSpace, width, height, usage);
|
|
}
|
|
|
|
private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo,
|
|
int hardwareBufferFormat, int dataSpace, int width, int height, long usage) {
|
|
mMaxImages = maxImages;
|
|
mUsage = usage;
|
|
int imageFormat;
|
|
// if useSurfaceImageFormatInfo is true, imageFormat will be set to UNKNOWN
|
|
// and retrieve corresponding hardwareBufferFormat and dataSpace here.
|
|
if (useSurfaceImageFormatInfo) {
|
|
imageFormat = ImageFormat.UNKNOWN;
|
|
} else {
|
|
imageFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace);
|
|
mHardwareBufferFormat = hardwareBufferFormat;
|
|
mDataSpace = dataSpace;
|
|
}
|
|
|
|
initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo,
|
|
imageFormat, hardwareBufferFormat, dataSpace, width, height, usage);
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Maximum number of Images that can be dequeued from the ImageWriter
|
|
* simultaneously (for example, with {@link #dequeueInputImage()}).
|
|
* </p>
|
|
* <p>
|
|
* An Image is considered dequeued after it's returned by
|
|
* {@link #dequeueInputImage()} from ImageWriter, and until the Image is
|
|
* sent back to ImageWriter via {@link #queueInputImage}, or
|
|
* {@link Image#close()}.
|
|
* </p>
|
|
* <p>
|
|
* Attempting to dequeue more than {@code maxImages} concurrently will
|
|
* result in the {@link #dequeueInputImage()} function throwing an
|
|
* {@link IllegalStateException}.
|
|
* </p>
|
|
*
|
|
* @return Maximum number of Images that can be dequeued from this
|
|
* ImageWriter.
|
|
* @see #dequeueInputImage
|
|
* @see #queueInputImage
|
|
* @see Image#close
|
|
*/
|
|
public int getMaxImages() {
|
|
return mMaxImages;
|
|
}
|
|
|
|
/**
|
|
* The width of {@link Image Images}, in pixels.
|
|
*
|
|
* <p>If {@link Builder#setWidthAndHeight} is not called, the default width of the Image
|
|
* depends on the Surface provided by customer end-point.</p>
|
|
*
|
|
* @return the expected actual width of an Image.
|
|
*/
|
|
public int getWidth() {
|
|
return mWidth;
|
|
}
|
|
|
|
/**
|
|
* The height of {@link Image Images}, in pixels.
|
|
*
|
|
* <p>If {@link Builder#setWidthAndHeight} is not called, the default height of the Image
|
|
* depends on the Surface provided by customer end-point.</p>
|
|
*
|
|
* @return the expected height of an Image.
|
|
*/
|
|
public int getHeight() {
|
|
return mHeight;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Dequeue the next available input Image for the application to produce
|
|
* data into.
|
|
* </p>
|
|
* <p>
|
|
* This method requests a new input Image from ImageWriter. The application
|
|
* owns this Image after this call. Once the application fills the Image
|
|
* data, it is expected to return this Image back to ImageWriter for
|
|
* downstream consumer components (e.g.
|
|
* {@link android.hardware.camera2.CameraDevice}) to consume. The Image can
|
|
* be returned to ImageWriter via {@link #queueInputImage} or
|
|
* {@link Image#close()}.
|
|
* </p>
|
|
* <p>
|
|
* This call will block if all available input images have been queued by
|
|
* the application and the downstream consumer has not yet consumed any.
|
|
* When an Image is consumed by the downstream consumer and released, an
|
|
* {@link OnImageReleasedListener#onImageReleased} callback will be fired,
|
|
* which indicates that there is one input Image available. For non-
|
|
* {@link ImageFormat#PRIVATE PRIVATE} formats (
|
|
* {@link ImageWriter#getFormat()} != {@link ImageFormat#PRIVATE}), it is
|
|
* recommended to dequeue the next Image only after this callback is fired,
|
|
* in the steady state.
|
|
* </p>
|
|
* <p>
|
|
* If the format of ImageWriter is {@link ImageFormat#PRIVATE PRIVATE} (
|
|
* {@link ImageWriter#getFormat()} == {@link ImageFormat#PRIVATE}), the
|
|
* image buffer is accessible to the application only through the hardware
|
|
* buffer obtained through {@link Image#getHardwareBuffer()}. (On Android
|
|
* versions prior to P, dequeueing private buffers will cause an
|
|
* {@link IllegalStateException} to be thrown). Alternatively,
|
|
* the application can acquire images from some other component (e.g. an
|
|
* {@link ImageReader}), and queue them directly to this ImageWriter via the
|
|
* {@link ImageWriter#queueInputImage queueInputImage()} method.
|
|
* </p>
|
|
*
|
|
* @return The next available input Image from this ImageWriter.
|
|
* @throws IllegalStateException if {@code maxImages} Images are currently
|
|
* dequeued, or the input {@link android.view.Surface Surface}
|
|
* has been abandoned by the consumer component that provided
|
|
* the {@link android.view.Surface Surface}. Prior to Android
|
|
* P, throws if the ImageWriter format is
|
|
* {@link ImageFormat#PRIVATE PRIVATE}.
|
|
* @see #queueInputImage
|
|
* @see Image#close
|
|
*/
|
|
public Image dequeueInputImage() {
|
|
if (mDequeuedImages.size() >= mMaxImages) {
|
|
throw new IllegalStateException(
|
|
"Already dequeued max number of Images " + mMaxImages);
|
|
}
|
|
WriterSurfaceImage newImage = new WriterSurfaceImage(this);
|
|
nativeDequeueInputImage(mNativeContext, newImage);
|
|
mDequeuedImages.add(newImage);
|
|
newImage.mIsImageValid = true;
|
|
return newImage;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Queue an input {@link Image} back to ImageWriter for the downstream
|
|
* consumer to access.
|
|
* </p>
|
|
* <p>
|
|
* The input {@link Image} could be from ImageReader (acquired via
|
|
* {@link ImageReader#acquireNextImage} or
|
|
* {@link ImageReader#acquireLatestImage}), or from this ImageWriter
|
|
* (acquired via {@link #dequeueInputImage}). In the former case, the Image
|
|
* data will be moved to this ImageWriter. Note that the Image properties
|
|
* (size, format, strides, etc.) must be the same as the properties of the
|
|
* images dequeued from this ImageWriter. In the latter case, the application has
|
|
* filled the input image with data. This method then passes the filled
|
|
* buffer to the downstream consumer. In both cases, it's up to the caller
|
|
* to ensure that the Image timestamp (in nanoseconds) is correctly set, as
|
|
* the downstream component may want to use it to indicate the Image data
|
|
* capture time.
|
|
* </p>
|
|
* <p>
|
|
* After this method is called and the downstream consumer consumes and
|
|
* releases the Image, an {@link OnImageReleasedListener#onImageReleased}
|
|
* callback will fire. The application can use this callback to avoid
|
|
* sending Images faster than the downstream consumer processing rate in
|
|
* steady state.
|
|
* </p>
|
|
* <p>
|
|
* Passing in an Image from some other component (e.g. an
|
|
* {@link ImageReader}) requires a free input Image from this ImageWriter as
|
|
* the destination. In this case, this call will block, as
|
|
* {@link #dequeueInputImage} does, if there are no free Images available.
|
|
* To avoid blocking, the application should ensure that there is at least
|
|
* one free Image available in this ImageWriter before calling this method.
|
|
* </p>
|
|
* <p>
|
|
* After this call, the input Image is no longer valid for further access,
|
|
* as if the Image is {@link Image#close closed}. Attempting to access the
|
|
* {@link ByteBuffer ByteBuffers} returned by an earlier
|
|
* {@link Image.Plane#getBuffer Plane#getBuffer} call will result in an
|
|
* {@link IllegalStateException}.
|
|
* </p>
|
|
*
|
|
* @param image The Image to be queued back to ImageWriter for future
|
|
* consumption.
|
|
* @throws IllegalStateException if the image was already queued previously,
|
|
* or the image was aborted previously, or the input
|
|
* {@link android.view.Surface Surface} has been abandoned by the
|
|
* consumer component that provided the
|
|
* {@link android.view.Surface Surface}.
|
|
* @see #dequeueInputImage()
|
|
*/
|
|
public void queueInputImage(Image image) {
|
|
if (image == null) {
|
|
throw new IllegalArgumentException("image shouldn't be null");
|
|
}
|
|
|
|
boolean ownedByMe = isImageOwnedByMe(image);
|
|
if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
|
|
throw new IllegalStateException("Image from ImageWriter is invalid");
|
|
}
|
|
|
|
// For images from other components that have non-null owner, need to detach first,
|
|
// then attach. Images without owners must already be attachable.
|
|
if (!ownedByMe) {
|
|
if ((image.getOwner() instanceof ImageReader)) {
|
|
ImageReader prevOwner = (ImageReader) image.getOwner();
|
|
|
|
prevOwner.detachImage(image);
|
|
} else if (image.getOwner() != null) {
|
|
throw new IllegalArgumentException(
|
|
"Only images from ImageReader can be queued to"
|
|
+ " ImageWriter, other image source is not supported yet!");
|
|
}
|
|
|
|
attachAndQueueInputImage(image);
|
|
// This clears the native reference held by the original owner.
|
|
// When this Image is detached later by this ImageWriter, the
|
|
// native memory won't be leaked.
|
|
image.close();
|
|
return;
|
|
}
|
|
|
|
Rect crop = image.getCropRect();
|
|
nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
|
|
crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
|
|
image.getScalingMode());
|
|
|
|
/**
|
|
* Only remove and cleanup the Images that are owned by this
|
|
* ImageWriter. Images detached from other owners are only temporarily
|
|
* owned by this ImageWriter and will be detached immediately after they
|
|
* are released by downstream consumers, so there is no need to keep
|
|
* track of them in mDequeuedImages.
|
|
*/
|
|
if (ownedByMe) {
|
|
mDequeuedImages.remove(image);
|
|
// Do not call close here, as close is essentially cancel image.
|
|
WriterSurfaceImage wi = (WriterSurfaceImage) image;
|
|
wi.clearSurfacePlanes();
|
|
wi.mIsImageValid = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the ImageWriter format.
|
|
* <p>
|
|
* This format may be different than the Image format returned by
|
|
* {@link Image#getFormat()}. However, if the ImageWriter format is
|
|
* {@link ImageFormat#PRIVATE PRIVATE}, calling {@link #dequeueInputImage()}
|
|
* will result in an {@link IllegalStateException}.
|
|
* </p>
|
|
*
|
|
* @return The ImageWriter format.
|
|
*/
|
|
public int getFormat() {
|
|
return mWriterFormat;
|
|
}
|
|
|
|
/**
|
|
* Get the ImageWriter usage flag.
|
|
*
|
|
* <p>It is not recommended to use this function if {@link Builder#setUsage} is not called.
|
|
* Invalid usage value will be returned if so.</p>
|
|
*
|
|
* @return The ImageWriter usage flag.
|
|
*/
|
|
public @Usage long getUsage() {
|
|
return mUsage;
|
|
}
|
|
|
|
/**
|
|
* Get the ImageWriter hardwareBuffer format.
|
|
*
|
|
* <p>Use this function if the ImageWriter instance is created by builder pattern
|
|
* {@code ImageWriter.Builder} and using {@link Builder#setHardwareBufferFormat} and
|
|
* {@link Builder#setDataSpace}.</p>
|
|
*
|
|
* @return The ImageWriter hardwareBuffer format.
|
|
*/
|
|
public @HardwareBuffer.Format int getHardwareBufferFormat() {
|
|
return mHardwareBufferFormat;
|
|
}
|
|
|
|
/**
|
|
* Get the ImageWriter dataspace.
|
|
*
|
|
* <p>Use this function if the ImageWriter instance is created by builder pattern
|
|
* {@code ImageWriter.Builder} and {@link Builder#setDataSpace}.</p>
|
|
*
|
|
* @return The ImageWriter dataspace.
|
|
*/
|
|
@SuppressLint("MethodNameUnits")
|
|
public @NamedDataSpace int getDataSpace() {
|
|
return mDataSpace;
|
|
}
|
|
|
|
/**
|
|
* ImageWriter callback interface, used to to asynchronously notify the
|
|
* application of various ImageWriter events.
|
|
*/
|
|
public interface OnImageReleasedListener {
|
|
/**
|
|
* <p>
|
|
* Callback that is called when an input Image is released back to
|
|
* ImageWriter after the data consumption.
|
|
* </p>
|
|
* <p>
|
|
* The client can use this callback to be notified that an input Image
|
|
* has been consumed and released by the downstream consumer. More
|
|
* specifically, this callback will be fired for below cases:
|
|
* <li>The application dequeues an input Image via the
|
|
* {@link ImageWriter#dequeueInputImage dequeueInputImage()} method,
|
|
* uses it, and then queues it back to this ImageWriter via the
|
|
* {@link ImageWriter#queueInputImage queueInputImage()} method. After
|
|
* the downstream consumer uses and releases this image to this
|
|
* ImageWriter, this callback will be fired. This image will be
|
|
* available to be dequeued after this callback.</li>
|
|
* <li>The application obtains an Image from some other component (e.g.
|
|
* an {@link ImageReader}), uses it, and then queues it to this
|
|
* ImageWriter via {@link ImageWriter#queueInputImage queueInputImage()}.
|
|
* After the downstream consumer uses and releases this image to this
|
|
* ImageWriter, this callback will be fired.</li>
|
|
* </p>
|
|
*
|
|
* @param writer the ImageWriter the callback is associated with.
|
|
* @see ImageWriter
|
|
* @see Image
|
|
*/
|
|
void onImageReleased(ImageWriter writer);
|
|
}
|
|
|
|
/**
|
|
* Register a listener to be invoked when an input Image is returned to the
|
|
* ImageWriter.
|
|
*
|
|
* @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 setOnImageReleasedListener(OnImageReleasedListener 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);
|
|
}
|
|
mListener = listener;
|
|
} else {
|
|
mListener = null;
|
|
mListenerHandler = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Free up all the resources associated with this ImageWriter.
|
|
* <p>
|
|
* After calling this method, this ImageWriter cannot be used. Calling any
|
|
* methods on this ImageWriter and Images previously provided by
|
|
* {@link #dequeueInputImage()} will result in an
|
|
* {@link IllegalStateException}, and attempting to write into
|
|
* {@link ByteBuffer ByteBuffers} returned by an earlier
|
|
* {@link Image.Plane#getBuffer Plane#getBuffer} call will have undefined
|
|
* behavior.
|
|
* </p>
|
|
*/
|
|
@Override
|
|
public void close() {
|
|
setOnImageReleasedListener(null, null);
|
|
synchronized (mCloseLock) {
|
|
if (!mIsWriterValid) {
|
|
return;
|
|
}
|
|
for (Image image : mDequeuedImages) {
|
|
image.close();
|
|
}
|
|
mDequeuedImages.clear();
|
|
nativeClose(mNativeContext);
|
|
mNativeContext = 0;
|
|
|
|
if (mEstimatedNativeAllocBytes > 0) {
|
|
VMRuntime.getRuntime().registerNativeFree(mEstimatedNativeAllocBytes);
|
|
mEstimatedNativeAllocBytes = 0;
|
|
}
|
|
mIsWriterValid = false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
close();
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Attach and queue input Image to this ImageWriter.
|
|
* </p>
|
|
* <p>
|
|
* When the format of an Image is {@link ImageFormat#PRIVATE PRIVATE}, or
|
|
* the source Image is so large that copying its data is too expensive, this
|
|
* method can be used to migrate the source Image into ImageWriter without a
|
|
* data copy, and then queue it to this ImageWriter. The source Image must
|
|
* be detached from its previous owner already, or this call will throw an
|
|
* {@link IllegalStateException}.
|
|
* </p>
|
|
* <p>
|
|
* After this call, the ImageWriter takes ownership of this Image. This
|
|
* ownership will automatically be removed from this writer after the
|
|
* consumer releases this Image, that is, after
|
|
* {@link OnImageReleasedListener#onImageReleased}. The caller is responsible for
|
|
* closing this Image through {@link Image#close()} to free up the resources
|
|
* held by this Image.
|
|
* </p>
|
|
*
|
|
* @param image The source Image to be attached and queued into this
|
|
* ImageWriter for downstream consumer to use.
|
|
* @throws IllegalStateException if the Image is not detached from its
|
|
* previous owner, or the Image is already attached to this
|
|
* ImageWriter, or the source Image is invalid.
|
|
*/
|
|
private void attachAndQueueInputImage(Image image) {
|
|
if (image == null) {
|
|
throw new IllegalArgumentException("image shouldn't be null");
|
|
}
|
|
if (isImageOwnedByMe(image)) {
|
|
throw new IllegalArgumentException(
|
|
"Can not attach an image that is owned ImageWriter already");
|
|
}
|
|
/**
|
|
* Throw ISE if the image is not attachable, which means that it is
|
|
* either owned by other entity now, or completely non-attachable (some
|
|
* stand-alone images are not backed by native gralloc buffer, thus not
|
|
* attachable).
|
|
*/
|
|
if (!image.isAttachable()) {
|
|
throw new IllegalStateException("Image was not detached from last owner, or image "
|
|
+ " is not detachable");
|
|
}
|
|
|
|
// TODO: what if attach failed, throw RTE or detach a slot then attach?
|
|
// need do some cleanup to make sure no orphaned
|
|
// buffer caused leak.
|
|
Rect crop = image.getCropRect();
|
|
int hardwareBufferFormat = PublicFormatUtils.getHalFormat(image.getFormat());
|
|
if (image.getNativeContext() != 0) {
|
|
nativeAttachAndQueueImage(mNativeContext, image.getNativeContext(),
|
|
hardwareBufferFormat, image.getTimestamp(), image.getDataSpace(),
|
|
crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
|
|
image.getScalingMode());
|
|
} else {
|
|
GraphicBuffer gb = GraphicBuffer.createFromHardwareBuffer(image.getHardwareBuffer());
|
|
nativeAttachAndQueueGraphicBuffer(mNativeContext, gb, hardwareBufferFormat,
|
|
image.getTimestamp(), image.getDataSpace(), crop.left, crop.top, crop.right,
|
|
crop.bottom, image.getTransform(), image.getScalingMode());
|
|
gb.destroy();
|
|
image.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 */);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
OnImageReleasedListener listener;
|
|
boolean isWriterValid;
|
|
synchronized (ImageWriter.this.mListenerLock) {
|
|
listener = mListener;
|
|
}
|
|
// Check to make sure we don't accidentally queue images after the writer is
|
|
// closed or closing
|
|
synchronized (ImageWriter.this.mCloseLock) {
|
|
isWriterValid = ImageWriter.this.mIsWriterValid;
|
|
}
|
|
if (listener != null && isWriterValid) {
|
|
listener.onImageReleased(ImageWriter.this);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called from Native code when an Event happens. This may be called from an
|
|
* arbitrary Binder thread, so access to the ImageWriter must be
|
|
* synchronized appropriately.
|
|
*/
|
|
private static void postEventFromNative(Object selfRef) {
|
|
@SuppressWarnings("unchecked")
|
|
WeakReference<ImageWriter> weakSelf = (WeakReference<ImageWriter>) selfRef;
|
|
final ImageWriter iw = weakSelf.get();
|
|
if (iw == null) {
|
|
return;
|
|
}
|
|
|
|
final Handler handler;
|
|
synchronized (iw.mListenerLock) {
|
|
handler = iw.mListenerHandler;
|
|
}
|
|
|
|
if (handler != null) {
|
|
// The ListenerHandler will take care of ensuring that the parent ImageWriter is valid
|
|
handler.sendEmptyMessage(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Abort the Images that were dequeued from this ImageWriter, and return
|
|
* them to this writer for reuse.
|
|
* </p>
|
|
* <p>
|
|
* This method is used for the cases where the application dequeued the
|
|
* Image, may have filled the data, but does not want the downstream
|
|
* component to consume it. The Image will be returned to this ImageWriter
|
|
* for reuse after this call, and the ImageWriter will immediately have an
|
|
* Image available to be dequeued. This aborted Image will be invisible to
|
|
* the downstream consumer, as if nothing happened.
|
|
* </p>
|
|
*
|
|
* @param image The Image to be aborted.
|
|
* @see #dequeueInputImage()
|
|
* @see Image#close()
|
|
*/
|
|
private void abortImage(Image image) {
|
|
if (image == null) {
|
|
throw new IllegalArgumentException("image shouldn't be null");
|
|
}
|
|
|
|
if (!mDequeuedImages.contains(image)) {
|
|
throw new IllegalStateException("It is illegal to abort some image that is not"
|
|
+ " dequeued yet");
|
|
}
|
|
|
|
WriterSurfaceImage wi = (WriterSurfaceImage) image;
|
|
if (!wi.mIsImageValid) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* We only need abort Images that are owned and dequeued by ImageWriter.
|
|
* For attached Images, no need to abort, as there are only two cases:
|
|
* attached + queued successfully, and attach failed. Neither of the
|
|
* cases need abort.
|
|
*/
|
|
cancelImage(mNativeContext, image);
|
|
mDequeuedImages.remove(image);
|
|
wi.clearSurfacePlanes();
|
|
wi.mIsImageValid = false;
|
|
}
|
|
|
|
private boolean isImageOwnedByMe(Image image) {
|
|
if (!(image instanceof WriterSurfaceImage)) {
|
|
return false;
|
|
}
|
|
WriterSurfaceImage wi = (WriterSurfaceImage) image;
|
|
if (wi.getOwner() != this) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Builder class for {@link ImageWriter} objects.
|
|
*/
|
|
public static final class Builder {
|
|
private Surface mSurface;
|
|
private int mWidth = -1;
|
|
private int mHeight = -1;
|
|
private int mMaxImages = 1;
|
|
private int mImageFormat = ImageFormat.UNKNOWN;
|
|
private long mUsage = -1;
|
|
private @HardwareBuffer.Format int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
|
|
private @NamedDataSpace int mDataSpace = DataSpace.DATASPACE_UNKNOWN;
|
|
private boolean mUseSurfaceImageFormatInfo = true;
|
|
private boolean mUseLegacyImageFormat = false;
|
|
|
|
/**
|
|
* Constructs a new builder for {@link ImageWriter}.
|
|
*
|
|
* @param surface The destination Surface this writer produces Image data into.
|
|
*
|
|
* @throws IllegalArgumentException if the surface is already abandoned.
|
|
*/
|
|
public Builder(@NonNull Surface surface) {
|
|
mSurface = surface;
|
|
}
|
|
|
|
/**
|
|
* Set the width and height of images. Default size is dependent on the Surface that is
|
|
* provided by the downstream end-point.
|
|
*
|
|
* @param width The width in pixels that will be passed to the producer.
|
|
* @param height The height in pixels that will be passed to the producer.
|
|
* @return the Builder instance with customized width and height.
|
|
*/
|
|
@SuppressLint("MissingGetterMatchingBuilder")
|
|
public @NonNull Builder setWidthAndHeight(@IntRange(from = 1) int width,
|
|
@IntRange(from = 1) int height) {
|
|
mWidth = width;
|
|
mHeight = height;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the maximum number of images. Default value is 1.
|
|
*
|
|
* @param maxImages The maximum number of Images the user will want to access simultaneously
|
|
* for producing Image data.
|
|
* @return the Builder instance with customized usage value.
|
|
*/
|
|
public @NonNull Builder setMaxImages(@IntRange(from = 1) int maxImages) {
|
|
mMaxImages = maxImages;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the image format of this ImageWriter.
|
|
* Default format depends on the Surface provided.
|
|
*
|
|
* @param imageFormat The format of the {@link ImageWriter}. It can be any valid specified
|
|
* by {@link ImageFormat} or {@link PixelFormat}.
|
|
* @return the Builder instance with customized image format.
|
|
*
|
|
* @throws IllegalArgumentException if {@code imageFormat} is invalid.
|
|
*/
|
|
@SuppressLint("MissingGetterMatchingBuilder")
|
|
public @NonNull Builder setImageFormat(@Format int imageFormat) {
|
|
if (!ImageFormat.isPublicFormat(imageFormat)
|
|
&& !PixelFormat.isPublicFormat(imageFormat)) {
|
|
throw new IllegalArgumentException(
|
|
"Invalid imageFormat is specified: " + imageFormat);
|
|
}
|
|
mImageFormat = imageFormat;
|
|
mUseLegacyImageFormat = true;
|
|
mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
|
|
mDataSpace = DataSpace.DATASPACE_UNKNOWN;
|
|
mUseSurfaceImageFormatInfo = false;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the hardwareBuffer format of this ImageWriter. The default value is
|
|
* {@link HardwareBuffer#RGBA_8888 HardwareBuffer.RGBA_8888}.
|
|
*
|
|
* <p>This function works together with {@link #setDataSpace} for an
|
|
* {@link ImageWriter} instance. Setting at least one of these two replaces
|
|
* {@link #setImageFormat} function.</p>
|
|
*
|
|
* @param hardwareBufferFormat The HardwareBuffer format of the image that this writer
|
|
* will produce.
|
|
* @return the Builder instance with customized buffer format.
|
|
*
|
|
* @see #setDataSpace
|
|
* @see #setImageFormat
|
|
*/
|
|
public @NonNull Builder setHardwareBufferFormat(
|
|
@HardwareBuffer.Format int hardwareBufferFormat) {
|
|
mHardwareBufferFormat = hardwareBufferFormat;
|
|
mImageFormat = ImageFormat.UNKNOWN;
|
|
mUseLegacyImageFormat = false;
|
|
mUseSurfaceImageFormatInfo = false;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the dataspace of this ImageWriter.
|
|
* The default value is {@link DataSpace#DATASPACE_UNKNOWN}.
|
|
*
|
|
* @param dataSpace The dataspace of the image that this writer will produce.
|
|
* @return the builder instance with customized dataspace value.
|
|
*
|
|
* @see #setHardwareBufferFormat
|
|
*/
|
|
public @NonNull Builder setDataSpace(@NamedDataSpace int dataSpace) {
|
|
mDataSpace = dataSpace;
|
|
mImageFormat = ImageFormat.UNKNOWN;
|
|
mUseLegacyImageFormat = false;
|
|
mUseSurfaceImageFormatInfo = false;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the usage flag of this ImageWriter.
|
|
*
|
|
* <p>If this function is not called, usage bit will be set
|
|
* to {@link HardwareBuffer#USAGE_CPU_WRITE_OFTEN} if the image format is not
|
|
* {@link ImageFormat#PRIVATE PRIVATE}.</p>
|
|
*
|
|
* @param usage The intended usage of the images produced by this ImageWriter.
|
|
* @return the Builder instance with customized usage flag.
|
|
*
|
|
* @see HardwareBuffer
|
|
* @see #getUsage
|
|
*/
|
|
public @NonNull Builder setUsage(@Usage long usage) {
|
|
mUsage = usage;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Builds a new ImageWriter object.
|
|
*
|
|
* @return The new ImageWriter object.
|
|
*/
|
|
public @NonNull ImageWriter build() {
|
|
if (mUseLegacyImageFormat) {
|
|
return new ImageWriter(mSurface, mMaxImages, mUseSurfaceImageFormatInfo,
|
|
mImageFormat, mWidth, mHeight, mUsage);
|
|
} else {
|
|
return new ImageWriter(mSurface, mMaxImages, mUseSurfaceImageFormatInfo,
|
|
mHardwareBufferFormat, mDataSpace, mWidth, mHeight, mUsage);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static class WriterSurfaceImage extends android.media.Image {
|
|
private ImageWriter mOwner;
|
|
// This field is used by native code, do not access or modify.
|
|
private long mNativeBuffer;
|
|
private int mNativeFenceFd = -1;
|
|
private SurfacePlane[] mPlanes;
|
|
private int mHeight = -1;
|
|
private int mWidth = -1;
|
|
private int mFormat = -1;
|
|
private @NamedDataSpace int mDataSpace = DataSpace.DATASPACE_UNKNOWN;
|
|
// When this default timestamp is used, timestamp for the input Image
|
|
// will be generated automatically when queueInputBuffer is called.
|
|
private final long DEFAULT_TIMESTAMP = Long.MIN_VALUE;
|
|
private long mTimestamp = DEFAULT_TIMESTAMP;
|
|
|
|
private int mTransform = 0; //Default no transform
|
|
private int mScalingMode = 0; //Default frozen scaling mode
|
|
|
|
private final Object mCloseLock = new Object(); // lock to protect against multiple
|
|
// simultaneous calls to close()
|
|
|
|
public WriterSurfaceImage(ImageWriter writer) {
|
|
mOwner = writer;
|
|
mWidth = writer.mWidth;
|
|
mHeight = writer.mHeight;
|
|
mDataSpace = writer.mDataSpace;
|
|
}
|
|
|
|
@Override
|
|
public @NamedDataSpace int getDataSpace() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
return mDataSpace;
|
|
}
|
|
|
|
@Override
|
|
public void setDataSpace(@NamedDataSpace int dataSpace) {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
mDataSpace = dataSpace;
|
|
}
|
|
|
|
@Override
|
|
public int getFormat() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
if (mFormat == -1) {
|
|
mFormat = nativeGetFormat(mDataSpace);
|
|
}
|
|
return mFormat;
|
|
}
|
|
|
|
@Override
|
|
public int getWidth() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
if (mWidth == -1) {
|
|
mWidth = nativeGetWidth();
|
|
}
|
|
|
|
return mWidth;
|
|
}
|
|
|
|
@Override
|
|
public int getHeight() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
if (mHeight == -1) {
|
|
mHeight = nativeGetHeight();
|
|
}
|
|
|
|
return mHeight;
|
|
}
|
|
|
|
@Override
|
|
public int getTransform() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
return mTransform;
|
|
}
|
|
|
|
@Override
|
|
public int getScalingMode() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
return mScalingMode;
|
|
}
|
|
|
|
@Override
|
|
public long getTimestamp() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
return mTimestamp;
|
|
}
|
|
|
|
@Override
|
|
public void setTimestamp(long timestamp) {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
mTimestamp = timestamp;
|
|
}
|
|
|
|
@Override
|
|
public HardwareBuffer getHardwareBuffer() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
return nativeGetHardwareBuffer();
|
|
}
|
|
|
|
@Override
|
|
public SyncFence getFence() throws IOException {
|
|
throwISEIfImageIsInvalid();
|
|
// if mNativeFenceFd is -1, the fence is closed
|
|
if (mNativeFenceFd != -1) {
|
|
return SyncFence.create(ParcelFileDescriptor.fromFd(mNativeFenceFd));
|
|
} else {
|
|
return SyncFence.createEmpty();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setFence(@NonNull SyncFence fence) throws IOException {
|
|
throwISEIfImageIsInvalid();
|
|
nativeSetFenceFd(fence.getFdDup().detachFd());
|
|
}
|
|
|
|
@Override
|
|
public Plane[] getPlanes() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
if (mPlanes == null) {
|
|
int numPlanes = ImageUtils.getNumPlanesForFormat(getFormat());
|
|
mPlanes = nativeCreatePlanes(numPlanes, getOwner().getFormat());
|
|
}
|
|
|
|
return mPlanes.clone();
|
|
}
|
|
|
|
@Override
|
|
public boolean isAttachable() {
|
|
throwISEIfImageIsInvalid();
|
|
// Don't allow Image to be detached from ImageWriter for now, as no
|
|
// detach API is exposed.
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
ImageWriter getOwner() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
return mOwner;
|
|
}
|
|
|
|
@Override
|
|
long getNativeContext() {
|
|
throwISEIfImageIsInvalid();
|
|
|
|
return mNativeBuffer;
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
synchronized (mCloseLock) {
|
|
if (mIsImageValid) {
|
|
getOwner().abortImage(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected final void finalize() throws Throwable {
|
|
try {
|
|
close();
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
private void clearSurfacePlanes() {
|
|
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 {
|
|
private ByteBuffer mBuffer;
|
|
final private int mPixelStride;
|
|
final private int mRowStride;
|
|
|
|
// 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 int getRowStride() {
|
|
throwISEIfImageIsInvalid();
|
|
return mRowStride;
|
|
}
|
|
|
|
@Override
|
|
public int getPixelStride() {
|
|
throwISEIfImageIsInvalid();
|
|
return mPixelStride;
|
|
}
|
|
|
|
@Override
|
|
public ByteBuffer getBuffer() {
|
|
throwISEIfImageIsInvalid();
|
|
return mBuffer;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
}
|
|
|
|
// Create the SurfacePlane object and fill the information
|
|
private synchronized native SurfacePlane[] nativeCreatePlanes(int numPlanes, int writerFmt);
|
|
|
|
private synchronized native int nativeGetWidth();
|
|
|
|
private synchronized native int nativeGetHeight();
|
|
|
|
private synchronized native int nativeGetFormat(int dataSpace);
|
|
|
|
private synchronized native HardwareBuffer nativeGetHardwareBuffer();
|
|
|
|
private synchronized native void nativeSetFenceFd(int fenceFd);
|
|
}
|
|
|
|
// Native implemented ImageWriter methods.
|
|
private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImages,
|
|
int width, int height, boolean useSurfaceImageFormatInfo, int hardwareBufferFormat,
|
|
int dataSpace, long usage);
|
|
|
|
private synchronized native void nativeClose(long nativeCtx);
|
|
|
|
private synchronized native void nativeDequeueInputImage(long nativeCtx, Image wi);
|
|
|
|
private synchronized native void nativeQueueInputImage(long nativeCtx, Image image,
|
|
long timestampNs, int dataSpace, int left, int top, int right, int bottom,
|
|
int transform, int scalingMode);
|
|
|
|
private synchronized native int nativeAttachAndQueueImage(long nativeCtx,
|
|
long imageNativeBuffer, int hardwareBufferFormat, long timestampNs, int dataSpace,
|
|
int left, int top, int right, int bottom, int transform, int scalingMode);
|
|
private synchronized native int nativeAttachAndQueueGraphicBuffer(long nativeCtx,
|
|
GraphicBuffer graphicBuffer, int hardwareBufferFormat, long timestampNs, int dataSpace,
|
|
int left, int top, int right, int bottom, int transform, int scalingMode);
|
|
|
|
private synchronized native void cancelImage(long nativeCtx, Image image);
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|