ImageReader.java revision 805f3c9428f9ebd5080aec48d3d9d77dbf4b41a9
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.os.Message; 24import android.view.Surface; 25 26import java.lang.ref.WeakReference; 27import java.nio.ByteBuffer; 28import java.nio.ByteOrder; 29import java.nio.NioUtils; 30 31/** 32 * <p>The ImageReader class allows direct application access to image data 33 * rendered into a {@link android.view.Surface}</p> 34 * 35 * <p>Several Android media API classes accept Surface objects as targets to 36 * render to, including {@link MediaPlayer}, {@link MediaCodec}, 37 * {@link android.hardware.camera2.CameraDevice}, and 38 * {@link android.renderscript.Allocation RenderScript Allocations}. The image 39 * sizes and formats that can be used with each source vary, and should be 40 * checked in the documentation for the specific API.</p> 41 * 42 * <p>The image data is encapsulated in {@link Image} objects, and multiple such 43 * objects can be accessed at the same time, up to the number specified by the 44 * {@code maxImages} constructor parameter. New images sent to an ImageReader 45 * through its {@link Surface} are queued until accessed through the {@link #acquireLatestImage} 46 * or {@link #acquireNextImage} call. Due to memory limits, an image source will 47 * eventually stall or drop Images in trying to render to the Surface if the 48 * ImageReader does not obtain and release Images at a rate equal to the 49 * production rate.</p> 50 */ 51public class ImageReader implements AutoCloseable { 52 53 /** 54 * Returned by nativeImageSetup when acquiring the image was successful. 55 */ 56 private static final int ACQUIRE_SUCCESS = 0; 57 /** 58 * Returned by nativeImageSetup when we couldn't acquire the buffer, 59 * because there were no buffers available to acquire. 60 */ 61 private static final int ACQUIRE_NO_BUFS = 1; 62 /** 63 * Returned by nativeImageSetup when we couldn't acquire the buffer 64 * because the consumer has already acquired {@maxImages} and cannot 65 * acquire more than that. 66 */ 67 private static final int ACQUIRE_MAX_IMAGES = 2; 68 69 /** 70 * <p>Create a new reader for images of the desired size and format.</p> 71 * 72 * <p>The {@code maxImages} parameter determines the maximum number of {@link Image} 73 * objects that can be be acquired from the {@code ImageReader} 74 * simultaneously. Requesting more buffers will use up more memory, so it is 75 * important to use only the minimum number necessary for the use case.</p> 76 * 77 * <p>The valid sizes and formats depend on the source of the image 78 * data.</p> 79 * 80 * @param width 81 * The default width in pixels of the Images that this reader will produce. 82 * @param height 83 * The default height in pixels of the Images that this reader will produce. 84 * @param format 85 * The format of the Image that this reader will produce. This 86 * must be one of the {@link android.graphics.ImageFormat} or 87 * {@link android.graphics.PixelFormat} constants. Note that 88 * not all formats is supported, like ImageFormat.NV21. 89 * @param maxImages 90 * The maximum number of images the user will want to 91 * access simultaneously. This should be as small as possible to limit 92 * memory use. Once maxImages Images are obtained by the user, one of them 93 * has to be released before a new Image will become available for access 94 * through {@link #acquireLatestImage()} or {@link #acquireNextImage()}. 95 * Must be greater than 0. 96 * 97 * @see Image 98 */ 99 public static ImageReader newInstance(int width, int height, int format, int maxImages) { 100 return new ImageReader(width, height, format, maxImages); 101 } 102 103 /** 104 * @hide 105 */ 106 protected ImageReader(int width, int height, int format, int maxImages) { 107 mWidth = width; 108 mHeight = height; 109 mFormat = format; 110 mMaxImages = maxImages; 111 112 if (width < 1 || height < 1) { 113 throw new IllegalArgumentException( 114 "The image dimensions must be positive"); 115 } 116 if (mMaxImages < 1) { 117 throw new IllegalArgumentException( 118 "Maximum outstanding image count must be at least 1"); 119 } 120 121 if (format == ImageFormat.NV21) { 122 throw new IllegalArgumentException( 123 "NV21 format is not supported"); 124 } 125 126 mNumPlanes = getNumPlanesFromFormat(); 127 128 nativeInit(new WeakReference<ImageReader>(this), width, height, format, maxImages); 129 130 mSurface = nativeGetSurface(); 131 } 132 133 /** 134 * The default width of {@link Image Images}, in pixels. 135 * 136 * <p>The width may be overridden by the producer sending buffers to this 137 * ImageReader's Surface. If so, the actual width of the images can be 138 * found using {@link Image#getWidth}.</p> 139 * 140 * @return the expected width of an Image 141 */ 142 public int getWidth() { 143 return mWidth; 144 } 145 146 /** 147 * The default height of {@link Image Images}, in pixels. 148 * 149 * <p>The height may be overridden by the producer sending buffers to this 150 * ImageReader's Surface. If so, the actual height of the images can be 151 * found using {@link Image#getHeight}.</p> 152 * 153 * @return the expected height of an Image 154 */ 155 public int getHeight() { 156 return mHeight; 157 } 158 159 /** 160 * The default {@link ImageFormat image format} of {@link Image Images}. 161 * 162 * <p>Some color formats may be overridden by the producer sending buffers to 163 * this ImageReader's Surface if the default color format allows. ImageReader 164 * guarantees that all {@link Image Images} acquired from ImageReader 165 * (for example, with {@link #acquireNextImage}) will have a "compatible" 166 * format to what was specified in {@link #newInstance}. 167 * As of now, each format is only compatible to itself. 168 * The actual format of the images can be found using {@link Image#getFormat}.</p> 169 * 170 * @return the expected format of an Image 171 * 172 * @see ImageFormat 173 */ 174 public int getImageFormat() { 175 return mFormat; 176 } 177 178 /** 179 * Maximum number of images that can be acquired from the ImageReader by any time (for example, 180 * with {@link #acquireNextImage}). 181 * 182 * <p>An image is considered acquired after it's returned by a function from ImageReader, and 183 * until the Image is {@link Image#close closed} to release the image back to the ImageReader. 184 * </p> 185 * 186 * <p>Attempting to acquire more than {@code maxImages} concurrently will result in the 187 * acquire function throwing a {@link IllegalStateException}. Furthermore, 188 * while the max number of images have been acquired by the ImageReader user, the producer 189 * enqueueing additional images may stall until at least one image has been released. </p> 190 * 191 * @return Maximum number of images for this ImageReader. 192 * 193 * @see Image#close 194 */ 195 public int getMaxImages() { 196 return mMaxImages; 197 } 198 199 /** 200 * <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this 201 * {@code ImageReader}.</p> 202 * 203 * <p>Until valid image data is rendered into this {@link Surface}, the 204 * {@link #acquireNextImage} method will return {@code null}. Only one source 205 * can be producing data into this Surface at the same time, although the 206 * same {@link Surface} can be reused with a different API once the first source is 207 * disconnected from the {@link Surface}.</p> 208 * 209 * @return A {@link Surface} to use for a drawing target for various APIs. 210 */ 211 public Surface getSurface() { 212 return mSurface; 213 } 214 215 /** 216 * <p> 217 * Acquire the latest {@link Image} from the ImageReader's queue, dropping older 218 * {@link Image images}. Returns {@code null} if no new image is available. 219 * </p> 220 * <p> 221 * This operation will acquire all the images possible from the ImageReader, 222 * but {@link #close} all images that aren't the latest. This function is 223 * recommended to use over {@link #acquireNextImage} for most use-cases, as it's 224 * more suited for real-time processing. 225 * </p> 226 * <p> 227 * Note that {@link #getMaxImages maxImages} should be at least 2 for 228 * {@link #acquireLatestImage} to be any different than {@link #acquireNextImage} - 229 * discarding all-but-the-newest {@link Image} requires temporarily acquiring two 230 * {@link Image Images} at once. Or more generally, calling {@link #acquireLatestImage} 231 * with less than two images of margin, that is 232 * {@code (maxImages - currentAcquiredImages < 2)} will not discard as expected. 233 * </p> 234 * <p> 235 * This operation will fail by throwing an {@link IllegalStateException} if 236 * {@code maxImages} have been acquired with {@link #acquireLatestImage} or 237 * {@link #acquireNextImage}. In particular a sequence of {@link #acquireLatestImage} 238 * calls greater than {@link #getMaxImages} without calling {@link Image#close} in-between 239 * will exhaust the underlying queue. At such a time, {@link IllegalStateException} 240 * will be thrown until more images are 241 * released with {@link Image#close}. 242 * </p> 243 * 244 * @return latest frame of image data, or {@code null} if no image data is available. 245 * @throws IllegalStateException if too many images are currently acquired 246 */ 247 public Image acquireLatestImage() { 248 Image image = acquireNextImage(); 249 if (image == null) { 250 return null; 251 } 252 try { 253 for (;;) { 254 Image next = acquireNextImageNoThrowISE(); 255 if (next == null) { 256 Image result = image; 257 image = null; 258 return result; 259 } 260 image.close(); 261 image = next; 262 } 263 } finally { 264 if (image != null) { 265 image.close(); 266 } 267 } 268 } 269 270 /** 271 * Don't throw IllegalStateException if there are too many images acquired. 272 * 273 * @return Image if acquiring succeeded, or null otherwise. 274 * 275 * @hide 276 */ 277 public Image acquireNextImageNoThrowISE() { 278 SurfaceImage si = new SurfaceImage(); 279 return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null; 280 } 281 282 /** 283 * Attempts to acquire the next image from the underlying native implementation. 284 * 285 * <p> 286 * Note that unexpected failures will throw at the JNI level. 287 * </p> 288 * 289 * @param si A blank SurfaceImage. 290 * @return One of the {@code ACQUIRE_*} codes that determine success or failure. 291 * 292 * @see #ACQUIRE_MAX_IMAGES 293 * @see #ACQUIRE_NO_BUFS 294 * @see #ACQUIRE_SUCCESS 295 */ 296 private int acquireNextSurfaceImage(SurfaceImage si) { 297 298 int status = nativeImageSetup(si); 299 300 switch (status) { 301 case ACQUIRE_SUCCESS: 302 si.createSurfacePlanes(); 303 si.setImageValid(true); 304 case ACQUIRE_NO_BUFS: 305 case ACQUIRE_MAX_IMAGES: 306 break; 307 default: 308 throw new AssertionError("Unknown nativeImageSetup return code " + status); 309 } 310 311 return status; 312 } 313 314 /** 315 * <p> 316 * Acquire the next Image from the ImageReader's queue. Returns {@code null} if 317 * no new image is available. 318 * </p> 319 * 320 * <p><i>Warning:</i> Consider using {@link #acquireLatestImage()} instead, as it will 321 * automatically release older images, and allow slower-running processing routines to catch 322 * up to the newest frame. Usage of {@link #acquireNextImage} is recommended for 323 * batch/background processing. Incorrectly using this function can cause images to appear 324 * with an ever-increasing delay, followed by a complete stall where no new images seem to 325 * appear. 326 * </p> 327 * 328 * <p> 329 * This operation will fail by throwing an {@link IllegalStateException} if 330 * {@code maxImages} have been acquired with {@link #acquireNextImage} or 331 * {@link #acquireLatestImage}. In particular a sequence of {@link #acquireNextImage} or 332 * {@link #acquireLatestImage} calls greater than {@link #getMaxImages maxImages} without 333 * calling {@link Image#close} in-between will exhaust the underlying queue. At such a time, 334 * {@link IllegalStateException} will be thrown until more images are released with 335 * {@link Image#close}. 336 * </p> 337 * 338 * @return a new frame of image data, or {@code null} if no image data is available. 339 * @throws IllegalStateException if {@code maxImages} images are currently acquired 340 * @see #acquireLatestImage 341 */ 342 public Image acquireNextImage() { 343 SurfaceImage si = new SurfaceImage(); 344 int status = acquireNextSurfaceImage(si); 345 346 switch (status) { 347 case ACQUIRE_SUCCESS: 348 return si; 349 case ACQUIRE_NO_BUFS: 350 return null; 351 case ACQUIRE_MAX_IMAGES: 352 throw new IllegalStateException( 353 String.format( 354 "maxImages (%d) has already been acquired, " + 355 "call #close before acquiring more.", mMaxImages)); 356 default: 357 throw new AssertionError("Unknown nativeImageSetup return code " + status); 358 } 359 } 360 361 /** 362 * <p>Return the frame to the ImageReader for reuse.</p> 363 */ 364 private void releaseImage(Image i) { 365 if (! (i instanceof SurfaceImage) ) { 366 throw new IllegalArgumentException( 367 "This image was not produced by an ImageReader"); 368 } 369 SurfaceImage si = (SurfaceImage) i; 370 if (si.getReader() != this) { 371 throw new IllegalArgumentException( 372 "This image was not produced by this ImageReader"); 373 } 374 375 si.clearSurfacePlanes(); 376 nativeReleaseImage(i); 377 si.setImageValid(false); 378 } 379 380 /** 381 * Register a listener to be invoked when a new image becomes available 382 * from the ImageReader. 383 * 384 * @param listener 385 * The listener that will be run. 386 * @param handler 387 * The handler on which the listener should be invoked, or null 388 * if the listener should be invoked on the calling thread's looper. 389 * @throws IllegalArgumentException 390 * If no handler specified and the calling thread has no looper. 391 */ 392 public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) { 393 synchronized (mListenerLock) { 394 if (listener != null) { 395 Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); 396 if (looper == null) { 397 throw new IllegalArgumentException( 398 "handler is null but the current thread is not a looper"); 399 } 400 if (mListenerHandler == null || mListenerHandler.getLooper() != looper) { 401 mListenerHandler = new ListenerHandler(looper); 402 } 403 mListener = listener; 404 } else { 405 mListener = null; 406 mListenerHandler = null; 407 } 408 } 409 } 410 411 /** 412 * Callback interface for being notified that a new image is available. 413 * 414 * <p> 415 * The onImageAvailable is called per image basis, that is, callback fires for every new frame 416 * available from ImageReader. 417 * </p> 418 */ 419 public interface OnImageAvailableListener { 420 /** 421 * Callback that is called when a new image is available from ImageReader. 422 * 423 * @param reader the ImageReader the callback is associated with. 424 * @see ImageReader 425 * @see Image 426 */ 427 void onImageAvailable(ImageReader reader); 428 } 429 430 /** 431 * Free up all the resources associated with this ImageReader. 432 * 433 * <p> 434 * After calling this method, this ImageReader can not be used. Calling 435 * any methods on this ImageReader and Images previously provided by 436 * {@link #acquireNextImage} or {@link #acquireLatestImage} 437 * will result in an {@link IllegalStateException}, and attempting to read from 438 * {@link ByteBuffer ByteBuffers} returned by an earlier 439 * {@link Image.Plane#getBuffer Plane#getBuffer} call will 440 * have undefined behavior. 441 * </p> 442 */ 443 @Override 444 public void close() { 445 setOnImageAvailableListener(null, null); 446 nativeClose(); 447 } 448 449 @Override 450 protected void finalize() throws Throwable { 451 try { 452 close(); 453 } finally { 454 super.finalize(); 455 } 456 } 457 458 /** 459 * Only a subset of the formats defined in 460 * {@link android.graphics.ImageFormat ImageFormat} and 461 * {@link android.graphics.PixelFormat PixelFormat} are supported by 462 * ImageReader. When reading RGB data from a surface, the formats defined in 463 * {@link android.graphics.PixelFormat PixelFormat} can be used, when 464 * reading YUV, JPEG or raw sensor data (for example, from camera or video 465 * decoder), formats from {@link android.graphics.ImageFormat ImageFormat} 466 * are used. 467 */ 468 private int getNumPlanesFromFormat() { 469 switch (mFormat) { 470 case ImageFormat.YV12: 471 case ImageFormat.YUV_420_888: 472 case ImageFormat.NV21: 473 return 3; 474 case ImageFormat.NV16: 475 return 2; 476 case PixelFormat.RGB_565: 477 case PixelFormat.RGBA_8888: 478 case PixelFormat.RGBX_8888: 479 case PixelFormat.RGB_888: 480 case ImageFormat.JPEG: 481 case ImageFormat.YUY2: 482 case ImageFormat.Y8: 483 case ImageFormat.Y16: 484 case ImageFormat.RAW_SENSOR: 485 case ImageFormat.RAW10: 486 case ImageFormat.DEPTH16: 487 case ImageFormat.DEPTH_POINT_CLOUD: 488 return 1; 489 default: 490 throw new UnsupportedOperationException( 491 String.format("Invalid format specified %d", mFormat)); 492 } 493 } 494 495 /** 496 * Called from Native code when an Event happens. 497 * 498 * This may be called from an arbitrary Binder thread, so access to the ImageReader must be 499 * synchronized appropriately. 500 */ 501 private static void postEventFromNative(Object selfRef) { 502 @SuppressWarnings("unchecked") 503 WeakReference<ImageReader> weakSelf = (WeakReference<ImageReader>)selfRef; 504 final ImageReader ir = weakSelf.get(); 505 if (ir == null) { 506 return; 507 } 508 509 final Handler handler; 510 synchronized (ir.mListenerLock) { 511 handler = ir.mListenerHandler; 512 } 513 if (handler != null) { 514 handler.sendEmptyMessage(0); 515 } 516 } 517 518 519 private final int mWidth; 520 private final int mHeight; 521 private final int mFormat; 522 private final int mMaxImages; 523 private final int mNumPlanes; 524 private final Surface mSurface; 525 526 private final Object mListenerLock = new Object(); 527 private OnImageAvailableListener mListener; 528 private ListenerHandler mListenerHandler; 529 530 /** 531 * This field is used by native code, do not access or modify. 532 */ 533 private long mNativeContext; 534 535 /** 536 * This custom handler runs asynchronously so callbacks don't get queued behind UI messages. 537 */ 538 private final class ListenerHandler extends Handler { 539 public ListenerHandler(Looper looper) { 540 super(looper, null, true /*async*/); 541 } 542 543 @Override 544 public void handleMessage(Message msg) { 545 OnImageAvailableListener listener; 546 synchronized (mListenerLock) { 547 listener = mListener; 548 } 549 if (listener != null) { 550 listener.onImageAvailable(ImageReader.this); 551 } 552 } 553 } 554 555 private class SurfaceImage extends android.media.Image { 556 public SurfaceImage() { 557 mIsImageValid = false; 558 } 559 560 @Override 561 public void close() { 562 if (mIsImageValid) { 563 ImageReader.this.releaseImage(this); 564 } 565 } 566 567 public ImageReader getReader() { 568 return ImageReader.this; 569 } 570 571 @Override 572 public int getFormat() { 573 if (mIsImageValid) { 574 return ImageReader.this.mFormat; 575 } else { 576 throw new IllegalStateException("Image is already released"); 577 } 578 } 579 580 @Override 581 public int getWidth() { 582 if (mIsImageValid) { 583 if (mWidth == -1) { 584 mWidth = (getFormat() == ImageFormat.JPEG) ? ImageReader.this.getWidth() : 585 nativeGetWidth(); 586 } 587 return mWidth; 588 } else { 589 throw new IllegalStateException("Image is already released"); 590 } 591 } 592 593 @Override 594 public int getHeight() { 595 if (mIsImageValid) { 596 if (mHeight == -1) { 597 mHeight = (getFormat() == ImageFormat.JPEG) ? ImageReader.this.getHeight() : 598 nativeGetHeight(); 599 } 600 return mHeight; 601 } else { 602 throw new IllegalStateException("Image is already released"); 603 } 604 } 605 606 @Override 607 public long getTimestamp() { 608 if (mIsImageValid) { 609 return mTimestamp; 610 } else { 611 throw new IllegalStateException("Image is already released"); 612 } 613 } 614 615 @Override 616 public Plane[] getPlanes() { 617 if (mIsImageValid) { 618 // Shallow copy is fine. 619 return mPlanes.clone(); 620 } else { 621 throw new IllegalStateException("Image is already released"); 622 } 623 } 624 625 @Override 626 protected final void finalize() throws Throwable { 627 try { 628 close(); 629 } finally { 630 super.finalize(); 631 } 632 } 633 634 private void setImageValid(boolean isValid) { 635 mIsImageValid = isValid; 636 } 637 638 private boolean isImageValid() { 639 return mIsImageValid; 640 } 641 642 private void clearSurfacePlanes() { 643 if (mIsImageValid) { 644 for (int i = 0; i < mPlanes.length; i++) { 645 if (mPlanes[i] != null) { 646 mPlanes[i].clearBuffer(); 647 mPlanes[i] = null; 648 } 649 } 650 } 651 } 652 653 private void createSurfacePlanes() { 654 mPlanes = new SurfacePlane[ImageReader.this.mNumPlanes]; 655 for (int i = 0; i < ImageReader.this.mNumPlanes; i++) { 656 mPlanes[i] = nativeCreatePlane(i, ImageReader.this.mFormat); 657 } 658 } 659 private class SurfacePlane extends android.media.Image.Plane { 660 // SurfacePlane instance is created by native code when a new SurfaceImage is created 661 private SurfacePlane(int index, int rowStride, int pixelStride) { 662 mIndex = index; 663 mRowStride = rowStride; 664 mPixelStride = pixelStride; 665 } 666 667 @Override 668 public ByteBuffer getBuffer() { 669 if (SurfaceImage.this.isImageValid() == false) { 670 throw new IllegalStateException("Image is already released"); 671 } 672 if (mBuffer != null) { 673 return mBuffer; 674 } else { 675 mBuffer = SurfaceImage.this.nativeImageGetBuffer(mIndex, 676 ImageReader.this.mFormat); 677 // Set the byteBuffer order according to host endianness (native order), 678 // otherwise, the byteBuffer order defaults to ByteOrder.BIG_ENDIAN. 679 return mBuffer.order(ByteOrder.nativeOrder()); 680 } 681 } 682 683 @Override 684 public int getPixelStride() { 685 if (SurfaceImage.this.isImageValid()) { 686 return mPixelStride; 687 } else { 688 throw new IllegalStateException("Image is already released"); 689 } 690 } 691 692 @Override 693 public int getRowStride() { 694 if (SurfaceImage.this.isImageValid()) { 695 return mRowStride; 696 } else { 697 throw new IllegalStateException("Image is already released"); 698 } 699 } 700 701 private void clearBuffer() { 702 // Need null check first, as the getBuffer() may not be called before an image 703 // is closed. 704 if (mBuffer == null) { 705 return; 706 } 707 708 if (mBuffer.isDirect()) { 709 NioUtils.freeDirectBuffer(mBuffer); 710 } 711 mBuffer = null; 712 } 713 714 final private int mIndex; 715 final private int mPixelStride; 716 final private int mRowStride; 717 718 private ByteBuffer mBuffer; 719 } 720 721 /** 722 * This field is used to keep track of native object and used by native code only. 723 * Don't modify. 724 */ 725 private long mLockedBuffer; 726 727 /** 728 * This field is set by native code during nativeImageSetup(). 729 */ 730 private long mTimestamp; 731 732 private SurfacePlane[] mPlanes; 733 private boolean mIsImageValid; 734 private int mHeight = -1; 735 private int mWidth = -1; 736 737 private synchronized native ByteBuffer nativeImageGetBuffer(int idx, int readerFormat); 738 private synchronized native SurfacePlane nativeCreatePlane(int idx, int readerFormat); 739 private synchronized native int nativeGetWidth(); 740 private synchronized native int nativeGetHeight(); 741 } 742 743 private synchronized native void nativeInit(Object weakSelf, int w, int h, 744 int fmt, int maxImgs); 745 private synchronized native void nativeClose(); 746 private synchronized native void nativeReleaseImage(Image i); 747 private synchronized native Surface nativeGetSurface(); 748 749 /** 750 * @return A return code {@code ACQUIRE_*} 751 * 752 * @see #ACQUIRE_SUCCESS 753 * @see #ACQUIRE_NO_BUFS 754 * @see #ACQUIRE_MAX_IMAGES 755 */ 756 private synchronized native int nativeImageSetup(Image i); 757 758 /** 759 * We use a class initializer to allow the native code to cache some 760 * field offsets. 761 */ 762 private static native void nativeClassInit(); 763 static { 764 System.loadLibrary("media_jni"); 765 nativeClassInit(); 766 } 767} 768