diff options
author | Paul B Mahol <onemda@gmail.com> | 2016-01-16 15:09:25 +0100 |
---|---|---|
committer | Paul B Mahol <onemda@gmail.com> | 2016-01-21 14:31:38 +0100 |
commit | fa04ec728da3f1acf65e82728f76d48d142a2a7e (patch) | |
tree | a82321efe765de19732870cd13561ba0736ae179 | |
parent | 56c182c4d716b8b72156ff57a0c8d72087db8549 (diff) | |
download | ffmpeg-fa04ec728da3f1acf65e82728f76d48d142a2a7e.tar.gz |
avfilter: add afftfilter
Signed-off-by: Paul B Mahol <onemda@gmail.com>
-rw-r--r-- | Changelog | 1 | ||||
-rwxr-xr-x | configure | 3 | ||||
-rw-r--r-- | doc/filters.texi | 77 | ||||
-rw-r--r-- | libavfilter/Makefile | 1 | ||||
-rw-r--r-- | libavfilter/af_afftfilt.c | 401 | ||||
-rw-r--r-- | libavfilter/allfilters.c | 1 | ||||
-rw-r--r-- | libavfilter/version.h | 2 |
7 files changed, 485 insertions, 1 deletions
@@ -56,6 +56,7 @@ version <next>: - ahistogram filter - only seek with the right mouse button in ffplay - toggle full screen when double-clicking with the left mouse button in ffplay +- afftfilt filter version 2.8: @@ -2841,6 +2841,8 @@ unix_protocol_deps="sys_un_h" unix_protocol_select="network" # filters +afftfilt_filter_deps="avcodec" +afftfilt_filter_select="fft" amovie_filter_deps="avcodec avformat" aresample_filter_deps="swresample" ass_filter_deps="libass" @@ -6065,6 +6067,7 @@ done enabled zlib && add_cppflags -DZLIB_CONST # conditional library dependencies, in linking order +enabled afftfilt_filter && prepend avfilter_deps "avcodec" enabled amovie_filter && prepend avfilter_deps "avformat avcodec" enabled aresample_filter && prepend avfilter_deps "swresample" enabled asyncts_filter && prepend avfilter_deps "avresample" diff --git a/doc/filters.texi b/doc/filters.texi index f5f4bfc3c5..053e229a5e 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -733,6 +733,83 @@ afade=t=out:st=875:d=25 @end example @end itemize +@section afftfilt +Apply arbitrary expressions to samples in frequency domain. + +@table @option +@item real +Set frequency domain real expression for each separate channel separated +by '|'. Default is "1". +If the number of input channels is greater than the number of +expressions, the last specified expression is used for the remaining +output channels. + +@item imag +Set frequency domain imaginary expression for each separate channel +separated by '|'. If not set, @var{real} option is used. + +Each expression in @var{real} and @var{imag} can contain the following +constants: + +@table @option +@item sr +sample rate + +@item b +current frequency bin number + +@item nb +number of available bins + +@item ch +channel number of the current expression + +@item chs +number of channels + +@item pts +current frame pts +@end table + +@item win_size +Set window size. + +It accepts the following values: +@table @samp +@item w16 +@item w32 +@item w64 +@item w128 +@item w256 +@item w512 +@item w1024 +@item w2048 +@item w4096 +@item w8192 +@item w16384 +@item w32768 +@item w65536 +@end table +Default is @code{w4096} + +@item win_func +Set window function. Default is @code{hann}. + +@item overlap +Set window overlap. If set to 1, the recommended overlap for selected +window function will be picked. Default is @code{0.75}. +@end table + +@subsection Examples + +@itemize +@item +Increase first 50 bins by 0.1 and lower all other frequencies by factor of 10: +@example +afftfilt="1.1*between(b\,0\,49)+0.1*between(b\,50\,f)" +@end example +@end itemize + @anchor{aformat} @section aformat diff --git a/libavfilter/Makefile b/libavfilter/Makefile index e3e3561933..242f56dbf3 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -29,6 +29,7 @@ OBJS-$(CONFIG_ACROSSFADE_FILTER) += af_afade.o OBJS-$(CONFIG_ADELAY_FILTER) += af_adelay.o OBJS-$(CONFIG_AECHO_FILTER) += af_aecho.o OBJS-$(CONFIG_AEMPHASIS_FILTER) += af_aemphasis.o +OBJS-$(CONFIG_AFFTFILT_FILTER) += af_afftfilt.o window_func.o OBJS-$(CONFIG_ANEQUALIZER_FILTER) += af_anequalizer.o OBJS-$(CONFIG_AEVAL_FILTER) += aeval.o OBJS-$(CONFIG_AFADE_FILTER) += af_afade.o diff --git a/libavfilter/af_afftfilt.c b/libavfilter/af_afftfilt.c new file mode 100644 index 0000000000..8c75b4f0ae --- /dev/null +++ b/libavfilter/af_afftfilt.c @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2016 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 + */ + +#include "libavutil/audio_fifo.h" +#include "libavutil/avstring.h" +#include "libavfilter/internal.h" +#include "libavutil/common.h" +#include "libavutil/opt.h" +#include "libavcodec/avfft.h" +#include "libavutil/eval.h" +#include "audio.h" +#include "window_func.h" + +typedef struct AFFTFiltContext { + const AVClass *class; + char *real_str; + char *img_str; + int fft_bits; + + FFTContext *fft, *ifft; + FFTComplex **fft_data; + int nb_exprs; + int window_size; + AVExpr **real; + AVExpr **imag; + AVAudioFifo *fifo; + int64_t pts; + int hop_size; + float overlap; + AVFrame *buffer; + int start, end; + int win_func; + float win_scale; + float *window_func_lut; +} AFFTFiltContext; + +static const char *const var_names[] = { "sr", "b", "nb", "ch", "chs", "pts", NULL }; +enum { VAR_SAMPLE_RATE, VAR_BIN, VAR_NBBINS, VAR_CHANNEL, VAR_CHANNELS, VAR_PTS, VAR_VARS_NB }; + +#define OFFSET(x) offsetof(AFFTFiltContext, x) +#define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption afftfilt_options[] = { + { "real", "set channels real expressions", OFFSET(real_str), AV_OPT_TYPE_STRING, {.str = "1" }, 0, 0, A }, + { "imag", "set channels imaginary expressions", OFFSET(img_str), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, A }, + { "win_size", "set window size", OFFSET(fft_bits), AV_OPT_TYPE_INT, {.i64=12}, 4, 16, A, "fft" }, + { "w16", 0, 0, AV_OPT_TYPE_CONST, {.i64=4}, 0, 0, A, "fft" }, + { "w32", 0, 0, AV_OPT_TYPE_CONST, {.i64=5}, 0, 0, A, "fft" }, + { "w64", 0, 0, AV_OPT_TYPE_CONST, {.i64=6}, 0, 0, A, "fft" }, + { "w128", 0, 0, AV_OPT_TYPE_CONST, {.i64=7}, 0, 0, A, "fft" }, + { "w256", 0, 0, AV_OPT_TYPE_CONST, {.i64=8}, 0, 0, A, "fft" }, + { "w512", 0, 0, AV_OPT_TYPE_CONST, {.i64=9}, 0, 0, A, "fft" }, + { "w1024", 0, 0, AV_OPT_TYPE_CONST, {.i64=10}, 0, 0, A, "fft" }, + { "w2048", 0, 0, AV_OPT_TYPE_CONST, {.i64=11}, 0, 0, A, "fft" }, + { "w4096", 0, 0, AV_OPT_TYPE_CONST, {.i64=12}, 0, 0, A, "fft" }, + { "w8192", 0, 0, AV_OPT_TYPE_CONST, {.i64=13}, 0, 0, A, "fft" }, + { "w16384", 0, 0, AV_OPT_TYPE_CONST, {.i64=14}, 0, 0, A, "fft" }, + { "w32768", 0, 0, AV_OPT_TYPE_CONST, {.i64=15}, 0, 0, A, "fft" }, + { "w65536", 0, 0, AV_OPT_TYPE_CONST, {.i64=16}, 0, 0, A, "fft" }, + { "win_func", "set window function", OFFSET(win_func), AV_OPT_TYPE_INT, {.i64 = WFUNC_HANNING}, 0, NB_WFUNC-1, A, "win_func" }, + { "rect", "Rectangular", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_RECT}, 0, 0, A, "win_func" }, + { "bartlett", "Bartlett", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_BARTLETT}, 0, 0, A, "win_func" }, + { "hann", "Hann", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HANNING}, 0, 0, A, "win_func" }, + { "hanning", "Hanning", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HANNING}, 0, 0, A, "win_func" }, + { "hamming", "Hamming", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_HAMMING}, 0, 0, A, "win_func" }, + { "sine", "Sine", 0, AV_OPT_TYPE_CONST, {.i64=WFUNC_SINE}, 0, 0, A, "win_func" }, + { "overlap", "set window overlap", OFFSET(overlap), AV_OPT_TYPE_FLOAT, {.dbl=0.75}, 0, 1, A }, + { NULL }, +}; + +AVFILTER_DEFINE_CLASS(afftfilt); + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + AFFTFiltContext *s = ctx->priv; + char *saveptr = NULL; + int ret = 0, ch, i; + float overlap; + char *args, *last_expr = NULL; + + s->fft = av_fft_init(s->fft_bits, 0); + s->ifft = av_fft_init(s->fft_bits, 1); + if (!s->fft || !s->ifft) + return AVERROR(ENOMEM); + + s->window_size = 1 << s->fft_bits; + + s->fft_data = av_calloc(inlink->channels, sizeof(*s->fft_data)); + if (!s->fft_data) + return AVERROR(ENOMEM); + + for (ch = 0; ch < inlink->channels; ch++) { + s->fft_data[ch] = av_calloc(s->window_size, sizeof(**s->fft_data)); + if (!s->fft_data[ch]) + return AVERROR(ENOMEM); + } + + s->real = av_calloc(inlink->channels, sizeof(*s->real)); + if (!s->real) + return AVERROR(ENOMEM); + + s->imag = av_calloc(inlink->channels, sizeof(*s->imag)); + if (!s->imag) + return AVERROR(ENOMEM); + + args = av_strdup(s->real_str); + if (!args) + return AVERROR(ENOMEM); + + for (ch = 0; ch < inlink->channels; ch++) { + char *arg = av_strtok(ch == 0 ? args : NULL, "|", &saveptr); + + ret = av_expr_parse(&s->real[ch], arg ? arg : last_expr, var_names, + NULL, NULL, NULL, NULL, 0, ctx); + if (ret < 0) + break; + if (arg) + last_expr = arg; + s->nb_exprs++; + } + + av_free(args); + + args = av_strdup(s->img_str ? s->img_str : s->real_str); + if (!args) + return AVERROR(ENOMEM); + + for (ch = 0; ch < inlink->channels; ch++) { + char *arg = av_strtok(ch == 0 ? args : NULL, "|", &saveptr); + + ret = av_expr_parse(&s->imag[ch], arg ? arg : last_expr, var_names, + NULL, NULL, NULL, NULL, 0, ctx); + if (ret < 0) + break; + if (arg) + last_expr = arg; + } + + av_free(args); + + s->fifo = av_audio_fifo_alloc(inlink->format, inlink->channels, s->window_size); + if (!s->fifo) + return AVERROR(ENOMEM); + + s->window_func_lut = av_realloc_f(s->window_func_lut, s->window_size, + sizeof(*s->window_func_lut)); + if (!s->window_func_lut) + return AVERROR(ENOMEM); + ff_generate_window_func(s->window_func_lut, s->window_size, s->win_func, &overlap); + if (s->overlap == 1) + s->overlap = overlap; + + for (s->win_scale = 0, i = 0; i < s->window_size; i++) { + s->win_scale += s->window_func_lut[i] * s->window_func_lut[i]; + } + + s->hop_size = s->window_size * (1 - s->overlap); + if (s->hop_size <= 0) + return AVERROR(EINVAL); + + s->buffer = ff_get_audio_buffer(inlink, s->window_size * 2); + if (!s->buffer) + return AVERROR(ENOMEM); + + return ret; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + AFFTFiltContext *s = ctx->priv; + const int window_size = s->window_size; + const float f = 1. / s->win_scale; + double values[VAR_VARS_NB]; + AVFrame *out, *in = NULL; + int ch, n, ret, i, j, k; + int start = s->start, end = s->end; + + av_audio_fifo_write(s->fifo, (void **)frame->extended_data, frame->nb_samples); + av_frame_free(&frame); + + while (av_audio_fifo_size(s->fifo) >= window_size) { + if (!in) { + in = ff_get_audio_buffer(outlink, window_size); + if (!in) + return AVERROR(ENOMEM); + } + + ret = av_audio_fifo_peek(s->fifo, (void **)in->extended_data, window_size); + if (ret < 0) + break; + + for (ch = 0; ch < inlink->channels; ch++) { + const float *src = (float *)in->extended_data[ch]; + FFTComplex *fft_data = s->fft_data[ch]; + + for (n = 0; n < in->nb_samples; n++) { + fft_data[n].re = src[n] * s->window_func_lut[n]; + fft_data[n].im = 0; + } + + for (; n < window_size; n++) { + fft_data[n].re = 0; + fft_data[n].im = 0; + } + } + + values[VAR_PTS] = s->pts; + values[VAR_SAMPLE_RATE] = inlink->sample_rate; + values[VAR_NBBINS] = window_size / 2; + values[VAR_CHANNELS] = inlink->channels; + + for (ch = 0; ch < inlink->channels; ch++) { + FFTComplex *fft_data = s->fft_data[ch]; + float *buf = (float *)s->buffer->extended_data[ch]; + int x; + + values[VAR_CHANNEL] = ch; + + av_fft_permute(s->fft, fft_data); + av_fft_calc(s->fft, fft_data); + + for (n = 0; n < window_size / 2; n++) { + float fr, fi; + + values[VAR_BIN] = n; + + fr = av_expr_eval(s->real[ch], values, s); + fi = av_expr_eval(s->imag[ch], values, s); + + fft_data[n].re *= fr; + fft_data[n].im *= fi; + } + + for (n = window_size / 2 + 1, x = window_size / 2 - 1; n < window_size; n++, x--) { + fft_data[n].re = fft_data[x].re; + fft_data[n].im = -fft_data[x].im; + } + + av_fft_permute(s->ifft, fft_data); + av_fft_calc(s->ifft, fft_data); + + start = s->start; + end = s->end; + k = end; + for (i = 0, j = start; j < k && i < window_size; i++, j++) { + buf[j] += s->fft_data[ch][i].re * f; + } + + for (; i < window_size; i++, j++) { + buf[j] = s->fft_data[ch][i].re * f; + } + + start += s->hop_size; + end = j; + } + + s->start = start; + s->end = end; + + if (start >= window_size) { + float *dst, *buf; + + start -= window_size; + end -= window_size; + + s->start = start; + s->end = end; + + out = ff_get_audio_buffer(outlink, window_size); + if (!out) { + ret = AVERROR(ENOMEM); + break; + } + + out->pts = s->pts; + s->pts += window_size; + + for (ch = 0; ch < inlink->channels; ch++) { + dst = (float *)out->extended_data[ch]; + buf = (float *)s->buffer->extended_data[ch]; + + for (n = 0; n < window_size; n++) { + dst[n] = buf[n] * (1 - s->overlap); + } + memmove(buf, buf + window_size, window_size * 4); + } + + ret = ff_filter_frame(outlink, out); + if (ret < 0) + break; + } + + av_audio_fifo_drain(s->fifo, s->hop_size); + } + + av_frame_free(&in); + return ret; +} + +static int query_formats(AVFilterContext *ctx) +{ + AVFilterFormats *formats; + AVFilterChannelLayouts *layouts; + static const enum AVSampleFormat sample_fmts[] = { + AV_SAMPLE_FMT_FLTP, + AV_SAMPLE_FMT_NONE + }; + int ret; + + layouts = ff_all_channel_counts(); + if (!layouts) + return AVERROR(ENOMEM); + ret = ff_set_common_channel_layouts(ctx, layouts); + if (ret < 0) + return ret; + + formats = ff_make_format_list(sample_fmts); + if (!formats) + return AVERROR(ENOMEM); + ret = ff_set_common_formats(ctx, formats); + if (ret < 0) + return ret; + + formats = ff_all_samplerates(); + if (!formats) + return AVERROR(ENOMEM); + return ff_set_common_samplerates(ctx, formats); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + AFFTFiltContext *s = ctx->priv; + int i; + + av_fft_end(s->fft); + av_fft_end(s->ifft); + + for (i = 0; i < s->nb_exprs; i++) { + if (s->fft_data) + av_freep(&s->fft_data[i]); + } + av_freep(&s->fft_data); + + for (i = 0; i < s->nb_exprs; i++) { + av_expr_free(s->real[i]); + av_expr_free(s->imag[i]); + } + + av_freep(&s->real); + av_freep(&s->imag); + av_frame_free(&s->buffer); +} + +static const AVFilterPad inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .config_props = config_input, + .filter_frame = filter_frame, + }, + { NULL } +}; + +static const AVFilterPad outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + }, + { NULL } +}; + +AVFilter ff_af_afftfilt = { + .name = "afftfilt", + .description = NULL_IF_CONFIG_SMALL("Apply arbitrary expressions to samples in frequency domain."), + .priv_size = sizeof(AFFTFiltContext), + .priv_class = &afftfilt_class, + .inputs = inputs, + .outputs = outputs, + .query_formats = query_formats, + .uninit = uninit, +}; diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 1faf39301a..f270bdf3a2 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -52,6 +52,7 @@ void avfilter_register_all(void) REGISTER_FILTER(AEMPHASIS, aemphasis, af); REGISTER_FILTER(AEVAL, aeval, af); REGISTER_FILTER(AFADE, afade, af); + REGISTER_FILTER(AFFTFILT, afftfilt, af); REGISTER_FILTER(AFORMAT, aformat, af); REGISTER_FILTER(AGATE, agate, af); REGISTER_FILTER(AINTERLEAVE, ainterleave, af); diff --git a/libavfilter/version.h b/libavfilter/version.h index d1f3802b68..e6c7aebd8f 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFILTER_VERSION_MAJOR 6 -#define LIBAVFILTER_VERSION_MINOR 25 +#define LIBAVFILTER_VERSION_MINOR 26 #define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ |