FFmpeg: поиск невозможен с файловым дескриптором на Android Q

Учитывая тот факт, что общедоступные пути к файлам, как правило, недоступны в Android Q с ограниченным хранилищем, я пытаюсь выяснить, как заставить мой аудиодекодер FFmpeg работать с файловыми дескрипторами, не копируя файл в частные каталоги моего приложения.

Мы можем легко получить дескриптор файла, используя методы, описанные в разделе изменения конфиденциальности Android Q. , и можно открыть файловый дескриптор, используя канальный протокол, как описано в Передача собственного fd int в FFMPEG из открываемого URI. Однако результат не доступен для поиска с использованием av_seek_frame, а также продолжительность недоступна с использованием члена продолжительности AVFormatContext.

Есть ли способ найти дескриптор файла с помощью FFmpeg и получить продолжительность?


person Steve M    schedule 10.08.2019    source источник
comment
Я надеюсь, что есть ответ для вас. В последний раз, когда я боролся с проблемой поисковых потоков, ответ был отрицательным. Только потоки с файловой поддержкой были доступны для поиска. Для короткого контента вы можете скопировать контент в какой-нибудь локальный файл, а затем воспроизвести его, но я предполагаю, что вы хотите поддерживать контент полупроизвольной длины...   -  person CommonsWare    schedule 10.08.2019
comment
@CommonsWare да, копирование мне не подходит. Я предполагаю, что мой ответ будет заключаться в использовании другого декодера, такого как платформа MediaCodec, но это тоже не идеально для меня.   -  person Steve M    schedule 11.08.2019
comment
Префикс cache: к URL канала, который вы открываете и проверяете. cache попытается создать временный файл в /tmp, если доступен mkstemp, или в pwd, если нет.   -  person Gyan    schedule 11.08.2019
comment
Извините, моя награда не принесла вам ответа! Если вы думаете об этом, и если вы найдете решение, дайте мне знать, что это такое! Такого рода сбои — это то, что нам нужно решить в Android R, если мы будем постоянно ограничены Storage Access Framework, MediaStore и т. д.   -  person CommonsWare    schedule 21.08.2019
comment
@CommonsWare это обсуждается на github.com/tanersener/mobile-ffmpeg/issues/334< /а>. Вдохновленный ответом gkv311, я добавил собственный протокол saf: для обработки такого рода получить доступ правильно.   -  person Alex Cohn    schedule 30.05.2020
comment
@AlexCohn: Очень круто! Спасибо, что указали на это!   -  person CommonsWare    schedule 30.05.2020
comment
@CommonsWare: все еще WIP   -  person Alex Cohn    schedule 30.05.2020
comment
Кажется, эта проблема больше не вызывает особого беспокойства, поскольку мы можем снова использовать столбец _data из MediaStore и использовать путь к файлу для FFmpeg, если захотим, в Android R.   -  person Steve M    schedule 30.05.2020
comment
@SteveM, прямой доступ к пути работает в последней версии эмулятора R (например, к /sdcard/Movies/test.avi, но не к файлам, которые не находятся в общедоступных каталогах). Следовательно, при использовании с настраиваемым поставщиком (например, для типа файла, который MediaStore не поддерживает), этот saf: все еще может быть полезен. …и никто не собирается выбрасывать все эти устройства с Android 10, и лишь немногие из них получат своевременное OTA-обновление.   -  person Alex Cohn    schedule 04.06.2020
comment
@CommonsWare еще лучше: чистый способ C для обработки content: Uri   -  person Alex Cohn    schedule 11.06.2020
comment
@AlexCohn, возможно, полезен для пользовательского провайдера. Но мы всегда можем использовать android:requestLegacyExternalStorage="true" для устройств Android 10 и без проблем использовать путь к файлу.   -  person Steve M    schedule 12.06.2020
comment
@SteveM: они говорят нам, что работа с путями может быть намного медленнее.   -  person Alex Cohn    schedule 05.08.2020


Ответы (2)


можно открыть дескриптор файла, используя протокол канала, как описано

Мне любопытно, почему необходимо открывать дескриптор файла через протокол канала? проигрыватель sView открывает дескриптор файла с помощью пользовательского AVIOContext, который доступен для поиска, по крайней мере на более старых протестированных версиях Android. Вот псевдокод, открывающий AVFormatContext с пользовательским AVIOContext.

    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;
};

person gkv311    schedule 16.03.2020
comment
Спасибо за вдохновение! - person Alex Cohn; 30.05.2020

пользовательский протокол может обрабатывать Uri как content://com.android.providers.downloads.documents/document/msf%3A62 или content://com.android.externalstorage.documents/document/primary%3ADownload%2Ftranscode.aac

Вот C код, открывающий такой Uri (проверки ошибок скрыты для краткости):

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;
}
person Alex Cohn    schedule 11.06.2020