Question

I'm currently trying to read small video files sent from a server

In order to read a file using libavformat, you are supposed to call

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

The problem is that in this case the file is not on the disk, but in memory.

What I'm doing for the moment is downloading the file, writing it on the disk using a temporary name, and then calling av_open_input_file with the temporary file name, which is not a very clean solution.

In fact what I want is a function like av_open_custom(&avFormatContext, &myReadFunction, &mySeekFunction); but I didn't find any in the documentation. I guess it is technically possible, since the name of the file is not something that helps the library determine which format it is using.

So is there a function like this, or an alternative to av_open_input_file?

Was it helpful?

Solution

It's funny how I always find the solution by myself right after I post the question on this site, even though I've been working on this problem for hours.

In fact you have to initialize avFormatContext->pb before calling av_open_input, and pass to it a fake filename. This is not written in the documentation but in a commentary directly in the library's source code.

Example code if you want to load from an istream (untested, just so somebody which has the same problem can get the 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);

OTHER TIPS

This is great information and helped me out quite a bit, but there are a couple of issues people should be aware of. libavformat can and will mess with your buffer that you gave to avio_alloc_context. This leads to really annoying double-free errors or possibly memory leaks. When I started searching for the problem, I found https://lists.ffmpeg.org/pipermail/libav-user/2012-December/003257.html which nailed it perfectly.

My workaround when cleaning up from this work is to just go ahead and call

    av_free(avioContext->buffer)

and then setting your own buffer pointer (that you allocated for your avio_alloc_context call) to NULL if you care.

Tomaka17's excellent answer gave me a good start toward solving an analogous problem using Qt QIODevice rather than std::istream. I found I needed to blend aspects of Tomaka17's solution, with aspects of the related experience at http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg/

My custom Read function looks like this:

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

...but I also needed to create a custom Seek function:

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

...and I tied it together like this:

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

Note I have not yet worked out the memory management issues.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top