2013-02-19 24 views
2

我使用MediaMetadataRetriever.java的源代碼作爲基礎,創建了自己的Android版MediaMetadataRetriever。我的版本使用FFmpeg來檢索元數據。這種方法可行,但它依賴於C代碼中的靜態變量來保持JNI調用之間的狀態。這意味着我一次只能使用這個類的一個實例,或者狀態可能會被損壞。這兩個Java函數定義如下:使用JNI而不使用靜態變量的Android

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

有人可以解釋我將如何構造此代碼,所以我可以使用MediaMetada的多個實例taRetriever沒有他們互相干擾?我不想將代碼切換到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)); 
+0

使'setDataSource'返回上下文指針的'long'表示,然後在隨後調用'extractMetadata'時需要該指針。完成後,您需要額外的處理功能來擺脫上下文。也許還有意義的是將這種處理方式隱藏起來,以便將其從MediaMetadataRetriever的使用者中隱藏起來,並且可能讓GC通過終結器處理丟棄。 – technomage 2013-02-19 22:18:05

+0

不是一個壞主意,我曾考慮過這個問題,但是Google怎麼能夠處理多個實例而沒有這樣做呢?看起來他們的解決方案駐留在本地(C)代碼中。 – 2013-02-19 22:27:41

+0

除非你以某種方式將上下文指針附加到'MediaMetadataRetriever'對象(作爲一個字段或一些hackery),否則你必須在本地代碼中存儲上下文_somewhere_。您必須維護檢索器對象和上下文指針之間的映射;你可以通過某種映射對象,pthread locals或其他東西來實現 - 不幸的是C沒有內置的映射對象。 – technomage 2013-02-19 23:39:20

回答

3

重組的代碼將是非常直截了當。您只需擴展JNI調用的接口,而不是使用pFormatCtx,以便您可以傳遞之前存儲在pFormatCtx中的指針。現在最大的麻煩是如何傳遞指針,而java不知道這樣的數據類型?最直接的方法是使用int(32位系統)或long(64位系統)來傳遞指向Java環境的指針。不幸的是,只要在庫和庫的64位和32位版本之間切換,就可以在熱水中找到一點。

幾個月前我試圖解決這個問題時,我偶然發現了一篇文章Clebert Suconic。他指出了一種非常優雅的方式,通過JNI安全地傳遞指針,而不用通過類型轉換來「竊取」。相反,他建議使用java.nio.ByteBuffer

概括地說:他建議創建一個長度爲零的新ByteBuffer對象:env->NewDirectByteBuffer(myPointer, 0);並將結果jobject通過JNI來回傳遞。

呼叫env->NewDirectByteBuffer(myPointer, 0);創建一個不可變的字節緩衝區對象,指向您想要傳遞的位置。緩衝區不可變的事實是完美的,因爲您不想修改內存位置,您只需要存儲位置本身。你得到的是封裝指針的對象,你可以將指針大小問題留給JVM。

編輯:只是爲了完整:稍後可以調用指針env->GetDirectBufferAddress(myPointer);

+0

這工作,謝謝。 – 2013-03-04 21:11:15

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,接着該對象的完全合格的類,和 用分號(;)結束。因此,你形成場簽名 String變量(CS在FieldAccess.java)如下:

"Ljava/lang/String;"

數組由隨後數組的類型的領先方括號 ([])表示。例如,你指定一個 整數數組如下:

"[I"

請參考先前的部分中的表,該表總結了 編碼的Java類型簽名和它們匹配的Java類型。

您可以使用帶選項「-s」的javap從類文件生成字段簽名 。例如,運行:

> javap -s -p FieldAccess

這給你包含輸出:

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