Lectura de un archivo ubicado en memoria con libavformat


Actualmente estoy intentando leer pequeños archivos de vídeo enviados desde un servidor

Para leer un archivo usando libavformat, se supone que debe llamar a

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

El problema es que en este caso el archivo no está en el disco, sino en la memoria.

Lo que estoy haciendo por el momento es descargar el archivo, escribirlo en el disco usando un nombre temporal, y luego llamar a av_open_input_file con el nombre del archivo temporal, que no es una solución muy limpia.

De hecho lo que quiero es una función como av_open_custom(&avFormatContext, &myReadFunction, &mySeekFunction); pero no encontré ninguna en la documentación. Supongo que es técnicamente posible, ya que el nombre del archivo no es algo que ayude a la biblioteca a determinar qué formato está utilizando.

Entonces, ¿hay una función como esta, o una alternativa a av_open_input_file?

Author: Anshul, 2012-03-07

3 answers

Es curioso cómo siempre encuentro la solución por mí mismo justo después de publicar la pregunta en este sitio, a pesar de que he estado trabajando en este problema durante horas.

De hecho, tienes que inicializar avFormatContext->pb antes de llamar a av_open_input, y pasarle un nombre de archivo falso. Esto no está escrito en la documentación sino en un comentario directamente en el código fuente de la biblioteca.

Código de ejemplo si desea cargar desde un istream (no probado, solo para que alguien que tiene el mismo problema pueda obtener el idea)

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);
 31
Author: Tomaka17,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-27 09:40:03

Esta es una gran información y me ayudó bastante, pero hay un par de problemas que la gente debe tener en cuenta. libavformat puede y se meterá con el búfer que le diste a avio_alloc_context. Esto conduce a errores realmente molestos de doble liberación o posiblemente fugas de memoria. Cuando empecé a buscar el problema, encontré https://lists.ffmpeg.org/pipermail/libav-user/2012-December/003257.html que lo clavó perfectamente.

Mi solución al limpiar de esto el trabajo es simplemente seguir adelante y llamar

    av_free(avioContext->buffer)

Y luego establecer su propio puntero de búfer (que asignó para su llamada avio_alloc_context) a NULL si le importa.

 10
Author: keefer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-22 15:32:55

La excelente respuesta de Tomaka17 me dio un buen comienzo para resolver un problema análogo usando Qt QIODevice en lugar de std::istream. Descubrí que necesitaba combinar aspectos de la solución de Tomaka17, con aspectos de la experiencia relacionada en http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg /

Mi función de lectura personalizada se ve así:

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

...pero también necesitaba crear una función de búsqueda personalizada:

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

...y lo até juntos como esto:

...
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);
...

Tenga en cuenta que aún no he resuelto los problemas de administración de memoria.

 7
Author: Christopher Bruns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-25 18:31:16