2

我使用 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));
4

2 回答 2

3

Restructuring the code would be very straight forward. Instead of using pFormatCtx you simply extend the interfaces of the JNI calls so you can pass around the pointer you stored in pFormatCtx before. Now the big guestion is how to pass around the pointers while java doesn't know such a datatype? The most straight forward soulution would be to use ints (for 32 bit systems) or longs (for 64 bit systems) to passing pointers to and from the Java environment. Unfortunately you could get you a bit in hot water as soon as you switch between 64 and 32 bit versions of your library.

While I was trying to solve this problem some months ago I stumbled over an article of Clebert Suconic. He pointed out a very elegant way for passing pointers safely through JNI without "hacking" around with typecasting. Instead he proposes to use java.nio.ByteBuffer.

The concept put in a nutshell is: He suggest to create a new ByteBuffer object of length zero: env->NewDirectByteBuffer(myPointer, 0); and pass the resulting jobject through the JNI back and forth.

The call env->NewDirectByteBuffer(myPointer, 0); creates a imutable byte buffer object pointing to the location you wanted to pass around. The fact that the buffer is imutable is perfect as you don't want to modify the memory location, you only want to store the location itself. What you get is an object encapsulating your pointer and you can leave the pointer size issues to the JVM.

Edit: Just for completeness: The pointer can later be retrieved calling env->GetDirectBufferAddress(myPointer);.

于 2013-02-21T15:53:38.503 回答
0

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

从 JNI 手册:

访问 Java 字段的过程

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

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

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

和:

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

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

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

和:

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

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

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

"field type"

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

Java 对象(如字符串)的签名以字母 L 开头,后跟对象的完全限定类,并以分号 (;) 结束。因此,您可以为字符串变量(FieldAccess.java 中的 cs)形成字段签名,如下所示:

"Ljava/lang/String;"

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

"[I"

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

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

> javap -s -p FieldAccess

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

... 
static si I 
s Ljava/lang/String; 
...
于 2013-02-21T12:58:11.927 回答