Android的使用JNI没有静态变量 [英] Android using JNI without static variables

查看:393
本文介绍了Android的使用JNI没有静态变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用源代码code为MediaMetadataRetriever.java为基础创建了自己的Andr​​oid的MediaMetadataRetriever版本。我的版本使用的FFmpeg检索的元数据。此方法适用但它在C code在JNI调用之间保持状态依赖于静态变量。这意味着我只能用这个类的一个实例在时间或状态可会损坏。两个Java函数定义如下:

 公共类MediaMetadataRetriever
{
    静态{
        的System.loadLibrary(metadata_retriever_jni);
    }    公共MediaMetadataRetriever(){    }    公共本土无效的setDataSource(字符串路径)抛出:IllegalArgumentException - ;
    公共本地字符串extractMetadata(字符串键);

}

相应的C(JNI)code code是:

 为const char * TAG =Java_com_example_metadataexample_MediaMetadataRetriever;
静态AVFormatContext * pFormatCtx = NULL;JNIEXPORT无效JNICALL
Java_com_example_metadataexample_MediaMetadataRetriever_setDataSource(JNIEnv的* ENV,JCLASS OBJ,的jstring jpath){    如果(pFormatCtx){
        avformat_close_input(安培; pFormatCtx);
    }    炭持续时间[30] =0;
    为const char * URI;    URI =(* ENV) - GT; GetStringUTFChars(ENV,jpath,NULL);    如果(avformat_open_input(安培;!pFormatCtx,URI,NULL,NULL)= 0){
        __android_log_write(ANDROID_LOG_INFO,TAG,元数据无法获取);
        (* ENV) - GT; ReleaseStringUTFChars(ENV,jpath,URI);
        jniThrowException(ENV,爪哇/郎/抛出:IllegalArgumentException,NULL);
        返回;
    }    如果(avformat_find_stream_info(pFormatCtx,NULL)℃,){
        __android_log_write(ANDROID_LOG_INFO,TAG,元数据无法获取);
        avformat_close_input(安培; pFormatCtx);
        (* ENV) - GT; ReleaseStringUTFChars(ENV,jpath,URI);
        jniThrowException(ENV,爪哇/郎/抛出:IllegalArgumentException,NULL);
        返回;
    }    (* ENV) - GT; ReleaseStringUTFChars(ENV,jpath,URI);
}JNIEXPORT的jstring JNICALL
Java_com_example_metadataexample_MediaMetadataRetriever_extractMetadata(JNIEnv的* ENV,JCLASS OBJ,的jstring jkey){    为const char *键;
    的jstring值= NULL;    键=(* ENV) - GT; GetStringUTFChars(ENV,jkey,NULL);    如果(!pFormatCtx){
        转到失败;
    }    如果(键){
        如果(av_dict_get(pFormatCtx->的元数据,键,NULL,AV_DICT_IGNORE_SUFFIX)){
            值=(* ENV) - GT; NewStringUTF(ENV,av_dict_get(pFormatCtx->的元数据,键,NULL,AV_DICT_IGNORE_SUFFIX) - GT;值);
        }
    }    失败:
    (* ENV) - GT; ReleaseStringUTFChars(ENV,jkey,键);    返回值;
}

这概括了我的问题样品用法是:

  MediaMetadataRetriever MMR =新MediaMetadataRetriever();
mmr.setDataSource(one.mp3);MediaMetadataRetriever MMR2 =新MediaMetadataRetriever();
//该行复位数据源two.mp3
mmr2.setDataSource(two.mp3);//应从one.mp3检索艺术家,但它检索来自two.mp3由于静
//变量被重置在previous声明
串艺术家= mmr.extractMetadata(MediaMetadataRetriever.ARTIST);

有人能解释我是如何将这种结构code,所以我可以用MediaMetadataRetriever的多个实例,没有他们相互干扰?我不希望到code切换到C ++和我相当肯定,我不需要修改MediaMetadataRetriever.java因为这code被采用线换线从Android框架(它允许多个实例,见下面的例子)。看来我需要重新构造C code,但我不能确定如何留住跨州JNI调用,而不使用静态变量。先谢谢了。

 文件文件1 =新的文件(Environment.getExternalStorageDirectory(),音乐/ one.mp3);
档案文件2 =新的文件(Environment.getExternalStorageDirectory(),音乐/ two.mp3);android.media.MediaMetadataRetriever MMR =新android.media.MediaMetadataRetriever();
mmr.setDataSource(file1.toString());android.media.MediaMetadataRetriever MMR2 =新android.media.MediaMetadataRetriever();
mmr2.setDataSource(file2.toString());//返回one.mp3的艺术家,不是two.mp3,符合市场预期。这是预期的行为
//并确认MediaMetadataRetriever的多个实例可以同时使用
mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_ARTIST));


解决方案

体改code将是非常直截了当。使用 pFormatCtx ,而不是你只是延长了JNI的接口调用,所以你可以通过你存储在 pFormatCtx 围绕指针之前。现在的大guestion是如何绕过指针,而Java不知道这样的数据类型?最直接的soulution是使用整​​数(32位系统)或多头(64位系统),以传递指针和从Java环境。不幸的是,你可以,只要你库的64位和32位版本之间切换,让你在热水位。

当我试图解决这个问题几个月前,我在迷迷糊糊的 Clebert Suconic 。他指出,安全地传递指针通过JNI没有黑客类型转换围绕一个非常优雅的方式。相反,他建议使用 java.nio.ByteBuffer中

把果壳中的概念是:他建议创建长度为零的一个新的ByteBuffer对象: env-> NewDirectByteBuffer(myPointer,0); 并传递通过JNI导致jobject来回。

呼叫 env-> NewDirectByteBuffer(myPointer,0); 将创建一个指向你想绕过位置的imutable字节的缓冲区对象。该缓冲区imutable事实是完美的,因为你不希望修改存储位置,你只希望存储位置本身。你得到的是一个对象封装的指针,你可以离开的指针大小问题向JVM。

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

I created my own version of Android's MediaMetadataRetriever using the source code for MediaMetadataRetriever.java as the basis. My version uses FFmpeg to retrieve the metadata. This approach works however it relies on static variables in the C code to retain state in between JNI calls. This means I can only use one instance of this class at a time or the state can get corrupted. The two Java functions are defined as follows:

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);

}

The corresponding C (JNI) code code is:

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;
}

Sample usage that outlines my issue would be:

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);

Can someone explain how I would structure this code so I could use multiple instances of MediaMetadataRetriever without them interfering with one another? I don't want to switch the code to C++ and I'm fairly certain I don't need to modify MediaMetadataRetriever.java since this code is taken line-for-line from the Android framework (which allows multiple instances, see example below). It appears I need to re-structure the C code but I'm unsure how to retain state across JNI calls without using a static variable. Thanks in advance.

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));

解决方案

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);.

这篇关于Android的使用JNI没有静态变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆