Build FFmpeg Filtergraph with avfilter_graph_parse_ptr

This guide demonstrates how to programmatically construct an audio/video processing pipeline using the avfilter_graph_parse_ptr function from the FFmpeg C API (libavfilter). You will learn the role of AVFilterInOut linked lists, how to bridge raw frame producers and consumers using buffer and buffersink filters, and the precise sequence of API calls needed to parse complex filter strings, configure the graph, and manage memory safely.


Understanding avfilter_graph_parse_ptr

The function avfilter_graph_parse_ptr allows you to define complex filtering pipelines via a standard FFmpeg filtergraph string (e.g., "scale=1280:720,fps=30") while explicitly mapping the graph's open inputs and outputs to programmatic filters using linked lists of AVFilterInOut structures.

The signature is:

int avfilter_graph_parse_ptr(AVFilterGraph *graph,
                             const char *filters,
                             AVFilterInOut **inputs,
                             AVFilterInOut **outputs,
                             void *log_ctx);
  • inputs: Refers to where the filter string takes its input. These correspond to the output pads of your input filters (e.g., buffer).
  • outputs: Refers to where the filter string sends its output. These correspond to the input pads of your output filters (e.g., buffersink).

Step-by-Step Implementation

  1. Allocate the Graph: Create an empty AVFilterGraph.
  2. Create Endpoint Filters: Instantiate and initialize the buffer (source) and buffersink (destination) filter contexts.
  3. Configure AVFilterInOut Chains: Set up labels mapping to the buffer and buffersink filters.
  4. Parse the Pipeline: Call avfilter_graph_parse_ptr.
  5. Validate and Finalize: Execute avfilter_graph_config.
  6. Free Temporary Resources: Free the AVFilterInOut structures.

Complete Code Example

The following example builds a video scaling pipeline converting frames to \(1280 \times 720\):

#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/opt.h>
#include <stdio.h>

int init_filtergraph(AVFilterGraph **graph_out,
                     AVFilterContext **src_ctx_out,
                     AVFilterContext **sink_ctx_out) 
{
    int ret = 0;
    AVFilterGraph *graph = avfilter_graph_alloc();
    if (!graph) return AVERROR(ENOMEM);

    const AVFilter *buffer_src = avfilter_get_by_name("buffer");
    const AVFilter *buffer_sink = avfilter_get_by_name("buffersink");
    AVFilterContext *src_ctx = NULL;
    AVFilterContext *sink_ctx = NULL;

    AVFilterInOut *inputs = avfilter_inout_alloc();
    AVFilterInOut *outputs = avfilter_inout_alloc();

    if (!inputs || !outputs) {
        ret = AVERROR(ENOMEM);
        goto cleanup;
    }

    // 1. Configure source parameters (e.g., 1920x1080 YUV420P)
    char args[512];
    snprintf(args, sizeof(args),
             "video_size=1920x1080:pix_fmt=%d:time_base=1/25:pixel_aspect=1/1",
             AV_PIX_FMT_YUV420P);

    ret = avfilter_graph_create_filter(&src_ctx, buffer_src, "in",
                                       args, NULL, graph);
    if (ret < 0) goto cleanup;

    // 2. Configure sink filter
    ret = avfilter_graph_create_filter(&sink_ctx, buffer_sink, "out",
                                       NULL, NULL, graph);
    if (ret < 0) goto cleanup;

    // 3. Set accepted pixel formats for sink
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
    ret = av_opt_set_int_list(sink_ctx, "pix_fmts", pix_fmts,
                              AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
    if (ret < 0) goto cleanup;

    // 4. Map the inputs and outputs
    // The filter string's input connects to src_ctx's output
    outputs->name       = av_strdup("in");
    outputs->filter_ctx = src_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = NULL;

    // The filter string's output connects to sink_ctx's input
    inputs->name        = av_strdup("out");
    inputs->filter_ctx  = sink_ctx;
    inputs->pad_idx     = 0;
    inputs->next        = NULL;

    // 5. Parse the filter string
    const char *filter_desc = "scale=1280:720";
    ret = avfilter_graph_parse_ptr(graph, filter_desc, &inputs, &outputs, NULL);
    if (ret < 0) goto cleanup;

    // 6. Finalize graph configuration
    ret = avfilter_graph_config(graph, NULL);
    if (ret < 0) goto cleanup;

    *graph_out = graph;
    *src_ctx_out = src_ctx;
    *sink_ctx_out = sink_ctx;

cleanup:
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);

    if (ret < 0 && graph) {
        avfilter_graph_free(&graph);
    }
    return ret;
}

Key Considerations

  • Naming Direction: In the AVFilterInOut mapping, the pointer labeled outputs connects the graph's origin (src_ctx) to the string's entry point, whereas inputs connects the string's termination point to the graph's destination (sink_ctx).
  • Memory Management: avfilter_graph_parse_ptr can modify the inputs and outputs lists during parsing. Always pass their addresses and call avfilter_inout_free afterwards to prevent leaks.
  • Frame Exchange: Once avfilter_graph_config returns 0, push raw frames into the graph via av_buffersrc_add_frame_flags(src_ctx, frame, flags) and pull processed frames via av_buffersink_get_frame(sink_ctx, frame).