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 return 1; 487 default: 488 throw new UnsupportedOperationException( 489 String.format("Invalid format specified %d", mFormat)); 490 } 491 } 492 493 /** 494 * Called from Native code when an Event happens. 495 * 496 * This may be called from an arbitrary Binder thread, so access to the ImageReader must be 497 * synchronized appropriately. 498 */ 499 private static void postEventFromNative(Object selfRef) { 500 @SuppressWarnings("unchecked") 501 WeakReference<ImageReader> weakSelf = (WeakReference<ImageReader>)selfRef; 502 final ImageReader ir = weakSelf.get(); 503 if (ir == null) { 504 return; 505 } 506 507 final Handler handler; 508 synchronized (ir.mListenerLock) { 509 handler = ir.mListenerHandler; 510 } 511 if (handler != null) { 512 handler.sendEmptyMessage(0); 513 } 514 } 515 516 517 private final int mWidth; 518 private final int mHeight; 519 private final int mFormat; 520 private final int mMaxImages; 521 private final int mNumPlanes; 522 private final Surface mSurface; 523 524 private final Object mListenerLock = new Object(); 525 private OnImageAvailableListener mListener; 526 private ListenerHandler mListenerHandler; 527 528 /** 529 * This field is used by native code, do not access or modify. 530 */ 531 private long mNativeContext; 532 533 /** 534 * This custom handler runs asynchronously so callbacks don't get queued behind UI messages. 535 */ 536 private final class ListenerHandler extends Handler { 537 public ListenerHandler(Looper looper) { 538 super(looper, null, true /*async*/); 539 } 540 541 @Override 542 public void handleMessage(Message msg) { 543 OnImageAvailableListener listener; 544 synchronized (mListenerLock) { 545 listener = mListener; 546 } 547 if (listener != null) { 548 listener.onImageAvailable(ImageReader.this); 549 } 550 } 551 } 552 553 private class SurfaceImage extends android.media.Image { 554 public SurfaceImage() { 555 mIsImageValid = false; 556 } 557 558 @Override 559 public void close() { 560 if (mIsImageValid) { 561 ImageReader.this.releaseImage(this); 562 } 563 } 564 565 public ImageReader getReader() { 566 return ImageReader.this; 567 } 568 569 @Override 570 public int getFormat() { 571 if (mIsImageValid) { 572 return ImageReader.this.mFormat; 573 } else { 574 throw new IllegalStateException("Image is already released"); 575 } 576 } 577 578 @Override 579 public int getWidth() { 580 if (mIsImageValid) { 581 if (mWidth == -1) { 582 mWidth = (getFormat() == ImageFormat.JPEG) ? ImageReader.this.getWidth() : 583 nativeGetWidth(); 584 } 585 return mWidth; 586 } else { 587 throw new IllegalStateException("Image is already released"); 588 } 589 } 590 591 @Override 592 public int getHeight() { 593 if (mIsImageValid) { 594 if (mHeight == -1) { 595 mHeight = (getFormat() == ImageFormat.JPEG) ? ImageReader.this.getHeight() : 596 nativeGetHeight(); 597 } 598 return mHeight; 599 } else { 600 throw new IllegalStateException("Image is already released"); 601 } 602 } 603 604 @Override 605 public long getTimestamp() { 606 if (mIsImageValid) { 607 return mTimestamp; 608 } else { 609 throw new IllegalStateException("Image is already released"); 610 } 611 } 612 613 @Override 614 public Plane[] getPlanes() { 615 if (mIsImageValid) { 616 // Shallow copy is fine. 617 return mPlanes.clone(); 618 } else { 619 throw new IllegalStateException("Image is already released"); 620 } 621 } 622 623 @Override 624 protected final void finalize() throws Throwable { 625 try { 626 close(); 627 } finally { 628 super.finalize(); 629 } 630 } 631 632 private void setImageValid(boolean isValid) { 633 mIsImageValid = isValid; 634 } 635 636 private boolean isImageValid() { 637 return mIsImageValid; 638 } 639 640 private void clearSurfacePlanes() { 641 if (mIsImageValid) { 642 for (int i = 0; i < mPlanes.length; i++) { 643 if (mPlanes[i] != null) { 644 mPlanes[i].clearBuffer(); 645 mPlanes[i] = null; 646 } 647 } 648 } 649 } 650 651 private void createSurfacePlanes() { 652 mPlanes = new SurfacePlane[ImageReader.this.mNumPlanes]; 653 for (int i = 0; i < ImageReader.this.mNumPlanes; i++) { 654 mPlanes[i] = nativeCreatePlane(i, ImageReader.this.mFormat); 655 } 656 } 657 private class SurfacePlane extends android.media.Image.Plane { 658 // SurfacePlane instance is created by native code when a new SurfaceImage is created 659 private SurfacePlane(int index, int rowStride, int pixelStride) { 660 mIndex = index; 661 mRowStride = rowStride; 662 mPixelStride = pixelStride; 663 } 664 665 @Override 666 public ByteBuffer getBuffer() { 667 if (SurfaceImage.this.isImageValid() == false) { 668 throw new IllegalStateException("Image is already released"); 669 } 670 if (mBuffer != null) { 671 return mBuffer; 672 } else { 673 mBuffer = SurfaceImage.this.nativeImageGetBuffer(mIndex, 674 ImageReader.this.mFormat); 675 // Set the byteBuffer order according to host endianness (native order), 676 // otherwise, the byteBuffer order defaults to ByteOrder.BIG_ENDIAN. 677 return mBuffer.order(ByteOrder.nativeOrder()); 678 } 679 } 680 681 @Override 682 public int getPixelStride() { 683 if (SurfaceImage.this.isImageValid()) { 684 return mPixelStride; 685 } else { 686 throw new IllegalStateException("Image is already released"); 687 } 688 } 689 690 @Override 691 public int getRowStride() { 692 if (SurfaceImage.this.isImageValid()) { 693 return mRowStride; 694 } else { 695 throw new IllegalStateException("Image is already released"); 696 } 697 } 698 699 private void clearBuffer() { 700 // Need null check first, as the getBuffer() may not be called before an image 701 // is closed. 702 if (mBuffer == null) { 703 return; 704 } 705 706 if (mBuffer.isDirect()) { 707 NioUtils.freeDirectBuffer(mBuffer); 708 } 709 mBuffer = null; 710 } 711 712 final private int mIndex; 713 final private int mPixelStride; 714 final private int mRowStride; 715 716 private ByteBuffer mBuffer; 717 } 718 719 /** 720 * This field is used to keep track of native object and used by native code only. 721 * Don't modify. 722 */ 723 private long mLockedBuffer; 724 725 /** 726 * This field is set by native code during nativeImageSetup(). 727 */ 728 private long mTimestamp; 729 730 private SurfacePlane[] mPlanes; 731 private boolean mIsImageValid; 732 private int mHeight = -1; 733 private int mWidth = -1; 734 735 private synchronized native ByteBuffer nativeImageGetBuffer(int idx, int readerFormat); 736 private synchronized native SurfacePlane nativeCreatePlane(int idx, int readerFormat); 737 private synchronized native int nativeGetWidth(); 738 private synchronized native int nativeGetHeight(); 739 } 740 741 private synchronized native void nativeInit(Object weakSelf, int w, int h, 742 int fmt, int maxImgs); 743 private synchronized native void nativeClose(); 744 private synchronized native void nativeReleaseImage(Image i); 745 private synchronized native Surface nativeGetSurface(); 746 747 /** 748 * @return A return code {@code ACQUIRE_*} 749 * 750 * @see #ACQUIRE_SUCCESS 751 * @see #ACQUIRE_NO_BUFS 752 * @see #ACQUIRE_MAX_IMAGES 753 */ 754 private synchronized native int nativeImageSetup(Image i); 755 756 /** 757 * We use a class initializer to allow the native code to cache some 758 * field offsets. 759 */ 760 private static native void nativeClassInit(); 761 static { 762 System.loadLibrary("media_jni"); 763 nativeClassInit(); 764 } 765} 766