merge in klp-factoryrom-release history after reset to klp-dev
diff --git a/api/current.txt b/api/current.txt
index a67512a..91fcab1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12334,7 +12334,7 @@
field public static final int EULER_Z = 2; // 0x2
}
- public abstract class Image implements java.lang.AutoCloseable {
+ public abstract interface Image implements java.lang.AutoCloseable {
method public abstract void close();
method public abstract int getFormat();
method public abstract int getHeight();
@@ -12343,23 +12343,23 @@
method public abstract int getWidth();
}
- public static abstract class Image.Plane {
+ public static abstract interface Image.Plane {
method public abstract java.nio.ByteBuffer getBuffer();
method public abstract int getPixelStride();
method public abstract int getRowStride();
}
- public class ImageReader implements java.lang.AutoCloseable {
- method public android.media.Image acquireLatestImage();
- method public android.media.Image acquireNextImage();
+ public final class ImageReader implements java.lang.AutoCloseable {
+ ctor public ImageReader(int, int, int, int);
method public void close();
method public int getHeight();
method public int getImageFormat();
method public int getMaxImages();
+ method public android.media.Image getNextImage();
method public android.view.Surface getSurface();
method public int getWidth();
- method public static android.media.ImageReader newInstance(int, int, int, int);
- method public void setOnImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler);
+ method public void releaseImage(android.media.Image);
+ method public void setImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler);
}
public static abstract interface ImageReader.OnImageAvailableListener {
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
index a2e9ae8..ebecf2e3 100644
--- a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -83,8 +83,8 @@
mImageReaderLock.lock();
try {
- mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
- mImageReader.setOnImageAvailableListener(mImageListener, mHandler);
+ mImageReader = new ImageReader(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
+ mImageReader.setImageAvailableListener(mImageListener, mHandler);
mSurface = mImageReader.getSurface();
} finally {
mImageReaderLock.unlock();
@@ -409,11 +409,19 @@
}
Log.d(TAG, "New image available from virtual display.");
-
- // Get the latest buffer.
- Image image = reader.acquireLatestImage();
+ Image image = reader.getNextImage();
if (image != null) {
try {
+ // Get the latest buffer.
+ for (;;) {
+ Image nextImage = reader.getNextImage();
+ if (nextImage == null) {
+ break;
+ }
+ reader.releaseImage(image);
+ image = nextImage;
+ }
+
// Scan for colors.
int color = scanImage(image);
synchronized (this) {
@@ -423,7 +431,7 @@
}
}
} finally {
- image.close();
+ reader.releaseImage(image);
}
}
} finally {
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index a346e17..9f442f5 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -16,19 +16,20 @@
package android.media;
+import android.graphics.ImageFormat;
import java.nio.ByteBuffer;
import java.lang.AutoCloseable;
/**
* <p>A single complete image buffer to use with a media source such as a
* {@link MediaCodec} or a
- * {@link android.hardware.camera2.CameraDevice CameraDevice}.</p>
+ * {@link android.hardware.camera2.CameraDevice}.</p>
*
* <p>This class allows for efficient direct application access to the pixel
* data of the Image through one or more
* {@link java.nio.ByteBuffer ByteBuffers}. Each buffer is encapsulated in a
* {@link Plane} that describes the layout of the pixel data in that plane. Due
- * to this direct access, and unlike the {@link android.graphics.Bitmap Bitmap} class,
+ * to this direct access, and unlike the {@link android.graphics.Bitmap} class,
* Images are not directly usable as as UI resources.</p>
*
* <p>Since Images are often directly produced or consumed by hardware
@@ -39,28 +40,19 @@
* from various media sources, not closing old Image objects will prevent the
* availability of new Images once
* {@link ImageReader#getMaxImages the maximum outstanding image count} is
- * reached. When this happens, the function acquiring new Images will typically
- * throw an {@link IllegalStateException}.</p>
+ * reached.</p>
*
* @see ImageReader
*/
-public abstract class Image implements AutoCloseable {
- /**
- * @hide
- */
- protected Image() {
- }
-
+public interface Image extends AutoCloseable {
/**
* Get the format for this image. This format determines the number of
* ByteBuffers needed to represent the image, and the general layout of the
* pixel data in each in ByteBuffer.
*
- * <p>
* The format is one of the values from
- * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the
- * formats and the planes is as follows:
- * </p>
+ * {@link android.graphics.ImageFormat}. The mapping between the formats and
+ * the planes is as follows:
*
* <table>
* <tr>
@@ -69,14 +61,13 @@
* <th>Layout details</th>
* </tr>
* <tr>
- * <td>{@link android.graphics.ImageFormat#JPEG JPEG}</td>
+ * <td>{@link android.graphics.ImageFormat#JPEG}</td>
* <td>1</td>
* <td>Compressed data, so row and pixel strides are 0. To uncompress, use
- * {@link android.graphics.BitmapFactory#decodeByteArray BitmapFactory#decodeByteArray}.
- * </td>
+ * {@link android.graphics.BitmapFactory#decodeByteArray}.</td>
* </tr>
* <tr>
- * <td>{@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888}</td>
+ * <td>{@link android.graphics.ImageFormat#YUV_420_888}</td>
* <td>3</td>
* <td>A luminance plane followed by the Cb and Cr chroma planes.
* The chroma planes have half the width and height of the luminance
@@ -84,60 +75,53 @@
* Each plane has its own row stride and pixel stride.</td>
* </tr>
* <tr>
- * <td>{@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}</td>
+ * <td>{@link android.graphics.ImageFormat#RAW_SENSOR}</td>
* <td>1</td>
* <td>A single plane of raw sensor image data, with 16 bits per color
* sample. The details of the layout need to be queried from the source of
* the raw sensor data, such as
- * {@link android.hardware.camera2.CameraDevice CameraDevice}.
+ * {@link android.hardware.camera2.CameraDevice}.
* </td>
* </tr>
* </table>
*
* @see android.graphics.ImageFormat
*/
- public abstract int getFormat();
+ public int getFormat();
/**
* The width of the image in pixels. For formats where some color channels
* are subsampled, this is the width of the largest-resolution plane.
*/
- public abstract int getWidth();
+ public int getWidth();
/**
* The height of the image in pixels. For formats where some color channels
* are subsampled, this is the height of the largest-resolution plane.
*/
- public abstract int getHeight();
+ public int getHeight();
/**
- * Get the timestamp associated with this frame.
- * <p>
- * The timestamp is measured in nanoseconds, and is monotonically
- * increasing. However, the zero point and whether the timestamp can be
- * compared against other sources of time or images depend on the source of
- * this image.
- * </p>
+ * Get the timestamp associated with this frame. The timestamp is measured
+ * in nanoseconds, and is monotonically increasing. However, the zero point
+ * and whether the timestamp can be compared against other sources of time
+ * or images depend on the source of this image.
*/
- public abstract long getTimestamp();
+ public long getTimestamp();
/**
* Get the array of pixel planes for this Image. The number of planes is
* determined by the format of the Image.
*/
- public abstract Plane[] getPlanes();
+ public Plane[] getPlanes();
/**
- * Free up this frame for reuse.
- * <p>
- * After calling this method, calling any methods on this {@code Image} will
- * result in an {@link IllegalStateException}, and attempting to read from
- * {@link ByteBuffer ByteBuffers} returned by an earlier
- * {@link Plane#getBuffer} call will have undefined behavior.
- * </p>
+ * Free up this frame for reuse. After calling this method, calling any
+ * methods on this Image will result in an IllegalStateException, and
+ * attempting to read from ByteBuffers returned by an earlier
+ * {@code Plane#getBuffer} call will have undefined behavior.
*/
- @Override
- public abstract void close();
+ public void close();
/**
* <p>A single color plane of image data.</p>
@@ -150,41 +134,29 @@
*
* @see #getFormat
*/
- public static abstract class Plane {
+ public interface Plane {
/**
- * @hide
- */
- protected Plane() {
- }
-
- /**
- * <p>The row stride for this color plane, in bytes.</p>
+ * <p>The row stride for this color plane, in bytes.
*
* <p>This is the distance between the start of two consecutive rows of
- * pixels in the image. The row stride is always greater than 0.</p>
+ * pixels in the image.</p>
*/
- public abstract int getRowStride();
+ public int getRowStride();
/**
* <p>The distance between adjacent pixel samples, in bytes.</p>
*
* <p>This is the distance between two consecutive pixel values in a row
* of pixels. It may be larger than the size of a single pixel to
- * account for interleaved image data or padded formats.
- * The pixel stride is always greater than 0.</p>
+ * account for interleaved image data or padded formats.</p>
*/
- public abstract int getPixelStride();
+ public int getPixelStride();
/**
- * <p>Get a direct {@link java.nio.ByteBuffer ByteBuffer}
+ * <p>Get a set of direct {@link java.nio.ByteBuffer byte buffers}
* containing the frame data.</p>
*
- * <p>In particular, the buffer returned will always have
- * {@link java.nio.ByteBuffer#isDirect isDirect} return {@code true}, so
- * the underlying data could be mapped as a pointer in JNI without doing
- * any copies with {@code GetDirectBufferAddress}.</p>
- *
* @return the byte buffer containing the image data for this plane.
*/
- public abstract ByteBuffer getBuffer();
+ public ByteBuffer getBuffer();
}
}
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index f9e48d4..b14a899 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -20,7 +20,6 @@
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.view.Surface;
import java.lang.ref.WeakReference;
@@ -41,67 +40,41 @@
* <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
+ * through its Surface are queued until accessed through the
+ * {@link #getNextImage} 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;
+public final class ImageReader implements AutoCloseable {
/**
* <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}
+ * <p>The maxImages parameter determines the maximum number of {@link Image}
+ * objects that can be be acquired from the 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>
*
- * @param width
- * The width in pixels of the Images that this reader will produce.
- * @param height
- * The 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.
- * @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 width the width in pixels of the Images that this reader will
+ * produce.
+ * @param height the 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.
+ * @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 getNextImage(). Must be greater than 0.
*
* @see Image
*/
- public static ImageReader newInstance(int width, int height, int format, int maxImages) {
- return new ImageReader(width, height, format, maxImages);
- }
-
- /**
- * @hide
- */
- protected ImageReader(int width, int height, int format, int maxImages) {
+ public ImageReader(int width, int height, int format, int maxImages) {
mWidth = width;
mHeight = height;
mFormat = format;
@@ -123,79 +96,33 @@
mSurface = nativeGetSurface();
}
- /**
- * The width of each {@link Image}, in pixels.
- *
- * <p>ImageReader guarantees that all Images acquired from ImageReader (for example, with
- * {@link #acquireNextImage}) will have the same dimensions as specified in
- * {@link #newInstance}.</p>
- *
- * @return the width of an Image
- */
public int getWidth() {
return mWidth;
}
- /**
- * The height of each {@link Image}, in pixels.
- *
- * <p>ImageReader guarantees that all Images acquired from ImageReader (for example, with
- * {@link #acquireNextImage}) will have the same dimensions as specified in
- * {@link #newInstance}.</p>
- *
- * @return the height of an Image
- */
public int getHeight() {
return mHeight;
}
- /**
- * The {@link ImageFormat image format} of each Image.
- *
- * <p>ImageReader guarantees that all {@link Image Images} acquired from ImageReader
- * (for example, with {@link #acquireNextImage}) will have the same format as specified in
- * {@link #newInstance}.</p>
- *
- * @return the format of an Image
- *
- * @see ImageFormat
- */
public int getImageFormat() {
return mFormat;
}
- /**
- * 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;
}
/**
- * <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this
- * {@code ImageReader}.</p>
+ * <p>Get a Surface that can be used to produce Images for this
+ * ImageReader.</p>
*
- * <p>Until valid image data is rendered into this {@link Surface}, the
- * {@link #acquireNextImage} method will return {@code null}. Only one source
+ * <p>Until valid image data is rendered into this Surface, the
+ * {@link #getNextImage} 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>
+ * same Surface can be reused with a different API once the first source is
+ * disconnected from the Surface.</p>
*
- * @return A {@link Surface} to use for a drawing target for various APIs.
+ * @return A Surface to use for a drawing target for various APIs.
*/
public Surface getSurface() {
return mSurface;
@@ -203,154 +130,41 @@
/**
* <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();
- }
- }
- }
-
- /**
- * 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();
- 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) {
-
- int status = nativeImageSetup(si);
-
- switch (status) {
- case ACQUIRE_SUCCESS:
- si.createSurfacePlanes();
- si.setImageValid(true);
- case ACQUIRE_NO_BUFS:
- case ACQUIRE_MAX_IMAGES:
- break;
- default:
- throw new AssertionError("Unknown nativeImageSetup return code " + status);
- }
-
- return status;
- }
-
- /**
- * <p>
- * Acquire the next Image from the ImageReader's queue. Returns {@code null} if
+ * Get 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}.
+ * This operation will fail by throwing an
+ * {@link Surface.OutOfResourcesException OutOfResourcesException} if too
+ * many images have been acquired with {@link #getNextImage}. In particular
+ * a sequence of {@link #getNextImage} calls greater than {@link #getMaxImages}
+ * without calling {@link Image#close} or {@link #releaseImage} in-between
+ * will exhaust the underlying queue. At such a time,
+ * {@link Surface.OutOfResourcesException OutOfResourcesException} will be
+ * thrown until more images are released with {@link Image#close} or
+ * {@link #releaseImage}.
* </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
+ * @return a new frame of image data, or {@code null} if no image data is
+ * available.
+ * @throws Surface.OutOfResourcesException if too many images are currently
+ * acquired
*/
- public Image acquireNextImage() {
+ public Image getNextImage() {
SurfaceImage si = new SurfaceImage();
- 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);
+ if (nativeImageSetup(si)) {
+ // create SurfacePlane objects
+ si.createSurfacePlanes();
+ si.setImageValid(true);
+ return si;
}
+ return null;
}
/**
* <p>Return the frame to the ImageReader for reuse.</p>
*/
- private void releaseImage(Image i) {
+ public void releaseImage(Image i) {
if (! (i instanceof SurfaceImage) ) {
throw new IllegalArgumentException(
"This image was not produced by an ImageReader");
@@ -369,46 +183,35 @@
/**
* 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.
*
- * @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.
+ * @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);
- }
- mListener = listener;
+ public void setImageAvailableListener(OnImageAvailableListener listener, Handler handler) {
+ mImageListener = listener;
+
+ Looper looper;
+ mHandler = handler;
+ if (listener != null && mHandler == null) {
+ if ((looper = Looper.myLooper()) != null) {
+ mHandler = new Handler();
} else {
- mListener = null;
- mListenerHandler = null;
+ throw new IllegalArgumentException(
+ "Looper doesn't exist in the calling thread");
}
}
}
/**
* 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
@@ -417,21 +220,15 @@
}
/**
- * 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
+ * Free up all the resources associated with this ImageReader. After
+ * Calling this method, this ImageReader can not be used. calling
+ * any methods on this ImageReader and Images previously provided by {@link #getNextImage}
+ * will result in an IllegalStateException, and attempting to read from
+ * ByteBuffers returned by an earlier {@code Plane#getBuffer} call will
* have undefined behavior.
- * </p>
*/
@Override
public void close() {
- setOnImageAvailableListener(null, null);
nativeClose();
}
@@ -445,14 +242,11 @@
}
/**
- * Only a subset of the formats defined in
- * {@link android.graphics.ImageFormat ImageFormat} and
- * {@link android.graphics.PixelFormat PixelFormat} are supported by
- * ImageReader. When reading RGB data from a surface, the formats defined in
- * {@link android.graphics.PixelFormat PixelFormat} can be used, when
- * reading YUV, JPEG or raw sensor data (for example, from camera or video
- * decoder), formats from {@link android.graphics.ImageFormat ImageFormat}
- * are used.
+ * Only a subset of the formats defined in {@link android.graphics.ImageFormat} and
+ * {@link android.graphics.PixelFormat} are supported by ImageReader. When reading RGB
+ * data from a surface, the formats defined in {@link android.graphics.PixelFormat}
+ * can be used, when reading YUV, JPEG or raw sensor data ( for example, from camera
+ * or video decoder), formats from {@link android.graphics.ImageFormat} are used.
*/
private int getNumPlanesFromFormat() {
switch (mFormat) {
@@ -480,9 +274,6 @@
/**
* 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")
@@ -492,16 +283,16 @@
return;
}
- final Handler handler;
- synchronized (ir.mListenerLock) {
- handler = ir.mListenerHandler;
- }
- if (handler != null) {
- handler.sendEmptyMessage(0);
+ if (ir.mHandler != null && ir.mImageListener != null) {
+ ir.mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ir.mImageListener.onImageAvailable(ir);
+ }
+ });
}
}
-
private final int mWidth;
private final int mHeight;
private final int mFormat;
@@ -509,36 +300,15 @@
private final int mNumPlanes;
private final Surface mSurface;
- private final Object mListenerLock = new Object();
- private OnImageAvailableListener mListener;
- private ListenerHandler mListenerHandler;
+ private Handler mHandler;
+ private OnImageAvailableListener mImageListener;
/**
* 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*/);
- }
-
- @Override
- public void handleMessage(Message msg) {
- OnImageAvailableListener listener;
- synchronized (mListenerLock) {
- listener = mListener;
- }
- if (listener != null) {
- listener.onImageAvailable(ImageReader.this);
- }
- }
- }
-
- private class SurfaceImage extends android.media.Image {
+ private class SurfaceImage implements android.media.Image {
public SurfaceImage() {
mIsImageValid = false;
}
@@ -634,7 +404,7 @@
mPlanes[i] = nativeCreatePlane(i);
}
}
- private class SurfacePlane extends android.media.Image.Plane {
+ private class SurfacePlane implements android.media.Image.Plane {
// SurfacePlane instance is created by native code when a new SurfaceImage is created
private SurfacePlane(int index, int rowStride, int pixelStride) {
mIndex = index;
@@ -709,17 +479,9 @@
private synchronized native void nativeClose();
private synchronized native void nativeReleaseImage(Image i);
private synchronized native Surface nativeGetSurface();
+ private synchronized native boolean nativeImageSetup(Image i);
- /**
- * @return A return code {@code ACQUIRE_*}
- *
- * @see #ACQUIRE_SUCCESS
- * @see #ACQUIRE_NO_BUFS
- * @see #ACQUIRE_MAX_IMAGES
- */
- private synchronized native int nativeImageSetup(Image i);
-
- /**
+ /*
* We use a class initializer to allow the native code to cache some
* field offsets.
*/
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index a03dbf3..94f20bc 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -43,14 +43,11 @@
using namespace android;
-enum {
- IMAGE_READER_MAX_NUM_PLANES = 3,
-};
+static const char* const OutOfResourcesException =
+ "android/view/Surface$OutOfResourcesException";
enum {
- ACQUIRE_SUCCESS = 0,
- ACQUIRE_NO_BUFFERS = 1,
- ACQUIRE_MAX_IMAGES = 2,
+ IMAGE_READER_MAX_NUM_PLANES = 3,
};
static struct {
@@ -688,14 +685,14 @@
ctx->returnLockedBuffer(buffer);
}
-static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz,
+static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
jobject image)
{
ALOGV("%s:", __FUNCTION__);
JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
if (ctx == NULL) {
jniThrowRuntimeException(env, "ImageReaderContext is not initialized");
- return -1;
+ return false;
}
CpuConsumer* consumer = ctx->getCpuConsumer();
@@ -703,22 +700,27 @@
if (buffer == NULL) {
ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than"
" maxImages buffers");
- return ACQUIRE_MAX_IMAGES;
+ jniThrowException(env, OutOfResourcesException,
+ "Too many outstanding images, close existing images"
+ " to be able to acquire more.");
+ return false;
}
status_t res = consumer->lockNextBuffer(buffer);
if (res != NO_ERROR) {
if (res != BAD_VALUE /*no buffers*/) {
if (res == NOT_ENOUGH_DATA) {
- return ACQUIRE_MAX_IMAGES;
+ jniThrowException(env, OutOfResourcesException,
+ "Too many outstanding images, close existing images"
+ " to be able to acquire more.");
} else {
ALOGE("%s Fail to lockNextBuffer with error: %d ",
__FUNCTION__, res);
- jniThrowExceptionFmt(env, "java/lang/AssertionError",
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Unknown error (%d) when we tried to lock buffer.",
res);
}
}
- return ACQUIRE_NO_BUFFERS;
+ return false;
}
// Check if the left-top corner of the crop rect is origin, we currently assume this point is
@@ -728,7 +730,7 @@
ALOGE("crop left: %d, top = %d", lt.x, lt.y);
jniThrowException(env, "java/lang/UnsupportedOperationException",
"crop left top corner need to at origin");
- return -1;
+ return false;
}
// Check if the producer buffer configurations match what ImageReader configured.
@@ -737,9 +739,11 @@
int outputHeight = buffer->height;
// Correct width/height when crop is set.
- if (!buffer->crop.isEmpty()) {
- outputWidth = buffer->crop.getWidth();
- outputHeight = buffer->crop.getHeight();
+ if (buffer->crop.getWidth() > 0) {
+ outputWidth = buffer->crop.getWidth() + 1;
+ }
+ if (buffer->crop.getHeight() > 0) {
+ outputHeight = buffer->crop.getHeight() + 1;
}
int imageReaderWidth = ctx->getBufferWidth();
@@ -757,7 +761,6 @@
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d",
outputWidth, outputHeight, imageReaderWidth, imageReaderHeight);
- return -1;
}
if (ctx->getBufferFormat() != buffer->format) {
@@ -774,14 +777,14 @@
buffer->format, ctx->getBufferFormat());
jniThrowException(env, "java/lang/UnsupportedOperationException",
msg.string());
- return -1;
+ return false;
}
// Set SurfaceImage instance member variables
Image_setBuffer(env, image, buffer);
env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
static_cast<jlong>(buffer->timestamp));
- return ACQUIRE_SUCCESS;
+ return true;
}
static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz)
@@ -852,7 +855,7 @@
{"nativeInit", "(Ljava/lang/Object;IIII)V", (void*)ImageReader_init },
{"nativeClose", "()V", (void*)ImageReader_close },
{"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease },
- {"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup },
+ {"nativeImageSetup", "(Landroid/media/Image;)Z", (void*)ImageReader_imageSetup },
{"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface },
};
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index 64b12b7..ecdc287 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -49,7 +49,6 @@
addMediaPlayerStateUnitTests(suite);
addMediaScannerUnitTests(suite);
addCameraUnitTests(suite);
- addImageReaderTests(suite);
return suite;
}
@@ -66,10 +65,6 @@
suite.addTestSuite(CameraMetadataTest.class);
}
- private void addImageReaderTests(TestSuite suite) {
- suite.addTestSuite(ImageReaderTest.class);
- }
-
// Running all unit tests checking the state machine may be time-consuming.
private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) {
suite.addTestSuite(MediaMetadataRetrieverTest.class);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
deleted file mode 100644
index f6cd990..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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 com.android.mediaframeworktest.unit;
-
-import static org.mockito.Mockito.*;
-
-import android.graphics.ImageFormat;
-import android.media.Image;
-import android.media.Image.Plane;
-import android.media.ImageReader;
-import android.media.ImageReader.OnImageAvailableListener;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-public class ImageReaderTest extends AndroidTestCase {
-
- private static final String TAG = "ImageReaderTest-unit";
-
- private static final int DEFAULT_WIDTH = 640;
- private static final int DEFAULT_HEIGHT = 480;
- private static final int DEFAULT_FORMAT = ImageFormat.YUV_420_888;
- private static final int DEFAULT_MAX_IMAGES = 3;
-
- private ImageReader mReader;
- private Image mImage1;
- private Image mImage2;
- private Image mImage3;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- /**
- * Workaround for mockito and JB-MR2 incompatibility
- *
- * Avoid java.lang.IllegalArgumentException: dexcache == null
- * https://code.google.com/p/dexmaker/issues/detail?id=2
- */
- System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
-
- // TODO: refactor above into one of the test runners
-
- mReader = spy(ImageReader.newInstance(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
- DEFAULT_MAX_IMAGES));
- mImage1 = mock(Image.class);
- mImage2 = mock(Image.class);
- mImage3 = mock(Image.class);
-
- /**
- * Ensure rest of classes are mockable
- */
- {
- mock(Plane.class);
- mock(OnImageAvailableListener.class);
- }
-
- }
-
- @Override
- protected void tearDown() throws Exception {
- mReader.close();
-
- super.tearDown();
- }
-
- /**
- * Return null when there is nothing in the image queue.
- */
- @SmallTest
- public void testGetLatestImageEmpty() {
- when(mReader.acquireNextImage()).thenReturn(null);
- when(mReader.acquireNextImageNoThrowISE()).thenReturn(null);
- assertEquals(null, mReader.acquireLatestImage());
- }
-
- /**
- * Return the last image from the image queue, close up the rest.
- */
- @SmallTest
- public void testGetLatestImage1() {
- when(mReader.acquireNextImage()).thenReturn(mImage1);
- when(mReader.acquireNextImageNoThrowISE()).thenReturn(null);
- assertEquals(mImage1, mReader.acquireLatestImage());
- verify(mImage1, never()).close();
- }
-
- /**
- * Return the last image from the image queue, close up the rest.
- */
- @SmallTest
- public void testGetLatestImage2() {
- when(mReader.acquireNextImage()).thenReturn(mImage1);
- when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).thenReturn(null);
- assertEquals(mImage2, mReader.acquireLatestImage());
- verify(mImage1, atLeastOnce()).close();
- verify(mImage2, never()).close();
- }
-
- /**
- * Return the last image from the image queue, close up the rest.
- */
- @SmallTest
- public void testGetLatestImage3() {
- when(mReader.acquireNextImage()).thenReturn(mImage1);
- when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
- thenReturn(mImage3).
- thenReturn(null);
- assertEquals(mImage3, mReader.acquireLatestImage());
- verify(mImage1, atLeastOnce()).close();
- verify(mImage2, atLeastOnce()).close();
- verify(mImage3, never()).close();
- }
-
- /**
- * Return null if get a IllegalStateException with no images in the queue.
- */
- @SmallTest
- public void testGetLatestImageTooManyBuffersAcquiredEmpty() {
- when(mReader.acquireNextImage()).thenThrow(new IllegalStateException());
- try {
- mReader.acquireLatestImage();
- fail("Expected IllegalStateException to be thrown");
- } catch(IllegalStateException e) {
- }
- }
-
- /**
- * All images are cleaned up when we get an unexpected Error.
- */
- @SmallTest
- public void testGetLatestImageExceptionalError() {
- when(mReader.acquireNextImage()).thenReturn(mImage1);
- when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
- thenReturn(mImage3).
- thenThrow(new OutOfMemoryError());
- try {
- mReader.acquireLatestImage();
- fail("Impossible");
- } catch(OutOfMemoryError e) {
- }
-
- verify(mImage1, atLeastOnce()).close();
- verify(mImage2, atLeastOnce()).close();
- verify(mImage3, atLeastOnce()).close();
- }
-
- /**
- * All images are cleaned up when we get an unexpected RuntimeException.
- */
- @SmallTest
- public void testGetLatestImageExceptionalRuntime() {
-
- when(mReader.acquireNextImage()).thenReturn(mImage1);
- when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
- thenReturn(mImage3).
- thenThrow(new RuntimeException());
- try {
- mReader.acquireLatestImage();
- fail("Impossible");
- } catch(RuntimeException e) {
- }
-
- verify(mImage1, atLeastOnce()).close();
- verify(mImage2, atLeastOnce()).close();
- verify(mImage3, atLeastOnce()).close();
- }
-}