【问题标题】:Android using JNI without static variablesAndroid 使用没有静态变量的 JNI
【发布时间】:2013-02-19 22:13:08
【问题描述】:

我使用 MediaMetadataRetriever.java 的源代码作为基础创建了我自己的 Android 版本的 MediaMetadataRetriever。我的版本使用 FFmpeg 来检索元数据。这种方法有效,但是它依赖于 C 代码中的静态变量来保留 JNI 调用之间的状态。这意味着我一次只能使用此类的一个实例,否则状态可能会损坏。这两个Java函数定义如下:

public class MediaMetadataRetriever
{
    static {
        System.loadLibrary("metadata_retriever_jni");
    }

    public MediaMetadataRetriever() {

    }

    public native void setDataSource(String path) throws IllegalArgumentException;
    public native String extractMetadata(String key);

}

对应的C(JNI)代码代码为:

const char *TAG = "Java_com_example_metadataexample_MediaMetadataRetriever";
static AVFormatContext *pFormatCtx = NULL;

JNIEXPORT void JNICALL
Java_com_example_metadataexample_MediaMetadataRetriever_setDataSource(JNIEnv *env, jclass obj, jstring jpath) {

    if (pFormatCtx) {
        avformat_close_input(&pFormatCtx);
    }

    char duration[30] = "0";
    const char *uri;

    uri = (*env)->GetStringUTFChars(env, jpath, NULL);

    if (avformat_open_input(&pFormatCtx, uri, NULL, NULL) != 0) {
        __android_log_write(ANDROID_LOG_INFO, TAG, "Metadata could not be retrieved");
        (*env)->ReleaseStringUTFChars(env, jpath, uri);
        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
        return;
    }

    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        __android_log_write(ANDROID_LOG_INFO, TAG, "Metadata could not be retrieved");
        avformat_close_input(&pFormatCtx);
        (*env)->ReleaseStringUTFChars(env, jpath, uri);
        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
        return;
    }

    (*env)->ReleaseStringUTFChars(env, jpath, uri);
}

JNIEXPORT jstring JNICALL
Java_com_example_metadataexample_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jclass obj, jstring jkey) {

    const char *key;
    jstring value = NULL;

    key = (*env)->GetStringUTFChars(env, jkey, NULL) ;

    if (!pFormatCtx) {
        goto fail;
    }

    if (key) {
        if (av_dict_get(pFormatCtx->metadata, key, NULL, AV_DICT_IGNORE_SUFFIX)) {
            value = (*env)->NewStringUTF(env, av_dict_get(pFormatCtx->metadata, key, NULL, AV_DICT_IGNORE_SUFFIX)->value);
        }
    }

    fail:
    (*env)->ReleaseStringUTFChars(env, jkey, key);

    return value;
}

概述我的问题的示例用法是:

MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource("one.mp3");

MediaMetadataRetriever mmr2 = new MediaMetadataRetriever();
// This line resets the data source to two.mp3
mmr2.setDataSource("two.mp3");

// should retrieve the artist from one.mp3 but retrieves it from two.mp3 due to the static
// variable being reset in the previous statement       
String artist = mmr.extractMetadata(MediaMetadataRetriever.ARTIST);

谁能解释我将如何构造这段代码,以便我可以使用 MediaMetadataRetriever 的多个实例而不会相互干扰?我不想将代码切换到 C++,而且我相当确定我不需要修改 MediaMetadataRetriever.java,因为此代码是从 Android 框架中逐行获取的(它允许多个实例,请参见下面的示例)。看来我需要重新构造 C 代码,但我不确定如何在不使用静态变量的情况下跨 JNI 调用保留状态。提前致谢。

File file1 = new File(Environment.getExternalStorageDirectory(), "Music/one.mp3");
File file2 = new File(Environment.getExternalStorageDirectory(), "Music/two.mp3");

android.media.MediaMetadataRetriever mmr = new android.media.MediaMetadataRetriever();
mmr.setDataSource(file1.toString());

android.media.MediaMetadataRetriever mmr2 = new android.media.MediaMetadataRetriever();
mmr2.setDataSource(file2.toString());

// Returns the artist of one.mp3, not two.mp3, as expected. This is the expected behavior
// and confirms that multiple instances of MediaMetadataRetriever can be used simultaneously
mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_ARTIST));

【问题讨论】:

  • 使setDataSource 返回上下文指针的long 表示,然后在对extractMetadata 的后续调用中要求该指针。完成后,您将需要一个额外的 dispose 函数来摆脱上下文。以一种对MediaMetadataRetriever 的消费者隐藏它的方式包装该处理可能也是有意义的,并且可能让 GC 通过终结器处理处理。
  • 不错的主意,我曾考虑过这一点,但谷歌如何能够在不这样做的情况下处理多个实例?看来他们的解决方案驻留在本机 (C) 代码中。
  • 除非您以某种方式将上下文指针附加到 MediaMetadataRetriever 对象(作为字段或一些hackery),否则您必须将上下文 somewhere 存储在本机代码中.您必须维护检索器对象和上下文指针之间的映射;你可以通过某种映射对象、pthread locals 或其他东西来做到这一点——不幸的是,C 没有内置的映射对象。
  • AOSP 代码通常通过将 C 指针放入 Java 整数字段中来将 C 语言对象与其 Java 语言对应物相关联,然后在 Java 语言对象被 GC 时使用终结器进行清理。大部分代码使用int 而不是long,在指针变为64 位之前,这一切都很有趣和游戏。
  • 感谢您的反馈。您或 technomage 可以为我提供一个工作示例的链接吗?我仍在学习如何使用 JNI,所以我还没有完全掌握如何存储和重用指针。再次感谢。

标签: android ffmpeg java-native-interface


【解决方案1】:

重构代码将非常简单。而不是使用pFormatCtx,您只需扩展JNI 调用的接口,这样您就可以传递您之前存储在pFormatCtx 中的指针。现在最大的问题是如何在 java 不知道这种数据类型的情况下传递指针?最直接的解决方案是使用整数(对于 32 位系统)或长整数(对于 64 位系统)来传递与 Java 环境之间的指针。不幸的是,当您在库的 64 位和 32 位版本之间切换时,您可能会陷入困境。

几个月前,当我试图解决这个问题时,我偶然发现了一篇Clebert Suconic 的文章。他指出了一种非常优雅的方式,可以通过 JNI 安全地传递指针,而无需“破解”类型转换。相反,他建议使用java.nio.ByteBuffer

简而言之,概念是:他建议创建一个长度为零的新 ByteBuffer 对象:env-&gt;NewDirectByteBuffer(myPointer, 0);,并通过 JNI 来回传递生成的 jobject。

调用env-&gt;NewDirectByteBuffer(myPointer, 0); 创建一个指向您想要传递的位置的不可变字节缓冲区对象。缓冲区是不可变的这一事实是完美的,因为您不想修改内存位置,您只想存储位置本身。您得到的是一个封装您的指针的对象,您可以将指针大小问题留给 JVM。

编辑:出于完整性考虑:稍后可以调用env-&gt;GetDirectBufferAddress(myPointer); 检索指针。

【讨论】:

    【解决方案2】:

    添加一个长字段(或 int,如果你知道你只会在 32 位系统上)来存储本机指针,并在对象被 GC 时将其丢弃在对象的终结器中。然后,您可以从 JNI 将“打开”资源时获得的指针保存到该字段。

    来自 JNI 手册:

    访问 Java 字段的过程

    要从本地方法获取和设置 Java 字段,您必须执行 以下:

    从该字段的类、名称和类型中获取该字段的标识符 签名。例如,在 FieldAccess.c 中,我们有:

    fid = (*env)-&gt;GetStaticFieldID(env, cls, "si", "I");

    和:

    fid = (*env)-&gt;GetFieldID(env, cls, "s", "Ljava/lang/String;");

    使用其中一种 几个 JNI 函数来获取或设置由 字段标识符。将类传递给适当的静态字段 访问功能。将对象传递给适当的实例字段 访问功能。例如,在 FieldAccess.c 中,我们有:

    si = (*env)-&gt;GetStaticIntField(env, cls, fid);

    和:

    jstr = (*env)-&gt;GetObjectField(env, obj, fid);

    类似于调用 Java 方法,我们使用两步计算字段查找的成本 过程。字段 ID 唯一标识给定类中的字段。 与方法 ID 类似,字段 ID 在类从 它派生的被卸载。字段签名

    字段签名按照相同的编码方案指定 方法签名。字段签名的一般形式是:

    "field type"

    字段签名是类型的编码符号 字段,用双引号 ("") 括起来。字段符号是 与方法签名中的参数符号相同。那是你 用“I”表示整数字段,用“F”表示浮点字段,用双精度表示 带有“D”的字段,带有“Z”的布尔字段,等等。

    Java 对象(如字符串)的签名以 字母 L,后跟对象的完全限定类,以及 以分号 (;) 结束。因此,您形成了字段签名 一个字符串变量(FieldAccess.java 中的 c.s)如下:

    "Ljava/lang/String;"

    数组由前导方括号表示 ([) 后跟数组的类型。例如,您指定一个 整数数组如下:

    "[I"

    请参阅上一节中的表格,该表格总结了 Java 类型签名及其匹配的 Java 类型的编码。

    您可以使用带有选项“-s”的 javap 来生成字段签名 从类文件。例如,运行:

    &gt; javap -s -p FieldAccess

    这会为您提供包含以下内容的输出:

    ... 
    static si I 
    s Ljava/lang/String; 
    ...
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多