Feed In-Memory Buffers to FFmpeg libavformat
This article demonstrates how to demux and decode multimedia data
residing in a RAM buffer using FFmpeg's libavformat
library. By setting up a custom AVIOContext with a
user-defined read callback, you can feed in-memory data directly into an
AVFormatContext without writing temporary files to disk,
drastically reducing I/O latency.
1. Define the Buffer State
To track your position within the in-memory buffer, define a custom structure to store the data pointer, the total size, and the current read position.
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/avutil.h>
#include <string.h>
typedef struct BufferData {
const uint8_t *ptr;
size_t size;
size_t offset;
} BufferData;2. Implement the Custom Read Callback
FFmpeg expects a callback matching the signature
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size).
This function calculates the remaining data, copies the requested number
of bytes into FFmpeg's internal buffer, updates the offset, and returns
the number of bytes read. If no bytes remain, return
AVERROR_EOF.
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
BufferData *bd = (BufferData *)opaque;
size_t remaining = bd->size - bd->offset;
if (remaining == 0) {
return AVERROR_EOF;
}
int to_copy = FFMIN((size_t)buf_size, remaining);
memcpy(buf, bd->ptr + bd->offset, to_copy);
bd->offset += to_copy;
return to_copy;
}If your media format requires seeking (such as MP4 with the moov atom at the end), you can optionally implement a seek callback:
static int64_t seek_packet(void *opaque, int64_t offset, int whence) {
BufferData *bd = (BufferData *)opaque;
switch (whence) {
case SEEK_SET:
bd->offset = offset;
break;
case SEEK_CUR:
bd->offset += offset;
break;
case SEEK_END:
bd->offset = bd->size + offset;
break;
case AVSEEK_SIZE:
return bd->size;
default:
return AVERROR(EINVAL);
}
return bd->offset;
}3. Create and Attach the AVIOContext
Allocate an internal buffer for AVIOContext using
av_malloc(). Pass this buffer along with your callback
functions and state structure to avio_alloc_context().
int open_buffer_context(const uint8_t *memory_data, size_t data_size, AVFormatContext **out_fmt_ctx) {
BufferData bd = {
.ptr = memory_data,
.size = data_size,
.offset = 0
};
size_t avio_buf_size = 4096;
uint8_t *avio_buf = (uint8_t *)av_malloc(avio_buf_size);
if (!avio_buf) {
return AVERROR(ENOMEM);
}
AVIOContext *avio_ctx = avio_alloc_context(
avio_buf,
avio_buf_size,
0, // 0 for read-only, 1 for write
&bd, // opaque user data
&read_packet, // read callback
NULL, // write callback (NULL for read-only)
&seek_packet // seek callback (or NULL if non-seekable)
);
if (!avio_ctx) {
av_free(avio_buf);
return AVERROR(ENOMEM);
}
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (!fmt_ctx) {
avio_context_free(&avio_ctx);
return AVERROR(ENOMEM);
}
fmt_ctx->pb = avio_ctx;
fmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
// Passing NULL as the filename tells FFmpeg to use fmt_ctx->pb
int ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
if (ret < 0) {
avio_context_free(&avio_ctx);
avformat_free_context(fmt_ctx);
return ret;
}
*out_fmt_ctx = fmt_ctx;
return 0;
}4. Proper Resource Cleanup
When you are finished processing the media packets, clean up the
format context and the custom I/O context. When using
avio_alloc_context(), FFmpeg manages the memory allocated
to avio_buf, so you must free the context using
avio_context_free().
void cleanup_buffer_context(AVFormatContext *fmt_ctx) {
if (!fmt_ctx) return;
AVIOContext *avio_ctx = fmt_ctx->pb;
avformat_close_input(&fmt_ctx);
if (avio_ctx) {
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
}
}