How to Use librav1e C API in Non-Rust Projects
This article explains how librav1e—the AV1 video encoder
library written in Rust—extends its compatibility to non-Rust
environments. It explores how the library’s C-compatible Application
Programming Interface (API) bridges the language gap, allowing
developers to seamlessly integrate high-performance AV1 encoding into
projects written in C, C++, Python, and other languages.
The Role of the Foreign Function Interface (FFI)
At the core of librav1e’s cross-language compatibility
is Rust’s Foreign Function Interface (FFI). Rust is designed to coexist
with C-based systems. By utilizing the extern "C" keyword
and the #[no_mangle] attribute in its source code,
librav1e prevents the Rust compiler from altering function
names during compilation. This ensures that the compiled binary exports
symbols in a standard format that any C-compatible linker can recognize
and resolve.
Header Generation and C Types
To allow C and C++ compilers to understand the library’s interfaces,
librav1e provides a C header file (rav1e.h).
This header file is often generated automatically using tools like
cbindgen.
The header maps Rust’s native data structures to standard C
equivalents: * Opaque Pointers: Complex Rust
structures, such as the encoder configuration (RaContext
and RaConfig), are exposed to C as opaque pointers. The
host application handles these as void pointers, preventing non-Rust
code from directly manipulating Rust’s memory layout. * Standard
Data Types: Standard integer types, floats, and character
arrays are mapped directly to their C counterparts (e.g.,
uint8_t, size_t). * Enums and
Structs: Configuration options, error codes, and frame metadata
are converted into standard C enums and structures.
Compilation and Linking
When librav1e is compiled for non-Rust use, the Cargo
build system generates standard system libraries rather than
Rust-specific library files (.rlib). Depending on the
target system and requirements, it compiles into: * Static
Libraries: .a (Unix) or .lib
(Windows) * Dynamic Libraries: .so
(Linux), .dylib (macOS), or .dll (Windows)
Non-Rust projects link against these compiled libraries just as they
would with any traditional C library. For example, a C project can
compile using gcc or clang by specifying the
path to the rav1e header and linking the library via the
-lrav1e flag.
Memory Management Across the Boundary
Because Rust and C manage memory differently, librav1e
exposes specific allocation and deallocation functions to prevent memory
leaks and undefined behavior.
- Allocation: The C host application requests the
creation of an encoder context using
rav1e_config_new()andrav1e_context_new(). This allocates memory on the heap within the Rust runtime. - Deallocation: Because C cannot automatically clean
up Rust objects, the host application must explicitly release this
memory using designated destructor functions, such as
rav1e_config_unref()andrav1e_context_unref().
Enabling Bindings for Other Languages
By exposing a standard C API, librav1e automatically
gains compatibility with almost every modern programming language.
Higher-level languages use their own C FFI libraries to wrap the
librav1e C API. For example, Python uses
ctypes or cffi, Swift uses Clang importer, and
Go uses cgo. These languages can call the C functions
exported by librav1e to perform AV1 encoding without
needing to interact with Rust directly.