[FFmpeg-devel] [PATCH] afade filter

Nicolas George nicolas.george at normalesup.org
Sat Jan 19 19:44:26 CET 2013


Le decadi 30 nivôse, an CCXXI, Paul B Mahol a écrit :
> Signed-off-by: Paul B Mahol <onemda at gmail.com>
> ---
>  doc/filters.texi         |  63 ++++++++++
>  libavfilter/Makefile     |   1 +
>  libavfilter/af_afade.c   | 316 +++++++++++++++++++++++++++++++++++++++++++++++
>  libavfilter/allfilters.c |   1 +
>  4 files changed, 381 insertions(+)
>  create mode 100644 libavfilter/af_afade.c

Nice.

> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 42c78b8..806ff48 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -282,6 +282,69 @@ aconvert=u8:auto
>  @end example
>  @end itemize
>  
> + at section afade
> +
> +Apply fade-in/out effect to input audio.
> +
> +The filter accepts parameters as a list of @var{key}=@var{value}
> +pairs, separated by ":".
> +
> +A description of the accepted parameters follows.
> +
> + at table @option
> + at item type, t
> +Specify the effect type, can be either @code{in} for fade-in, or
> + at code{out} for a fade-out effect. Default is @code{in}.
> +

> + at item start_sample, s

Can we keep the short "s" option for if someone implements it in seconds
instead of samples? I feel that it would be more user friendly.

> +Specify the number of the start sample for starting to apply the fade
> +effect. Default is 0.
> +
> + at item nb_frames, n
> +Specify the number of samples for which the fade effect has to last. At
> +the end of the fade-in effect the output audio will have the same
> +volume as the input audio, at the end of the fade-out transition
> +the output audio will be silence. Default is 44100.
> +
> + at item curve
> +Set cuve for fade transition.
> + at table @option
> + at item @var{triangular, linear slope}
> + at code{tri}
> + at item @var{quarter of sine wave}
> + at code{qsin}
> + at item @var{half of sine wave}
> + at code{hsin}
> + at item @var{logarithmic}
> + at code{log}
> + at item @var{inverted parabola}
> + at code{par}
> + at item @var{quadratic}
> + at code{qua}
> + at item @var{cubic}
> + at code{cub}
> + at item @var{square root}
> + at code{squ}
> + at item @var{cubic root}
> + at code{cbr}
> + at end table
> + at end table
> +
> + at subsection Examples
> + at itemize
> + at item
> +Fade in first 15 seconds of audio with 44100 sample rate:
> + at example
> +afade=t=in:s=0:n=661500
> + at end example
> +
> + at item
> +Fade out last 25 seconds of a 900 seconds audio with 44100 sample rate:
> + at example
> +afade=t=out:s=38587500:n=102500
> + at end example
> + at end itemize
> +
>  @section aformat
>  
>  Set output format constraints for the input audio. The framework will
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 9bb9fa3..5835a7e 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -51,6 +51,7 @@ OBJS-$(CONFIG_AVFORMAT)                      += lavfutils.o
>  OBJS-$(CONFIG_SWSCALE)                       += lswsutils.o
>  
>  OBJS-$(CONFIG_ACONVERT_FILTER)               += af_aconvert.o
> +OBJS-$(CONFIG_AFADE_FILTER)                  += af_afade.o
>  OBJS-$(CONFIG_AFORMAT_FILTER)                += af_aformat.o
>  OBJS-$(CONFIG_AMERGE_FILTER)                 += af_amerge.o
>  OBJS-$(CONFIG_AMIX_FILTER)                   += af_amix.o
> diff --git a/libavfilter/af_afade.c b/libavfilter/af_afade.c
> new file mode 100644
> index 0000000..cfb93c1
> --- /dev/null
> +++ b/libavfilter/af_afade.c
> @@ -0,0 +1,316 @@
> +/*
> + * Copyright (c) 2013 Paul B Mahol
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * @file
> + * fade audio filter
> + */
> +
> +#include "libavutil/opt.h"
> +#include "audio.h"
> +#include "avfilter.h"
> +#include "internal.h"
> +
> +typedef struct {
> +    const AVClass *class;
> +    int type;
> +    int curve;
> +    int nb_samples;
> +    int64_t start_sample;
> +
> +    void (*fade_samples)(uint8_t **dst, uint8_t **src,
> +                         int nb_samples, int planes,
> +                         int64_t start, int range, int curve);
> +} AudioFadeContext;
> +
> +enum curve {
> +    TRI,
> +    QSIN,
> +    HSIN,
> +    LOG,
> +    PAR,
> +    QUA,
> +    CUB,
> +    SQU,
> +    CBR,
> +};
> +
> +#define OFFSET(x) offsetof(AudioFadeContext, x)
> +#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
> +
> +static const AVOption afade_options[] = {
> +    { "type",         "set the fade direction",                      OFFSET(type),         AV_OPT_TYPE_INT,    {.i64 = 0    }, 0, 1, FLAGS, "type" },
> +    { "t",            "set the fade direction",                      OFFSET(type),         AV_OPT_TYPE_INT,    {.i64 = 0    }, 0, 1, FLAGS, "type" },
> +    { "in",           NULL,                                          0,                    AV_OPT_TYPE_CONST,  {.i64 = 0    }, 0, 0, FLAGS, "type" },
> +    { "out",          NULL,                                          0,                    AV_OPT_TYPE_CONST,  {.i64 = 1    }, 0, 0, FLAGS, "type" },
> +    { "start_sample", "set expression of sample to start fading",    OFFSET(start_sample), AV_OPT_TYPE_INT64,  {.i64 = 0    }, 0, INT64_MAX, FLAGS },
> +    { "s",            "set expression of sample to start fading",    OFFSET(start_sample), AV_OPT_TYPE_INT64,  {.i64 = 0    }, 0, INT64_MAX, FLAGS },
> +    { "nb_samples",   "set expression for fade duration in samples", OFFSET(nb_samples),   AV_OPT_TYPE_INT,    {.i64 = 44100}, 1, INT32_MAX, FLAGS },
> +    { "n",            "set expression for fade duration in samples", OFFSET(nb_samples),   AV_OPT_TYPE_INT,    {.i64 = 44100}, 1, INT32_MAX, FLAGS },
> +    { "curve",        "set expression for fade curve",               OFFSET(curve),        AV_OPT_TYPE_INT,    {.i64 = TRI  }, TRI, CBR, FLAGS, "curve" },
> +    { "c",            "set expression for fade curve",               OFFSET(curve),        AV_OPT_TYPE_INT,    {.i64 = TRI  }, TRI, CBR, FLAGS, "curve" },
> +    { "tri",          "linear slope",                                0,                    AV_OPT_TYPE_CONST,  {.i64 = TRI  }, 0, 0, FLAGS, "curve" },
> +    { "qsin",         "quarter of sine wave",                        0,                    AV_OPT_TYPE_CONST,  {.i64 = QSIN }, 0, 0, FLAGS, "curve" },
> +    { "hsin",         "half of sine wave",                           0,                    AV_OPT_TYPE_CONST,  {.i64 = HSIN }, 0, 0, FLAGS, "curve" },
> +    { "log",          "logarithmic",                                 0,                    AV_OPT_TYPE_CONST,  {.i64 = LOG  }, 0, 0, FLAGS, "curve" },
> +    { "par",          "inverted parabola",                           0,                    AV_OPT_TYPE_CONST,  {.i64 = PAR  }, 0, 0, FLAGS, "curve" },
> +    { "qua",          "quadratic",                                   0,                    AV_OPT_TYPE_CONST,  {.i64 = QUA  }, 0, 0, FLAGS, "curve" },
> +    { "cub",          "cubic",                                       0,                    AV_OPT_TYPE_CONST,  {.i64 = CUB  }, 0, 0, FLAGS, "curve" },
> +    { "squ",          "square root",                                 0,                    AV_OPT_TYPE_CONST,  {.i64 = SQU  }, 0, 0, FLAGS, "curve" },
> +    { "cbr",          "cubic root",                                  0,                    AV_OPT_TYPE_CONST,  {.i64 = CBR  }, 0, 0, FLAGS, "curve" },
> +    {NULL},
> +};
> +
> +AVFILTER_DEFINE_CLASS(afade);
> +
> +static av_cold int init(AVFilterContext *ctx, const char *args)
> +{
> +    AudioFadeContext *afade = ctx->priv;
> +    int ret;
> +
> +    afade->class = &afade_class;
> +    av_opt_set_defaults(afade);
> +
> +    if ((ret = av_set_options_string(afade, args, "=", ":")) < 0)
> +        return ret;
> +

> +    if (INT64_MAX - afade->nb_samples < afade->start_sample)
> +        return AVERROR(EINVAL);

Error message?

> +
> +    return 0;
> +}
> +
> +static double fade_gain(int curve, int index, int range)
> +{
> +    double gain;
> +
> +    gain = FFMAX(0.0, FFMIN(1.0, 1.0 * index / range));
> +
> +    switch (curve) {
> +    case QSIN:
> +        gain = sin(gain * M_PI / 2.0);
> +        break;
> +    case HSIN:
> +        gain = (1.0 - cos(gain * M_PI)) / 2.0;
> +        break;
> +    case LOG:
> +        gain = pow(0.1, (1 - gain) * 5.0);
> +        break;
> +    case PAR:
> +        gain = (1 - (1 - gain) * (1 - gain));
> +        break;
> +    case QUA:
> +        gain *= gain;
> +        break;
> +    case CUB:
> +        gain = gain * gain * gain;
> +        break;
> +    case SQU:
> +        gain = sqrt(gain);
> +        break;
> +    case CBR:
> +        gain = cbrt(gain);
> +        break;
> +    }
> +
> +    return gain;
> +}

I wonder whether a function pointer would be more efficient than a switch.
But it probably does not matter much.

> +
> +static void fade_samples_u8(uint8_t **dst, uint8_t **src,
> +                            int nb_samples, int planes,
> +                            int64_t start, int fade_range, int curve)
> +{
> +    int i, p;
> +
> +    for (p = 0; p < planes; p++) {
> +        uint8_t *d = dst[p];
> +        const uint8_t *s = src[p];
> +
> +        for (i = 0; i < nb_samples; i++) {
> +            d[i] = av_clip_uint8(((int64_t)s[i] - 128) *
> +                   fade_gain(curve, start + i, fade_range) + 128);
> +        }
> +    }
> +}
> +
> +static void fade_samples_s16(uint8_t **dst, uint8_t **src,
> +                             int nb_samples, int planes,
> +                             int64_t start, int fade_range, int curve)
> +{
> +    int i, p;
> +
> +    for (p = 0; p < planes; p++) {
> +        int16_t *d = (int16_t *)dst[p];
> +        const int16_t *s = (int16_t *)src[p];
> +
> +        for (i = 0; i < nb_samples; i++) {
> +            d[i] = s[i] * fade_gain(curve, start + i, fade_range);
> +        }
> +    }
> +}
> +
> +static void fade_samples_s32(uint8_t **dst, uint8_t **src,
> +                             int nb_samples, int planes,
> +                             int64_t start, int fade_range, int curve)
> +{
> +    int i, p;
> +
> +    for (p = 0; p < planes; p++) {
> +        int32_t *d = (int32_t *)dst[p];
> +        const int32_t *s = (int32_t *)src[p];
> +
> +        for (i = 0; i < nb_samples; i++) {
> +            d[i] = s[i] * fade_gain(curve, start + i, fade_range);
> +        }
> +    }
> +}
> +
> +static void fade_samples_flt(uint8_t **dst, uint8_t **src,
> +                             int nb_samples, int planes,
> +                             int64_t start, int fade_range, int curve)
> +{
> +    int i, p;
> +
> +    for (p = 0; p < planes; p++) {
> +        float *d = (float *)dst[p];
> +        const float *s = (float *)src[p];
> +
> +        for (i = 0; i < nb_samples; i++) {
> +            d[i] = s[i] * fade_gain(curve, start + i, fade_range);
> +        }
> +    }
> +}
> +
> +static void fade_samples_dbl(uint8_t **dst, uint8_t **src,
> +                             int nb_samples, int planes,
> +                             int64_t start, int fade_range, int curve)
> +{
> +    int i, p;
> +
> +    for (p = 0; p < planes; p++) {
> +        double *d = (double *)dst[p];
> +        const double *s = (double *)src[p];
> +
> +        for (i = 0; i < nb_samples; i++) {
> +            d[i] = s[i] * fade_gain(curve, start + i, fade_range);
> +        }
> +    }
> +}

A macro could probably allow to define the five functions with much less
lines of code.

> +
> +static int config_output(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx    = outlink->src;
> +    AudioFadeContext *afade = ctx->priv;
> +    AVFilterLink *inlink    = ctx->inputs[0];
> +
> +    switch (av_get_packed_sample_fmt(inlink->format)) {
> +    case AV_SAMPLE_FMT_U8:
> +        afade->fade_samples = fade_samples_u8;
> +        break;
> +    case AV_SAMPLE_FMT_S16:
> +        afade->fade_samples = fade_samples_s16;
> +        break;
> +    case AV_SAMPLE_FMT_S32:
> +        afade->fade_samples = fade_samples_s32;
> +        break;
> +    case AV_SAMPLE_FMT_FLT:
> +        afade->fade_samples = fade_samples_flt;
> +        break;
> +    case AV_SAMPLE_FMT_DBL:
> +        afade->fade_samples = fade_samples_dbl;
> +        break;
> +    }
> +
> +    return 0;
> +}
> +
> +static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *buf)
> +{
> +    AudioFadeContext *afade = inlink->dst->priv;
> +    AVFilterLink *outlink   = inlink->dst->outputs[0];
> +    int nb_samples          = buf->audio->nb_samples;
> +    AVFilterBufferRef *out_buf;
> +    int planes = av_sample_fmt_is_planar(buf->format) ? buf->audio->channels : 1;
> +    int plane_samples = planes > 1 ? nb_samples : nb_samples * buf->audio->channels;
> +    int64_t cur_sample = av_rescale_q(buf->pts, (AVRational){1, outlink->sample_rate}, outlink->time_base);
> +
> +    if ((!afade->type && (afade->start_sample + afade->nb_samples < cur_sample)) ||
> +        ( afade->type && (cur_sample + afade->nb_samples < afade->start_sample)))
> +        return ff_filter_frame(outlink, buf);
> +

> +    if (buf->perms & AV_PERM_WRITE) {
> +        out_buf = buf;
> +    } else {
> +        out_buf = ff_get_audio_buffer(inlink, AV_PERM_WRITE, nb_samples);
> +        if (!out_buf)
> +            return AVERROR(ENOMEM);
> +        out_buf->pts = buf->pts;
> +    }

Did you test the case where AV_PERM_WRITE is missing? At first glance, it
looks like it is missing the copy of the unmodified original sample.

> +
> +    if ((!afade->type && (cur_sample + nb_samples < afade->start_sample)) ||
> +        ( afade->type && (afade->start_sample + afade->nb_samples < cur_sample))) {
> +        av_samples_set_silence(out_buf->extended_data, 0, nb_samples,
> +                               out_buf->audio->channels, out_buf->format);
> +    } else {
> +        int64_t start;
> +
> +        if (!afade->type)
> +            start = cur_sample - afade->start_sample;
> +        else
> +            start = afade->start_sample + afade->nb_samples - cur_sample;
> +
> +        afade->fade_samples(out_buf->extended_data, buf->extended_data,
> +                            plane_samples, planes, start,
> +                            afade->nb_samples, afade->curve);
> +    }
> +
> +    if (buf != out_buf)
> +        avfilter_unref_buffer(buf);
> +
> +    return ff_filter_frame(outlink, out_buf);
> +}
> +
> +static const AVFilterPad avfilter_af_afade_inputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_AUDIO,
> +        .filter_frame = filter_frame,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad avfilter_af_afade_outputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_AUDIO,
> +        .config_props = config_output,
> +    },
> +    { NULL }
> +};
> +
> +AVFilter avfilter_af_afade = {
> +    .name          = "afade",
> +    .description   = NULL_IF_CONFIG_SMALL("Fade in/out input audio."),
> +    .priv_size     = sizeof(AudioFadeContext),
> +    .init          = init,
> +    .inputs        = avfilter_af_afade_inputs,
> +    .outputs       = avfilter_af_afade_outputs,
> +    .priv_class    = &afade_class,
> +};
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 4815c4a..24df561 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -45,6 +45,7 @@ void avfilter_register_all(void)
>      initialized = 1;
>  
>      REGISTER_FILTER(ACONVERT,       aconvert,       af);
> +    REGISTER_FILTER(AFADE,          afade,          af);
>      REGISTER_FILTER(AFORMAT,        aformat,        af);
>      REGISTER_FILTER(AMERGE,         amerge,         af);
>      REGISTER_FILTER(AMIX,           amix,           af);

Regards,

-- 
  Nicolas George
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 198 bytes
Desc: Digital signature
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20130119/da024185/attachment.asc>


More information about the ffmpeg-devel mailing list