34

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?

Anshul
  • 360
  • 3
  • 15
Tomaka17
  • 4,832
  • 5
  • 29
  • 38

3 Answers3

46

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);
Tomaka17
  • 4,832
  • 5
  • 29
  • 38
  • this is great to know. has anyone tried the solution w/o using std library? – tom Jul 24 '12 at 08:06
  • for gcc, `me._stream.read` and `me._stream.gcount` should just be me.read and me.gcount, see http://www.cplusplus.com/reference/iostream/istream/ – tmatth Oct 17 '12 at 18:30
  • This solution works great up until I need to do seeking. Any idea how to get this solution working with seeking? I'm currently using avformat_seek_file with the format context on the video stream and audio stream separately. When using seeking on a streaming file (url) it works great. When on a local mp4 with this method, I get `[mov,mp4,m4a,3gp,3g2,mj2 @ 00ee8360] stream 0, offset 0xfd97fc: partial file`. – leetNightshade Mar 20 '16 at 23:50
  • I'm also getting a crash in x64 but not x86 when using this method, crashes inside of avformat_open_input. – leetNightshade Apr 07 '16 at 16:13
  • 1
    Hi, I seem to be getting 'null' on `avFormat.get()' - do you perhaps know the reason why? – dk123 Jun 24 '16 at 21:17
  • leetNightshade, your crash comes from reinterpret_cast(av_malloc(8192)), replace with (unsigned char*)(av_malloc(8192)) or static_cast(fav_malloc(8192)) – Niki Nov 22 '17 at 09:56
  • Why is duration field not set in this case for avFormatPtr? if used avformat_open_input with simple file path and after avformat_find_stream_info. This filed is set – Yuriy Pryyma Feb 05 '18 at 16:02
15

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.

keefer
  • 385
  • 2
  • 7
  • 1
    Thanks. Doing this right before av_free(avioContext) solved my memory leak problem! – Michel Dec 03 '14 at 15:04
  • @keefer nowadays, maybe because of that complaint you linked, it seems to be possible to pass NULL as the buffer and 0 as its size and ffmpeg will do the sane thing, instead of requiring this crazy kind of memory management where you alloc one buffer and it may decide to realloc it at will and leave you with a dangling pointer. – user1593842 Apr 18 '19 at 22:15
9

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.

Christopher Bruns
  • 9,160
  • 7
  • 46
  • 61