am 26cb5fd5: Merge "media: Update ImageReader APIs" into klp-dev

* commit '26cb5fd5fffb2204ec12994cacb3b32639eb4d59':
  media: Update ImageReader APIs
This commit is contained in:
Igor Murashkin
2013-09-16 13:22:06 -07:00
committed by Android Git Automerger
7 changed files with 549 additions and 119 deletions

View File

@@ -12334,7 +12334,7 @@ package android.media {
field public static final int EULER_Z = 2; // 0x2 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 void close();
method public abstract int getFormat(); method public abstract int getFormat();
method public abstract int getHeight(); method public abstract int getHeight();
@@ -12343,23 +12343,30 @@ package android.media {
method public abstract int getWidth(); 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 java.nio.ByteBuffer getBuffer();
method public abstract int getPixelStride(); method public abstract int getPixelStride();
method public abstract int getRowStride(); method public abstract int getRowStride();
} }
public final class ImageReader implements java.lang.AutoCloseable { public class ImageReader implements java.lang.AutoCloseable {
ctor public ImageReader(int, int, int, int); 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 void close();
method public int getHeight(); method public int getHeight();
method public int getImageFormat(); method public int getImageFormat();
method public int getMaxImages(); method public int getMaxImages();
method public android.media.Image getNextImage();
method public android.view.Surface getSurface(); method public android.view.Surface getSurface();
method public int getWidth(); method public int getWidth();
method public void releaseImage(android.media.Image); method public static android.media.ImageReader newInstance(int, int, int, int);
method public void setImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler); 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 { public static abstract interface ImageReader.OnImageAvailableListener {

View File

@@ -26,6 +26,7 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplay;
import android.media.Image; import android.media.Image;
import android.media.ImageReader; import android.media.ImageReader;
import android.media.ImageReader.MaxImagesAcquiredException;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@@ -83,8 +84,8 @@ public class VirtualDisplayTest extends AndroidTestCase {
mImageReaderLock.lock(); mImageReaderLock.lock();
try { try {
mImageReader = new ImageReader(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2); mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
mImageReader.setImageAvailableListener(mImageListener, mHandler); mImageReader.setOnImageAvailableListener(mImageListener, mHandler);
mSurface = mImageReader.getSurface(); mSurface = mImageReader.getSurface();
} finally { } finally {
mImageReaderLock.unlock(); mImageReaderLock.unlock();
@@ -409,19 +410,11 @@ public class VirtualDisplayTest extends AndroidTestCase {
} }
Log.d(TAG, "New image available from virtual display."); Log.d(TAG, "New image available from virtual display.");
Image image = reader.getNextImage();
// Get the latest buffer.
Image image = reader.acquireLatestImage();
if (image != null) { if (image != null) {
try { try {
// Get the latest buffer.
for (;;) {
Image nextImage = reader.getNextImage();
if (nextImage == null) {
break;
}
reader.releaseImage(image);
image = nextImage;
}
// Scan for colors. // Scan for colors.
int color = scanImage(image); int color = scanImage(image);
synchronized (this) { synchronized (this) {
@@ -431,9 +424,12 @@ public class VirtualDisplayTest extends AndroidTestCase {
} }
} }
} finally { } finally {
reader.releaseImage(image); image.close();
} }
} }
} catch (MaxImagesAcquiredException e) {
// We should never try to consume more buffers than maxImages.
throw new IllegalStateException(e);
} finally { } finally {
mImageReaderLock.unlock(); mImageReaderLock.unlock();
} }

View File

@@ -16,20 +16,19 @@
package android.media; package android.media;
import android.graphics.ImageFormat;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.lang.AutoCloseable; import java.lang.AutoCloseable;
/** /**
* <p>A single complete image buffer to use with a media source such as a * <p>A single complete image buffer to use with a media source such as a
* {@link MediaCodec} or 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 * <p>This class allows for efficient direct application access to the pixel
* data of the Image through one or more * data of the Image through one or more
* {@link java.nio.ByteBuffer ByteBuffers}. Each buffer is encapsulated in a * {@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 * {@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> * Images are not directly usable as as UI resources.</p>
* *
* <p>Since Images are often directly produced or consumed by hardware * <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 * from various media sources, not closing old Image objects will prevent the
* availability of new Images once * availability of new Images once
* {@link ImageReader#getMaxImages the maximum outstanding image count} is * {@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 * @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 * Get the format for this image. This format determines the number of
* ByteBuffers needed to represent the image, and the general layout of the * ByteBuffers needed to represent the image, and the general layout of the
* pixel data in each in ByteBuffer. * pixel data in each in ByteBuffer.
* *
* <p>
* The format is one of the values from * The format is one of the values from
* {@link android.graphics.ImageFormat}. The mapping between the formats and * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the
* the planes is as follows: * formats and the planes is as follows:
* </p>
* *
* <table> * <table>
* <tr> * <tr>
@@ -61,13 +70,14 @@ public interface Image extends AutoCloseable {
* <th>Layout details</th> * <th>Layout details</th>
* </tr> * </tr>
* <tr> * <tr>
* <td>{@link android.graphics.ImageFormat#JPEG}</td> * <td>{@link android.graphics.ImageFormat#JPEG JPEG}</td>
* <td>1</td> * <td>1</td>
* <td>Compressed data, so row and pixel strides are 0. To uncompress, use * <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>
* <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>3</td>
* <td>A luminance plane followed by the Cb and Cr chroma planes. * <td>A luminance plane followed by the Cb and Cr chroma planes.
* The chroma planes have half the width and height of the luminance * 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> * Each plane has its own row stride and pixel stride.</td>
* </tr> * </tr>
* <tr> * <tr>
* <td>{@link android.graphics.ImageFormat#RAW_SENSOR}</td> * <td>{@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}</td>
* <td>1</td> * <td>1</td>
* <td>A single plane of raw sensor image data, with 16 bits per color * <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 * sample. The details of the layout need to be queried from the source of
* the raw sensor data, such as * the raw sensor data, such as
* {@link android.hardware.camera2.CameraDevice}. * {@link android.hardware.camera2.CameraDevice CameraDevice}.
* </td> * </td>
* </tr> * </tr>
* </table> * </table>
* *
* @see android.graphics.ImageFormat * @see android.graphics.ImageFormat
*/ */
public int getFormat(); public abstract int getFormat();
/** /**
* The width of the image in pixels. For formats where some color channels * The width of the image in pixels. For formats where some color channels
* are subsampled, this is the width of the largest-resolution plane. * 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 * The height of the image in pixels. For formats where some color channels
* are subsampled, this is the height of the largest-resolution plane. * 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 * Get the timestamp associated with this frame.
* in nanoseconds, and is monotonically increasing. However, the zero point * <p>
* and whether the timestamp can be compared against other sources of time * The timestamp is measured in nanoseconds, and is monotonically
* or images depend on the source of this image. * 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 * Get the array of pixel planes for this Image. The number of planes is
* determined by the format of the Image. * 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 * Free up this frame for reuse.
* methods on this Image will result in an IllegalStateException, and * <p>
* attempting to read from ByteBuffers returned by an earlier * After calling this method, calling any methods on this {@code Image} will
* {@code Plane#getBuffer} call will have undefined behavior. * 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> * <p>A single color plane of image data.</p>
@@ -134,29 +151,41 @@ public interface Image extends AutoCloseable {
* *
* @see #getFormat * @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 * <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>The distance between adjacent pixel samples, in bytes.</p>
* *
* <p>This is the distance between two consecutive pixel values in a row * <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 * 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> * 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. * @return the byte buffer containing the image data for this plane.
*/ */
public ByteBuffer getBuffer(); public abstract ByteBuffer getBuffer();
} }
} }

View File

@@ -40,41 +40,90 @@ import java.nio.ByteOrder;
* <p>The image data is encapsulated in {@link Image} objects, and multiple such * <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 * 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 * {@code maxImages} constructor parameter. New images sent to an ImageReader
* through its Surface are queued until accessed through the * through its {@link Surface} are queued until accessed through the {@link #acquireLatestImage}
* {@link #getNextImage} call. Due to memory limits, an image source will * 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 * 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 * ImageReader does not obtain and release Images at a rate equal to the
* production rate.</p> * 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>Create a new reader for images of the desired size and format.</p>
* *
* <p>The maxImages parameter determines the maximum number of {@link Image} * <p>The {@code maxImages} parameter determines the maximum number of {@link Image}
* objects that can be be acquired from the ImageReader * objects that can be be acquired from the {@code ImageReader}
* simultaneously. Requesting more buffers will use up more memory, so it is * 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> * 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 * <p>The valid sizes and formats depend on the source of the image
* data.</p> * data.</p>
* *
* @param width the width in pixels of the Images that this reader will * @param width
* produce. * 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 * @param height
* produce. * 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 * @param format
* The format of the Image that this reader will produce. This
* must be one of the {@link android.graphics.ImageFormat} or * must be one of the {@link android.graphics.ImageFormat} or
* {@link android.graphics.PixelFormat} constants. * {@link android.graphics.PixelFormat} constants.
* @param maxImages the maximum number of images the user will want to * @param maxImages
* The maximum number of images the user will want to
* access simultaneously. This should be as small as possible to limit * access simultaneously. This should be as small as possible to limit
* memory use. Once maxImages Images are obtained by the user, one of them * 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 * has to be released before a new Image will become available for access
* through getNextImage(). Must be greater than 0. * through {@link #acquireLatestImage()} or {@link #acquireNextImage()}.
* Must be greater than 0.
* *
* @see Image * @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; mWidth = width;
mHeight = height; mHeight = height;
mFormat = format; mFormat = format;
@@ -96,33 +145,79 @@ public final class ImageReader implements AutoCloseable {
mSurface = nativeGetSurface(); 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() { public int getWidth() {
return mWidth; 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() { public int getHeight() {
return mHeight; 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() { public int getImageFormat() {
return mFormat; 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() { public int getMaxImages() {
return mMaxImages; return mMaxImages;
} }
/** /**
* <p>Get a Surface that can be used to produce Images for this * <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this
* ImageReader.</p> * {@code ImageReader}.</p>
* *
* <p>Until valid image data is rendered into this Surface, the * <p>Until valid image data is rendered into this {@link Surface}, the
* {@link #getNextImage} method will return {@code null}. Only one source * {@link #acquireNextImage} method will return {@code null}. Only one source
* can be producing data into this Surface at the same time, although the * 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 * same {@link Surface} can be reused with a different API once the first source is
* disconnected from the Surface.</p> * 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() { public Surface getSurface() {
return mSurface; return mSurface;
@@ -130,27 +225,96 @@ public final class ImageReader implements AutoCloseable {
/** /**
* <p> * <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
* no new image is available. * {@link Image images}. Returns {@code null} if no new image is available.
* </p> * </p>
* <p> * <p>
* This operation will fail by throwing an * This operation will acquire all the images possible from the ImageReader,
* {@link Surface.OutOfResourcesException OutOfResourcesException} if too * but {@link #close} all images that aren't the latest. This function is
* many images have been acquired with {@link #getNextImage}. In particular * recommended to use over {@link #acquireNextImage} for most use-cases, as it's
* a sequence of {@link #getNextImage} calls greater than {@link #getMaxImages} * more suited for real-time processing.
* without calling {@link Image#close} or {@link #releaseImage} in-between * </p>
* will exhaust the underlying queue. At such a time, * <p>
* {@link Surface.OutOfResourcesException OutOfResourcesException} will be * Note that {@link #getMaxImages maxImages} should be at least 2 for
* thrown until more images are released with {@link Image#close} or * {@link #acquireLatestImage} to be any different than {@link #acquireNextImage} -
* {@link #releaseImage}. * 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> * </p>
* *
* @return a new frame of image data, or {@code null} if no image data is * @return latest frame of image data, or {@code null} if no image data is available.
* available. * @throws MaxImagesAcquiredException if too many images are currently acquired
* @throws Surface.OutOfResourcesException if too many images are currently
* acquired
*/ */
public Image getNextImage() { 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 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 MaxImagesAcquiredException if {@code maxImages} images are currently acquired
* @see #acquireLatestImage
*/
public Image acquireNextImage() throws MaxImagesAcquiredException {
SurfaceImage si = new SurfaceImage(); SurfaceImage si = new SurfaceImage();
if (nativeImageSetup(si)) { if (nativeImageSetup(si)) {
// create SurfacePlane objects // create SurfacePlane objects
@@ -164,7 +328,7 @@ public final class ImageReader implements AutoCloseable {
/** /**
* <p>Return the frame to the ImageReader for reuse.</p> * <p>Return the frame to the ImageReader for reuse.</p>
*/ */
public void releaseImage(Image i) { private void releaseImage(Image i) {
if (! (i instanceof SurfaceImage) ) { if (! (i instanceof SurfaceImage) ) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"This image was not produced by an ImageReader"); "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 * Register a listener to be invoked when a new image becomes available
* from the ImageReader. * 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; mImageListener = listener;
Looper looper; Looper looper;
@@ -206,12 +373,16 @@ public final class ImageReader implements AutoCloseable {
/** /**
* Callback interface for being notified that a new image is available. * 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 * The onImageAvailable is called per image basis, that is, callback fires for every new frame
* available from ImageReader. * available from ImageReader.
* </p>
*/ */
public interface OnImageAvailableListener { public interface OnImageAvailableListener {
/** /**
* Callback that is called when a new image is available from ImageReader. * Callback that is called when a new image is available from ImageReader.
*
* @param reader the ImageReader the callback is associated with. * @param reader the ImageReader the callback is associated with.
* @see ImageReader * @see ImageReader
* @see Image * @see Image
@@ -220,12 +391,17 @@ public final class ImageReader implements AutoCloseable {
} }
/** /**
* Free up all the resources associated with this ImageReader. After * Free up all the resources associated with this ImageReader.
* Calling this method, this ImageReader can not be used. calling *
* any methods on this ImageReader and Images previously provided by {@link #getNextImage} * <p>
* will result in an IllegalStateException, and attempting to read from * After calling this method, this ImageReader can not be used. Calling
* ByteBuffers returned by an earlier {@code Plane#getBuffer} call will * 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. * have undefined behavior.
* </p>
*/ */
@Override @Override
public void close() { 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 * Only a subset of the formats defined in
* {@link android.graphics.PixelFormat} are supported by ImageReader. When reading RGB * {@link android.graphics.ImageFormat ImageFormat} and
* data from a surface, the formats defined in {@link android.graphics.PixelFormat} * {@link android.graphics.PixelFormat PixelFormat} are supported by
* can be used, when reading YUV, JPEG or raw sensor data ( for example, from camera * ImageReader. When reading RGB data from a surface, the formats defined in
* or video decoder), formats from {@link android.graphics.ImageFormat} are used. * {@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() { private int getNumPlanesFromFormat() {
switch (mFormat) { switch (mFormat) {
@@ -308,7 +487,7 @@ public final class ImageReader implements AutoCloseable {
*/ */
private long mNativeContext; private long mNativeContext;
private class SurfaceImage implements android.media.Image { private class SurfaceImage extends android.media.Image {
public SurfaceImage() { public SurfaceImage() {
mIsImageValid = false; mIsImageValid = false;
} }
@@ -404,7 +583,7 @@ public final class ImageReader implements AutoCloseable {
mPlanes[i] = nativeCreatePlane(i); 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 // SurfacePlane instance is created by native code when a new SurfaceImage is created
private SurfacePlane(int index, int rowStride, int pixelStride) { private SurfacePlane(int index, int rowStride, int pixelStride) {
mIndex = index; mIndex = index;
@@ -481,7 +660,7 @@ public final class ImageReader implements AutoCloseable {
private synchronized native Surface nativeGetSurface(); private synchronized native Surface nativeGetSurface();
private synchronized native boolean nativeImageSetup(Image i); private synchronized native boolean nativeImageSetup(Image i);
/* /**
* We use a class initializer to allow the native code to cache some * We use a class initializer to allow the native code to cache some
* field offsets. * field offsets.
*/ */

View File

@@ -43,8 +43,8 @@
using namespace android; using namespace android;
static const char* const OutOfResourcesException = static const char* const MaxImagesAcquiredException =
"android/view/Surface$OutOfResourcesException"; "android/media/ImageReader$MaxImagesAcquiredException";
enum { enum {
IMAGE_READER_MAX_NUM_PLANES = 3, IMAGE_READER_MAX_NUM_PLANES = 3,
@@ -700,7 +700,7 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
if (buffer == NULL) { if (buffer == NULL) {
ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than" ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than"
" maxImages buffers"); " maxImages buffers");
jniThrowException(env, OutOfResourcesException, jniThrowException(env, MaxImagesAcquiredException,
"Too many outstanding images, close existing images" "Too many outstanding images, close existing images"
" to be able to acquire more."); " to be able to acquire more.");
return false; return false;
@@ -709,7 +709,7 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
if (res != NO_ERROR) { if (res != NO_ERROR) {
if (res != BAD_VALUE /*no buffers*/) { if (res != BAD_VALUE /*no buffers*/) {
if (res == NOT_ENOUGH_DATA) { if (res == NOT_ENOUGH_DATA) {
jniThrowException(env, OutOfResourcesException, jniThrowException(env, MaxImagesAcquiredException,
"Too many outstanding images, close existing images" "Too many outstanding images, close existing images"
" to be able to acquire more."); " to be able to acquire more.");
} else { } else {

View File

@@ -49,6 +49,7 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {
addMediaPlayerStateUnitTests(suite); addMediaPlayerStateUnitTests(suite);
addMediaScannerUnitTests(suite); addMediaScannerUnitTests(suite);
addCameraUnitTests(suite); addCameraUnitTests(suite);
addImageReaderTests(suite);
return suite; return suite;
} }
@@ -65,6 +66,10 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {
suite.addTestSuite(CameraMetadataTest.class); 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. // Running all unit tests checking the state machine may be time-consuming.
private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) { private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) {
suite.addTestSuite(MediaMetadataRetrieverTest.class); suite.addTestSuite(MediaMetadataRetrieverTest.class);

View File

@@ -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();
}
}