Integrate libdav1d AV1 Decoder in iOS

This article provides a straightforward guide on how iOS developers can integrate libdav1d, the high-performance open-source AV1 video decoder, into their mobile applications. It covers the essential steps of acquiring or compiling the library for iOS, bridging the C-based library into a Swift or Objective-C environment, and handling the core decoding loop to render AV1 video frames.

Step 1: Obtain or Compile libdav1d for iOS

Because libdav1d is written in C and assembly, it must be compiled for the iOS architecture (arm64 for physical devices, and arm64/x86_64 for the iOS Simulator). Developers can compile it manually using Meson and Ninja with an iOS toolchain file, or use pre-compiled binaries via dependency managers.

The easiest way to integrate libdav1d is through community-maintained wrappers or podspecs. For example, adding the following to your Podfile pulls in the pre-compiled binaries:

pod 'dav1d'

Option B: Compiling from Source

To compile manually, clone the official VideoLAN repository and use Meson to set up a cross-compilation build targeting iOS:

git clone https://code.videolan.org/videolan/dav1d.git
cd dav1d
meson build --cross-file package/crossfiles/ios64.meson --buildtype release
ninja -C build

This generates static (.a) or dynamic (.dylib) libraries that you must manually drag and drop into your Xcode project under Frameworks, Libraries, and Embedded Content.


Step 2: Configure the Xcode Project

After adding the compiled libdav1d.a and its header files (the include directory) to Xcode, you need to configure your build settings so your code can interact with the library.

  1. Header Search Paths: Go to your target’s Build Settings and add the path to the libdav1d header files (containing dav1d/dav1d.h) to Header Search Paths.

  2. Bridging Header (for Swift): If your project uses Swift, create a bridging header (e.g., YourProject-Bridging-Header.h) and import the main header:

    #import <dav1d/dav1d.h>

Step 3: Implement the Decoding Loop

With the C headers exposed to your Swift or Objective-C code, you can initialize the decoder and feed it raw AV1 bitstream packets.

1. Initialize the Decoder Context

Create and configure the Dav1dContext:

var context: OpaquePointer? = nil
var settings = Dav1dSettings()

// Initialize default settings
dav1d_default_settings(&settings)

// Allocate the context
let result = dav1d_open(&context, &settings)
if result < 0 {
    print("Failed to initialize dav1d decoder")
}

2. Send Bitstream Data to the Decoder

For every compressed AV1 frame (packet) you receive from your demuxer, wrap it in a Dav1dData structure and send it to the decoder:

var data = Dav1dData()
let rawDataPointer = ... // Pointer to your AV1 frame buffer
let dataSize = ... // Size of the frame buffer

// Pass the data buffer to the dav1d data structure
dav1d_data_wrap(&data, rawDataPointer, dataSize, nil, nil)

// Submit packet to the decoder
let sendResult = dav1d_send_data(context, &data)
if sendResult < 0 && sendResult != -EAGAIN {
    print("Error feeding data to the decoder")
}

3. Retrieve Decoded YUV Frames

Once data is submitted, retrieve the decoded picture frame from the decoder pipeline:

var picture = Dav1dPicture()
let getResult = dav1d_get_picture(context, &picture)

if getResult == 0 {
    // Successfully retrieved a decoded frame
    processDecodedFrame(picture)
    
    // Release the picture when done
    dav1d_picture_unref(&picture)
}

Step 4: Render the Decoded Frames

libdav1d outputs raw YUV frames (usually YUV420p). iOS devices cannot display this format directly using standard UIImageViews without conversion.

To display the decoded frames on screen: 1. Metal or OpenGL ES: Upload the Y, U, and V planes as separate textures to a custom Metal shader, and perform YUV-to-RGB conversion directly on the GPU for maximum performance. 2. CoreVideo: Convert the Dav1dPicture buffer into a CVPixelBuffer (using kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange), which can then be rendered using an AVSampleBufferDisplayLayer or converted into a CIImage/UIImage.