ImageReader.java revision dd0643202de80cc4ced37d1844e722c8a5e89154
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import android.graphics.ImageFormat;
20import android.graphics.PixelFormat;
21import android.os.Handler;
22import android.os.Looper;
23import android.view.Surface;
24
25import java.lang.ref.WeakReference;
26import java.nio.ByteBuffer;
27import java.nio.ByteOrder;
28
29/**
30 * <p>The ImageReader class allows direct application access to image data
31 * rendered into a {@link android.view.Surface}</p>
32 *
33 * <p>Several Android media API classes accept Surface objects as targets to
34 * render to, including {@link MediaPlayer}, {@link MediaCodec},
35 * {@link android.hardware.camera2.CameraDevice}, and
36 * {@link android.renderscript.Allocation RenderScript Allocations}. The image
37 * sizes and formats that can be used with each source vary, and should be
38 * checked in the documentation for the specific API.</p>
39 *
40 * <p>The image data is encapsulated in {@link Image} objects, and multiple such
41 * objects can be accessed at the same time, up to the number specified by the
42 * {@code maxImages} constructor parameter. New images sent to an ImageReader
43 * through its Surface are queued until accessed through the
44 * {@link #getNextImage} call. Due to memory limits, an image source will
45 * eventually stall or drop Images in trying to render to the Surface if the
46 * ImageReader does not obtain and release Images at a rate equal to the
47 * production rate.</p>
48 */
49public final class ImageReader implements AutoCloseable {
50
51    /**
52     * <p>Create a new reader for images of the desired size and format.</p>
53     *
54     * <p>The maxImages parameter determines the maximum number of {@link Image}
55     * objects that can be be acquired from the ImageReader
56     * simultaneously. Requesting more buffers will use up more memory, so it is
57     * important to use only the minimum number necessary for the use case.</p>
58     *
59     * <p>The valid sizes and formats depend on the source of the image
60     * data.</p>
61     *
62     * @param width the width in pixels of the Images that this reader will
63     * produce.
64     * @param height the height in pixels of the Images that this reader will
65     * produce.
66     * @param format the format of the Image that this reader will produce. This
67     * must be one of the {@link android.graphics.ImageFormat} or
68     * {@link android.graphics.PixelFormat} constants.
69     * @param maxImages the maximum number of images the user will want to
70     * access simultaneously. This should be as small as possible to limit
71     * memory use. Once maxImages Images are obtained by the user, one of them
72     * has to be released before a new Image will become available for access
73     * through getNextImage(). Must be greater than 0.
74     *
75     * @see Image
76     */
77    public ImageReader(int width, int height, int format, int maxImages) {
78        mWidth = width;
79        mHeight = height;
80        mFormat = format;
81        mMaxImages = maxImages;
82
83        if (width < 1 || height < 1) {
84            throw new IllegalArgumentException(
85                "The image dimensions must be positive");
86        }
87        if (mMaxImages < 1) {
88            throw new IllegalArgumentException(
89                "Maximum outstanding image count must be at least 1");
90        }
91
92        mNumPlanes = getNumPlanesFromFormat();
93
94        nativeInit(new WeakReference<ImageReader>(this), width, height, format, maxImages);
95
96        mSurface = nativeGetSurface();
97    }
98
99    public int getWidth() {
100        return mWidth;
101    }
102
103    public int getHeight() {
104        return mHeight;
105    }
106
107    public int getImageFormat() {
108        return mFormat;
109    }
110
111    public int getMaxImages() {
112        return mMaxImages;
113    }
114
115    /**
116     * <p>Get a Surface that can be used to produce Images for this
117     * ImageReader.</p>
118     *
119     * <p>Until valid image data is rendered into this Surface, the
120     * {@link #getNextImage} method will return {@code null}. Only one source
121     * can be producing data into this Surface at the same time, although the
122     * same Surface can be reused with a different API once the first source is
123     * disconnected from the Surface.</p>
124     *
125     * @return A Surface to use for a drawing target for various APIs.
126     */
127    public Surface getSurface() {
128        return mSurface;
129    }
130
131    /**
132     * <p>
133     * Get the next Image from the ImageReader's queue. Returns {@code null} if
134     * no new image is available.
135     * </p>
136     * <p>
137     * This operation will fail by throwing an
138     * {@link Surface.OutOfResourcesException OutOfResourcesException} if too
139     * many images have been acquired with {@link #getNextImage}. In particular
140     * a sequence of {@link #getNextImage} calls greater than {@link #getMaxImages}
141     * without calling {@link Image#close} or {@link #releaseImage} in-between
142     * will exhaust the underlying queue. At such a time,
143     * {@link Surface.OutOfResourcesException OutOfResourcesException} will be
144     * thrown until more images are released with {@link Image#close} or
145     * {@link #releaseImage}.
146     * </p>
147     *
148     * @return a new frame of image data, or {@code null} if no image data is
149     *         available.
150     * @throws Surface.OutOfResourcesException if too many images are currently
151     *         acquired
152     */
153    public Image getNextImage() {
154        SurfaceImage si = new SurfaceImage();
155        if (nativeImageSetup(si)) {
156            // create SurfacePlane objects
157            si.createSurfacePlanes();
158            si.setImageValid(true);
159            return si;
160        }
161        return null;
162    }
163
164    /**
165     * <p>Return the frame to the ImageReader for reuse.</p>
166     */
167    public void releaseImage(Image i) {
168        if (! (i instanceof SurfaceImage) ) {
169            throw new IllegalArgumentException(
170                "This image was not produced by an ImageReader");
171        }
172        SurfaceImage si = (SurfaceImage) i;
173        if (si.getReader() != this) {
174            throw new IllegalArgumentException(
175                "This image was not produced by this ImageReader");
176        }
177
178        si.clearSurfacePlanes();
179        nativeReleaseImage(i);
180        si.setImageValid(false);
181    }
182
183    /**
184     * Register a listener to be invoked when a new image becomes available
185     * from the ImageReader.
186     * @param listener the listener that will be run
187     * @param handler The handler on which the listener should be invoked, or null
188     * if the listener should be invoked on the calling thread's looper.
189     *
190     * @throws IllegalArgumentException if no handler specified and the calling thread has no looper
191     */
192   public void setImageAvailableListener(OnImageAvailableListener listener, Handler handler) {
193        mImageListener = listener;
194
195        Looper looper;
196        mHandler = handler;
197        if (mHandler == null) {
198            if ((looper = Looper.myLooper()) != null) {
199                mHandler = new Handler();
200            } else {
201                throw new IllegalArgumentException(
202                        "Looper doesn't exist in the calling thread");
203            }
204        }
205    }
206
207    /**
208     * Callback interface for being notified that a new image is available.
209     * The onImageAvailable is called per image basis, that is, callback fires for every new frame
210     * available from ImageReader.
211     */
212    public interface OnImageAvailableListener {
213        /**
214         * Callback that is called when a new image is available from ImageReader.
215         * @param reader the ImageReader the callback is associated with.
216         * @see ImageReader
217         * @see Image
218         */
219        void onImageAvailable(ImageReader reader);
220    }
221
222    /**
223     * Free up all the resources associated with this ImageReader. After
224     * Calling this method, this ImageReader can not be used. calling
225     * any methods on this ImageReader and Images previously provided by {@link #getNextImage}
226     * will result in an IllegalStateException, and attempting to read from
227     * ByteBuffers returned by an earlier {@code Plane#getBuffer} call will
228     * have undefined behavior.
229     */
230    @Override
231    public void close() {
232        nativeClose();
233    }
234
235    @Override
236    protected void finalize() throws Throwable {
237        try {
238            close();
239        } finally {
240            super.finalize();
241        }
242    }
243
244    /**
245     * Only a subset of the formats defined in {@link android.graphics.ImageFormat} and
246     * {@link android.graphics.PixelFormat} are supported by ImageReader. When reading RGB
247     * data from a surface, the formats defined in {@link android.graphics.PixelFormat}
248     * can be used, when reading YUV, JPEG or raw sensor data ( for example, from camera
249     *  or video decoder), formats from {@link android.graphics.ImageFormat} are used.
250     */
251    private int getNumPlanesFromFormat() {
252        switch (mFormat) {
253            case ImageFormat.YV12:
254            case ImageFormat.YUV_420_888:
255            case ImageFormat.NV21:
256                return 3;
257            case ImageFormat.NV16:
258                return 2;
259            case PixelFormat.RGB_565:
260            case PixelFormat.RGBA_8888:
261            case PixelFormat.RGBX_8888:
262            case PixelFormat.RGB_888:
263            case ImageFormat.JPEG:
264            case ImageFormat.YUY2:
265            case ImageFormat.Y8:
266            case ImageFormat.Y16:
267            case ImageFormat.RAW_SENSOR:
268                return 1;
269            default:
270                throw new UnsupportedOperationException(
271                        String.format("Invalid format specified %d", mFormat));
272        }
273    }
274
275    /**
276     * Called from Native code when an Event happens.
277     */
278    private static void postEventFromNative(Object selfRef) {
279        @SuppressWarnings("unchecked")
280        WeakReference<ImageReader> weakSelf = (WeakReference<ImageReader>)selfRef;
281        final ImageReader ir = weakSelf.get();
282        if (ir == null) {
283            return;
284        }
285
286        if (ir.mHandler != null) {
287            ir.mHandler.post(new Runnable() {
288                @Override
289                public void run() {
290                    ir.mImageListener.onImageAvailable(ir);
291                }
292              });
293        }
294    }
295
296    private final int mWidth;
297    private final int mHeight;
298    private final int mFormat;
299    private final int mMaxImages;
300    private final int mNumPlanes;
301    private final Surface mSurface;
302
303    private Handler mHandler;
304    private OnImageAvailableListener mImageListener;
305
306    /**
307     * This field is used by native code, do not access or modify.
308     */
309    private long mNativeContext;
310
311    private class SurfaceImage implements android.media.Image {
312        public SurfaceImage() {
313            mIsImageValid = false;
314        }
315
316        @Override
317        public void close() {
318            if (mIsImageValid) {
319                ImageReader.this.releaseImage(this);
320            }
321        }
322
323        public ImageReader getReader() {
324            return ImageReader.this;
325        }
326
327        @Override
328        public int getFormat() {
329            if (mIsImageValid) {
330                return ImageReader.this.mFormat;
331            } else {
332                throw new IllegalStateException("Image is already released");
333            }
334        }
335
336        @Override
337        public int getWidth() {
338            if (mIsImageValid) {
339                return ImageReader.this.mWidth;
340            } else {
341                throw new IllegalStateException("Image is already released");
342            }
343        }
344
345        @Override
346        public int getHeight() {
347            if (mIsImageValid) {
348                return ImageReader.this.mHeight;
349            } else {
350                throw new IllegalStateException("Image is already released");
351            }
352        }
353
354        @Override
355        public long getTimestamp() {
356            if (mIsImageValid) {
357                return mTimestamp;
358            } else {
359                throw new IllegalStateException("Image is already released");
360            }
361        }
362
363        @Override
364        public Plane[] getPlanes() {
365            if (mIsImageValid) {
366                // Shallow copy is fine.
367                return mPlanes.clone();
368            } else {
369                throw new IllegalStateException("Image is already released");
370            }
371        }
372
373        @Override
374        protected final void finalize() throws Throwable {
375            try {
376                close();
377            } finally {
378                super.finalize();
379            }
380        }
381
382        private void setImageValid(boolean isValid) {
383            mIsImageValid = isValid;
384        }
385
386        private boolean isImageValid() {
387            return mIsImageValid;
388        }
389
390        private void clearSurfacePlanes() {
391            if (mIsImageValid) {
392                for (int i = 0; i < mPlanes.length; i++) {
393                    if (mPlanes[i] != null) {
394                        mPlanes[i].clearBuffer();
395                        mPlanes[i] = null;
396                    }
397                }
398            }
399        }
400
401        private void createSurfacePlanes() {
402            mPlanes = new SurfacePlane[ImageReader.this.mNumPlanes];
403            for (int i = 0; i < ImageReader.this.mNumPlanes; i++) {
404                mPlanes[i] = nativeCreatePlane(i);
405            }
406        }
407        private class SurfacePlane implements android.media.Image.Plane {
408            // SurfacePlane instance is created by native code when a new SurfaceImage is created
409            private SurfacePlane(int index, int rowStride, int pixelStride) {
410                mIndex = index;
411                mRowStride = rowStride;
412                mPixelStride = pixelStride;
413            }
414
415            @Override
416            public ByteBuffer getBuffer() {
417                if (SurfaceImage.this.isImageValid() == false) {
418                    throw new IllegalStateException("Image is already released");
419                }
420                if (mBuffer != null) {
421                    return mBuffer;
422                } else {
423                    mBuffer = SurfaceImage.this.nativeImageGetBuffer(mIndex);
424                    // Set the byteBuffer order according to host endianness (native order),
425                    // otherwise, the byteBuffer order defaults to ByteOrder.BIG_ENDIAN.
426                    return mBuffer.order(ByteOrder.nativeOrder());
427                }
428            }
429
430            @Override
431            public int getPixelStride() {
432                if (SurfaceImage.this.isImageValid()) {
433                    return mPixelStride;
434                } else {
435                    throw new IllegalStateException("Image is already released");
436                }
437            }
438
439            @Override
440            public int getRowStride() {
441                if (SurfaceImage.this.isImageValid()) {
442                    return mRowStride;
443                } else {
444                    throw new IllegalStateException("Image is already released");
445                }
446            }
447
448            private void clearBuffer() {
449                mBuffer = null;
450            }
451
452            final private int mIndex;
453            final private int mPixelStride;
454            final private int mRowStride;
455
456            private ByteBuffer mBuffer;
457        }
458
459        /**
460         * This field is used to keep track of native object and used by native code only.
461         * Don't modify.
462         */
463        private long mLockedBuffer;
464
465        /**
466         * This field is set by native code during nativeImageSetup().
467         */
468        private long mTimestamp;
469
470        private SurfacePlane[] mPlanes;
471        private boolean mIsImageValid;
472
473        private synchronized native ByteBuffer nativeImageGetBuffer(int idx);
474        private synchronized native SurfacePlane nativeCreatePlane(int idx);
475    }
476
477    private synchronized native void nativeInit(Object weakSelf, int w, int h,
478                                                    int fmt, int maxImgs);
479    private synchronized native void nativeClose();
480    private synchronized native void nativeReleaseImage(Image i);
481    private synchronized native Surface nativeGetSurface();
482    private synchronized native boolean nativeImageSetup(Image i);
483
484    /*
485     * We use a class initializer to allow the native code to cache some
486     * field offsets.
487     */
488    private static native void nativeClassInit();
489    static {
490        System.loadLibrary("media_jni");
491        nativeClassInit();
492    }
493}
494