Merge "media: Update ImageReader APIs" into klp-dev

This commit is contained in:
Igor Murashkin
2013-09-16 20:19:15 +00:00
committed by Android (Google) Code Review
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
}
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();
@@ -12343,23 +12343,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 {

View File

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

View File

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

View File

@@ -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
* no new image is available.
* 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 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 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 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 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 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();
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.
*/

View File

@@ -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 {

View File

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

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