Merge "media: Update ImageReader to remove MaxImagesAcquiredException" into klp-dev

This commit is contained in:
Igor Murashkin
2013-09-16 21:02:32 +00:00
committed by Android (Google) Code Review
6 changed files with 136 additions and 153 deletions

View File

@@ -12350,8 +12350,8 @@ package android.media {
}
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();
@@ -12362,13 +12362,6 @@ package android.media {
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);
}

View File

@@ -26,7 +26,6 @@ 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;
@@ -427,9 +426,6 @@ public class VirtualDisplayTest extends AndroidTestCase {
image.close();
}
}
} catch (MaxImagesAcquiredException e) {
// We should never try to consume more buffers than maxImages.
throw new IllegalStateException(e);
} finally {
mImageReaderLock.unlock();
}

View File

@@ -40,8 +40,7 @@ import java.lang.AutoCloseable;
* 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
*/

View File

@@ -49,43 +49,20 @@ import java.nio.ByteOrder;
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>
@@ -195,7 +172,7 @@ public class ImageReader implements AutoCloseable {
* </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>
*
@@ -243,26 +220,26 @@ public class ImageReader implements AutoCloseable {
* {@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;
@@ -278,12 +255,48 @@ public class ImageReader implements AutoCloseable {
}
}
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;
}
/**
@@ -301,28 +314,36 @@ public class ImageReader implements AutoCloseable {
* </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;
}
/**
@@ -658,7 +679,15 @@ public class ImageReader implements AutoCloseable {
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

View File

@@ -43,13 +43,16 @@
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;
@@ -685,14 +688,14 @@ static void ImageReader_imageRelease(JNIEnv* env, jobject thiz, jobject image)
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();
@@ -700,27 +703,22 @@ 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, 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
@@ -730,7 +728,7 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
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.
@@ -759,6 +757,7 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
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) {
@@ -775,14 +774,14 @@ static jboolean ImageReader_imageSetup(JNIEnv* env, jobject thiz,
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)
@@ -853,7 +852,7 @@ static JNINativeMethod gImageReaderMethods[] = {
{"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 },
};

View File

@@ -23,7 +23,6 @@ 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;
@@ -67,7 +66,6 @@ public class ImageReaderTest extends AndroidTestCase {
{
mock(Plane.class);
mock(OnImageAvailableListener.class);
mock(MaxImagesAcquiredException.class);
}
}
@@ -83,8 +81,9 @@ public class ImageReaderTest extends AndroidTestCase {
* 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());
}
@@ -92,8 +91,9 @@ public class ImageReaderTest extends AndroidTestCase {
* 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();
}
@@ -102,8 +102,9 @@ public class ImageReaderTest extends AndroidTestCase {
* 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();
@@ -113,10 +114,11 @@ public class ImageReaderTest extends AndroidTestCase {
* 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();
@@ -124,64 +126,27 @@ public class ImageReaderTest extends AndroidTestCase {
}
/**
* 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");
@@ -197,10 +162,12 @@ public class ImageReaderTest extends AndroidTestCase {
* 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");