blob: 7bc2b31b16fae211d0fdc64748777577d0bebe4b [file] [log] [blame]
/*
* 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.graphics.ImageFormat;
import android.graphics.ImageFormat.Format;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.camera2.utils.SurfaceUtils;
import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Size;
import android.view.Surface;
import dalvik.system.VMRuntime;
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 long mNativeContext;
// Field below is used by native code, do not access or modify.
private int mWriterFormat;
private final int mMaxImages;
// 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, ImageFormat.UNKNOWN);
}
/**
* <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, format);
}
/**
* @hide
*/
protected ImageWriter(Surface surface, int maxImages, int format) {
if (surface == null || maxImages < 1) {
throw new IllegalArgumentException("Illegal input argument: surface " + surface
+ ", maxImages: " + maxImages);
}
mMaxImages = maxImages;
// Note that the underlying BufferQueue is working in synchronous mode
// to avoid dropping any buffers.
mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format);
// nativeInit internally overrides UNKNOWN format. So does surface format query after
// nativeInit and before getEstimatedNativeAllocBytes().
if (format == ImageFormat.UNKNOWN) {
format = SurfaceUtils.getSurfaceFormat(surface);
}
// 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);
mEstimatedNativeAllocBytes =
ImageUtils.getEstimatedNativeAllocBytes(surfSize.getWidth(),surfSize.getHeight(),
format, /*buffer count*/ 1);
VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
}
/**
* <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;
}
/**
* <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, or this method will throw an
* {@link IllegalArgumentException}. 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, need to detach first, then attach.
if (!ownedByMe) {
if (!(image.getOwner() instanceof ImageReader)) {
throw new IllegalArgumentException("Only images from ImageReader can be queued to"
+ " ImageWriter, other image source is not supported yet!");
}
ImageReader prevOwner = (ImageReader) image.getOwner();
prevOwner.detachImage(image);
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(), 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;
}
/**
* 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);
for (Image image : mDequeuedImages) {
image.close();
}
mDequeuedImages.clear();
nativeClose(mNativeContext);
mNativeContext = 0;
if (mEstimatedNativeAllocBytes > 0) {
VMRuntime.getRuntime().registerNativeFree(mEstimatedNativeAllocBytes);
mEstimatedNativeAllocBytes = 0;
}
}
@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();
nativeAttachAndQueueImage(mNativeContext, image.getNativeContext(), image.getFormat(),
image.getTimestamp(), crop.left, crop.top, crop.right, crop.bottom,
image.getTransform(), image.getScalingMode());
}
/**
* 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;
synchronized (mListenerLock) {
listener = mListener;
}
if (listener != null) {
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) {
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;
}
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;
// 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
public WriterSurfaceImage(ImageWriter writer) {
mOwner = writer;
}
@Override
public int getFormat() {
throwISEIfImageIsInvalid();
if (mFormat == -1) {
mFormat = nativeGetFormat();
}
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 Plane[] getPlanes() {
throwISEIfImageIsInvalid();
if (mPlanes == null) {
int numPlanes = ImageUtils.getNumPlanesForFormat(getFormat());
mPlanes = nativeCreatePlanes(numPlanes, getOwner().getFormat());
}
return mPlanes.clone();
}
@Override
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() {
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();
private synchronized native HardwareBuffer nativeGetHardwareBuffer();
}
// Native implemented ImageWriter methods.
private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImgs,
int format);
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 left, int top, int right, int bottom, int transform,
int scalingMode);
private synchronized native int nativeAttachAndQueueImage(long nativeCtx,
long imageNativeBuffer, int imageFormat, long timestampNs, 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();
}
}