Introduction
In this short Article I will explain how to use a custom IO-Context with FFmpeg. Although I used an IStream Object the code can be used for other Streams like std::istream
.
FFmpeg can only read from files or named pipes easily, but if you want to read directly from memory, from sockets or IStreams you have to provide a custom IO-Context. I could not find any resource in the internet which offeres a complete and working solution with the current version of FFmpeg explaining how to deal correctly with an IO-Context. After some hours of experimentation I finally managed to get this working without getting access-violations in FFmpeg functions.
Creating the IO-Context
FFmpeg uses a custom IO-Context, when you allocate the AVFormatContext
-structure yourself and provide your own version of AVIOContext
but there are several other things to consider. At first we will create the AVIOContext and the AVFormatContext
structures. The size of the internal buffer is up to you, I decided to provide 32kb for internal buffering. The two functions ReadFunc
and SeekFunc
are shown later.
IStream* pInStream;
const int iBufSize = 32 * 1024;
BYTE* pBuffer = new BYTE[iBufSize];
AVIOContext* pIOCtx = avio_alloc_context(pBuffer, iBufSize, 0, pInStream, ReadFunc,
0, SeekFunc);
AVFormatContext* pCtx = avformat_alloc_context();
pCtx->pb = pIOCtx;
Note: As you can see, the custom IO-Context can also be used for writing, but this is not explained here.
Now you have to tell FFmpeg, which input format it has to use. For a custom IO-Context this is necessary! FFmpeg will otherwise read about 5Mb data from the stream by default to determine the input format. By doing this, FFmpeg will crash because of a buffer overrun. I have not tested whether it will work, if the internal buffer is large enough to hold the 5Mb data because it was easier for me to determine the input format on my own.
ULONG ulReadBytes = 0;
if(FAILED(pInStream->Read(pBuffer, iBufSize, &ulReadBytes)))
if(FAILED(pInStream->Seek(0, SEEK_SET)))
AVProbeData probeData;
probeData.buf = pBuffer;
probeData.buf_size = ulReadBytes;
probeData.filename = "";
pCtx->iformat = av_probe_input_format(&probeData, 1);
The last thing to do is to set the flags of the AVFormatContext
. This is not directly mentioned in the documentation and although FFmpeg realizes that you have set your own AVIOContext
you have to set the AVFMT_FLAG_CUSTOM_IO
-flag on your own.
pCtx->flags = AVFMT_FLAG_CUSTOM_IO;
Now we use the avformat_open_input function to tell FFmpeg that it can start to read from the stream.
if(avformat_open_input(&pCtx, "", 0, 0)) != 0)
The second parameter of avformat_open_input is the filename. This is not used, because we want to use the custom IO-Context. Older versions of FFmpeg will crash if you pass 0 as filename instead of "". This issue was fixed in newer versions of FFmpeg.
The callback functions ReadFunc
and SeekFunc
are easily implemented:
int ReadFunc(void* ptr, uint8_t* buf, int buf_size)
{
IStream* pStream = reinterpret_cast<IStream*>(ptr);
ULONG bytesRead = 0;
HRESULT hr = pStream->Read(buf, buf_size, &bytesRead);
if(hr == S_FALSE)
return AVERROR_EOF; if(FAILED(hr))
return -1;
return bytesRead;
}
int64_t SeekFunc(void* ptr, int64_t pos, int whence)
{
IStream* pStream = reinterpret_cast<IStream*>(ptr);
LARGE_INTEGER in = { pos };
ULARGE_INTEGER out = { 0 };
if(FAILED(pStream->Seek(in, whence, &out)))
return -1;
return out.QuadPart;
}
The whence-parameter has one more option than fseek: AVSEEK_SIZE
. When this option is passed to the seek function it should return the file size (if possible). If its not possible, the function may return and do nothing -1. In my implementation pStream->Seek(...)
will fail with AVSEEK_SIZE
and SeekFunc
will return -1.
Freeing resources
One last comment on which functions are to use to release all the allocated resources:
avformat_close_input(pCtx); av_free(pIOCtx); delete[] pBuffer;