Use libsvtav1 C API in Custom Video Applications

This article provides a practical guide on natively integrating the libsvtav1 (Scalable Video Technology for AV1) C API into a custom video processing application. We will cover the essential steps, from initializing the encoder and configuring encoding parameters to sending raw video frames and retrieving the compressed AV1 bitstream.

Prerequisites and Header Inclusion

To use libsvtav1 in your C/C++ project, you need to link against the SvtAv1Enc library and include the main API header:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "EbSvtAv1Enc.h"

Step 1: Initialize the Encoder Handle and Configure Parameters

The first step is to declare the encoder handle (EbComponentType) and the configuration structure (EbSvtAv1EncConfiguration). You must initialize the handle with default settings before customizing them.

EbComponentType *svt_handle = NULL;
EbSvtAv1EncConfiguration svt_config;

// 1. Initialize handle with default parameters
EbErrorType return_value = svt_av1_enc_init_handle(&svt_handle, NULL, &svt_config);
if (return_value != EB_ErrorNone) {
    fprintf(stderr, "Failed to initialize SVT-AV1 handle\n");
    return 1;
}

// 2. Customize video and encoding settings
svt_config.source_width = 1920;
svt_config.source_height = 1080;
svt_config.frame_rate_numerator = 30;
svt_config.frame_rate_denominator = 1;
svt_config.encoder_color_format = EB_YUV420;
svt_config.encoder_bit_depth = 8;

// Set rate control (e.g., Constant Rate Factor - CRF)
svt_config.rate_control_mode = 0; // 0 = CRF/CQP
svt_config.qp = 35;               // QP value (0-63)
svt_config.enc_mode = 8;          // Preset/Speed (0-13, where 13 is fastest)

// 3. Apply the updated configurations to the handle
return_value = svt_av1_enc_set_parameter(svt_handle, &svt_config);
if (return_value != EB_ErrorNone) {
    fprintf(stderr, "Failed to set encoder parameters\n");
    return 1;
}

// 4. Initialize the encoder instance
return_value = svt_av1_enc_init(svt_handle);
if (return_value != EB_ErrorNone) {
    fprintf(stderr, "Failed to start the encoder\n");
    return 1;
}

Step 2: Set Up Input and Output Buffers

SVT-AV1 uses the EbBufferHeaderType structure to pass input (raw YUV frames) and receive output (compressed AV1 packets).

// Allocate the input buffer container
EbBufferHeaderType *input_buffer = NULL;
return_value = svt_av1_to_create_input_buffer(svt_handle, &input_buffer);

// Allocate the structure containing actual raw image data
EbSvtIOFormat *input_picture = (EbSvtIOFormat*)input_buffer->p_buffer;
input_picture->width = svt_config.source_width;
input_picture->height = svt_config.source_height;
input_picture->org_x = 0;
input_picture->org_y = 0;

// Allocate memory for Y, U, and V planes
size_t luma_size = svt_config.source_width * svt_config.source_height;
size_t chroma_size = luma_size / 4; // Assuming YUV 4:2:0

input_picture->luma = (uint8_t*)malloc(luma_size);
input_picture->cb = (uint8_t*)malloc(chroma_size);
input_picture->cr = (uint8_t*)malloc(chroma_size);

input_picture->y_stride = svt_config.source_width;
input_picture->cb_stride = svt_config.source_width / 2;
input_picture->cr_stride = svt_config.source_width / 2;

Step 3: The Encoding Loop (Send and Receive)

The encoding process is asynchronous. You send raw frames to the encoder using svt_av1_enc_send_picture and retrieve encoded packets using svt_av1_enc_get_packet.

int is_finished = 0;
EbBufferHeaderType *output_buffer = NULL;

while (!is_finished) {
    // 1. Populate YUV data into input_picture->luma, cb, and cr from your video source.
    // If you reach the end of your source stream, flag the buffer as End of Stream (EOS).
    int has_more_frames = get_next_raw_frame(input_picture); 

    if (!has_more_frames) {
        input_buffer->flags = EB_BUFFERFLAG_EOS;
        input_buffer->n_filled_len = 0;
    } else {
        input_buffer->flags = 0;
        input_buffer->n_filled_len = luma_size + (chroma_size * 2);
    }

    // 2. Send the raw frame to the encoder
    svt_av1_enc_send_picture(svt_handle, input_buffer);

    // 3. Retrieve available encoded packets
    EbErrorType packet_status;
    do {
        // Non-blocking call (0 parameter means non-blocking; 1 is blocking)
        packet_status = svt_av1_enc_get_packet(svt_handle, &output_buffer, 0);

        if (packet_status == EB_ErrorMax) {
            fprintf(stderr, "Error during encoding process\n");
            break;
        }

        if (packet_status == EB_ErrorNone && output_buffer != NULL) {
            // Process the compressed output packet
            fwrite(output_buffer->p_buffer, 1, output_buffer->n_filled_len, output_file);

            // Check if this was the final packet (EOS)
            if (output_buffer->flags & EB_BUFFERFLAG_EOS) {
                is_finished = 1;
            }

            // Release the output buffer back to the library
            svt_av1_enc_release_out_buffer(&output_buffer);
        }
    } while (packet_status == EB_ErrorNone && !is_finished);
}

Step 4: Deinitialization and Cleanup

Once encoding is complete, you must release all allocated resources to prevent memory leaks.

// Free allocated raw buffers
free(input_picture->luma);
free(input_picture->cb);
free(input_picture->cr);
free(input_buffer);

// Deinitialize and destroy the encoder instance
svt_av1_enc_deinit(svt_handle);
svt_av1_enc_deinit_handle(svt_handle);