From patchwork Sat Jun 17 16:00:01 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 4008 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.22.4 with SMTP id 4csp162904vsw; Sat, 17 Jun 2017 09:00:22 -0700 (PDT) X-Received: by 10.223.164.214 with SMTP id h22mr11605678wrb.174.1497715222663; Sat, 17 Jun 2017 09:00:22 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1497715222; cv=none; d=google.com; s=arc-20160816; b=fzozF/T8ayABn25Pef2o/33ObPwXYg8EYzs/SMiypg0SiQSbDLZX4qoaYG171Vmt7m dwqsXR4cDDTNRGriQsD/VzfnqX0X5MivAcoE4Txqv8JF8ui2Z9ok21R5VdlLCnZ0E704 shn7edxjMmi7dJjSwOOy4uQH9e+4STHHhmIMuNTuO3CLBspAiInTv77JMUyY9zyxW012 ZHd8IjBiSJX9JgkBEHqG2odcWD4B3dKrFyGIh02EwThZwm39aCTcbDPoSm0MOnH1K662 nTdgUevUsuNfOIFYqyTNKq7n/NIfwsWXZkWD3TPrPZVsb8Ni3gXJfwd2DjDqBFa9cmgd rmVA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:message-id:date:to:from:dkim-signature :delivered-to:arc-authentication-results; bh=RRXY8hleKxZW3PdOngRM2RfpEcaHajZKFGlAhyk+uiY=; b=InPRXElCeN34LYosfO+8e3Qk7jAS5/Y74/YLRO3upk74AMoeQ9Ua9pm124BOIWR3Zr NMXt+UHiNhXasQaUGDCZMFrp9uzBvhpXr55qi1Gxf5PnLetn/jU+sy3UovaT/bosXN7/ ynwFSsz9F0FgjnOEXil3+XpJ3b/P+WlloMSbgNnCn6oT0ZaRpBpKTcKxWmwwkFrq0Nkr QqjdXWWaspb7atASeHTmyK2m0GRV94QbdLHpOOhB+a3C2xj1f3jz9pG2MygqG7nfaM9g Us6g5qrFv7vYmc44l7PSlQUwt6JBZL8are+pj8f0LYlB5N4P+M2z/Z3AlU8NMGs9nxoj twcw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.b=ay9yp/UU; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id i6si5809354wra.141.2017.06.17.09.00.21; Sat, 17 Jun 2017 09:00:22 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.b=ay9yp/UU; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 53851689F98; Sat, 17 Jun 2017 19:00:14 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wm0-f65.google.com (mail-wm0-f65.google.com [74.125.82.65]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 4C7D2689F84 for ; Sat, 17 Jun 2017 19:00:08 +0300 (EEST) Received: by mail-wm0-f65.google.com with SMTP id f90so9865432wmh.0 for ; Sat, 17 Jun 2017 09:00:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=OHhj9tgb/dP3isl+U16Hlqcnx22I3g2MXvRJpEJ4unc=; b=ay9yp/UUXXsSwPEXYdJhAOV7yuQ45ROcMMrZtdW8WxiXCED1ItMz1eimYENLoxL0uL jNUI1VmoEiyh8jolxFeqfW6ovkdoscDYmLpiMHaYLaphuA1O+SJR3Vr1tpo4fuIX8Xbi JelE72x9Uhw6DRlSDU9Ag9/CP0Uz83SXfoUk9KuEBVp9uoJQXP9D353LUGDuSqm3qge6 06eP+MiXvTf3p0CDFxWTd1Y9zEltRUoXbXz2o2bGZa0NJw8lOO9KfEpK4KhUIldP70rF ZXVEyLl2UvuI3B6KgRJVv0o/8NK7BTIzggNZjHQ1EcYtBSdGmPFgsdkxP6mzK7Cf5Nel jJgg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=OHhj9tgb/dP3isl+U16Hlqcnx22I3g2MXvRJpEJ4unc=; b=R66GLTWlb8fZvtckteUs2/Y3MaWDryuFT8OEpAgqMllJ4pk1UrEw7qvJ6t4OebkHbW 7zbPRfT6N62r0LN1X+z+Df3tWbQIUWJ3dkLkh+8f3daTePCop8t+K99+VkVlbf22B69j zCozQUrJAXWy6atqAAIyC7qAL741VNdRV+11NL3eiZ4UWjjesQxp4/jb4sqIfgEWIfqg QliomwZ7taQ1W/BrGymD9hiMiYGgwZik9eOu/LiWv8zNKJ3X12l6yTbYH1HWdNFn8pFC GWSVvV8VT2cin6ZWJCH12yrEFDeHd2QPTc4/3c7M+hJ6m21lNd2pU1XHXRXdnhaZvFPH 8MRw== X-Gm-Message-State: AKS2vOxc4bIZgA0/G+qf2sekmRv02yJpyQWjOQZVVYmP+uSYuU2M1dAO 7hzHgJur14fGAeAM X-Received: by 10.28.22.65 with SMTP id 62mr10240787wmw.37.1497715211505; Sat, 17 Jun 2017 09:00:11 -0700 (PDT) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id e24sm4978961wre.54.2017.06.17.09.00.09 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 17 Jun 2017 09:00:10 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sat, 17 Jun 2017 18:00:01 +0200 Message-Id: <20170617160001.19604-1-onemda@gmail.com> X-Mailer: git-send-email 2.9.3 Subject: [FFmpeg-devel] [PATCH] avfilter: add superequalizer filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- libavfilter/Makefile | 1 + libavfilter/af_superequalizer.c | 381 ++++++++++++++++++++++++++++++++++++++++ libavfilter/allfilters.c | 1 + 3 files changed, 383 insertions(+) create mode 100644 libavfilter/af_superequalizer.c diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 04ec9b8..52c44d2 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -109,6 +109,7 @@ OBJS-$(CONFIG_SILENCEREMOVE_FILTER) += af_silenceremove.o OBJS-$(CONFIG_SOFALIZER_FILTER) += af_sofalizer.o OBJS-$(CONFIG_STEREOTOOLS_FILTER) += af_stereotools.o OBJS-$(CONFIG_STEREOWIDEN_FILTER) += af_stereowiden.o +OBJS-$(CONFIG_SUPEREQUALIZER_FILTER) += af_superequalizer.o OBJS-$(CONFIG_SURROUND_FILTER) += af_surround.o OBJS-$(CONFIG_TREBLE_FILTER) += af_biquads.o OBJS-$(CONFIG_TREMOLO_FILTER) += af_tremolo.o diff --git a/libavfilter/af_superequalizer.c b/libavfilter/af_superequalizer.c new file mode 100644 index 0000000..c569394 --- /dev/null +++ b/libavfilter/af_superequalizer.c @@ -0,0 +1,381 @@ +/* + * 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/opt.h" + +#include "libavcodec/avfft.h" + +#include "audio.h" +#include "avfilter.h" +#include "internal.h" + +#define NBANDS 17 +#define M 15 + +typedef struct EqParameter { + float lower, upper, gain; +} EqParameter; + +typedef struct SuperEqualizerContext { + const AVClass *class; + + EqParameter params[NBANDS+1]; + + float gains[NBANDS+1]; + + float fact[M+1]; + float aa; + float iza; + float *lires, *rires, *irest; + float *fsamples; + int chg_ires, cur_ires; + int winlen, winlenbit, tabsize; + + AVFrame *in, *out; + RDFTContext *rdft, *irdft; +} SuperEqualizerContext; + +static const float bands[] = { + 65.406392, 92.498606, 130.81278, 184.99721, 261.62557, 369.99442, 523.25113, 739.9884, 1046.5023, + 1479.9768, 2093.0045, 2959.9536, 4186.0091, 5919.9072, 8372.0181, 11839.814, 16744.036 +}; + +static float izero(SuperEqualizerContext *s, float x) +{ + float ret = 1; + int m; + + for (m = 1; m <= M; m++) { + float t; + + t = pow(x / 2, m) / s->fact[m]; + ret += t*t; + } + + return ret; +} + +static float hn_lpf(int n, float f, float fs) +{ + float t = 1 / fs; + float omega = 2 * M_PI * f; + + if (n * omega * t == 0) + return 2 * f * t; + return 2 * f * t * sin(n * omega * t)/(n * omega * t); +} + +static float hn_imp(int n) +{ + return n == 0 ? 1.0 : 0.0; +} + +static float hn(int n, EqParameter *param, float fs) +{ + float ret, lhn; + int i; + + lhn = hn_lpf(n, param[0].upper, fs); + ret = param[0].gain*lhn; + + for (i = 1; i < NBANDS+1 && param[i].upper < fs / 2; i++) { + float lhn2 = hn_lpf(n, param[i].upper, fs); + ret += param[i].gain * (lhn2-lhn); + lhn = lhn2; + } + + ret += param[i].gain * (hn_imp(n) - lhn); + + return ret; +} + +static float alpha(float a) +{ + if (a <= 21) + return 0; + if (a <= 50) + return .5842 * pow(a - 21,0.4) + 0.07886 * (a - 21); + return .1102 * (a - 8.7); +} + +static float win(SuperEqualizerContext *s, float n, int N) +{ + return izero(s, alpha(s->aa) * sqrt(1 - 4 * n * n / ((N - 1) * (N - 1)))) / s->iza; +} + +static void +process_param(float *bc, EqParameter *param, float fs, int ch) +{ + int i; + + for (i = 0; i <= NBANDS; i++) { + param[i].lower = i == 0 ? 0 : bands[i - 1]; + param[i].upper = i == NBANDS - 1 ? fs : bands[i]; + param[i].gain = bc[i]; + } +} + +static int equ_init(SuperEqualizerContext *s, int wb) +{ + int i,j; + + s->rdft = av_rdft_init(wb, DFT_R2C); + s->irdft = av_rdft_init(wb, IDFT_C2R); + if (!s->rdft || !s->irdft) + return AVERROR(ENOMEM); + + s->aa = 96; + s->winlen = (1 << (wb-1))-1; + s->winlenbit = wb; + s->tabsize = 1 << wb; + + s->lires = av_calloc(s->tabsize, sizeof(float)); + s->rires = av_calloc(s->tabsize, sizeof(float)); + s->irest = av_calloc(s->tabsize, sizeof(float)); + s->fsamples = av_calloc(s->tabsize, sizeof(float)); + + s->cur_ires = 1; + s->chg_ires = 1; + + for (i = 0; i <= M; i++) { + s->fact[i] = 1; + for (j = 1; j <= i; j++) + s->fact[i] *= j; + } + + s->iza = izero(s, alpha(s->aa)); + + return 0; +} + +static void make_fir(SuperEqualizerContext *s, float *lbc, float *rbc, EqParameter *param, float fs) +{ + const int winlen = s->winlen; + const int tabsize = s->tabsize; + int i, cires = s->cur_ires; + float *nires; + + if (fs <= 0) + return; + + process_param(lbc, param, fs, 0); + for (i = 0; i < winlen; i++) + s->irest[i] = hn(i - winlen / 2, param, fs) * win(s, i - winlen / 2, winlen); + for (; i < tabsize; i++) + s->irest[i] = 0; + + av_rdft_calc(s->rdft, s->irest); + nires = s->lires; + for (i = 0; i < tabsize; i++) + nires[i] = s->irest[i]; + + process_param(rbc, param, fs, 1); + for (i = 0; i < winlen; i++) + s->irest[i] = hn(i - winlen / 2, param, fs) * win(s, i - winlen / 2, winlen); + for (; i < tabsize; i++) + s->irest[i] = 0; + + av_rdft_calc(s->rdft, s->irest); + nires = s->rires; + for (i = 0; i < tabsize; i++) + nires[i] = s->irest[i]; + s->chg_ires = cires == 1 ? 2 : 1; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + SuperEqualizerContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + const float *ires = s->lires; + float *fsamples = s->fsamples; + int ch, i; + + AVFrame *out = ff_get_audio_buffer(outlink, s->winlen); + float *src, *dst, *ptr; + + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + + for (ch = 0; ch < in->channels; ch++) { + ptr = (float *)out->extended_data[ch]; + dst = (float *)s->out->extended_data[ch]; + src = (float *)in->extended_data[ch]; + + for (i = 0; i < s->winlen; i++) + fsamples[i] = src[i]; + for (; i < s->tabsize; i++) + fsamples[i] = 0; + + av_rdft_calc(s->rdft, fsamples); + + fsamples[0] = ires[0] * fsamples[0]; + fsamples[1] = ires[1] * fsamples[1]; + for (i = 1; i < s->tabsize / 2; i++) { + float re, im; + + re = ires[i*2 ] * fsamples[i*2] - ires[i*2+1] * fsamples[i*2+1]; + im = ires[i*2+1] * fsamples[i*2] + ires[i*2 ] * fsamples[i*2+1]; + + fsamples[i*2 ] = re; + fsamples[i*2+1] = im; + } + + av_rdft_calc(s->irdft, fsamples); + + for (i = 0; i < s->winlen; i++) + dst[i] += fsamples[i] / s->tabsize * 2; + for (i = s->winlen; i < s->tabsize; i++) + dst[i] = fsamples[i] / s->tabsize * 2; + for (i = 0; i < s->winlen; i++) + ptr[i] = dst[i]; + for (i = 0; i < s->winlen; i++) + dst[i] = dst[i+s->winlen]; + } + + out->pts = in->pts; + av_frame_free(&in); + + return ff_filter_frame(outlink, out); +} + +static av_cold int init(AVFilterContext *ctx) +{ + SuperEqualizerContext *s = ctx->priv; + + return equ_init(s, 14); +} + +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 ((ret = ff_set_common_formats(ctx, formats)) < 0) + return ret; + + formats = ff_all_samplerates(); + return ff_set_common_samplerates(ctx, formats); +} + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + SuperEqualizerContext *s = ctx->priv; + + inlink->partial_buf_size = + inlink->min_samples = + inlink->max_samples = s->winlen; + + s->out = ff_get_audio_buffer(inlink, s->tabsize); + if (!s->out) + return AVERROR(ENOMEM); + + return 0; +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + SuperEqualizerContext *s = ctx->priv; + + make_fir(s, s->gains, s->gains, s->params, outlink->sample_rate); + + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + SuperEqualizerContext *s = ctx->priv; + + av_rdft_end(s->rdft); + av_rdft_end(s->irdft); +} + +static const AVFilterPad superequalizer_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad superequalizer_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .config_props = config_output, + }, + { NULL } +}; + +#define AF AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM +#define OFFSET(x) offsetof(SuperEqualizerContext, x) + +static const AVOption superequalizer_options[] = { + { "1b", "set 1st band gain", OFFSET(gains [0]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "2b", "set 2nd band gain", OFFSET(gains [1]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "3b", "set 3rd band gain", OFFSET(gains [2]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "4b", "set 4th band gain", OFFSET(gains [3]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "5b", "set 5th band gain", OFFSET(gains [4]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "6b", "set 6th band gain", OFFSET(gains [5]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "7b", "set 7th band gain", OFFSET(gains [6]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "8b", "set 8th band gain", OFFSET(gains [7]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "9b", "set 9th band gain", OFFSET(gains [8]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "10b", "set 10nd band gain", OFFSET(gains [9]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "11b", "set 11nd band gain", OFFSET(gains[10]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "12b", "set 12nd band gain", OFFSET(gains[11]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "13b", "set 13nd band gain", OFFSET(gains[12]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "14b", "set 14th band gain", OFFSET(gains[13]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "15b", "set 15th band gain", OFFSET(gains[14]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "16b", "set 16th band gain", OFFSET(gains[15]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "17b", "set 17th band gain", OFFSET(gains[16]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { "18b", "set 18th band gain", OFFSET(gains[17]), AV_OPT_TYPE_FLOAT, {.dbl=1}, 0, 20, AF }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(superequalizer); + +AVFilter ff_af_superequalizer = { + .name = "superequalizer", + .description = NULL_IF_CONFIG_SMALL("Apply 18-th band equalization filter."), + .priv_size = sizeof(SuperEqualizerContext), + .priv_class = &superequalizer_class, + .init = init, + .query_formats = query_formats, + .uninit = uninit, + .inputs = superequalizer_inputs, + .outputs = superequalizer_outputs, +}; diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 94f7cf3..bd81091 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -122,6 +122,7 @@ static void register_all(void) REGISTER_FILTER(SOFALIZER, sofalizer, af); REGISTER_FILTER(STEREOTOOLS, stereotools, af); REGISTER_FILTER(STEREOWIDEN, stereowiden, af); + REGISTER_FILTER(SUPEREQUALIZER, superequalizer, af); REGISTER_FILTER(SURROUND, surround, af); REGISTER_FILTER(TREBLE, treble, af); REGISTER_FILTER(TREMOLO, tremolo, af);