Android 使用 JNI 而不使用静态变量

标签 android ffmpeg java-native-interface

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

最佳答案

重构代码将非常简单。您无需使用 pFormatCtx,只需扩展 JNI 调用的接口(interface),以便可以传递之前存储在 pFormatCtx 中的指针。现在最大的问题是当java不知道这样的数据类型时如何传递指针?最直接的解决方案是使用 int(对于 32 位系统)或 long(对于 64 位系统)来向 Java 环境传递指针或从 Java 环境传递指针。不幸的是,一旦您在 64 位和 32 位版本的库之间切换,您可能会遇到麻烦。

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

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

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

编辑:只是为了完整性:稍后可以调用 env->GetDirectBufferAddress(myPointer); 检索指针。

关于Android 使用 JNI 而不使用静态变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14968547/

相关文章:

Android 下载管理器类 : getUriForDownloadedFile return wrong path

iphone - 为 IOS 编译 ffmpeg(R_ABS reloc 错误)

c++ - 如何在 C++ 类中使用 JNI registerNatives?

适用于 Windows 7 的 Java native 库、printf

android - Android 7.0(牛轧糖)上的 Activity 转换动画中的 Z 顺序发生了变化?

android - 动画不流畅

android - 通过套接字流式传输视频并从客户端播放

ffmpeg绘制文本在一个命令中覆盖电影

video - 将 mkv 转换为 h264 FFmpeg

java - (将 c# 转换为 Java JNA) - 从 hwnd GetModuleFileName