[FFmpeg-devel] [PATCH 2/2] avfilter: add sidechaingate filter

Paul B Mahol onemda at gmail.com
Tue Dec 1 15:00:30 CET 2015


On 12/1/15, Ganesh Ajjanagadde <gajjanag at mit.edu> wrote:
> On Tue, Dec 1, 2015 at 3:50 AM, Paul B Mahol <onemda at gmail.com> wrote:
>> On 12/1/15, Ganesh Ajjanagadde <gajjanag at mit.edu> wrote:
>>> On Sun, Nov 29, 2015 at 6:03 PM, Paul B Mahol <onemda at gmail.com> wrote:
>>>> Signed-off-by: Paul B Mahol <onemda at gmail.com>
>>>> ---
>>>>  doc/filters.texi         |  61 +++++++++++++++++
>>>>  libavfilter/Makefile     |   1 +
>>>>  libavfilter/af_agate.c   | 170
>>>> ++++++++++++++++++++++++++++++++++++++++++++++-
>>>>  libavfilter/allfilters.c |   1 +
>>>>  4 files changed, 230 insertions(+), 3 deletions(-)
>>>>
>>>> diff --git a/doc/filters.texi b/doc/filters.texi
>>>> index 0395c7a..ed4a376 100644
>>>> --- a/doc/filters.texi
>>>> +++ b/doc/filters.texi
>>>> @@ -2531,6 +2531,67 @@ ffmpeg -i main.flac -i sidechain.flac
>>>> -filter_complex "[1:a]asplit=2[sc][mix];[0
>>>>  @end example
>>>>  @end itemize
>>>>
>>>> + at section sidechaingate
>>>> +
>>>> +A sidechain gate acts like a normal (wideband) gate but has the ability
>>>> to
>>>> +filter the detected signal before sending it to the gain reduction
>>>> stage.
>>>> +Normally a gate uses the full range signal to detect a level above the
>>>> +threshold.
>>>> +For example: If you cut all lower frequencies from your sidechain
>>>> signal
>>>> +the gate will decrease the volume of your track only if not enough
>>>> highs
>>>> +appear. With this technique you are able to reduce the resonation of a
>>>> +natural drum or remove "rumbling" of muted strokes from a heavily
>>>> distorted
>>>> +guitar.
>>>> +It needs two input streams and returns one output stream.
>>>> +First input stream will be processed depending on second stream signal.
>>>> +
>>>> +The filter accepts the following options:
>>>> +
>>>> + at table @option
>>>> + at item level_in
>>>> +Set input level before filtering.
>>>> +Default is 1. Allowed range is from 0.015625 to 64.
>>>> +
>>>> + at item range
>>>> +Set the level of gain reduction when the signal is below the threshold.
>>>> +Default is 0.06125. Allowed range is from 0 to 1.
>>>> +
>>>> + at item threshold
>>>> +If a signal rises above this level the gain reduction is released.
>>>> +Default is 0.125. Allowed range is from 0 to 1.
>>>> +
>>>> + at item ratio
>>>> +Set a ratio about which the signal is reduced.
>>>> +Default is 2. Allowed range is from 1 to 9000.
>>>> +
>>>> + at item attack
>>>> +Amount of milliseconds the signal has to rise above the threshold
>>>> before
>>>> gain
>>>> +reduction stops.
>>>> +Default is 20 milliseconds. Allowed range is from 0.01 to 9000.
>>>> +
>>>> + at item release
>>>> +Amount of milliseconds the signal has to fall below the threshold
>>>> before
>>>> the
>>>> +reduction is increased again. Default is 250 milliseconds.
>>>> +Allowed range is from 0.01 to 9000.
>>>> +
>>>> + at item makeup
>>>> +Set amount of amplification of signal after processing.
>>>> +Default is 1. Allowed range is from 1 to 64.
>>>> +
>>>> + at item knee
>>>> +Curve the sharp knee around the threshold to enter gain reduction more
>>>> softly.
>>>> +Default is 2.828427125. Allowed range is from 1 to 8.
>>>> +
>>>> + at item detection
>>>> +Choose if exact signal should be taken for detection or an RMS like
>>>> one.
>>>> +Default is peak. Can be peak or rms.
>>>> +
>>>> + at item link
>>>> +Choose if the average level between all channels or the louder channel
>>>> affects
>>>> +the reduction.
>>>> +Default is average. Can be average or maximum.
>>>> + at end table
>>>> +
>>>>  @section silencedetect
>>>>
>>>>  Detect silence in an audio stream.
>>>> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
>>>> index e31bdaa..582fd0e 100644
>>>> --- a/libavfilter/Makefile
>>>> +++ b/libavfilter/Makefile
>>>> @@ -82,6 +82,7 @@ OBJS-$(CONFIG_REPLAYGAIN_FILTER)             +=
>>>> af_replaygain.o
>>>>  OBJS-$(CONFIG_RESAMPLE_FILTER)               += af_resample.o
>>>>  OBJS-$(CONFIG_RUBBERBAND_FILTER)             += af_rubberband.o
>>>>  OBJS-$(CONFIG_SIDECHAINCOMPRESS_FILTER)      += af_sidechaincompress.o
>>>> +OBJS-$(CONFIG_SIDECHAINGATE_FILTER)          += af_agate.o
>>>>  OBJS-$(CONFIG_SILENCEDETECT_FILTER)          += af_silencedetect.o
>>>>  OBJS-$(CONFIG_SILENCEREMOVE_FILTER)          += af_silenceremove.o
>>>>  OBJS-$(CONFIG_STEREOTOOLS_FILTER)            += af_stereotools.o
>>>> diff --git a/libavfilter/af_agate.c b/libavfilter/af_agate.c
>>>> index d3f74fb..23e45c6 100644
>>>> --- a/libavfilter/af_agate.c
>>>> +++ b/libavfilter/af_agate.c
>>>> @@ -18,6 +18,12 @@
>>>>   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
>>>> 02110-1301 USA
>>>>   */
>>>>
>>>> +/**
>>>> + * @file
>>>> + * Audio (Sidechain) Gate filter
>>>> + */
>>>> +
>>>> +#include "libavutil/avassert.h"
>>>>  #include "libavutil/channel_layout.h"
>>>>  #include "libavutil/opt.h"
>>>>  #include "avfilter.h"
>>>> @@ -46,12 +52,14 @@ typedef struct AudioGateContext {
>>>>      double lin_slope;
>>>>      double attack_coeff;
>>>>      double release_coeff;
>>>> +
>>>> +    AVFrame *input_frame[2];
>>>>  } AudioGateContext;
>>>>
>>>>  #define OFFSET(x) offsetof(AudioGateContext, x)
>>>>  #define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
>>>>
>>>> -static const AVOption agate_options[] = {
>>>> +static const AVOption options[] = {
>>>>      { "level_in",  "set input level",        OFFSET(level_in),
>>>> AV_OPT_TYPE_DOUBLE, {.dbl=1},           0.015625,   64, A },
>>>>      { "range",     "set max gain reduction", OFFSET(range),
>>>> AV_OPT_TYPE_DOUBLE, {.dbl=0.06125},     0, 1, A },
>>>>      { "threshold", "set threshold",          OFFSET(threshold),
>>>> AV_OPT_TYPE_DOUBLE, {.dbl=0.125},       0, 1, A },
>>>> @@ -69,6 +77,7 @@ static const AVOption agate_options[] = {
>>>>      { NULL }
>>>>  };
>>>>
>>>> +#define agate_options options
>>>>  AVFILTER_DEFINE_CLASS(agate);
>>>>
>>>>  static int query_formats(AVFilterContext *ctx)
>>>> @@ -97,7 +106,7 @@ static int query_formats(AVFilterContext *ctx)
>>>>      return ff_set_common_samplerates(ctx, formats);
>>>>  }
>>>>
>>>> -static int config_input(AVFilterLink *inlink)
>>>> +static int agate_config_input(AVFilterLink *inlink)
>>>>  {
>>>>      AVFilterContext *ctx = inlink->dst;
>>>>      AudioGateContext *s = ctx->priv;
>>>> @@ -221,7 +230,7 @@ static const AVFilterPad inputs[] = {
>>>>          .name         = "default",
>>>>          .type         = AVMEDIA_TYPE_AUDIO,
>>>>          .filter_frame = filter_frame,
>>>> -        .config_props = config_input,
>>>> +        .config_props = agate_config_input,
>>>>      },
>>>>      { NULL }
>>>>  };
>>>> @@ -243,3 +252,158 @@ AVFilter ff_af_agate = {
>>>>      .inputs         = inputs,
>>>>      .outputs        = outputs,
>>>>  };
>>>> +
>>>> +#if CONFIG_SIDECHAINGATE_FILTER
>>>> +
>>>> +#define sidechaingate_options options
>>>> +AVFILTER_DEFINE_CLASS(sidechaingate);
>>>> +
>>>> +static int scfilter_frame(AVFilterLink *link, AVFrame *in)
>>>> +{
>>>> +    AVFilterContext *ctx = link->dst;
>>>> +    AudioGateContext *s = ctx->priv;
>>>> +    AVFilterLink *outlink = ctx->outputs[0];
>>>> +    const double *scsrc;
>>>> +    double *sample;
>>>> +    int nb_samples;
>>>> +    int ret, i;
>>>> +
>>>> +    for (i = 0; i < 2; i++)
>>>> +        if (link == ctx->inputs[i])
>>>> +            break;
>>>> +    av_assert0(i < 2 && !s->input_frame[i]);
>>>> +    s->input_frame[i] = in;
>>>> +
>>>> +    if (!s->input_frame[0] || !s->input_frame[1])
>>>> +        return 0;
>>>> +
>>>> +    nb_samples = FFMIN(s->input_frame[0]->nb_samples,
>>>> +                       s->input_frame[1]->nb_samples);
>>>> +
>>>> +    sample = (double *)s->input_frame[0]->data[0];
>>>> +    scsrc = (const double *)s->input_frame[1]->data[0];
>>>> +
>>>> +    gate(s, sample, sample, scsrc, nb_samples,
>>>> +         ctx->inputs[0], ctx->inputs[1]);
>>>> +    ret = ff_filter_frame(outlink, s->input_frame[0]);
>>>> +
>>>> +    s->input_frame[0] = NULL;
>>>> +    av_frame_free(&s->input_frame[1]);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +static int screquest_frame(AVFilterLink *outlink)
>>>> +{
>>>> +    AVFilterContext *ctx = outlink->src;
>>>> +    AudioGateContext *s = ctx->priv;
>>>> +    int i, ret;
>>>> +
>>>> +    /* get a frame on each input */
>>>> +    for (i = 0; i < 2; i++) {
>>>> +        AVFilterLink *inlink = ctx->inputs[i];
>>>> +        if (!s->input_frame[i] &&
>>>> +            (ret = ff_request_frame(inlink)) < 0)
>>>> +            return ret;
>>>> +
>>>> +        /* request the same number of samples on all inputs */
>>>> +        if (i == 0)
>>>> +            ctx->inputs[1]->request_samples =
>>>> s->input_frame[0]->nb_samples;
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int scquery_formats(AVFilterContext *ctx)
>>>> +{
>>>> +    AVFilterFormats *formats;
>>>> +    AVFilterChannelLayouts *layouts = NULL;
>>>> +    static const enum AVSampleFormat sample_fmts[] = {
>>>> +        AV_SAMPLE_FMT_DBL,
>>>> +        AV_SAMPLE_FMT_NONE
>>>> +    };
>>>> +    int ret, i;
>>>> +
>>>> +    if (!ctx->inputs[0]->in_channel_layouts ||
>>>> +        !ctx->inputs[0]->in_channel_layouts->nb_channel_layouts) {
>>>> +        av_log(ctx, AV_LOG_WARNING,
>>>> +               "No channel layout for input 1\n");
>>>> +            return AVERROR(EAGAIN);
>>>> +    }
>>>> +
>>>> +    if ((ret = ff_add_channel_layout(&layouts,
>>>> ctx->inputs[0]->in_channel_layouts->channel_layouts[0])) < 0 ||
>>>> +        (ret = ff_channel_layouts_ref(layouts,
>>>> &ctx->outputs[0]->in_channel_layouts)) < 0)
>>>> +        return ret;
>>>> +
>>>> +    for (i = 0; i < 2; i++) {
>>>> +        layouts = ff_all_channel_counts();
>>>> +        if ((ret = ff_channel_layouts_ref(layouts,
>>>> &ctx->inputs[i]->out_channel_layouts)) < 0)
>>>> +            return ret;
>>>> +    }
>>>> +
>>>> +    formats = ff_make_format_list(sample_fmts);
>>>> +    if ((ret = ff_set_common_formats(ctx, formats)) < 0)
>>>> +        return ret;
>>>> +
>>>> +    formats = ff_all_samplerates();
>>>> +    return ff_set_common_samplerates(ctx, formats);
>>>
>>> Please fix the memleak issue - similar such things have been pointed
>>> out for previous reviews by me. Please repost - you have ignored this
>>> point in the past: see commit ID
>>> 3f895dcb0dcbcbf10a621bf4bfae6d8879899015 which you pushed recently, in
>>> spite of the issue being pointed out in review:
>>> https://ffmpeg.org/pipermail/ffmpeg-devel/2015-November/183861.html.
>>>
>>> This does not seem cool to me. What is even worse is that this is
>>> something that flags Coverity (legitimately AFAIK), see a patch I have
>>> posted:
>>> https://ffmpeg.org/pipermail/ffmpeg-devel/2015-November/183335.html.
>>>
>>> I do not mind fixing broken code that went past review, but I do mind
>>> when it is recognized, ignored during patch review, and could have
>>> easily been avoided with a 5 minute effort addressing the comment.
>>>
>>> If this continues to be ignored, I am going to find a way to mark
>>> filters as "experimental" and send out a patch, similar to
>>> encoders/decoders. Users should know about problematic filters like
>>> this that have easily recognized problems.
>>
>> Mark something, that leaks on error that never happens, as experimental
>> sounds unfair to me.
>
> Prove to me that "it does not happen". I admit it does rarely do so in
> practice, nevertheless it must be addressed for completeness. However,
> the "experimental" comment is really not for this per se. I made this
> comment simply because it was an issue pointed out in review that you
> failed to address in any way (not even with a simple one-liner saying
> "it does not happen in practice, so I don't care"), and simply pushed
> into master. This was just an illustration of it that I noticed once I
> started reviewing some filters posted by you.
>
> You have called me out in the past for pushing without approval:
> https://ffmpeg.org/pipermail/ffmpeg-devel/2015-October/182511.html,
> which in fact was not even true as they had been approved. This is far
> worse IMO, since the proposed code is actually defective and the
> defect has been pointed out during review.
>
> I appreciate all your hard work and very good effort in getting new
> filters into FFmpeg. What I don't like is your lax standard for
> getting things into the master repo. Unless you start addressing such
> things, I do not consider such a move unfair. All you need to do is
> spend 5-10 minutes of your time per filter actually addressing things
> pointed out by reviewers. That is all I ask.
>
>>
>> Anyway, shouldn't this be called in one place instead of hundreds in each
>> filter?
>
> Then create an API for this if you don't like the way it is being
> done. Until such a thing is done, the issue stands, and must be
> addressed.

I don't think API is needed for this. Just one call in right place IIRC.

There is more than 50 audio filter that needs to be updated to address this?

>
>>
>> This all is really very low hanging fruit to me.
>
> Since you are stating an opinion here, I will state mine below.
>
> So what? I would go so far to say that pulling in filters into FFmpeg
> that already exist in the wild but can't be integrated naturally into
> FFmpeg until some trivial massaging of the code is done is "low
> hanging fruit". Many filters integrated by you are of that nature. In
> fact, I now understand where your "bit exact" concerns come from - you
> simply want to match an existing filter (of course there is nothing
> wrong with that per se). On this note, I freely admit the work I do is
> extremely low hanging fruit. In fact, most human activity is low
> hanging and incremental in nature, we all "stand on the shoulders of
> giants".
>
> That does not change what should be done. A reviewer spends time and
> effort to improve something; you fail to address the reviewer.
>
>>
>>>
>>> BTW (for all FFmpeg users out there though this may be well known),
>>> but for completeness I remark that avfilter tends to be far worse in
>>> Coverity than the other components. It is not a surprise at all given
>>> incidents like this. I entirely sympathize with wm4 regarding the
>>> quality of filters entering the repo.
>>>
>>>> +}
>>>> +
>>>> +static int scconfig_output(AVFilterLink *outlink)
>>>> +{
>>>> +    AVFilterContext *ctx = outlink->src;
>>>> +
>>>> +    if (ctx->inputs[0]->sample_rate != ctx->inputs[1]->sample_rate) {
>>>> +        av_log(ctx, AV_LOG_ERROR,
>>>> +               "Inputs must have the same sample rate "
>>>> +               "%d for in0 vs %d for in1\n",
>>>> +               ctx->inputs[0]->sample_rate,
>>>> ctx->inputs[1]->sample_rate);
>>>> +        return AVERROR(EINVAL);
>>>> +    }
>>>> +
>>>> +    outlink->sample_rate = ctx->inputs[0]->sample_rate;
>>>> +    outlink->time_base   = ctx->inputs[0]->time_base;
>>>> +    outlink->channel_layout = ctx->inputs[0]->channel_layout;
>>>> +    outlink->channels = ctx->inputs[0]->channels;
>>>> +
>>>> +    agate_config_input(ctx->inputs[0]);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static const AVFilterPad sidechaingate_inputs[] = {
>>>> +    {
>>>> +        .name           = "main",
>>>> +        .type           = AVMEDIA_TYPE_AUDIO,
>>>> +        .filter_frame   = scfilter_frame,
>>>> +        .needs_writable = 1,
>>>> +        .needs_fifo     = 1,
>>>> +    },{
>>>> +        .name           = "sidechain",
>>>> +        .type           = AVMEDIA_TYPE_AUDIO,
>>>> +        .filter_frame   = scfilter_frame,
>>>> +        .needs_fifo     = 1,
>>>> +    },
>>>> +    { NULL }
>>>> +};
>>>> +
>>>> +static const AVFilterPad sidechaingate_outputs[] = {
>>>> +    {
>>>> +        .name          = "default",
>>>> +        .type          = AVMEDIA_TYPE_AUDIO,
>>>> +        .config_props  = scconfig_output,
>>>> +        .request_frame = screquest_frame,
>>>> +    },
>>>> +    { NULL }
>>>> +};
>>>> +
>>>> +AVFilter ff_af_sidechaingate = {
>>>> +    .name           = "sidechaingate",
>>>> +    .description    = NULL_IF_CONFIG_SMALL("Audio sidechain gate."),
>>>> +    .priv_size      = sizeof(AudioGateContext),
>>>> +    .priv_class     = &sidechaingate_class,
>>>> +    .query_formats  = scquery_formats,
>>>> +    .inputs         = sidechaingate_inputs,
>>>> +    .outputs        = sidechaingate_outputs,
>>>> +};
>>>> +#endif  /* CONFIG_SIDECHAINGATE_FILTER */
>>>> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
>>>> index ccd3f35..9c3778d 100644
>>>> --- a/libavfilter/allfilters.c
>>>> +++ b/libavfilter/allfilters.c
>>>> @@ -104,6 +104,7 @@ void avfilter_register_all(void)
>>>>      REGISTER_FILTER(RESAMPLE,       resample,       af);
>>>>      REGISTER_FILTER(RUBBERBAND,     rubberband,     af);
>>>>      REGISTER_FILTER(SIDECHAINCOMPRESS, sidechaincompress, af);
>>>> +    REGISTER_FILTER(SIDECHAINGATE,  sidechaingate,  af);
>>>>      REGISTER_FILTER(SILENCEDETECT,  silencedetect,  af);
>>>>      REGISTER_FILTER(SILENCEREMOVE,  silenceremove,  af);
>>>>      REGISTER_FILTER(STEREOTOOLS,    stereotools,    af);
>>>> --
>>>> 1.9.1
>>>>
>>>> _______________________________________________
>>>> ffmpeg-devel mailing list
>>>> ffmpeg-devel at ffmpeg.org
>>>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>> _______________________________________________
>>> ffmpeg-devel mailing list
>>> ffmpeg-devel at ffmpeg.org
>>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>>
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel at ffmpeg.org
>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>


More information about the ffmpeg-devel mailing list