我使用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));
使'setDataSource'返回上下文指針的'long'表示,然後在隨後調用'extractMetadata'時需要該指針。完成後,您需要額外的處理功能來擺脫上下文。也許還有意義的是將這種處理方式隱藏起來,以便將其從MediaMetadataRetriever的使用者中隱藏起來,並且可能讓GC通過終結器處理丟棄。 – technomage 2013-02-19 22:18:05
不是一個壞主意,我曾考慮過這個問題,但是Google怎麼能夠處理多個實例而沒有這樣做呢?看起來他們的解決方案駐留在本地(C)代碼中。 – 2013-02-19 22:27:41
除非你以某種方式將上下文指針附加到'MediaMetadataRetriever'對象(作爲一個字段或一些hackery),否則你必須在本地代碼中存儲上下文_somewhere_。您必須維護檢索器對象和上下文指針之間的映射;你可以通過某種映射對象,pthread locals或其他東西來實現 - 不幸的是C沒有內置的映射對象。 – technomage 2013-02-19 23:39:20