Android NDK: как заменить AAsset для работы с файлами из внешнего хранилища для декодирования FFmpeg - PullRequest
0 голосов
/ 08 января 2020

Я использую Пример Oboe RhytmGame в качестве руководства для реализации декодирования гобоя и mp3 с использованием FFmpeg в моем приложении. Так как я довольно новичок в NDK и новичке в C ++, я все еще борюсь с некоторыми базовыми понятиями c, с которыми я сталкиваюсь. Моя проблема: в приведенном выше примере обрабатываются только файлы из папки ресурсов, используя собственную реализацию AssetManager Android. Поскольку я ищу доступ к файлам на внешнем хранилище, я должен изменить это, но мне неясно, как это сделать.

Вот где я застрял: у меня есть класс FFmpegExtractor, который вызывает этот метод в FFmpeg's avio.h:

 * Allocate and initialize an AVIOContext for buffered I/O. It must be later
 * freed with avio_context_free().
 *
 * @param buffer Memory block for input/output operations via AVIOContext.
 *        The buffer must be allocated with av_malloc() and friends.
 *        It may be freed and replaced with a new buffer by libavformat.
 *        AVIOContext.buffer holds the buffer currently in use,
 *        which must be later freed with av_free().
 * @param buffer_size The buffer size is very important for performance.
 *        For protocols with fixed blocksize it should be set to this blocksize.
 *        For others a typical size is a cache page, e.g. 4kb.
 * @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
 * @param opaque An opaque pointer to user-specific data.
 * @param read_packet  A function for refilling the buffer, may be NULL.
 *                     For stream protocols, must never return 0 but rather
 *                     a proper AVERROR code.
 * @param write_packet A function for writing the buffer contents, may be NULL.
 *        The function may not change the input buffers content.
 * @param seek A function for seeking to specified byte position, may be NULL.
 *
 * @return Allocated AVIOContext or NULL on failure.
 */
AVIOContext *avio_alloc_context(
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence));

Вызов сделан здесь:

bool FFMpegExtractor::createAVIOContext(AAsset *asset, uint8_t *buffer, uint32_t bufferSize,
                                        AVIOContext **avioContext) {

    constexpr int isBufferWriteable = 0;

    *avioContext = avio_alloc_context(
            buffer, // internal buffer for FFmpeg to use
            bufferSize, // For optimal decoding speed this should be the protocol block size
            isBufferWriteable,
            asset, // Will be passed to our callback functions as a (void *)
            read, // Read callback function
            nullptr, // Write callback function (not used)
            seek); // Seek callback function

    if (*avioContext == nullptr){
        LOGE("Failed to create AVIO context");
        return false;
    } else {
        return true;
    }
}

Я ищу заменить аргументы asset, read и seek так что я могу использовать файлы из хранилища вместо объектов AAsset.

Это обратный вызов read, переданный выше:

int read(void *opaque, uint8_t *buf, int buf_size) {

    auto asset = (AAsset *) opaque;
    int bytesRead = AAsset_read(asset, buf, (size_t)buf_size);
    return bytesRead;
}

И это обратный вызов seek:

int64_t seek(void *opaque, int64_t offset, int whence){

    auto asset = (AAsset*)opaque;

    // See https://www.ffmpeg.org/doxygen/3.0/avio_8h.html#a427ff2a881637b47ee7d7f9e368be63f
    if (whence == AVSEEK_SIZE) return AAsset_getLength(asset);
    if (AAsset_seek(asset, offset, whence) == -1){
        return -1;
    } else {
        return 0;
    }
}

Я пытался просто заменить AAsset на FILE, но, конечно, этого не происходит. Я знаю, как открывать и читать файлы, но мне неясно, что именно здесь ожидается, и как я могу преобразовать методы в AAsset в операции, которые возвращают нужные значения для файлов в хранилище. Кто-нибудь может направить меня в правильном направлении?

Редактировать: Поскольку это не могло поместиться там, вот блок кода, упомянутый в моем ответе на полезные комментарии @ BrianChen:

bool FFMpegExtractor::openAVFormatContext(AVFormatContext *avFormatContext) {

    int result = avformat_open_input(&avFormatContext,
                                     "", /* URL is left empty because we're providing our own I/O */
                                     nullptr /* AVInputFormat *fmt */,
                                     nullptr /* AVDictionary **options */
    );

К сожалению avformat_open_input() производит
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x20 in tid 23767, pid 23513

1 Ответ

0 голосов
/ 10 января 2020

Это то же самое, что и использование AAsset.

// wrapper class for file stream 
class  MediaSource {
public:
    MediaSource() {
    }
    ~MediaSource() {
        source.close();
    }
    void open(const string& filePath) {
        source.open(filePath, ios::in | ios::binary);
    }
    int read(uint8_t *buffer, int buf_size) {
        // read data to buffer
        source.read((char *)buffer, buf_size)
        // return how many bytes were read
        return source.gcount();
    }
    int64_t seek(int64_t offset, int whence) {
        if (whence == AVSEEK_SIZE) {
            // FFmpeg needs file size.
            int oldPos = source.tellg();
            source.seekg(0,ios::end);
            int64_t length = source.tellg();
            // seek to old pos
            source.seekg(oldPos);
            return length;
        } else if (whence == SEEK_SET) {
            // set pos to offset
            source.seekg(offset);
        } else if (whence == SEEK_CUR) {
            // add offset to pos
            source.seekg(offset, ios::cur);
        } else {
            // do not support other flags, return -1
            return -1;
        }
        // return current pos
        return source.tellg();
    }
private:
    ifstream source;
};

// If FFmpeg needs to read the file, it will call this function.
// We need to fill the buffer with file's data.
int read(void *opaque, uint8_t *buffer, int buf_size) {
    MediaSource *source = (MediaSource *)opaque;
    return source->read(buffer, buf_size);
}

// If FFmpeg needs to seek in the file, it will call this function.
// We need to change the read pos.
int64_t seek(void *opaque, int64_t offset, int whence) {
    MediaSource *source = (MediaSource *)opaque;
    return source->seek(offset, whence);
}

// add MediaSource to class FFMpegExtractor
class FFMpegExtractor {
private:
    // add this line to declare of class FFMpegExtractor
    MediaSource* mSource;
};

FFMpegExtractor::FFMpegExtractor() {
    // add this line in constructor, new a instance
    mSource = new MediaSource;
}

FFMpegExtractor::~FFMpegExtractor() {
    // add this line in destructor, release instance
    delete mSource;
}

bool FFMpegExtractor::createAVIOContext(const string& filePath, uint8_t *buffer, uint32_t bufferSize,
                                        AVIOContext **avioContext) {

    mSource.open(filePath);
    constexpr int isBufferWriteable = 0;

    *avioContext = avio_alloc_context(
            buffer, // internal buffer for FFmpeg to use
            bufferSize, // For optimal decoding speed this should be the protocol block size
            isBufferWriteable,
            mSource, // Will be passed to our callback functions as a (void *)
            read, // Read callback function
            nullptr, // Write callback function (not used)
            seek); // Seek callback function

    if (*avioContext == nullptr){
        LOGE("Failed to create AVIO context");
        return false;
    } else {
        return true;
    }
}
...