}
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 android.media.Image acquireLatestImage();
+ method public android.media.Image acquireNextImage();
method public void close();
method public int getHeight();
method public int getImageFormat();
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 {
method public abstract void onImageAvailable(android.media.ImageReader);
}
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;
image.close();
}
}
- } catch (MaxImagesAcquiredException e) {
- // We should never try to consume more buffers than maxImages.
- throw new IllegalStateException(e);
} finally {
mImageReaderLock.unlock();
}
* availability of new Images once
* {@link ImageReader#getMaxImages the maximum outstanding image count} is
* reached. When this happens, the function acquiring new Images will typically
- * throw a
- * {@link ImageReader.MaxImagesAcquiredException MaxImagesAcquiredException}.</p>
+ * throw an {@link IllegalStateException}.</p>
*
* @see ImageReader
*/
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
+ * Returned by nativeImageSetup when acquiring the image was successful.
*/
- 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);
- }
- }
+ private static final int ACQUIRE_SUCCESS = 0;
+ /**
+ * Returned by nativeImageSetup when we couldn't acquire the buffer,
+ * because there were no buffers available to acquire.
+ */
+ private static final int ACQUIRE_NO_BUFS = 1;
+ /**
+ * Returned by nativeImageSetup when we couldn't acquire the buffer
+ * because the consumer has already acquired {@maxImages} and cannot
+ * acquire more than that.
+ */
+ private static final int ACQUIRE_MAX_IMAGES = 2;
/**
* <p>Create a new reader for images of the desired size and format.</p>
* </p>
*
* <p>Attempting to acquire more than {@code maxImages} concurrently will result in the
- * acquire function throwing a {@link MaxImagesAcquiredException}. Furthermore,
+ * acquire function throwing a {@link IllegalStateException}. Furthermore,
* while the max number of images have been acquired by the ImageReader user, the producer
* enqueueing additional images may stall until at least one image has been released. </p>
*
* {@code (maxImages - currentAcquiredImages < 2)} will not discard as expected.
* </p>
* <p>
- * This operation will fail by throwing an {@link MaxImagesAcquiredException} if
+ * This operation will fail by throwing an {@link IllegalStateException} if
* {@code maxImages} have been acquired with {@link #acquireLatestImage} or
* {@link #acquireNextImage}. In particular a sequence of {@link #acquireLatestImage}
* calls greater than {@link #getMaxImages} without calling {@link Image#close} in-between
- * will exhaust the underlying queue. At such a time, {@link MaxImagesAcquiredException}
+ * will exhaust the underlying queue. At such a time, {@link IllegalStateException}
* will be thrown until more images are
* released with {@link Image#close}.
* </p>
*
* @return latest frame of image data, or {@code null} if no image data is available.
- * @throws MaxImagesAcquiredException if too many images are currently acquired
+ * @throws IllegalStateException if too many images are currently acquired
*/
- public Image acquireLatestImage() throws MaxImagesAcquiredException {
+ public Image acquireLatestImage() {
Image image = acquireNextImage();
if (image == null) {
return null;
}
try {
for (;;) {
- Image next = acquireNextImageNoThrow();
+ Image next = acquireNextImageNoThrowISE();
if (next == null) {
Image result = image;
image = null;
}
}
- private Image acquireNextImageNoThrow() {
- try {
- return acquireNextImage();
- } catch (MaxImagesAcquiredException ex) {
- return null;
+ /**
+ * Don't throw IllegalStateException if there are too many images acquired.
+ *
+ * @return Image if acquiring succeeded, or null otherwise.
+ *
+ * @hide
+ */
+ public Image acquireNextImageNoThrowISE() {
+ SurfaceImage si = new SurfaceImage();
+ return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null;
+ }
+
+ /**
+ * Attempts to acquire the next image from the underlying native implementation.
+ *
+ * <p>
+ * Note that unexpected failures will throw at the JNI level.
+ * </p>
+ *
+ * @param si A blank SurfaceImage.
+ * @return One of the {@code ACQUIRE_*} codes that determine success or failure.
+ *
+ * @see #ACQUIRE_MAX_IMAGES
+ * @see #ACQUIRE_NO_BUFS
+ * @see #ACQUIRE_SUCCESS
+ */
+ private int acquireNextSurfaceImage(SurfaceImage si) {
+
+ int status = nativeImageSetup(si);
+
+ switch (status) {
+ case ACQUIRE_SUCCESS:
+ si.createSurfacePlanes();
+ si.setImageValid(true);
+ case ACQUIRE_NO_BUFS:
+ case ACQUIRE_MAX_IMAGES:
+ break;
+ default:
+ throw new AssertionError("Unknown nativeImageSetup return code " + status);
}
+
+ return status;
}
/**
* </p>
*
* <p>
- * This operation will fail by throwing an {@link MaxImagesAcquiredException} if
+ * This operation will fail by throwing an {@link IllegalStateException} if
* {@code maxImages} have been acquired with {@link #acquireNextImage} or
* {@link #acquireLatestImage}. In particular a sequence of {@link #acquireNextImage} or
* {@link #acquireLatestImage} calls greater than {@link #getMaxImages maxImages} without
* calling {@link Image#close} in-between will exhaust the underlying queue. At such a time,
- * {@link MaxImagesAcquiredException} will be thrown until more images are released with
+ * {@link IllegalStateException} 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
+ * @throws IllegalStateException if {@code maxImages} images are currently acquired
* @see #acquireLatestImage
*/
- public Image acquireNextImage() throws MaxImagesAcquiredException {
+ public Image acquireNextImage() {
SurfaceImage si = new SurfaceImage();
- if (nativeImageSetup(si)) {
- // create SurfacePlane objects
- si.createSurfacePlanes();
- si.setImageValid(true);
- return si;
+ int status = acquireNextSurfaceImage(si);
+
+ switch (status) {
+ case ACQUIRE_SUCCESS:
+ return si;
+ case ACQUIRE_NO_BUFS:
+ return null;
+ case ACQUIRE_MAX_IMAGES:
+ throw new IllegalStateException(
+ String.format(
+ "maxImages (%d) has already been acquired, " +
+ "call #close before acquiring more.", mMaxImages));
+ default:
+ throw new AssertionError("Unknown nativeImageSetup return code " + status);
}
- return null;
}
/**
private synchronized native void nativeClose();
private synchronized native void nativeReleaseImage(Image i);
private synchronized native Surface nativeGetSurface();
- private synchronized native boolean nativeImageSetup(Image i);
+
+ /**
+ * @return A return code {@code ACQUIRE_*}
+ *
+ * @see #ACQUIRE_SUCCESS
+ * @see #ACQUIRE_NO_BUFS
+ * @see #ACQUIRE_MAX_IMAGES
+ */
+ private synchronized native int nativeImageSetup(Image i);
/**
* We use a class initializer to allow the native code to cache some
using namespace android;
-static const char* const MaxImagesAcquiredException =
- "android/media/ImageReader$MaxImagesAcquiredException";
-
enum {
IMAGE_READER_MAX_NUM_PLANES = 3,
};
+enum {
+ ACQUIRE_SUCCESS = 0,
+ ACQUIRE_NO_BUFFERS = 1,
+ ACQUIRE_MAX_IMAGES = 2,
+};
+
static struct {
jfieldID mNativeContext;
jmethodID postEventFromNative;
ctx->returnLockedBuffer(buffer);
}
-static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
+static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz,
jobject image)
{
ALOGV("%s:", __FUNCTION__);
JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
if (ctx == NULL) {
jniThrowRuntimeException(env, "ImageReaderContext is not initialized");
- return false;
+ return -1;
}
CpuConsumer* consumer = ctx->getCpuConsumer();
if (buffer == NULL) {
ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than"
" maxImages buffers");
- jniThrowException(env, MaxImagesAcquiredException,
- "Too many outstanding images, close existing images"
- " to be able to acquire more.");
- return false;
+ return ACQUIRE_MAX_IMAGES;
}
status_t res = consumer->lockNextBuffer(buffer);
if (res != NO_ERROR) {
if (res != BAD_VALUE /*no buffers*/) {
if (res == NOT_ENOUGH_DATA) {
- jniThrowException(env, MaxImagesAcquiredException,
- "Too many outstanding images, close existing images"
- " to be able to acquire more.");
+ return ACQUIRE_MAX_IMAGES;
} else {
ALOGE("%s Fail to lockNextBuffer with error: %d ",
__FUNCTION__, res);
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ jniThrowExceptionFmt(env, "java/lang/AssertionError",
"Unknown error (%d) when we tried to lock buffer.",
res);
}
}
- return false;
+ return ACQUIRE_NO_BUFFERS;
}
// Check if the left-top corner of the crop rect is origin, we currently assume this point is
ALOGE("crop left: %d, top = %d", lt.x, lt.y);
jniThrowException(env, "java/lang/UnsupportedOperationException",
"crop left top corner need to at origin");
- return false;
+ return -1;
}
// Check if the producer buffer configurations match what ImageReader configured.
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d",
outputWidth, outputHeight, imageReaderWidth, imageReaderHeight);
+ return -1;
}
if (ctx->getBufferFormat() != buffer->format) {
buffer->format, ctx->getBufferFormat());
jniThrowException(env, "java/lang/UnsupportedOperationException",
msg.string());
- return false;
+ return -1;
}
// Set SurfaceImage instance member variables
Image_setBuffer(env, image, buffer);
env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
static_cast<jlong>(buffer->timestamp));
- return true;
+ return ACQUIRE_SUCCESS;
}
static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz)
{"nativeInit", "(Ljava/lang/Object;IIII)V", (void*)ImageReader_init },
{"nativeClose", "()V", (void*)ImageReader_close },
{"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease },
- {"nativeImageSetup", "(Landroid/media/Image;)Z", (void*)ImageReader_imageSetup },
+ {"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup },
{"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface },
};
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;
{
mock(Plane.class);
mock(OnImageAvailableListener.class);
- mock(MaxImagesAcquiredException.class);
}
}
* Return null when there is nothing in the image queue.
*/
@SmallTest
- public void testGetLatestImageEmpty() throws MaxImagesAcquiredException {
+ public void testGetLatestImageEmpty() {
when(mReader.acquireNextImage()).thenReturn(null);
+ when(mReader.acquireNextImageNoThrowISE()).thenReturn(null);
assertEquals(null, mReader.acquireLatestImage());
}
* Return the last image from the image queue, close up the rest.
*/
@SmallTest
- public void testGetLatestImage1() throws MaxImagesAcquiredException {
- when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(null);
+ public void testGetLatestImage1() {
+ when(mReader.acquireNextImage()).thenReturn(mImage1);
+ when(mReader.acquireNextImageNoThrowISE()).thenReturn(null);
assertEquals(mImage1, mReader.acquireLatestImage());
verify(mImage1, never()).close();
}
* Return the last image from the image queue, close up the rest.
*/
@SmallTest
- public void testGetLatestImage2() throws MaxImagesAcquiredException {
- when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).thenReturn(null);
+ public void testGetLatestImage2() {
+ when(mReader.acquireNextImage()).thenReturn(mImage1);
+ when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).thenReturn(null);
assertEquals(mImage2, mReader.acquireLatestImage());
verify(mImage1, atLeastOnce()).close();
verify(mImage2, never()).close();
* Return the last image from the image queue, close up the rest.
*/
@SmallTest
- public void testGetLatestImage3() throws MaxImagesAcquiredException {
- when(mReader.acquireNextImage()).thenReturn(mImage1).thenReturn(mImage2).
- thenReturn(mImage3).
- thenReturn(null);
+ public void testGetLatestImage3() {
+ when(mReader.acquireNextImage()).thenReturn(mImage1);
+ when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
+ thenReturn(mImage3).
+ thenReturn(null);
assertEquals(mImage3, mReader.acquireLatestImage());
verify(mImage1, atLeastOnce()).close();
verify(mImage2, atLeastOnce()).close();
}
/**
- * Return null if get a MaxImagesAcquiredException with no images in the queue.
+ * Return null if get a IllegalStateException with no images in the queue.
*/
@SmallTest
- public void testGetLatestImageTooManyBuffersAcquiredEmpty() throws MaxImagesAcquiredException {
- when(mReader.acquireNextImage()).thenThrow(new MaxImagesAcquiredException());
+ public void testGetLatestImageTooManyBuffersAcquiredEmpty() {
+ when(mReader.acquireNextImage()).thenThrow(new IllegalStateException());
try {
mReader.acquireLatestImage();
- fail("Expected MaxImagesAcquiredException to be thrown");
- } catch(MaxImagesAcquiredException e) {
+ fail("Expected IllegalStateException to be thrown");
+ } catch(IllegalStateException 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());
+ public void testGetLatestImageExceptionalError() {
+ when(mReader.acquireNextImage()).thenReturn(mImage1);
+ when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
+ thenReturn(mImage3).
+ thenThrow(new OutOfMemoryError());
try {
mReader.acquireLatestImage();
fail("Impossible");
* 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());
+ public void testGetLatestImageExceptionalRuntime() {
+
+ when(mReader.acquireNextImage()).thenReturn(mImage1);
+ when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).
+ thenReturn(mImage3).
+ thenThrow(new RuntimeException());
try {
mReader.acquireLatestImage();
fail("Impossible");