Чтение файла, расположенного в памяти, с помощью libavformat

В настоящее время я пытаюсь прочитать небольшие видеофайлы, отправленные с сервера.

Чтобы прочитать файл с использованием libavformat, вы должны позвонить

av_open_input_file(&avFormatContext, "C:\\path\\to\\video.avi", 0, 0, 0);

Проблема в том, что в этом случае файл находится не на диске, а в памяти.

На данный момент я загружаю файл, записываю его на диск, используя временное имя, а затем вызываю av_open_input_file с именем временного файла, что не очень чистое решение.

На самом деле мне нужна такая функция, как av_open_custom(&avFormatContext, &myReadFunction, &mySeekFunction);, но я не нашел ее в документации. Я предполагаю, что это технически возможно, так как имя файла не является чем-то, что помогает библиотеке определить, какой формат она использует.

Так есть ли такая функция или альтернатива av_open_input_file?


person Tomaka17    schedule 07.03.2012    source источник


Ответы (3)


Забавно, что я всегда сам нахожу решение сразу после того, как задаю вопрос на этом сайте, хотя работаю над этой проблемой часами.

На самом деле вы должны инициализировать avFormatContext->pb перед вызовом av_open_input и передать ему фальшивое имя файла. Об этом написано не в документации, а в комментарии непосредственно в исходном коде библиотеки.

Пример кода, если вы хотите загрузить из istream (непроверенный, просто чтобы кто-то, у кого есть такая же проблема, мог понять идею)

static int readFunction(void* opaque, uint8_t* buf, int buf_size) {
    auto& me = *reinterpret_cast<std::istream*>(opaque);
    me.read(reinterpret_cast<char*>(buf), buf_size);
    return me.gcount();
}

std::ifstream stream("file.avi", std::ios::binary);

const std::shared_ptr<unsigned char> buffer(reinterpret_cast<unsigned char*>(av_malloc(8192)), &av_free);
const std::shared_ptr<AVIOContext> avioContext(avio_alloc_context(buffer.get(), 8192, 0, reinterpret_cast<void*>(static_cast<std::istream*>(&stream)), &readFunction, nullptr, nullptr), &av_free);

const auto avFormat = std::shared_ptr<AVFormatContext>(avformat_alloc_context(), &avformat_free_context);
auto avFormatPtr = avFormat.get();
avFormat->pb = avioContext.get();
avformat_open_input(&avFormatPtr, "dummyFilename", nullptr, nullptr);
person Tomaka17    schedule 07.03.2012
comment
это здорово знать. Кто-нибудь пробовал решение без использования стандартной библиотеки? - person tom; 24.07.2012
comment
для gcc me._stream.read и me._stream.gcount должны быть просто me.read и me.gcount, см. cplusplus.com /ссылка/iostream/istream - person tmatth; 17.10.2012
comment
Это решение отлично работает до тех пор, пока мне не нужно искать. Любая идея, как заставить это решение работать с поиском? В настоящее время я использую avformat_seek_file с контекстом формата для видеопотока и аудиопотока отдельно. При использовании поиска в потоковом файле (url) он отлично работает. Когда на локальном mp4 с помощью этого метода я получаю [mov,mp4,m4a,3gp,3g2,mj2 @ 00ee8360] stream 0, offset 0xfd97fc: partial file. - person leetNightshade; 21.03.2016
comment
Я также получаю сбой в x64, но не в x86 при использовании этого метода, происходит сбой внутри avformat_open_input. - person leetNightshade; 07.04.2016
comment
Привет, кажется, я получаю 'null' в 'avFormat.get()' - возможно, вы знаете, почему? - person dk123; 25.06.2016
comment
leetNightshade, сбой происходит из-за reinterpret_cast‹unsigned char*›(av_malloc(8192)), замены на (unsigned char*)(av_malloc(8192)) или static_cast‹unsigned char*›(fav_malloc(8192)) - person Niki; 22.11.2017
comment
Почему в этом случае для avFormatPtr не задано поле продолжительности? если используется avformat_open_input с простым путем к файлу и после avformat_find_stream_info. Это поле установлено - person Yuriy Pryyma; 05.02.2018

Это отличная информация, и она немного помогла мне, но есть пара вопросов, о которых люди должны знать. libavformat может и будет возиться с вашим буфером, который вы передали в avio_alloc_context. Это приводит к очень раздражающим ошибкам с двойным освобождением или, возможно, к утечкам памяти. Когда я начал искать проблему, я нашел https://lists.ffmpeg.org/pipermail/libav-user/2012-December/003257.html, который идеально подходит.

Мой обходной путь при очистке от этой работы - просто пойти и позвонить

    av_free(avioContext->buffer)

а затем установите свой собственный указатель буфера (который вы выделили для вызова avio_alloc_context) в NULL, если вам это нужно.

person keefer    schedule 22.03.2013
comment
Спасибо. Делая это прямо перед тем, как av_free(avioContext) решил мою проблему с утечкой памяти! - person Michel; 03.12.2014
comment
@keefer в настоящее время, может быть, из-за той жалобы, которую вы связали, кажется возможным передать NULL в качестве буфера и 0 в качестве его размера, и ffmpeg сделает разумную вещь, вместо того, чтобы требовать этого сумасшедшего управления памятью, когда вы выделяете один буфер и он может решить перераспределить его по желанию и оставить вас с оборванным указателем. - person user1593842; 19.04.2019

Отличный ответ Tomaka17 дал мне хорошее начало для решения аналогичной проблемы с использованием Qt QIODevice, а не std::istream. Я обнаружил, что мне нужно объединить аспекты решения Tomaka17 с аспектами соответствующего опыта на http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg./

Моя пользовательская функция чтения выглядит так:

int readFunction(void* opaque, uint8_t* buf, int buf_size)
{
    QIODevice* stream = (QIODevice*)opaque;
    int numBytes = stream->read((char*)buf, buf_size);
    return numBytes;
}

... но мне также нужно было создать пользовательскую функцию Seek:

int64_t seekFunction(void* opaque, int64_t offset, int whence)
{
    if (whence == AVSEEK_SIZE)
        return -1; // I don't know "size of my handle in bytes"
    QIODevice* stream = (QIODevice*)opaque;
    if (stream->isSequential())
        return -1; // cannot seek a sequential stream
    if (! stream->seek(offset) )
        return -1;
    return stream->pos();
}

... и я связал это вместе следующим образом:

...
const int ioBufferSize = 32768;
unsigned char * ioBuffer = (unsigned char *)av_malloc(ioBufferSize + FF_INPUT_BUFFER_PADDING_SIZE); // can get av_free()ed by libav
AVIOContext * avioContext = avio_alloc_context(ioBuffer, ioBufferSize, 0, (void*)(&fileStream), &readFunction, NULL, &seekFunction);
AVFormatContext * container = avformat_alloc_context();
container->pb = avioContext;
avformat_open_input(&container, "dummyFileName", NULL, NULL);
...

Примечание. Я еще не разобрался с проблемами управления памятью.

person Christopher Bruns    schedule 25.01.2013
comment
FF_INPUT_BUFFER_PADDING_SIZE был изменен на AV_INPUT_BUFFER_PADDING_SIZE в последних версиях ffmpeg - person Shmil The Cat; 20.12.2017