summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIgor Murashkin <iam@google.com>2013-08-29 15:38:17 -0700
committerIgor Murashkin <iam@google.com>2013-09-12 16:57:57 -0700
commit5e712064dfe48992f8f732208fa4fc13f3455b30 (patch)
tree5b94b96e4dc152d88cc6b0cdb678f004e4d25cd5
parente850c973b0662975137cee8a05f8ee2cb82d9b2a (diff)
downloadframeworks_base-5e712064dfe48992f8f732208fa4fc13f3455b30.zip
frameworks_base-5e712064dfe48992f8f732208fa4fc13f3455b30.tar.gz
frameworks_base-5e712064dfe48992f8f732208fa4fc13f3455b30.tar.bz2
media: Update ImageReader APIs
Bug: 10461757 Change-Id: Ic04e4c41965e3d417b29004f3f08e0cd56b8f4cb
-rw-r--r--api/current.txt21
-rw-r--r--core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java24
-rw-r--r--media/java/android/media/Image.java97
-rw-r--r--media/java/android/media/ImageReader.java297
-rw-r--r--media/jni/android_media_ImageReader.cpp8
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java5
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java214
7 files changed, 548 insertions, 118 deletions
diff --git a/api/current.txt b/api/current.txt
index 1a9b77c..b0a18de 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12332,7 +12332,7 @@ package android.media {
field public static final int EULER_Z = 2; // 0x2
}
- public abstract interface Image implements java.lang.AutoCloseable {
+ public abstract class Image implements java.lang.AutoCloseable {
method public abstract void close();
method public abstract int getFormat();
method public abstract int getHeight();
@@ -12341,23 +12341,30 @@ package android.media {
method public abstract int getWidth();
}
- public static abstract interface Image.Plane {
+ public static abstract class Image.Plane {
method public abstract java.nio.ByteBuffer getBuffer();
method public abstract int getPixelStride();
method public abstract int getRowStride();
}
- public final class ImageReader implements java.lang.AutoCloseable {
- ctor public ImageReader(int, int, int, int);
+ public class ImageReader implements java.lang.AutoCloseable {
+ method public android.media.Image acquireLatestImage() throws android.media.ImageReader.MaxImagesAcquiredException;
+ method public android.media.Image acquireNextImage() throws android.media.ImageReader.MaxImagesAcquiredException;
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 void releaseImage(android.media.Image);
- method public void setImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler);
+ method public static android.media.ImageReader newInstance(int, int, int, int);
+ method public void setOnImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler);
+ }
+
+ public static class ImageReader.MaxImagesAcquiredException extends java.lang.Exception {
+ ctor public ImageReader.MaxImagesAcquiredException();
+ ctor public ImageReader.MaxImagesAcquiredException(java.lang.String);
+ ctor public ImageReader.MaxImagesAcquiredException(java.lang.String, java.lang.Throwable);
+ ctor public ImageReader.MaxImagesAcquiredException(java.lang.Throwable);
}
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 ebecf2e..8278579 100644
--- a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -26,6 +26,7 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
+import android.media.ImageReader.MaxImagesAcquiredException;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -83,8 +84,8 @@ public class VirtualDisplayTest extends AndroidTestCase {
mImageReaderLock.lock();
try {
- mImageReader = new ImageReader(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
- mImageReader.setImageAvailableListener(mImageListener, mHandler);
+ mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
+ mImageReader.setOnImageAvailableListener(mImageListener, mHandler);
mSurface = mImageReader.getSurface();
} finally {
mImageReaderLock.unlock();
@@ -409,19 +410,11 @@ public class VirtualDisplayTest extends AndroidTestCase {
}
Log.d(TAG, "New image available from virtual display.");
- Image image = reader.getNextImage();
+
+ // Get the latest buffer.
+ Image image = reader.acquireLatestImage();
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) {
@@ -431,9 +424,12 @@ public class VirtualDisplayTest extends AndroidTestCase {
}
}
} finally {
- reader.releaseImage(image);
+ image.close();
}
}
+ } catch (MaxImagesAcquiredException e) {
+ // We should never try to consume more buffers than maxImages.
+ throw new IllegalStateException(e);
} finally {
mImageReaderLock.unlock();
}
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 9f442f5..915b09e 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -16,20 +16,19 @@
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}.</p>
+ * {@link android.hardware.camera2.CameraDevice 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} class,
+ * to this direct access, and unlike the {@link android.graphics.Bitmap Bitmap} class,
* Images are not directly usable as as UI resources.</p>
*
* <p>Since Images are often directly produced or consumed by hardware
@@ -40,19 +39,29 @@ import java.lang.AutoCloseable;
* 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.</p>
+ * reached. When this happens, the function acquiring new Images will typically
+ * throw a
+ * {@link ImageReader.MaxImagesAcquiredException MaxImagesAcquiredException}.</p>
*
* @see ImageReader
*/
-public interface Image extends AutoCloseable {
+public abstract class Image implements AutoCloseable {
+ /**
+ * @hide
+ */
+ protected Image() {
+ }
+
/**
* 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}. The mapping between the formats and
- * the planes is as follows:
+ * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the
+ * formats and the planes is as follows:
+ * </p>
*
* <table>
* <tr>
@@ -61,13 +70,14 @@ public interface Image extends AutoCloseable {
* <th>Layout details</th>
* </tr>
* <tr>
- * <td>{@link android.graphics.ImageFormat#JPEG}</td>
+ * <td>{@link android.graphics.ImageFormat#JPEG JPEG}</td>
* <td>1</td>
* <td>Compressed data, so row and pixel strides are 0. To uncompress, use
- * {@link android.graphics.BitmapFactory#decodeByteArray}.</td>
+ * {@link android.graphics.BitmapFactory#decodeByteArray BitmapFactory#decodeByteArray}.
+ * </td>
* </tr>
* <tr>
- * <td>{@link android.graphics.ImageFormat#YUV_420_888}</td>
+ * <td>{@link android.graphics.ImageFormat#YUV_420_888 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
@@ -75,53 +85,60 @@ public interface Image extends AutoCloseable {
* Each plane has its own row stride and pixel stride.</td>
* </tr>
* <tr>
- * <td>{@link android.graphics.ImageFormat#RAW_SENSOR}</td>
+ * <td>{@link android.graphics.ImageFormat#RAW_SENSOR 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}.
+ * {@link android.hardware.camera2.CameraDevice CameraDevice}.
* </td>
* </tr>
* </table>
*
* @see android.graphics.ImageFormat
*/
- public int getFormat();
+ public abstract 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 int getWidth();
+ public abstract 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 int getHeight();
+ public abstract int getHeight();
/**
- * 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.
+ * 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>
*/
- public long getTimestamp();
+ public abstract long getTimestamp();
/**
* Get the array of pixel planes for this Image. The number of planes is
* determined by the format of the Image.
*/
- public Plane[] getPlanes();
+ public abstract Plane[] getPlanes();
/**
- * 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.
+ * 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>
*/
- public void close();
+ @Override
+ public abstract void close();
/**
* <p>A single color plane of image data.</p>
@@ -134,29 +151,41 @@ public interface Image extends AutoCloseable {
*
* @see #getFormat
*/
- public interface Plane {
+ public static abstract class Plane {
/**
- * <p>The row stride for this color plane, in bytes.
+ * @hide
+ */
+ protected Plane() {
+ }
+
+ /**
+ * <p>The row stride for this color plane, in bytes.</p>
*
* <p>This is the distance between the start of two consecutive rows of
- * pixels in the image.</p>
+ * pixels in the image. The row stride is always greater than 0.</p>
*/
- public int getRowStride();
+ public abstract 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.</p>
+ * account for interleaved image data or padded formats.
+ * The pixel stride is always greater than 0.</p>
*/
- public int getPixelStride();
+ public abstract int getPixelStride();
/**
- * <p>Get a set of direct {@link java.nio.ByteBuffer byte buffers}
+ * <p>Get a direct {@link java.nio.ByteBuffer ByteBuffer}
* 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 ByteBuffer getBuffer();
+ public abstract ByteBuffer getBuffer();
}
}
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index b14a899..e5bcb3e 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -40,41 +40,90 @@ import java.nio.ByteOrder;
* <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 Surface are queued until accessed through the
- * {@link #getNextImage} call. Due to memory limits, an image source will
+ * through its {@link Surface} are queued until accessed through the {@link #acquireLatestImage}
+ * or {@link #acquireNextImage} call. Due to memory limits, an image source will
* eventually stall or drop Images in trying to render to the Surface if the
* ImageReader does not obtain and release Images at a rate equal to the
* production rate.</p>
*/
-public final class ImageReader implements AutoCloseable {
+public class ImageReader implements AutoCloseable {
+
+ /**
+ * <p>
+ * This exception is thrown when the user of an {@link ImageReader} tries to acquire a new
+ * {@link Image} when the maximum number of {@link Image Images} have already been acquired.
+ * The maximum number is determined by the {@code maxBuffers} argument of
+ * {@link ImageReader#newInstance newInstance}.
+ * </p>
+ *
+ * <p>
+ * To recover from this exception, release existing {@link Image images} back to the
+ * reader with {@link Image#close}.
+ * </p>
+ *
+ * @see Image#close
+ * @see ImageReader#acquireLatestImage
+ * @see ImageReader#acquireNextImage
+ */
+ public static class MaxImagesAcquiredException extends Exception {
+ /**
+ * Suppress Eclipse warnings
+ */
+ private static final long serialVersionUID = 761231231236L;
+
+ public MaxImagesAcquiredException() {
+ }
+
+ public MaxImagesAcquiredException(String message) {
+ super(message);
+ }
+
+ public MaxImagesAcquiredException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+
+ public MaxImagesAcquiredException(Throwable throwable) {
+ super(throwable);
+ }
+ }
/**
* <p>Create a new reader for images of the desired size and format.</p>
*
- * <p>The maxImages parameter determines the maximum number of {@link Image}
- * objects that can be be acquired from the ImageReader
+ * <p>The {@code maxImages} parameter determines the maximum number of {@link Image}
+ * objects that can be be acquired from the {@code ImageReader}
* simultaneously. Requesting more buffers will use up more memory, so it is
* important to use only the minimum number necessary for the use case.</p>
*
* <p>The valid sizes and formats depend on the source of the image
* data.</p>
*
- * @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.
+ * @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.
*
* @see Image
*/
- public ImageReader(int width, int height, int format, int maxImages) {
+ 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) {
mWidth = width;
mHeight = height;
mFormat = format;
@@ -96,33 +145,79 @@ public final class ImageReader implements AutoCloseable {
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 MaxImagesAcquiredException}. 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 Surface that can be used to produce Images for this
- * ImageReader.</p>
+ * <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this
+ * {@code ImageReader}.</p>
*
- * <p>Until valid image data is rendered into this Surface, the
- * {@link #getNextImage} method will return {@code null}. Only one source
+ * <p>Until valid image data is rendered into this {@link Surface}, the
+ * {@link #acquireNextImage} method will return {@code null}. Only one source
* can be producing data into this Surface at the same time, although the
- * same Surface can be reused with a different API once the first source is
- * disconnected from the Surface.</p>
+ * same {@link Surface} can be reused with a different API once the first source is
+ * disconnected from the {@link Surface}.</p>
*
- * @return A Surface to use for a drawing target for various APIs.
+ * @return A {@link Surface} to use for a drawing target for various APIs.
*/
public Surface getSurface() {
return mSurface;
@@ -130,27 +225,96 @@ public final class ImageReader implements AutoCloseable {
/**
* <p>
- * Get the next Image from the ImageReader's queue. Returns {@code null} if
+ * 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 MaxImagesAcquiredException} 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 MaxImagesAcquiredException}
+ * 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 MaxImagesAcquiredException if too many images are currently acquired
+ */
+ public Image acquireLatestImage() throws MaxImagesAcquiredException {
+ Image image = acquireNextImage();
+ if (image == null) {
+ return null;
+ }
+ try {
+ for (;;) {
+ Image next = acquireNextImageNoThrow();
+ if (next == null) {
+ Image result = image;
+ image = null;
+ return result;
+ }
+ image.close();
+ image = next;
+ }
+ } finally {
+ if (image != null) {
+ image.close();
+ }
+ }
+ }
+
+ private Image acquireNextImageNoThrow() {
+ try {
+ return acquireNextImage();
+ } catch (MaxImagesAcquiredException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * <p>
+ * Acquire the next Image from the ImageReader's queue. Returns {@code null} if
* no new image is available.
* </p>
+ *
+ * <p><i>Warning:</i> Consider using {@link #acquireLatestImage()} instead, as it will
+ * automatically release older images, and allow slower-running processing routines to catch
+ * up to the newest frame. Usage of {@link #acquireNextImage} is recommended for
+ * batch/background processing. Incorrectly using this function can cause images to appear
+ * with an ever-increasing delay, followed by a complete stall where no new images seem to
+ * appear.
+ * </p>
+ *
* <p>
- * This operation will fail by throwing an
- * {@link 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}.
+ * This operation will fail by throwing an {@link MaxImagesAcquiredException} 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 MaxImagesAcquiredException} will be thrown until more images are released with
+ * {@link Image#close}.
* </p>
*
- * @return a new frame of image data, or {@code null} if no image data is
- * available.
- * @throws Surface.OutOfResourcesException if too many images are currently
- * acquired
+ * @return a new frame of image data, or {@code null} if no image data is available.
+ * @throws MaxImagesAcquiredException if {@code maxImages} images are currently acquired
+ * @see #acquireLatestImage
*/
- public Image getNextImage() {
+ public Image acquireNextImage() throws MaxImagesAcquiredException {
SurfaceImage si = new SurfaceImage();
if (nativeImageSetup(si)) {
// create SurfacePlane objects
@@ -164,7 +328,7 @@ public final class ImageReader implements AutoCloseable {
/**
* <p>Return the frame to the ImageReader for reuse.</p>
*/
- public void releaseImage(Image i) {
+ private void releaseImage(Image i) {
if (! (i instanceof SurfaceImage) ) {
throw new IllegalArgumentException(
"This image was not produced by an ImageReader");
@@ -183,13 +347,16 @@ public final class ImageReader implements AutoCloseable {
/**
* Register a listener to be invoked when a new image becomes available
* from the ImageReader.
- * @param listener the listener that will be run
- * @param handler The handler on which the listener should be invoked, or null
- * if the listener should be invoked on the calling thread's looper.
*
- * @throws IllegalArgumentException if no handler specified and the calling thread has no looper
+ * @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 setImageAvailableListener(OnImageAvailableListener listener, Handler handler) {
+ public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) {
mImageListener = listener;
Looper looper;
@@ -206,12 +373,16 @@ public final class ImageReader implements AutoCloseable {
/**
* 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
@@ -220,12 +391,17 @@ public final class ImageReader implements AutoCloseable {
}
/**
- * 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
+ * Free up all the resources associated with this ImageReader.
+ *
+ * <p>
+ * After calling this method, this ImageReader can not be used. Calling
+ * any methods on this ImageReader and Images previously provided by
+ * {@link #acquireNextImage} or {@link #acquireLatestImage}
+ * will result in an {@link IllegalStateException}, and attempting to read from
+ * {@link ByteBuffer ByteBuffers} returned by an earlier
+ * {@link Image.Plane#getBuffer Plane#getBuffer} call will
* have undefined behavior.
+ * </p>
*/
@Override
public void close() {
@@ -242,11 +418,14 @@ public final class ImageReader implements AutoCloseable {
}
/**
- * 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.
+ * 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.
*/
private int getNumPlanesFromFormat() {
switch (mFormat) {
@@ -308,7 +487,7 @@ public final class ImageReader implements AutoCloseable {
*/
private long mNativeContext;
- private class SurfaceImage implements android.media.Image {
+ private class SurfaceImage extends android.media.Image {
public SurfaceImage() {
mIsImageValid = false;
}
@@ -404,7 +583,7 @@ public final class ImageReader implements AutoCloseable {
mPlanes[i] = nativeCreatePlane(i);
}
}
- private class SurfacePlane implements android.media.Image.Plane {
+ private class SurfacePlane extends 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;
@@ -481,7 +660,7 @@ public final class ImageReader implements AutoCloseable {
private synchronized native Surface nativeGetSurface();
private synchronized native boolean 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 94f20bc..56ae6b4 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -43,8 +43,8 @@
using namespace android;
-static const char* const OutOfResourcesException =
- "android/view/Surface$OutOfResourcesException";
+static const char* const MaxImagesAcquiredException =
+ "android/media/ImageReader$MaxImagesAcquiredException";
enum {
IMAGE_READER_MAX_NUM_PLANES = 3,
@@ -700,7 +700,7 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
if (buffer == NULL) {
ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than"
" maxImages buffers");
- jniThrowException(env, OutOfResourcesException,
+ jniThrowException(env, MaxImagesAcquiredException,
"Too many outstanding images, close existing images"
" to be able to acquire more.");
return false;
@@ -709,7 +709,7 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
if (res != NO_ERROR) {
if (res != BAD_VALUE /*no buffers*/) {
if (res == NOT_ENOUGH_DATA) {
- jniThrowException(env, OutOfResourcesException,
+ jniThrowException(env, MaxImagesAcquiredException,
"Too many outstanding images, close existing images"
" to be able to acquire more.");
} else {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index ecdc287..64b12b7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -49,6 +49,7 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {
addMediaPlayerStateUnitTests(suite);
addMediaScannerUnitTests(suite);
addCameraUnitTests(suite);
+ addImageReaderTests(suite);
return suite;
}
@@ -65,6 +66,10 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {
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
new file mode 100644
index 0000000..900fff4
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.media.ImageReader.MaxImagesAcquiredException;
+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);
+ mock(MaxImagesAcquiredException.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() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenReturn(null);
+ assertEquals(null, mReader.acquireLatestImage());
+ }
+
+ /**
+ * Return the last image from the image queue, close up the rest.
+ */
+ @SmallTest
+ public void testGetLatestImage1() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenReturn(mImage1).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() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenReturn(mImage1).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() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenReturn(mImage1).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 MaxImagesAcquiredException with no images in the queue.
+ */
+ @SmallTest
+ public void testGetLatestImageTooManyBuffersAcquiredEmpty() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenThrow(new MaxImagesAcquiredException());
+ try {
+ mReader.acquireLatestImage();
+ fail("Expected MaxImagesAcquiredException to be thrown");
+ } catch(MaxImagesAcquiredException e) {
+ }
+ }
+
+ /**
+ * Return the last image before we get a MaxImagesAcquiredException. Close up the rest.
+ */
+ @SmallTest
+ public void testGetLatestImageTooManyBuffersAcquired1() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenReturn(mImage1).
+ thenThrow(new MaxImagesAcquiredException());
+ assertEquals(mImage1, mReader.acquireLatestImage());
+ verify(mImage1, never()).close();
+ }
+
+ /**
+ * Return the last image before we get a MaxImagesAcquiredException. Close up the rest.
+ */
+ @SmallTest
+ public void testGetLatestImageTooManyBuffersAcquired2() throws MaxImagesAcquiredException {
+
+ when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).
+ thenThrow(new MaxImagesAcquiredException());
+ assertEquals(mImage2, mReader.acquireLatestImage());
+ verify(mImage1, atLeastOnce()).close();
+ verify(mImage2, never()).close();
+ }
+
+ /**
+ * Return the last image before we get a MaxImagesAcquiredException. Close up the rest.
+ */
+ @SmallTest
+ public void testGetLatestImageTooManyBuffersAcquired3() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).
+ thenReturn(mImage3).
+ thenThrow(new MaxImagesAcquiredException());
+ assertEquals(mImage3, mReader.acquireLatestImage());
+ verify(mImage1, atLeastOnce()).close();
+ verify(mImage2, atLeastOnce()).close();
+ verify(mImage3, never()).close();
+ }
+
+ /**
+ * All images are cleaned up when we get an unexpected Error.
+ */
+ @SmallTest
+ public void testGetLatestImageExceptionalError() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenReturn(mImage1).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() throws MaxImagesAcquiredException {
+ when(mReader.acquireNextImage()).thenReturn(mImage1).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();
+ }
+}