FFMPEG Чтение аудио из памяти не работает

Когда я пытаюсь создать экземпляр этой структуры, моя программа падает:

struct MemoryAVFormat {
    MemoryAVFormat(const MemoryAVFormat &) = delete;

    AVFormatContext *ctx;
    AVIOContext *ioCtx;

    MemoryAVFormat(char *audio, size_t audio_length) :
            ctx(avformat_alloc_context()),
            ioCtx(create_audio_buffer_io_context(audio, audio_length)) {

        if (ctx == nullptr)
            throw audio_processing_exception("Failed to allocate context");

        if (ioCtx == nullptr)
            throw audio_processing_exception("Failed to allocate IO context for audio buffer");

        ctx->pb = ioCtx;
        ctx->flags |= AVFMT_FLAG_CUSTOM_IO;

        int err = avformat_open_input(&ctx, "nullptr", NULL, NULL);
        if (err != 0)
            throwAvError("Error configuring context from audio buffer", err);
    }

    AVIOContext *create_audio_buffer_io_context(char *audio, size_t audio_length) const {
        return avio_alloc_context(reinterpret_cast<unsigned char *>(audio),
                                  audio_length,
                                  0,
                                  audio,
                                  [](void *, uint8_t *, int buf_size) { return buf_size; },
                                  NULL,
                                  NULL);
    }

    ~MemoryAVFormat() {
        av_free(ioCtx);
        avformat_close_input(&ctx);
    }
}

Я читал и пробовал каждый учебник по этому вопросу, и ни один из них не работает.

У кого-нибудь это работало раньше?

вылетает на линии: int err = avformat_open_input(&ctx, "nullptr", NULL, NULL);


person Tobi Akinyemi    schedule 02.02.2021    source источник
comment
Попытка создать экземпляр этой структуры не удалась: - что вы имеете в виду? Выдает ли он ошибки во время компиляции? во время выполнения?   -  person Programmer    schedule 02.02.2021
comment
@Programmer Приложение вылетает   -  person Tobi Akinyemi    schedule 02.02.2021
comment
Я попробую показать трассировку стека, но это все машинный код - я его не понимаю   -  person Tobi Akinyemi    schedule 02.02.2021
comment
@Programmer это помогает?   -  person Tobi Akinyemi    schedule 02.02.2021
comment
Уф, я не знаю, как это понять, извините .... Но я думаю, что это должно что-то делать с передачей nullptr в качестве аргументов в int err = avformat_open_input(&ctx, nullptr, nullptr, nullptr); ...   -  person Programmer    schedule 02.02.2021
comment
@Programmer нормально передавать в Nulls   -  person Tobi Akinyemi    schedule 02.02.2021
comment
@HTNW посмотри туториалы. люди либо передают нулевое имя файла, либо DUMMYNAME. API получает звук из файла ctx->pb. Отсюда и флаг: AVFMT_FLAG_CUSTOM_IO   -  person Tobi Akinyemi    schedule 02.02.2021
comment
@HTNW Если вы посмотрите документацию, там есть даже старый пример, который делает это, но он не работает. Все сообщения на SO не работают   -  person Tobi Akinyemi    schedule 02.02.2021
comment
Я уже знаю, я только что прочитал документы...   -  person Programmer    schedule 02.02.2021


Ответы (1)


В документации avio_alloc_context() указано, что параметр buffer должен быть выделен av_malloc(), более того, он будет освобожден деструктором AVIOContext и может быть перераспределен в любое время:

 * @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().

В вашем примере кода вы опускаете детали выделения буфера audio, но я полагаю, что он не соответствует этим требованиям, поэтому сбой происходит, когда FFmpeg пытается освободить или перераспределить буфер audio.

Я предполагаю, что передача всего содержимого аудиофайла в качестве внешнего буфера не будет работать с AVIOContext - этот API действительно предназначен для использования с временным буфером для потоковой передачи данных из другого места (файла, сети или другого буфера памяти).

У меня нет полного образца, чтобы увидеть, будет ли он работать должным образом, но код может выглядеть так (вам, вероятно, потребуется настроить функцию read() и рассмотреть возможность реализации процедура поиска):

struct MemoryAVFormat {
    MemoryAVFormat(const MemoryAVFormat &) = delete;

    AVFormatContext *ctx;
    AVIOContext *ioCtx;

    char *audio;
    size_t audio_length;
    size_t audio_offset;

    MemoryAVFormat(char *theAudio, size_t theAudioLength)
    : ctx(avformat_alloc_context()),
      ioCtx(nullptr),
      audio(theAudio),
      audio_length(theAudioLength),
      audio_offset(0) {
        ioCtx = create_audio_buffer_io_context();
        if (ctx == nullptr)
            throw audio_processing_exception("Failed to allocate context");

        if (ioCtx == nullptr)
            throw audio_processing_exception("Failed to allocate IO context for audio buffer");

        ctx->pb = ioCtx;
        ctx->flags |= AVFMT_FLAG_CUSTOM_IO;

        int err = avformat_open_input(&ctx, "nullptr", NULL, NULL);
        if (err != 0)
            throwAvError("Error configuring context from audio buffer", err);
    }

    int read (uint8_t* theBuf, int theBufSize) {
        int aNbRead = std::min (int(audio_length - audio_offset), theBufSize);
        if(aNbRead == 0) { return AVERROR_EOF; }
        memcpy(theBuf, audio + audio_offset, aNbRead);
        audio_offset += aNbRead;
        return aNbRead;
    }

    int64_t seek(int64_t offset, int whence) {
         if (whence == AVSEEK_SIZE) { return audio_length; }
         audio_offset = offset;

         if(audio == NULL || audio_length == 0) { return -1; }
         if     (whence == SEEK_SET) { audio_offset = offset; }
         else if(whence == SEEK_CUR) { audio_offset += offset; }
         else if(whence == SEEK_END) { audio_offset = audio_length + offset; }

         //if(audio_offset < 0) { audio_offset  = 0; } else
         //if(audio_offset > audio_length) { audio_offset = audio_length; }
         return offset;
    }

    AVIOContext *create_audio_buffer_io_context() {
        const int aBufferSize = 4096;
        unsigned char* aBufferIO = (unsigned char* )av_malloc(aBufferSize + AV_INPUT_BUFFER_PADDING_SIZE);
        return avio_alloc_context(aBufferIO,
                                  aBufferSize,
                                  0,
                                  this,
                                  [](void* opaque, uint8_t* buf, int bufSize)
                                  { return ((MemoryAVFormat* )opaque)->read(buf, bufSize); },
                                  NULL,
                                  [](void* opaque, int64_t offset, int whence)
                                  { return ((MemoryAVFormat* )opaque)->seek(offset, whence); });
    }

    ~MemoryAVFormat() {
        av_free(ioCtx);
        avformat_close_input(&ctx);
    }
}

Альтернативой реализации интерфейса AVIOContext и использованию avformat_open_input() может быть передача аудиобуфера в качестве полезной нагрузки пользовательского AVPacket непосредственно в декодер, если вы заранее знаете, в каком аудиоформате находится ваш поток (например, вообще пропустив создание AVFormatContext). Я сделал это для декодирования растровых изображений, но не знаю, можно ли (легко) применить его к аудио.

person gkv311    schedule 02.02.2021
comment
Я получаю сообщение об ошибке из-за перераспределения массива для audio_stream (я думал, потому что чтение AV было повреждено), но на самом деле было повреждено только свойство AVStream::duration. Чего я не понимаю, так это почему это свойство повреждается только при использовании customIO? И можно ли получить длину потока другим способом? - person Tobi Akinyemi; 02.02.2021
comment
prnt.sc/y06jg5 - person Tobi Akinyemi; 02.02.2021
comment
Может быть, это не потому, что это пользовательский ввод-вывод, а потому, что его невозможно найти? - person gkv311; 02.02.2021
comment
Это то, что вы говорили с: (you will probably need to tune read() function and consider implementing seeking procedure as well)? - person Tobi Akinyemi; 02.02.2021
comment
Реализовал inline auto read(uint8_t *theBuf, int theBufSize) { int read_count = std::min(int(audio_length - audio_offset), theBufSize); if (read_count == 0) { return AVERROR_EOF; } std::memcpy(theBuf, audio + audio_offset, read_count); audio_offset += read_count; return read_count; } inline int64_t seek(int64_t offset, int whence) { if (whence == AVSEEK_SIZE) return audio_length; audio_offset = offset; return offset; } и все заработало. Большое спасибо, я искал везде для этого материала! - person Tobi Akinyemi; 02.02.2021
comment
Да, это то, что я имел в виду. Я добавил функцию seek() к ответу для полноты картины. - person gkv311; 02.02.2021