【发布时间】:2013-02-19 22:13:08
【问题描述】:
我使用 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));
【问题讨论】:
-
使
setDataSource返回上下文指针的long表示,然后在对extractMetadata的后续调用中要求该指针。完成后,您将需要一个额外的 dispose 函数来摆脱上下文。以一种对MediaMetadataRetriever的消费者隐藏它的方式包装该处理可能也是有意义的,并且可能让 GC 通过终结器处理处理。 -
不错的主意,我曾考虑过这一点,但谷歌如何能够在不这样做的情况下处理多个实例?看来他们的解决方案驻留在本机 (C) 代码中。
-
除非您以某种方式将上下文指针附加到
MediaMetadataRetriever对象(作为字段或一些hackery),否则您必须将上下文 somewhere 存储在本机代码中.您必须维护检索器对象和上下文指针之间的映射;你可以通过某种映射对象、pthread locals 或其他东西来做到这一点——不幸的是,C 没有内置的映射对象。 -
AOSP 代码通常通过将 C 指针放入 Java 整数字段中来将 C 语言对象与其 Java 语言对应物相关联,然后在 Java 语言对象被 GC 时使用终结器进行清理。大部分代码使用
int而不是long,在指针变为64 位之前,这一切都很有趣和游戏。 -
感谢您的反馈。您或 technomage 可以为我提供一个工作示例的链接吗?我仍在学习如何使用 JNI,所以我还没有完全掌握如何存储和重用指针。再次感谢。
标签: android ffmpeg java-native-interface