How to Use Custom Memory Allocators in libavif

This article explains how the libavif API handles custom memory allocation. It covers the core avifMemoryAllocator structure, how to implement custom allocation functions, and the steps required to register these custom allocators globally within your application. By overriding the default memory management, developers can easily integrate libavif into constrained environments, game engines, or systems requiring strict memory tracking.

The avifMemoryAllocator Structure

By default, libavif relies on the standard C library functions (malloc, realloc, and free) for dynamic memory management. However, for systems that require custom heap management, libavif provides the avifMemoryAllocator structure. This structure encapsulates custom allocation behavior by holding function pointers to your custom allocator implementations.

The avifMemoryAllocator struct generally contains the following function pointer members:

Registering a Custom Allocator

To override the default memory functions, you must configure and register an instance of avifMemoryAllocator before performing any operations with libavif. This is done using the global configuration function:

void avifSetMemoryAllocator(const avifMemoryAllocator * allocator);

Passing a pointer to your custom avifMemoryAllocator populates the library’s internal function pointers. If you pass NULL to this function, libavif will revert to using the standard system allocators.

Implementation Example

Below is a basic implementation showing how to define custom memory routines and register them with libavif:

#include "avif/avif.h"
#include <stdlib.h>
#include <stdio.h>

// Custom allocation wrapper
void * MyCustomAlloc(size_t size) {
    // Implement custom logic (e.g., tracking allocation size or using a custom arena)
    return malloc(size);
}

// Custom reallocation wrapper
void * MyCustomRealloc(void * ptr, size_t size) {
    return realloc(ptr, size);
}

// Custom free wrapper
void MyCustomFree(void * ptr) {
    free(ptr);
}

int main() {
    // Define the custom allocator structure
    avifMemoryAllocator myAllocator;
    myAllocator.alloc = MyCustomAlloc;
    myAllocator.realloc = MyCustomRealloc;
    myAllocator.free = MyCustomFree;

    // Register the custom allocator globally
    avifSetMemoryAllocator(&myAllocator);

    // Any libavif operations from this point forward will use the custom allocator
    avifDecoder * decoder = avifDecoderCreate();
    
    // Clean up
    avifDecoderDestroy(decoder);
    
    return 0;
}

Thread Safety and Usage Considerations

When using custom memory allocators with libavif, keep the following considerations in mind:

  1. Global Scope: The memory allocator set via avifSetMemoryAllocator is global. It affects all encoder, decoder, and image operations across the entire application process.
  2. Thread Safety: If your application decodes or encodes AVIF images concurrently across multiple threads, your custom alloc, realloc, and free functions must be completely thread-safe.
  3. Initialization Timing: You must call avifSetMemoryAllocator before calling any other libavif API functions. Changing the memory allocator while libavif structures are actively allocated will lead to undefined behavior and memory corruption, as memory allocated with one allocator might be freed by another.