From patchwork Tue May 16 20:59:39 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: 3681 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.10.2 with SMTP id 2csp637642vsk; Tue, 16 May 2017 14:00:02 -0700 (PDT) X-Received: by 10.28.45.13 with SMTP id t13mr340861wmt.98.1494968402268; Tue, 16 May 2017 14:00:02 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1494968402; cv=none; d=google.com; s=arc-20160816; b=ZCqfJ7D3wzLvGv/Wz+5Twmay4dh+/Y62sQpHAytQPoHVWyrtrfRo+cxi53DAIuDd1R 8Flpq5iH5HuRXHCr54lk8KKMGqBt/P0PVOEmjFVr9184OOLL3C31zv9tCUr/xueHKDt+ 1l5HakQUdCb/j/AupUwSUdwJbQ3HdxDu+lq9R22u8uu1fd/BVhgERjyu35j28ATw1tTv M7pM4OVhVXT5LGa5HuV4Ih+G1+rDvAui6EYNiy5F9QyPsnGC7Eo26HiRAIBwkSd8ojCO gK1zFPkqKiAEInQvIN9FYVnWyCY/e7OSH6JYPDUtfmlr85ekqeSoHXJ+4URIIbkxPClC 0cVw== 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=dqSIxzVKyz1jcf6jCFZa8vSTPSuz0mH/PYadRES5SyA=; b=G44T60YsY47Tih2V0Gl1zyeMCgxnOnHUPv2q4v+HwQLZk1SzdjWq8c6P3B0y8UH8MR kAAn6rrboqxR4lH8SXSmuvMZ1YRK3Ku7s+acg7eEynKrL/UAMM9b2d9lZnyax+4UVqqy AsZIwsByDyADgf0yC1+kRZAYQuMRHnyz69t48w7XTebWDKbn63NrYJW2eE4agyA8dvx8 62YHB//i+mx7B3jNgD5ynLIJRqEJRotujV24oo4S9Lr0K07k0ZwIVFCCYF4wmTHXftRi Qpgk6r3T3rPnbJxRSUFy48ec8OKtPU/L+eGZ0rI3o0Gz4gIfvQT0nyDkZUIvJO5R8FZo WaYQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com; 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 w62si343852wmb.139.2017.05.16.14.00.01; Tue, 16 May 2017 14:00:02 -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; 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 A9D5F688337; Tue, 16 May 2017 23:59:58 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wm0-f49.google.com (mail-wm0-f49.google.com [74.125.82.49]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 7800F68046E for ; Tue, 16 May 2017 23:59:52 +0300 (EEST) Received: by mail-wm0-f49.google.com with SMTP id v15so105030503wmv.1 for ; Tue, 16 May 2017 13:59:52 -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=KCFZIgMLcQfG5LZEvcuYDl5nSonI8m3IbmwS0kdgiz8=; b=KJCCohkG2Rrhh83wKfwEXb64CRar4RhZW4V5z+sxk8HH/YuOrkveit/wgq/hp7C5PL mJfo7tputEDUx74o/gp4LO4+HGMdLN3gj9zOcWV+wTsTilyE6kiYDzoB5GHs2xl1VQDL sSNfZbnzEae6McqTMqLtcOrAM6BHWs23OpYNfKJznB0uLzfTZQUKba74PBTjESOJ0kZM PQNy8e8vvD2laafAu7NyPB0zKHP4Qty9yd+Egu1BTHiqwSWFBAY2EGImUqrFZAY+Bhyw 5gFBrMJENIBNBi4wm14sJ8e7AxuNmrmJNK7VAeo/WzMfBrEduIsUsoi5YP9I2SaoI4wL /rQg== 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=KCFZIgMLcQfG5LZEvcuYDl5nSonI8m3IbmwS0kdgiz8=; b=XtPbWbTsUuu8h1oEIzqMotlJYOMCieptnuRbBSj8cBfZ237Pc6wdfARplA8bAyo67Y VIikfHKimwi96pP522zK7VVkBFT8SGkX5wObLWMT1M3ZNt8BHJWkEpLCcFcBAb9RqI0X nUqrHwmfeTBgQRJXNVev8Burd869JndrFHoY6wEFcSHNQc/4EfQp1+xEhDcc1J3zi40T AhmvKkhtwlLBJLLU60SsS7Ia5nxoQxaVaBlzsmm0s7N0QsRw0ZHOfD62cbtG8OX/Uz9P SKFzlHFWRtvKwolIqcEatWuM2wDgx6cWvY4iJOLQv0gVySzE7TnX5fvgwJjvUbdRtrc3 fUaA== X-Gm-Message-State: AODbwcBelS30KLK914noENtDXTa3NHavlAnIWuED4YL4nFNEVOZNms3j Hly7IKiBOxH81Nuh X-Received: by 10.28.67.130 with SMTP id q124mr322106wma.17.1494968392063; Tue, 16 May 2017 13:59:52 -0700 (PDT) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id u76sm3523771wrb.27.2017.05.16.13.59.50 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 16 May 2017 13:59:51 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Tue, 16 May 2017 22:59:39 +0200 Message-Id: <20170516205939.15355-1-onemda@gmail.com> X-Mailer: git-send-email 2.9.3 Subject: [FFmpeg-devel] [PATCH] avfilter: add audio crossfeed 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 --- doc/filters.texi | 16 +++++ libavfilter/Makefile | 1 + libavfilter/af_crossfeed.c | 161 +++++++++++++++++++++++++++++++++++++++++++++ libavfilter/allfilters.c | 1 + 4 files changed, 179 insertions(+) create mode 100644 libavfilter/af_crossfeed.c diff --git a/doc/filters.texi b/doc/filters.texi index 9611a41..2e7caf3 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -2248,6 +2248,22 @@ Set temperature degree in Celsius. This is the temperature of the environment. Default is 20. @end table +@section crossfeed +Apply headphone crossfeed filter. + +The filter accepts the following options: + +@table @option +@item strength +Set strength of crossfeed. Default is 0.1. Allowed range is from 0 to 1. + +@item level_in +Set input gain. Default is 0.9. + +@item level_out +Set output gain. Default is 1. +@end table + @section crystalizer Simple algorithm to expand audio dynamic range. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index f177fdb..434a989 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -81,6 +81,7 @@ OBJS-$(CONFIG_CHANNELSPLIT_FILTER) += af_channelsplit.o OBJS-$(CONFIG_CHORUS_FILTER) += af_chorus.o generate_wave_table.o OBJS-$(CONFIG_COMPAND_FILTER) += af_compand.o OBJS-$(CONFIG_COMPENSATIONDELAY_FILTER) += af_compensationdelay.o +OBJS-$(CONFIG_CROSSFEED_FILTER) += af_crossfeed.o OBJS-$(CONFIG_CRYSTALIZER_FILTER) += af_crystalizer.o OBJS-$(CONFIG_DCSHIFT_FILTER) += af_dcshift.o OBJS-$(CONFIG_DYNAUDNORM_FILTER) += af_dynaudnorm.o diff --git a/libavfilter/af_crossfeed.c b/libavfilter/af_crossfeed.c new file mode 100644 index 0000000..8a9ecad --- /dev/null +++ b/libavfilter/af_crossfeed.c @@ -0,0 +1,161 @@ +/* + * 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/channel_layout.h" +#include "libavutil/opt.h" +#include "avfilter.h" +#include "audio.h" +#include "formats.h" + +typedef struct CrossfeedContext { + const AVClass *class; + + double strength; + double level_in; + double level_out; + + double a0, a1, a2; + double b0, b1, b2; + + double i1, i2; + double o1, o2; +} CrossfeedContext; + +static int query_formats(AVFilterContext *ctx) +{ + AVFilterFormats *formats = NULL; + AVFilterChannelLayouts *layout = NULL; + int ret; + + if ((ret = ff_add_format (&formats, AV_SAMPLE_FMT_DBL )) < 0 || + (ret = ff_set_common_formats (ctx , formats )) < 0 || + (ret = ff_add_channel_layout (&layout , AV_CH_LAYOUT_STEREO)) < 0 || + (ret = ff_set_common_channel_layouts (ctx , layout )) < 0 || + (ret = ff_set_common_samplerates (ctx , ff_all_samplerates())) < 0) + return ret; + + return 0; +} + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + CrossfeedContext *s = ctx->priv; + double w0 = 2 * M_PI * s->strength * 1000 / inlink->sample_rate; + double alpha; + + alpha = sin(w0) / 2 * sqrt(2 * (1 / 0.707 - 1) + 2); + + s->a0 = 1 + alpha; + s->a1 = -2 * cos(w0); + s->a2 = 1 - alpha; + s->b0 = (1 + cos(w0)) / 2; + s->b1 = -(1 + cos(w0)); + s->b2 = (1 + cos(w0)) / 2; + + s->a1 /= s->a0; + s->a2 /= s->a0; + s->b0 /= s->a0; + s->b1 /= s->a0; + s->b2 /= s->a0; + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + CrossfeedContext *s = ctx->priv; + const double *src = (const double *)in->data[0]; + const double level_in = s->level_in; + const double level_out = s->level_out; + AVFrame *out; + double *dst; + int n; + + if (av_frame_is_writable(in)) { + out = in; + } else { + out = ff_get_audio_buffer(inlink, in->nb_samples); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + } + dst = (double *)out->data[0]; + + for (n = 0; n < out->nb_samples; n++, src += 2, dst += 2) { + double mid = (src[0] + src[1]) * level_in * .5; + double side = (src[0] - src[1]) * level_in * .5; + + double oside = side * s->b0 + s->i1 * s->b1 + s->i2 * s->b2 - s->o1 * s->a1 - s->o2 * s->a2; + s->i2 = s->i1; + s->i1 = side; + s->o2 = s->o1; + s->o1 = oside; + + dst[0] = (mid + oside) * level_out; + dst[1] = (mid - oside) * level_out; + } + + if (out != in) + av_frame_free(&in); + return ff_filter_frame(outlink, out); +} + +#define OFFSET(x) offsetof(CrossfeedContext, x) +#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption crossfeed_options[] = { + { "strength", "set crossfeed strength", OFFSET(strength), AV_OPT_TYPE_DOUBLE, {.dbl=.1}, 0, 1, FLAGS }, + { "level_in", "set level in", OFFSET(level_in), AV_OPT_TYPE_DOUBLE, {.dbl=.9}, 0, 1, FLAGS }, + { "level_out", "set level out", OFFSET(level_out), AV_OPT_TYPE_DOUBLE, {.dbl=1.}, 0, 1, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(crossfeed); + +static const AVFilterPad inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + }, + { NULL } +}; + +AVFilter ff_af_crossfeed = { + .name = "crossfeed", + .description = NULL_IF_CONFIG_SMALL("Apply headphone crossfeed filter."), + .query_formats = query_formats, + .priv_size = sizeof(CrossfeedContext), + .priv_class = &crossfeed_class, + .inputs = inputs, + .outputs = outputs, +}; diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index a8939b9..2bcfce7 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -94,6 +94,7 @@ static void register_all(void) REGISTER_FILTER(CHORUS, chorus, af); REGISTER_FILTER(COMPAND, compand, af); REGISTER_FILTER(COMPENSATIONDELAY, compensationdelay, af); + REGISTER_FILTER(CROSSFEED, crossfeed, af); REGISTER_FILTER(CRYSTALIZER, crystalizer, af); REGISTER_FILTER(DCSHIFT, dcshift, af); REGISTER_FILTER(DYNAUDNORM, dynaudnorm, af);