Home » Android » FFmpeg: seeking not possible with file descriptor on Android Q

FFmpeg: seeking not possible with file descriptor on Android Q

Posted by: admin May 14, 2020 Leave a comment

Questions:

Given the fact that public file paths will generally not be available in Android Q with scoped storage, I am attempting to figure out how to make my FFmpeg audio decoder work with file descriptors, without copying the file to my app’s private directories.

We can easily get a file descriptor using the methods described in Android Q privacy changes, and it is possible to open the file descriptor using the pipe protocol as described in Passing a native fd int to FFMPEG from openable URI. However, the result is not seekable using av_seek_frame and also the duration is not available using the duration member of AVFormatContext.

Is there way to seek with a file descriptor with FFmpeg and retrieve the duration?

How to&Answers:

it is possible to open the file descriptor using the pipe protocol as described

I’m curious why it is necessary opening file descriptor via pipe protocol?
sView player opens file descriptor by custom AVIOContext, which is seekable, at least on older tested versions of Android. Here is a pseudo-code opening AVFormatContext with custom 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);

Below is an attempt to extract a simplified StAVIOFileContext class definition.

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