Search code examples
c++ffmpegandroid-ffmpeg

FFmpeg filter config with aecho fails to configure all the links and formats - avfilter_graph_config


I am following the official tutorial of FFMpeg to create a filter chain. This tutorial shows how to pass data through a chain as:

The filter chain it uses is: * (input) -> abuffer -> volume -> aformat -> abuffersink -> (output)

Here is my code - sorry for boiler code, it is just ffmpeg way :(

    frame = av_frame_alloc();
    filterGraph = avfilter_graph_alloc();

    if (!frame) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not allocate memory for frame");
        return;
    }

    if (!filterGraph) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor FXProcessor! %s", av_err2str(AVERROR(ENOMEM)));
        return;
    }

    const AVFilter *abuffer;
    const AVFilter *abuffersink;
    AVFilterContext *aformat_ctx;
    const AVFilter *aformat;
    AVFilterContext *choisen_beat_fx_ctx;
    const AVFilter *choisen_beat_fx;

    /* Create the abuffer filter;
     * it will be used for feeding the data into the graph. */
    abuffer = avfilter_get_by_name("abuffer");
    if (!abuffer) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not find the abuffer filter!");
        return;
    }
    abuffer_ctx = avfilter_graph_alloc_filter(filterGraph, abuffer, "src");
    if (!abuffer_ctx) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not allocate the abuffer_ctx instance! %s",
             av_err2str(AVERROR(ENOMEM)));
        return;
    }

    char ch_layout[64];
    /* Set the filter options through the AVOptions API. */
    av_get_channel_layout_string(ch_layout, sizeof(ch_layout), 0, AV_CH_LAYOUT_STEREO);
    av_opt_set(abuffer_ctx, "channel_layout", ch_layout, AV_OPT_SEARCH_CHILDREN);
    av_opt_set(abuffer_ctx, "sample_fmt", av_get_sample_fmt_name(AV_SAMPLE_FMT_FLT),
               AV_OPT_SEARCH_CHILDREN);
    av_opt_set_q(abuffer_ctx, "time_base", (AVRational) {1, defaultSampleRate},
                 AV_OPT_SEARCH_CHILDREN);
    av_opt_set_int(abuffer_ctx, "sample_rate", defaultSampleRate, AV_OPT_SEARCH_CHILDREN);
    /* Now initialize the filter; we pass NULL options, since we have already
     * set all the options above. */

    if (avfilter_init_str(abuffer_ctx, nullptr) < 0) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not initialize the abuffer filter!");
        return;
    }

    // TODO: select FX's dynamically
    /* Create aecho filter. */
    if (true) {

        choisen_beat_fx = avfilter_get_by_name("volume");
        if (!choisen_beat_fx) {
            *mediaLoadPointer = FAILED_TO_LOAD;
            LOGE("FXProcessor::FXProcessor Could not find the aecho filter!");
            return;
        }

        choisen_beat_fx_ctx = avfilter_graph_alloc_filter(filterGraph, choisen_beat_fx, "echo");
        if (!choisen_beat_fx_ctx) {
            *mediaLoadPointer = FAILED_TO_LOAD;
            LOGE("FXProcessor::FXProcessor Could not allocate the choisen_beat_fx_ctx instance! %s",
                 av_err2str(AVERROR(ENOMEM)));
            return;
        }

        av_opt_set    (choisen_beat_fx_ctx, "volume",     AV_STRINGIFY(0.5), AV_OPT_SEARCH_CHILDREN);

        if (avfilter_init_str(choisen_beat_fx_ctx, nullptr) < 0) {
            *mediaLoadPointer = FAILED_TO_LOAD;
            LOGE("FXProcessor::FXProcessor Could not initialize the choisen_beat_fx_ctx filter!");
            return;
        }
    }

    /* Create the aformat filter;
     * it ensures that the output is of the format we want. */
    aformat = avfilter_get_by_name("aformat");
    if (!aformat) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not find the aformat filter!");
        return;
    }
    aformat_ctx = avfilter_graph_alloc_filter(filterGraph, aformat, "aformat");
    if (!aformat_ctx) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not allocate the aformat instance!");
        return;
    }

    av_opt_set(aformat_ctx, "sample_fmts", av_get_sample_fmt_name(AV_SAMPLE_FMT_FLT),
               AV_OPT_SEARCH_CHILDREN);
    av_opt_set_int(aformat_ctx, "sample_rates", defaultSampleRate, AV_OPT_SEARCH_CHILDREN);
    av_get_channel_layout_string(ch_layout, sizeof(ch_layout), 0, AV_CH_LAYOUT_STEREO);
    av_opt_set(aformat_ctx, "channel_layouts", ch_layout, AV_OPT_SEARCH_CHILDREN);

    if (avfilter_init_str(aformat_ctx, nullptr) < 0) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not initialize the aformat filter!");
        return;
    }

    /* Finally create the abuffersink filter;
     * it will be used to get the filtered data out of the graph. */
    abuffersink = avfilter_get_by_name("abuffersink");
    if (!abuffersink) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not find the abuffersink filter!");
        return;
    }

    abuffersink_ctx = avfilter_graph_alloc_filter(filterGraph, abuffersink, "sink");
    if (!abuffersink_ctx) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not allocate the abuffersink instance!");
        return;
    }

    /* This filter takes no options. */
    if (avfilter_init_str(abuffersink_ctx, nullptr) < 0) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Could not initialize the abuffersink instance.!");
        return;
    }

    /* Connect the filters;
     * in this simple case the filters just form a linear chain. */
    if (avfilter_link(abuffer_ctx, 0, choisen_beat_fx_ctx, 0) != 0) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Error connecting filters.!");
        return;
    }
    if (avfilter_link(choisen_beat_fx_ctx, 0, aformat_ctx, 0) != 0) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Error connecting filters.!");
        return;
    }
    if (avfilter_link(aformat_ctx, 0, abuffersink_ctx, 0) != 0) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Error connecting filters.!");
        return;
    }

    /* Configure the graph. */
    if (avfilter_graph_config(filterGraph, nullptr) < 0) {
        *mediaLoadPointer = FAILED_TO_LOAD;
        LOGE("FXProcessor::FXProcessor Error configuring the filter graph!");
        return;
    }

This code works fine when the chain is

  • (input) -> abuffer -> aecho-> aformat -> abuffersink -> (output)

However, I would like to use adelay instead of volume filter. So I want:

The filter chain it uses is: * (input) -> abuffer -> volume -> aformat -> abuffersink -> (output)

I changed the code at

choisen_beat_fx = avfilter_get_by_name("volume");

to

choisen_beat_fx = avfilter_get_by_name("aecho");

and removed the line

av_opt_set    (choisen_beat_fx_ctx, "volume",     AV_STRINGIFY(0.5), AV_OPT_SEARCH_CHILDREN);

everything goes smooth until the last line. avfilter_graph_config fails and returns negative value. Functions document:

avfilter_graph_config: Check validity and configure all the links and formats in the graph.

So my guess is I need extra links to insert aecho to my chain? How can I insert aecho into my filter chain?


Solution

  • Okay the problem was, I needed to compile with aresample filter for this to work. I recompiled and magically it works now.

    Here is how I found out the problem. Create a general log callback:

    void ffmpegErrorCallback(void *ptr, int level, const char *fmt, va_list vargs) {
        LOGE("ffmpegErrorCallback Error Occurred! Err: %s", fmt);
    }
    
    av_log_set_level(AV_LOG_ERROR);
    av_log_set_callback(ffmpegErrorCallback);
    

    FFmpeg will now log the errors as a human readable message. It told me that it couldnt find aresample filter. Thats how I solved the issue.