Merge "media: Update ImageReader APIs" into klp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
26cb5fd5ff
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user