Custom I/O Protocol in libavformat Using AVIOContext
This article provides a practical guide on implementing a custom I/O
layer in FFmpeg’s libavformat using the
AVIOContext structure. You will learn how to define custom
callback functions for reading, writing, and seeking, allocate the
internal memory buffer, initialize the AVIOContext, and
bind it to an AVFormatContext to stream media from
non-standard sources such as in-memory buffers, encrypted storage, or
proprietary network protocols.
1. Define the Custom Callback Functions
AVIOContext relies on function pointers to perform
low-level byte operations. Depending on whether you are reading,
writing, or both, implement the required callbacks:
- Read Callback: Reads up to
buf_sizebytes intobuf. Returns the number of bytes read, or anAVERRORcode (such asAVERROR_EOF) on failure or completion. - Write Callback: Writes up to
buf_sizebytes frombuf. Returns the number of bytes written, or anAVERRORcode on failure. - Seek Callback: Repositions the stream offset.
Returns the resulting offset in bytes from the beginning of the
resource, or an
AVERRORcode.
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
// User data passed via the opaque pointer
struct CustomIOData {
uint8_t *data;
size_t size;
size_t offset;
};
// Custom read function
static int custom_read(void *opaque, uint8_t *buf, int buf_size) {
struct CustomIOData *io = (struct CustomIOData *)opaque;
if (io->offset >= io->size) {
return AVERROR_EOF;
}
int bytes_to_read = FFMIN(buf_size, io->size - io->offset);
memcpy(buf, io->data + io->offset, bytes_to_read);
io->offset += bytes_to_read;
return bytes_to_read;
}
// Custom seek function
static int64_t custom_seek(void *opaque, int64_t offset, int whence) {
struct CustomIOData *io = (struct CustomIOData *)opaque;
int64_t new_offset = -1;
switch (whence) {
case SEEK_SET:
new_offset = offset;
break;
case SEEK_CUR:
new_offset = (int64_t)io->offset + offset;
break;
case SEEK_END:
new_offset = (int64_t)io->size + offset;
break;
case AVSEEK_SIZE:
return io->size; // Return total size without seeking
default:
return AVERROR(EINVAL);
}
if (new_offset < 0 || (size_t)new_offset > io->size) {
return AVERROR(EINVAL);
}
io->offset = (size_t)new_offset;
return io->offset;
}2. Allocate and Initialize the AVIOContext
FFmpeg requires an internal buffer allocated via
av_malloc(). Allocate this buffer along with the
AVIOContext using avio_alloc_context().
size_t buffer_size = 4096; // Standard buffer size (typically 4 KB to 32 KB)
unsigned char *avio_buffer = (unsigned char *)av_malloc(buffer_size);
if (!avio_buffer) {
// Handle allocation error
}
struct CustomIOData custom_data = {
.data = my_source_pointer,
.size = my_source_size,
.offset = 0
};
AVIOContext *avio_ctx = avio_alloc_context(
avio_buffer, // Internal buffer
buffer_size, // Buffer size
0, // Write flag: 0 for read-only, 1 for write
&custom_data, // Opaque user data
&custom_read, // Read callback
NULL, // Write callback (NULL if read-only)
&custom_seek // Seek callback (NULL if non-seekable)
);
if (!avio_ctx) {
av_freep(&avio_buffer);
// Handle initialization error
}3. Attach AVIOContext to AVFormatContext
Allocate an AVFormatContext and assign your custom
AVIOContext to its pb field before opening the
stream.
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (!fmt_ctx) {
// Handle error
}
// Assign the custom I/O context
fmt_ctx->pb = avio_ctx;
// Open the stream (pass NULL for filename since custom I/O is used)
int ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
if (ret < 0) {
// Handle open failure
}
// Retrieve stream information
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
// Handle stream info failure
}4. Proper Cleanup and Destruction
When closing the context, handle buffer and context deallocations in
the correct sequence. If the AVIOContext was allocated with
an internal buffer from av_malloc(),
avio_context_free() can release both the context and the
internal buffer safely.
// Close the format context input
avformat_close_input(&fmt_ctx);
// Free the AVIOContext and its underlying buffer
if (avio_ctx) {
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
}