2

MediaMetadataRetriever.java のソース コードをベースとして、Android の MediaMetadataRetriever の独自のバージョンを作成しました。私のバージョンでは、FFmpeg を使用してメタデータを取得しています。このアプローチは機能しますが、JNI 呼び出し間で状態を保持するために C コードの静的変数に依存しています。これは、一度にこのクラスのインスタンスを 1 つしか使用できないことを意味します。そうしないと、状態が破損する可能性があります。2 つの 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++ に切り替えたくありません。このコードは Android フレームワークから行ごとに取得されるため、MediaMetadataRetriever.java を変更する必要はないと確信しています (複数のインスタンスを許可します。以下の例を参照してください)。 )。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));
4

2 に答える 2

3

コードの再構築は非常に簡単です。を使用する代わりにpFormatCtx、JNI 呼び出しのインターフェイスを拡張するだけで、以前に格納したポインターを渡すことができますpFormatCtx。Javaがそのようなデータ型を知らない間にポインターを渡す方法が大きな問題です。最も簡単な解決策は、int (32 ビット システムの場合) または long (64 ビット システムの場合) を使用して、Java 環境との間でポインターを渡すことです。残念なことに、ライブラリの 64 ビット バージョンと 32 ビット バージョンを切り替えるとすぐに、ちょっとした問題が発生する可能性があります。

数か月前にこの問題を解決しようとしていたときに、Clebert Suconicの記事に出くわしました。彼は、型キャストで「ハッキング」することなく、JNI を介してポインタを安全に渡すための非常に洗練された方法を指摘しました。代わりに、彼は を使用することを提案していますjava.nio.ByteBuffer

一言で言えば、概念は次のとおりです。彼は、長さゼロの新しい ByteBuffer オブジェクトを作成しenv->NewDirectByteBuffer(myPointer, 0);、結果のジョブジェクトを JNI を介してやり取りすることを提案しています。

この呼び出しenv->NewDirectByteBuffer(myPointer, 0);により、受け渡したい場所を指す不変のバイト バッファー オブジェクトが作成されます。メモリの場所を変更したくないので、バッファが不変であるという事実は完璧です。場所自体を保存したいだけです。得られるのはポインタをカプセル化したオブジェクトであり、ポインタ サイズの問題は JVM に任せることができます。

編集:完全を期すために: ポインタは後で呼び出して取得できますenv->GetDirectBufferAddress(myPointer);

于 2013-02-21T15:53:38.503 に答える
0

long フィールド (32 ビット システムしか使用しないことがわかっている場合は int) を追加して、ネイティブ ポインターを格納し、オブジェクトが 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 メソッドの呼び出しと同様に、2 段階のプロセスを使用してフィールド ルックアップのコストを計算に入れます。フィールド ID は、特定のクラスのフィールドを一意に識別します。メソッド ID と同様に、フィールド ID は、派生元のクラスがアンロードされるまで有効です。フィールド署名

フィールド シグネチャは、メソッド シグネチャと同じコード化スキームに従って指定されます。フィールド署名の一般的な形式は次のとおりです。

"field type"

フィールド署名は、二重引用符 ("") で囲まれた、フィールドのタイプのエンコードされたシンボルです。フィールド シンボルは、メソッド シグネチャの引数シンボルと同じです。つまり、「I」で整数フィールド、「F」で浮動小数点フィールド、「D」で倍精度フィールド、「Z」でブール値フィールドなどを表します。

String などの Java オブジェクトの署名は、文字 L で始まり、オブジェクトの完全修飾クラスが続き、セミコロン (;) で終了します。したがって、次のように String 変数 (FieldAccess.java の cs) のフィールド署名を形成します。

"Ljava/lang/String;"

配列は、先頭の角括弧 ([) とそれに続く配列の型で示されます。たとえば、次のように整数配列を指定します。

"[I"

前のセクションの表を参照してください。これは、Java 型シグネチャのエンコーディングと、それらに一致する Java 型をまとめたものです。

オプション「-s」を指定して javap を使用すると、クラス ファイルからフィールド シグネチャを生成できます。たとえば、次を実行します。

> javap -s -p FieldAccess

これにより、以下を含む出力が得られます。

... 
static si I 
s Ljava/lang/String; 
...
于 2013-02-21T12:58:11.927 に答える