How to properly pass an asset FileDescriptor to FFmpeg using JNI in Android

后端 未结 3 1040
梦谈多话
梦谈多话 2020-12-05 16:44

I\'m trying to retrieve metadata in Android using FFmpeg, JNI and a Java FileDescriptor and it isn\'t\' working. I know FFmpeg supports the pipe protocol so I\'m trying to e

相关标签:
3条回答
  • 2020-12-05 17:12
     FileDescriptor fd = getContext().getAssets().openFd("test.mp3").getFileDescriptor();
    

    Think you should start with AssetFileDescripter. http://developer.android.com/reference/android/content/res/AssetFileDescriptor.html

    0 讨论(0)
  • 2020-12-05 17:23

    Thks a lot for this post. That help me a lot to integrate Android 10 and scoped storage with FFmpeg using FileDescriptor.

    Here the solution I'm using on Android 10:

    Java

    URI uri = ContentUris.withAppendedId(
       MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
       trackId // Coming from `MediaStore.Audio.Media._ID`
    );
    ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(
       uri,
       "r"
    );
    int pid = android.os.Process.myPid();
    String path = "/proc/" + pid + "/fd/" + parcelFileDescriptor.dup().getFd();
    loadFFmpeg(path); // Call native code
    

    CPP

    // Native code, `path` coming from Java `loadFFmpeg(String)`
    avformat_open_input(&format, path, nullptr, nullptr);
    
    0 讨论(0)
  • 2020-12-05 17:39

    Java

    AssetFileDescriptor afd = getContext().getAssets().openFd("test.mp3");
    setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), fd.getLength());
    

    C

    void ***_setDataSource(JNIEnv *env, jobject thiz, 
        jobject fileDescriptor, jlong offset, jlong length)
    {
        int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    
        char path[20];
        sprintf(path, "pipe:%d", fd);
    
        State *state = av_mallocz(sizeof(State));
        state->pFormatCtx =  avformat_alloc_context();
        state->pFormatCtx->skip_initial_bytes = offset;
        state->pFormatCtx->iformat = av_find_input_format("mp3");
    

    and now we can continue as usual:

    if (avformat_open_input(&state->pFormatCtx, path, NULL, &options) != 0) {
        printf("Metadata could not be retrieved\n");
        *ps = NULL;
        return FAILURE;
    }
    ...
    

    Even better, use <android/asset_manager.h>, like this:

    Java

    setDataSource(getContext().getAssets(), "test.mp3");
    

    C

    #include <android/asset_manager_jni.h>
    
    void ***_setDataSource(JNIEnv *env, jobject thiz, 
        jobject assetManager, jstring assetName)
    {
        AAssetManager* assetManager = AAssetManager_fromJava(env, assetManager);
        const char *szAssetName = (*env)->GetStringUTFChars(env, assetName, NULL);
        AAsset* asset = AAssetManager_open(assetManager, szAssetName, AASSET_MODE_RANDOM);
        (*env)->ReleaseStringUTFChars(env, assetName, szAssetName);
        off_t offset, length;
        int fd = AAsset_openFileDescriptor(asset, &offset, &length);
        AAsset_close(asset);
    

    Disclaimer: error checking was omitted for brevity, but resources are released correctly, except for fd. You must close(fd) when finished.

    Post Scriptum: note that some media formats, e.g. mp4 need seekable protocol, and pipe: cannot help. In such case, you may try sprintf(path, "/proc/self/fd/%d", fd);, or use the custom saf: protocol.

    0 讨论(0)
提交回复
热议问题