/*1* Copyright (c) 2011 Mina Nagy Zaki2* Copyright (c) 2000 Edward Beingessner And Sundry Contributors.3* This source code is freely redistributable and may be used for any purpose.4* This copyright notice must be maintained. Edward Beingessner And Sundry5* Contributors are not responsible for the consequences of using this6* software.7*8* This file is part of FFmpeg.9*10* FFmpeg is free software; you can redistribute it and/or11* modify it under the terms of the GNU Lesser General Public12* License as published by the Free Software Foundation; either13* version 2.1 of the License, or (at your option) any later version.14*15* FFmpeg is distributed in the hope that it will be useful,16* but WITHOUT ANY WARRANTY; without even the implied warranty of17* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU18* Lesser General Public License for more details.19*20* You should have received a copy of the GNU Lesser General Public21* License along with FFmpeg; if not, write to the Free Software22* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA23*/2425/**26* @file27* Stereo Widening Effect. Adds audio cues to move stereo image in28* front of the listener. Adapted from the libsox earwax effect.29*/3031#include "libavutil/channel_layout.h"32#include "avfilter.h"33#include "audio.h"34#include "formats.h"3536#define NUMTAPS 643738static const int8_t filt[NUMTAPS] = {39/* 30° 330° */404, -6, /* 32 tap stereo FIR filter. */414, -11, /* One side filters as if the */42-1, -5, /* signal was from 30 degrees */433, 3, /* from the ear, the other as */44-2, 5, /* if 330 degrees. */45-5, 0,469, 1,476, 3, /* Input */48-4, -1, /* Left Right */49-5, -3, /* __________ __________ */50-2, -5, /* | | | | */51-7, 1, /* .---| Hh,0(f) | | Hh,0(f) |---. */526, -7, /* / |__________| |__________| \ */5330, -29, /* / \ / \ */5412, -3, /* / X \ */55-11, 4, /* / / \ \ */56-3, 7, /* ____V_____ __________V V__________ _____V____ */57-20, 23, /* | | | | | | | | */582, 0, /* | Hh,30(f) | | Hh,330(f)| | Hh,330(f)| | Hh,30(f) | */591, -6, /* |__________| |__________| |__________| |__________| */60-14, -5, /* \ ___ / \ ___ / */6115, -18, /* \ / \ / _____ \ / \ / */626, 7, /* `->| + |<--' / \ `-->| + |<-' */6315, -10, /* \___/ _/ \_ \___/ */64-14, 22, /* \ / \ / \ / */65-7, -2, /* `--->| | | |<---' */66-4, 9, /* \_/ \_/ */676, -12, /* */686, -6, /* Headphones */690, -11,700, -5,714, 0};7273typedef struct {74int16_t taps[NUMTAPS * 2];75} EarwaxContext;7677static int query_formats(AVFilterContext *ctx)78{79static const int sample_rates[] = { 44100, -1 };80int ret;8182AVFilterFormats *formats = NULL;83AVFilterChannelLayouts *layout = NULL;8485if ((ret = ff_add_format (&formats, AV_SAMPLE_FMT_S16 )) < 0 ||86(ret = ff_set_common_formats (ctx , formats )) < 0 ||87(ret = ff_add_channel_layout (&layout , AV_CH_LAYOUT_STEREO )) < 0 ||88(ret = ff_set_common_channel_layouts (ctx , layout )) < 0 ||89(ret = ff_set_common_samplerates (ctx , ff_make_format_list(sample_rates) )) < 0)90return ret;9192return 0;93}9495//FIXME: replace with DSPContext.scalarproduct_int1696static inline int16_t *scalarproduct(const int16_t *in, const int16_t *endin, int16_t *out)97{98int32_t sample;99int16_t j;100101while (in < endin) {102sample = 0;103for (j = 0; j < NUMTAPS; j++)104sample += in[j] * filt[j];105*out = av_clip_int16(sample >> 6);106out++;107in++;108}109110return out;111}112113static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)114{115AVFilterLink *outlink = inlink->dst->outputs[0];116int16_t *taps, *endin, *in, *out;117AVFrame *outsamples = ff_get_audio_buffer(inlink, insamples->nb_samples);118int len;119120if (!outsamples) {121av_frame_free(&insamples);122return AVERROR(ENOMEM);123}124av_frame_copy_props(outsamples, insamples);125126taps = ((EarwaxContext *)inlink->dst->priv)->taps;127out = (int16_t *)outsamples->data[0];128in = (int16_t *)insamples ->data[0];129130len = FFMIN(NUMTAPS, 2*insamples->nb_samples);131// copy part of new input and process with saved input132memcpy(taps+NUMTAPS, in, len * sizeof(*taps));133out = scalarproduct(taps, taps + len, out);134135// process current input136if (2*insamples->nb_samples >= NUMTAPS ){137endin = in + insamples->nb_samples * 2 - NUMTAPS;138scalarproduct(in, endin, out);139140// save part of input for next round141memcpy(taps, endin, NUMTAPS * sizeof(*taps));142} else143memmove(taps, taps + 2*insamples->nb_samples, NUMTAPS * sizeof(*taps));144145av_frame_free(&insamples);146return ff_filter_frame(outlink, outsamples);147}148149static const AVFilterPad earwax_inputs[] = {150{151.name = "default",152.type = AVMEDIA_TYPE_AUDIO,153.filter_frame = filter_frame,154},155{ NULL }156};157158static const AVFilterPad earwax_outputs[] = {159{160.name = "default",161.type = AVMEDIA_TYPE_AUDIO,162},163{ NULL }164};165166AVFilter ff_af_earwax = {167.name = "earwax",168.description = NULL_IF_CONFIG_SMALL("Widen the stereo image."),169.query_formats = query_formats,170.priv_size = sizeof(EarwaxContext),171.inputs = earwax_inputs,172.outputs = earwax_outputs,173};174175176