Feed in-memory buffer to FFmpeg libavformat

This article explains how to feed media data from an in-memory buffer directly into FFmpeg’s libavformat using a custom I/O read callback. You will learn how to configure an AVIOContext with your own read function and associate it with an AVFormatContext to demux and decode media data without using physical file paths.

The Custom I/O Mechanism

By default, FFmpeg’s avformat_open_input expects a file path or network URL to open a media stream. However, if your media data is already loaded in memory (such as a buffer received over a custom network protocol, encrypted storage, or an IPC pipe), you must intercept FFmpeg’s read requests.

This is achieved by creating a custom AVIOContext using avio_alloc_context and assigning it to the pb field of your AVFormatContext.

Step-by-Step Implementation

To feed in-memory data to libavformat, you must define a custom state structure, implement a read callback, allocate a buffer for FFmpeg’s internal use, and initialize the contexts.

1. Define the Buffer State Structure

You need a custom structure to keep track of the pointer to your in-memory buffer and the amount of data remaining.

#include <libavformat/avformat.h>
#include <libavutil/mem.h>
#include <string.h>

struct BufferData {
    uint8_t *ptr;
    size_t size; // Number of bytes remaining in the buffer
};

2. Implement the Read Callback

The read callback is called by FFmpeg whenever it needs more data to parse the container or extract packets. It must copy data from your custom buffer to FFmpeg’s internal buffer.

int read_packet(void *opaque, uint8_t *buf, int buf_size) {
    struct BufferData *bd = (struct BufferData *)opaque;
    
    // Determine how much data can actually be read
    buf_size = FFMIN(buf_size, bd->size);

    if (buf_size <= 0) {
        return AVERROR_EOF; // Return End of File
    }

    // Copy data to FFmpeg's internal buffer
    memcpy(buf, bd->ptr, buf_size);
    bd->ptr  += buf_size;
    bd->size -= buf_size;

    return buf_size;
}

3. Allocate and Bind the Contexts

Configure the AVFormatContext to use your custom read callback instead of its default file I/O operations.

int open_memory_buffer(uint8_t *my_buffer, size_t my_buffer_len) {
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    if (!fmt_ctx) {
        return AVERROR(ENOMEM);
    }

    // Initialize your custom buffer state struct
    struct BufferData bd = {
        .ptr = my_buffer,
        .size = my_buffer_len
    };

    // Allocate the buffer that AVIOContext will use internally
    size_t avio_ctx_buffer_size = 4096;
    uint8_t *avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
    if (!avio_ctx_buffer) {
        avformat_free_context(fmt_ctx);
        return AVERROR(ENOMEM);
    }

    // Allocate the AVIOContext
    AVIOContext *avio_ctx = avio_alloc_context(
        avio_ctx_buffer, 
        avio_ctx_buffer_size,
        0,                  // Write flag (0 = read-only, 1 = write-only)
        &bd,                // Opaque pointer passed to callbacks
        &read_packet,       // Read callback
        NULL,               // Write callback (NULL for read-only)
        NULL                // Seek callback (optional, NULL if seeking is not supported)
    );

    if (!avio_ctx) {
        av_free(avio_ctx_buffer);
        avformat_free_context(fmt_ctx);
        return AVERROR(ENOMEM);
    }

    // Bind the custom I/O context to the format context
    fmt_ctx->pb = avio_ctx;

    // Open the input. The URL parameter must be NULL or a dummy string.
    int ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
    if (ret < 0) {
        // Cleaning up avio_ctx and fmt_ctx is required on failure
        avio_context_free(&avio_ctx);
        avformat_close_input(&fmt_ctx);
        return ret;
    }

    // Now you can call avformat_find_stream_info and read frames
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0) {
        avformat_close_input(&fmt_ctx);
        avio_context_free(&avio_ctx);
        return ret;
    }

    // Process your media here...

    // Cleanup resources
    avformat_close_input(&fmt_ctx);
    
    // Note: av_free(avio_ctx_buffer) is handled automatically inside avio_context_free
    avio_context_free(&avio_ctx); 

    return 0;
}

Crucial Considerations