FFmpeg:在Android Q上无法使用文件描述符进行查找

13
考虑到在Android Q中,使用作用域存储(public file paths)通常不可用,我正试图找出如何使我的FFmpeg音频解码器使用文件描述符(file descriptors),而无需将文件复制到我的应用程序的私有目录。
我们可以使用Android Q privacy changes中描述的方法轻松地获取文件描述符,也可以按照Passing a native fd int to FFMPEG from openable URI中所述的方式使用管道协议打开文件描述符。但是,使用av_seek_frame无法进行搜索(seek),并且使用AVFormatContext的duration成员无法获得持续时间。
是否有办法使用FFmpeg进行文件描述符搜索和检索持续时间?

3
希望你能找到答案。上次我处理可寻址流的问题时,答案是“不行”。只有文件支持的流才能被定位。对于短内容,你可以将内容复制到本地文件然后播放,但我猜你想支持半随意长度的内容... - CommonsWare
很抱歉我的赏金并没有为您提供答案!如果您想到了解决方案,请告诉我!如果我们永久限制于存储访问框架、MediaStore等,这些小问题就需要在Android R中得到解决。 - CommonsWare
1
@CommonsWare 这个问题在 https://github.com/tanersener/mobile-ffmpeg/issues/334 中有讨论。受 gkv311 的回答 的启发,我添加了一个自定义的saf:协议来正确处理这种访问方式。 - Alex Cohn
1
@AlexCohn:非常酷!感谢你指出来! - CommonsWare
@CommonsWare:仍在进行中 - Alex Cohn
显示剩余8条评论
2个回答

4

使用管道协议可以打开文件描述符,如上所述。

我很好奇为什么需要通过管道协议来打开文件描述符?sView播放器通过自定义的可寻址AVIOContext打开文件描述符,在较旧的已测试Android版本中是可寻址的。下面是使用自定义AVIOContext打开AVFormatContext的伪代码:

    int aFileDescriptor = myResMgr->openFileDescriptor(theFileToLoad);
    AVFormatContext* aFormatCtx = avformat_alloc_context();
    StAVIOContext myAvioContext;
    if(!myAvioContext.openFromDescriptor(aFileDescriptor, "rb")) {
       // error
    }

    aFormatCtx->pb = myAvioContext.getAvioContext();
    int avErrCode = avformat_open_input(&aFormatCtx, theFileToLoad, NULL, NULL);

以下是一个试图提取简化的 StAVIOFileContext 类定义的尝试。
//! Wrapper over AVIOContext for passing the custom I/O.
class StAVIOContext {
public:
  //! Main constructor.
  StAVIOContext() {
    const int aBufferSize = 32768;
    unsigned char* aBufferIO = (unsigned char* )av_malloc(aBufferSize + AV_INPUT_BUFFER_PADDING_SIZE);
    AVIOContext* myAvioCtx = avio_alloc_context (aBufferIO, aBufferSize, 0, this, readCallback, writeCallback, seekCallback);
  }

  //! Destructor.
  virtual ~StAVIOContext() {
    close();
    if (myAvioCtx != NULL) { av_free (myAvioCtx); }
  }

  //! Close the file.
  void close() {
    if(myFile != NULL) {
        fclose(myFile);
        myFile = NULL;
    }
  }

  //! Associate a stream with a file that was previously opened for low-level I/O.
  //! The associated file will be automatically closed on destruction.
  bool openFromDescriptor(int theFD, const char* theMode) {
    close();
  #ifdef _WIN32
    myFile = ::_fdopen(theFD, theMode);
  #else
    myFile =  ::fdopen(theFD, theMode);
  #endif
    return myFile != NULL;
  }

  //! Access AVIO context.
  AVIOContext* getAvioContext() const { return myAvioCtx; }

public:

  //! Virtual method for reading the data.
  virtual int read (uint8_t* theBuf,
                    int theBufSize) {
    if(myFile == NULL) { return -1; }

    int aNbRead = (int )::fread(theBuf, 1, theBufSize, myFile);
    if(aNbRead == 0 && feof(myFile) != 0) { return AVERROR_EOF; }
    return aNbRead;
  }

  //! Virtual method for writing the data.
  virtual int write (uint8_t* theBuf,
                     int theBufSize) {
    if(myFile == NULL) { return -1; }
    return (int )::fwrite(theBuf, 1, theBufSize, myFile);
  }

  //! Virtual method for seeking to new position.
  virtual int64_t seek (int64_t theOffset,
                        int theWhence) {
    if(theWhence == AVSEEK_SIZE || myFile == NULL) { return -1; }
  #ifdef _WIN32
    bool isOk = ::_fseeki64(myFile, theOffset, theWhence) == 0;
  #else
    bool isOk =    ::fseeko(myFile, theOffset, theWhence) == 0;
  #endif
    if(!isOk) { return -1; }
  #ifdef _WIN32
    return ::_ftelli64(myFile);
  #else
    return ::ftello(myFile);
  #endif
  }

private:
  //! Callback for reading the data.
  static int readCallback(void* theOpaque,
                          uint8_t* theBuf,
                          int  theBufSize) {
    return theOpaque != NULL
         ? ((StAVIOContext* )theOpaque)->read(theBuf, theBufSize)
         : 0;
  }

  //! Callback for writing the data.
  static int writeCallback(void* theOpaque,
                           uint8_t* theBuf,
                           int theBufSize) {
    return theOpaque != NULL
         ? ((StAVIOContext* )theOpaque)->write(theBuf, theBufSize)
         : 0;
  }

  //! Callback for seeking to new position.
  static int64_t seekCallback(void*   theOpaque,
                              int64_t theOffset,
                              int     theWhence) {
    return theOpaque != NULL
        ? ((StAVIOContext* )theOpaque)->seek(theOffset, theWhence)
        : -1;
  }

protected:
  AVIOContext* myAvioCtx;
  FILE* myFile;
};


感谢您的启发! - Alex Cohn

1

自定义协议 可以处理类似于 content://com.android.providers.downloads.documents/document/msf%3A62content://com.android.externalstorage.documents/document/primary%3ADownload%2Ftranscode.aac 的 Uri。

以下是打开此类 Uri 的 C 代码(为简洁起见,隐藏了错误检查):

int get_fd_from_content(const char *content, int access) {

    static jclass    android_net_Uri;
    static jmethodID android_net_Uri_parse = 0;
    static jmethodID android_content_Context_getContentResolver = 0;
    static jmethodID android_content_ContentResolver_openFileDescriptor = 0;
    static jmethodID android_os_ParcelFileDescriptor_getFd = 0;

    int fd = -1;

    JNIEnv *env;
    int ret = (*globalVm)->GetEnv(globalVm, (void **)&env, JNI_VERSION_1_6);

    android_net_Uri_parse = get_static_method_id(env, "android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", &android_net_Uri);
    android_content_Context_getContentResolver = get_method_id(env, "android/content/Context", "getContentResolver", "()Landroid/content/ContentResolver;");
    android_content_ContentResolver_openFileDescriptor = get_method_id(env, "android/content/ContentResolver", "openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;");
    android_os_ParcelFileDescriptor_getFd = get_method_id(env, "android/os/ParcelFileDescriptor", "getFd", "()I"));

    const char *fmode = "r";
    if (access & (O_WRONLY | O_RDWR)) {
        fmode = "w";
    }

    LOGI("get_fd_from_content" " \"%s\" fd from %s", fmode, content);

    jstring uriString = (*env)->NewStringUTF(env, content);
    jstring fmodeString = (*env)->NewStringUTF(env, fmode);
    jobject uri = (*env)->CallStaticObjectMethod(env, android_net_Uri, android_net_Uri_parse, uriString);
    jobject contentResolver = (*env)->CallObjectMethod(env, appContext, android_content_Context_getContentResolver);
    jobject parcelFileDescriptor = (*env)->CallObjectMethod(env, contentResolver, android_content_ContentResolver_openFileDescriptor, uri, fmodeString);

    fd = (*env)->CallIntMethod(env, parcelFileDescriptor, android_os_ParcelFileDescriptor_getFd);

    (*env)->DeleteLocalRef(env, uriString);
    (*env)->DeleteLocalRef(env, fmodeString);
    (*env)->DeleteLocalRef(env, uri);
    (*env)->DeleteLocalRef(env, contentResolver);
    (*env)->DeleteLocalRef(env, parcelFileDescriptor);

    return fd;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接