From a89f6e1bb2076518068084fea53c4ee5c1306b4c Mon Sep 17 00:00:00 2001 From: Chong Zhang Date: Wed, 7 Mar 2018 16:22:18 -0800 Subject: [PATCH] heif: add option for specifying bitmap pixel format Add an option similar to BitmapFactory.Options to the bitmap extraction APIs added in P to allow the app to specify bitmap's pixel format. MediaMetadataRetriever's old getFrameAtTime() only allows extraction in RGB565, for image use case the bitdepth could be too low. Also change return type of getFramesAtIndex to List as Lint is complaining about returning raw arrays. bug: 63633199 bug: 73886998 Change-Id: I40f0a421c767483e32c7744180dc5a187681e066 --- api/current.txt | 15 +- .../android/media/MediaMetadataRetriever.java | 114 ++++++++-- .../android_media_MediaMetadataRetriever.cpp | 205 ++++++++++++++---- 3 files changed, 265 insertions(+), 69 deletions(-) diff --git a/api/current.txt b/api/current.txt index fff502a577db8..b7a869ef3177d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -24015,13 +24015,13 @@ package android.media { ctor public MediaMetadataRetriever(); method public java.lang.String extractMetadata(int); method public byte[] getEmbeddedPicture(); - method public android.graphics.Bitmap getFrameAtIndex(int); + method public android.graphics.Bitmap getFrameAtIndex(int, android.media.MediaMetadataRetriever.BitmapParams); method public android.graphics.Bitmap getFrameAtTime(long, int); method public android.graphics.Bitmap getFrameAtTime(long); method public android.graphics.Bitmap getFrameAtTime(); - method public android.graphics.Bitmap[] getFramesAtIndex(int, int); - method public android.graphics.Bitmap getImageAtIndex(int); - method public android.graphics.Bitmap getPrimaryImage(); + method public java.util.List getFramesAtIndex(int, int, android.media.MediaMetadataRetriever.BitmapParams); + method public android.graphics.Bitmap getImageAtIndex(int, android.media.MediaMetadataRetriever.BitmapParams); + method public android.graphics.Bitmap getPrimaryImage(android.media.MediaMetadataRetriever.BitmapParams); method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int); method public void release(); method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException; @@ -24067,6 +24067,13 @@ package android.media { field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0 } + public static final class MediaMetadataRetriever.BitmapParams { + ctor public MediaMetadataRetriever.BitmapParams(); + method public android.graphics.Bitmap.Config getActualConfig(); + method public android.graphics.Bitmap.Config getPreferredConfig(); + method public void setPreferredConfig(android.graphics.Bitmap.Config); + } + public final class MediaMuxer { ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException; ctor public MediaMuxer(java.io.FileDescriptor, int) throws java.io.IOException; diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 745eb74d6e20b..1aeed6da3a317 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -17,6 +17,8 @@ package android.media; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; @@ -30,7 +32,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; - +import java.util.List; import java.util.Map; /** @@ -367,27 +369,79 @@ public class MediaMetadataRetriever private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height); + public static final class BitmapParams { + private Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888; + private Bitmap.Config outActualConfig = Bitmap.Config.ARGB_8888; + + /** + * Create a default BitmapParams object. By default, it uses {@link Bitmap.Config#ARGB_8888} + * as the preferred bitmap config. + */ + public BitmapParams() {} + + /** + * Set the preferred bitmap config for the decoder to decode into. + * + * If not set, or the request cannot be met, the decoder will output + * in {@link Bitmap.Config#ARGB_8888} config by default. + * + * After decode, the actual config used can be retrieved by {@link #getActualConfig()}. + * + * @param config the preferred bitmap config to use. + */ + public void setPreferredConfig(@NonNull Bitmap.Config config) { + if (config == null) { + throw new IllegalArgumentException("preferred config can't be null"); + } + inPreferredConfig = config; + } + + /** + * Retrieve the preferred bitmap config in the params. + * + * @return the preferred bitmap config. + */ + public @NonNull Bitmap.Config getPreferredConfig() { + return inPreferredConfig; + } + + /** + * Get the actual bitmap config used to decode the bitmap after the decoding. + * + * @return the actual bitmap config used. + */ + public @NonNull Bitmap.Config getActualConfig() { + return outActualConfig; + } + } + /** * This method retrieves a video frame by its index. It should only be called * after {@link #setDataSource}. * + * After the bitmap is returned, you can query the actual parameters that were + * used to create the bitmap from the {@code BitmapParams} argument, for instance + * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. + * * @param frameIndex 0-based index of the video frame. The frame index must be that of * a valid frame. The total number of frames available for retrieval can be queried * via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key. + * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). + * If null, default config will be chosen. * * @throws IllegalStateException if the container doesn't contain video or image sequences. * @throws IllegalArgumentException if the requested frame index does not exist. * * @return A Bitmap containing the requested video frame, or null if the retrieval fails. * - * @see #getFramesAtIndex(int, int) + * @see #getFramesAtIndex(int, int, BitmapParams) */ - public Bitmap getFrameAtIndex(int frameIndex) { - Bitmap[] bitmaps = getFramesAtIndex(frameIndex, 1); - if (bitmaps == null || bitmaps.length < 1) { + public Bitmap getFrameAtIndex(int frameIndex, @Nullable BitmapParams params) { + List bitmaps = getFramesAtIndex(frameIndex, 1, params); + if (bitmaps == null || bitmaps.size() < 1) { return null; } - return bitmaps[0]; + return bitmaps.get(0); } /** @@ -395,24 +449,31 @@ public class MediaMetadataRetriever * specified index. It should only be called after {@link #setDataSource}. * * If the caller intends to retrieve more than one consecutive video frames, - * this method is preferred over {@link #getFrameAtIndex(int)} for efficiency. + * this method is preferred over {@link #getFrameAtIndex(int, BitmapParams)} for efficiency. + * + * After the bitmaps are returned, you can query the actual parameters that were + * used to create the bitmaps from the {@code BitmapParams} argument, for instance + * to query the bitmap config used for the bitmaps with {@link BitmapParams#getActualConfig}. * * @param frameIndex 0-based index of the first video frame to retrieve. The frame index * must be that of a valid frame. The total number of frames available for retrieval * can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key. * @param numFrames number of consecutive video frames to retrieve. Must be a positive * value. The stream must contain at least numFrames frames starting at frameIndex. + * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). + * If null, default config will be chosen. * * @throws IllegalStateException if the container doesn't contain video or image sequences. * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the * stream doesn't contain at least numFrames starting at frameIndex. - * @return An array of Bitmaps containing the requested video frames. The returned + * @return An list of Bitmaps containing the requested video frames. The returned * array could contain less frames than requested if the retrieval fails. * - * @see #getFrameAtIndex(int) + * @see #getFrameAtIndex(int, BitmapParams) */ - public Bitmap[] getFramesAtIndex(int frameIndex, int numFrames) { + public List getFramesAtIndex( + int frameIndex, int numFrames, @Nullable BitmapParams params) { if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) { throw new IllegalStateException("Does not contail video or image sequences"); } @@ -424,24 +485,32 @@ public class MediaMetadataRetriever throw new IllegalArgumentException("Invalid frameIndex or numFrames: " + frameIndex + ", " + numFrames); } - return _getFrameAtIndex(frameIndex, numFrames); + return _getFrameAtIndex(frameIndex, numFrames, params); } - private native Bitmap[] _getFrameAtIndex(int frameIndex, int numFrames); + private native List _getFrameAtIndex( + int frameIndex, int numFrames, @Nullable BitmapParams params); /** * This method retrieves a still image by its index. It should only be called * after {@link #setDataSource}. * + * After the bitmap is returned, you can query the actual parameters that were + * used to create the bitmap from the {@code BitmapParams} argument, for instance + * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. + * * @param imageIndex 0-based index of the image, with negative value indicating * the primary image. + * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). + * If null, default config will be chosen. + * * @throws IllegalStateException if the container doesn't contain still images. * @throws IllegalArgumentException if the requested image does not exist. * * @return the requested still image, or null if the image cannot be retrieved. * - * @see #getPrimaryImage + * @see #getPrimaryImage(BitmapParams) */ - public Bitmap getImageAtIndex(int imageIndex) { + public Bitmap getImageAtIndex(int imageIndex, @Nullable BitmapParams params) { if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) { throw new IllegalStateException("Does not contail still images"); } @@ -451,24 +520,31 @@ public class MediaMetadataRetriever throw new IllegalArgumentException("Invalid image index: " + imageCount); } - return _getImageAtIndex(imageIndex); + return _getImageAtIndex(imageIndex, params); } /** * This method retrieves the primary image of the media content. It should only * be called after {@link #setDataSource}. * + * After the bitmap is returned, you can query the actual parameters that were + * used to create the bitmap from the {@code BitmapParams} argument, for instance + * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}. + * + * @param params BitmapParams that controls the returned bitmap config (such as pixel formats). + * If null, default config will be chosen. + * * @return the primary image, or null if it cannot be retrieved. * * @throws IllegalStateException if the container doesn't contain still images. * - * @see #getImageAtIndex(int) + * @see #getImageAtIndex(int, BitmapParams) */ - public Bitmap getPrimaryImage() { - return getImageAtIndex(-1); + public Bitmap getPrimaryImage(@Nullable BitmapParams params) { + return getImageAtIndex(-1, params); } - private native Bitmap _getImageAtIndex(int imageIndex); + private native Bitmap _getImageAtIndex(int imageIndex, @Nullable BitmapParams params); /** * Call this method after setDataSource(). This method finds the optional diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp index ff854c51c437c..3a6714279bb79 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "jni.h" @@ -45,6 +46,12 @@ struct fields_t { jmethodID createScaledBitmapMethod; jclass configClazz; // Must be a global ref jmethodID createConfigMethod; + jclass bitmapParamsClazz; // Must be a global ref + jfieldID inPreferredConfig; + jfieldID outActualConfig; + jclass arrayListClazz; // Must be a global ref + jmethodID arrayListInit; + jmethodID arrayListAdd; }; static fields_t fields; @@ -254,16 +261,18 @@ static void rotate(T *dst, const T *src, size_t width, size_t height, int angle) } static jobject getBitmapFromVideoFrame( - JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height) { + JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height, + SkColorType outColorType) { ALOGV("getBitmapFromVideoFrame: dimension = %dx%d and bytes = %d", videoFrame->mDisplayWidth, videoFrame->mDisplayHeight, videoFrame->mSize); - jobject config = env->CallStaticObjectMethod( - fields.configClazz, - fields.createConfigMethod, - GraphicsJNI::colorTypeToLegacyBitmapConfig(kRGB_565_SkColorType)); + ScopedLocalRef config(env, + env->CallStaticObjectMethod( + fields.configClazz, + fields.createConfigMethod, + GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType))); uint32_t width, height, displayWidth, displayHeight; bool swapWidthAndHeight = false; @@ -285,7 +294,7 @@ static jobject getBitmapFromVideoFrame( fields.createBitmapMethod, width, height, - config); + config.get()); if (jBitmap == NULL) { if (env->ExceptionCheck()) { env->ExceptionClear(); @@ -297,11 +306,19 @@ static jobject getBitmapFromVideoFrame( SkBitmap bitmap; GraphicsJNI::getSkBitmap(env, jBitmap, &bitmap); - rotate((uint16_t*)bitmap.getPixels(), - (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)), - videoFrame->mWidth, - videoFrame->mHeight, - videoFrame->mRotationAngle); + if (outColorType == kRGB_565_SkColorType) { + rotate((uint16_t*)bitmap.getPixels(), + (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)), + videoFrame->mWidth, + videoFrame->mHeight, + videoFrame->mRotationAngle); + } else { + rotate((uint32_t*)bitmap.getPixels(), + (uint32_t*)((char*)videoFrame + sizeof(VideoFrame)), + videoFrame->mWidth, + videoFrame->mHeight, + videoFrame->mRotationAngle); + } if (dst_width <= 0 || dst_height <= 0) { dst_width = displayWidth; @@ -323,12 +340,46 @@ static jobject getBitmapFromVideoFrame( dst_width, dst_height, true); + + env->DeleteLocalRef(jBitmap); return scaledBitmap; } return jBitmap; } +static int getColorFormat(JNIEnv *env, jobject options) { + if (options == NULL) { + return HAL_PIXEL_FORMAT_RGBA_8888; + } + + ScopedLocalRef inConfig(env, env->GetObjectField(options, fields.inPreferredConfig)); + SkColorType prefColorType = GraphicsJNI::getNativeBitmapColorType(env, inConfig.get()); + + if (prefColorType == kRGB_565_SkColorType) { + return HAL_PIXEL_FORMAT_RGB_565; + } + return HAL_PIXEL_FORMAT_RGBA_8888; +} + +static SkColorType setOutColorType(JNIEnv *env, int colorFormat, jobject options) { + SkColorType outColorType = kN32_SkColorType; + if (colorFormat == HAL_PIXEL_FORMAT_RGB_565) { + outColorType = kRGB_565_SkColorType; + } + + if (options != NULL) { + ScopedLocalRef config(env, + env->CallStaticObjectMethod( + fields.configClazz, + fields.createConfigMethod, + GraphicsJNI::colorTypeToLegacyBitmapConfig(outColorType))); + + env->SetObjectField(options, fields.outActualConfig, config.get()); + } + return outColorType; +} + static jobject android_media_MediaMetadataRetriever_getFrameAtTime( JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height) { @@ -351,11 +402,11 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime( return NULL; } - return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height); + return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, kRGB_565_SkColorType); } static jobject android_media_MediaMetadataRetriever_getImageAtIndex( - JNIEnv *env, jobject thiz, jint index) + JNIEnv *env, jobject thiz, jint index, jobject params) { ALOGV("getImageAtIndex: index %d", index); sp retriever = getRetriever(env, thiz); @@ -364,9 +415,11 @@ static jobject android_media_MediaMetadataRetriever_getImageAtIndex( return NULL; } + int colorFormat = getColorFormat(env, params); + // Call native method to retrieve an image VideoFrame *videoFrame = NULL; - sp frameMemory = retriever->getImageAtIndex(index); + sp frameMemory = retriever->getImageAtIndex(index, colorFormat); if (frameMemory != 0) { // cast the shared structure to a VideoFrame object videoFrame = static_cast(frameMemory->pointer()); } @@ -375,11 +428,13 @@ static jobject android_media_MediaMetadataRetriever_getImageAtIndex( return NULL; } - return getBitmapFromVideoFrame(env, videoFrame, -1, -1); + SkColorType outColorType = setOutColorType(env, colorFormat, params); + + return getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType); } -static jobjectArray android_media_MediaMetadataRetriever_getFrameAtIndex( - JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames) +static jobject android_media_MediaMetadataRetriever_getFrameAtIndex( + JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames, jobject params) { ALOGV("getFrameAtIndex: frameIndex %d, numFrames %d", frameIndex, numFrames); sp retriever = getRetriever(env, thiz); @@ -389,31 +444,34 @@ static jobjectArray android_media_MediaMetadataRetriever_getFrameAtIndex( return NULL; } + int colorFormat = getColorFormat(env, params); + std::vector > frames; - status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames); + status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames, colorFormat); if (err != OK || frames.size() == 0) { ALOGE("failed to get frames from retriever, err=%d, size=%zu", err, frames.size()); return NULL; } - - jobjectArray bitmapArrayObj = env->NewObjectArray( - frames.size(), fields.bitmapClazz, NULL); - if (bitmapArrayObj == NULL) { - ALOGE("can't create bitmap array object"); + jobject arrayList = env->NewObject(fields.arrayListClazz, fields.arrayListInit); + if (arrayList == NULL) { + ALOGE("can't create bitmap array list object"); return NULL; } + SkColorType outColorType = setOutColorType(env, colorFormat, params); + for (size_t i = 0; i < frames.size(); i++) { if (frames[i] == NULL || frames[i]->pointer() == NULL) { ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i); continue; } VideoFrame *videoFrame = static_cast(frames[i]->pointer()); - jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1); - env->SetObjectArrayElement(bitmapArrayObj, i, bitmapObj); + jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType); + env->CallBooleanMethod(arrayList, fields.arrayListAdd, bitmapObj); + env->DeleteLocalRef(bitmapObj); } - return bitmapArrayObj; + return arrayList; } static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture( @@ -488,21 +546,21 @@ static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jo // first time an instance of this class is used. static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) { - jclass clazz = env->FindClass(kClassPathName); - if (clazz == NULL) { + ScopedLocalRef clazz(env, env->FindClass(kClassPathName)); + if (clazz.get() == NULL) { return; } - fields.context = env->GetFieldID(clazz, "mNativeContext", "J"); + fields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J"); if (fields.context == NULL) { return; } - jclass bitmapClazz = env->FindClass("android/graphics/Bitmap"); - if (bitmapClazz == NULL) { + clazz.reset(env->FindClass("android/graphics/Bitmap")); + if (clazz.get() == NULL) { return; } - fields.bitmapClazz = (jclass) env->NewGlobalRef(bitmapClazz); + fields.bitmapClazz = (jclass) env->NewGlobalRef(clazz.get()); if (fields.bitmapClazz == NULL) { return; } @@ -521,11 +579,11 @@ static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) return; } - jclass configClazz = env->FindClass("android/graphics/Bitmap$Config"); - if (configClazz == NULL) { + clazz.reset(env->FindClass("android/graphics/Bitmap$Config")); + if (clazz.get() == NULL) { return; } - fields.configClazz = (jclass) env->NewGlobalRef(configClazz); + fields.configClazz = (jclass) env->NewGlobalRef(clazz.get()); if (fields.configClazz == NULL) { return; } @@ -535,6 +593,42 @@ static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) if (fields.createConfigMethod == NULL) { return; } + + clazz.reset(env->FindClass("android/media/MediaMetadataRetriever$BitmapParams")); + if (clazz.get() == NULL) { + return; + } + fields.bitmapParamsClazz = (jclass) env->NewGlobalRef(clazz.get()); + if (fields.bitmapParamsClazz == NULL) { + return; + } + fields.inPreferredConfig = env->GetFieldID(fields.bitmapParamsClazz, + "inPreferredConfig", "Landroid/graphics/Bitmap$Config;"); + if (fields.inPreferredConfig == NULL) { + return; + } + fields.outActualConfig = env->GetFieldID(fields.bitmapParamsClazz, + "outActualConfig", "Landroid/graphics/Bitmap$Config;"); + if (fields.outActualConfig == NULL) { + return; + } + + clazz.reset(env->FindClass("java/util/ArrayList")); + if (clazz.get() == NULL) { + return; + } + fields.arrayListClazz = (jclass) env->NewGlobalRef(clazz.get()); + if (fields.arrayListClazz == NULL) { + return; + } + fields.arrayListInit = env->GetMethodID(clazz.get(), "", "()V"); + if (fields.arrayListInit == NULL) { + return; + } + fields.arrayListAdd = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z"); + if (fields.arrayListAdd == NULL) { + return; + } } static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz) @@ -556,17 +650,36 @@ static const JNINativeMethod nativeMethods[] = { (void *)android_media_MediaMetadataRetriever_setDataSourceAndHeaders }, - {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, - {"_setDataSource", "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback}, - {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime}, - {"_getImageAtIndex", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getImageAtIndex}, - {"_getFrameAtIndex", "(II)[Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtIndex}, - {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata}, - {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture}, - {"release", "()V", (void *)android_media_MediaMetadataRetriever_release}, - {"native_finalize", "()V", (void *)android_media_MediaMetadataRetriever_native_finalize}, - {"native_setup", "()V", (void *)android_media_MediaMetadataRetriever_native_setup}, - {"native_init", "()V", (void *)android_media_MediaMetadataRetriever_native_init}, + {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", + (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, + {"_setDataSource", "(Landroid/media/MediaDataSource;)V", + (void *)android_media_MediaMetadataRetriever_setDataSourceCallback}, + {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", + (void *)android_media_MediaMetadataRetriever_getFrameAtTime}, + { + "_getImageAtIndex", + "(ILandroid/media/MediaMetadataRetriever$BitmapParams;)Landroid/graphics/Bitmap;", + (void *)android_media_MediaMetadataRetriever_getImageAtIndex + }, + + { + "_getFrameAtIndex", + "(IILandroid/media/MediaMetadataRetriever$BitmapParams;)Ljava/util/List;", + (void *)android_media_MediaMetadataRetriever_getFrameAtIndex + }, + + {"extractMetadata", "(I)Ljava/lang/String;", + (void *)android_media_MediaMetadataRetriever_extractMetadata}, + {"getEmbeddedPicture", "(I)[B", + (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture}, + {"release", "()V", + (void *)android_media_MediaMetadataRetriever_release}, + {"native_finalize", "()V", + (void *)android_media_MediaMetadataRetriever_native_finalize}, + {"native_setup", "()V", + (void *)android_media_MediaMetadataRetriever_native_setup}, + {"native_init", "()V", + (void *)android_media_MediaMetadataRetriever_native_init}, }; // This function only registers the native methods, and is called from