[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