1/*
2 * Copyright (c) 2011 Mina Nagy Zaki
3 * Copyright (c) 2000 Edward Beingessner And Sundry Contributors.
4 * This source code is freely redistributable and may be used for any purpose.
5 * This copyright notice must be maintained.  Edward Beingessner And Sundry
6 * Contributors are not responsible for the consequences of using this
7 * software.
8 *
9 * This file is part of FFmpeg.
10 *
11 * FFmpeg is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
15 *
16 * FFmpeg is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 * Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with FFmpeg; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 */
25
26/**
27 * @file
28 * Stereo Widening Effect. Adds audio cues to move stereo image in
29 * front of the listener. Adapted from the libsox earwax effect.
30 */
31
32#include "libavutil/channel_layout.h"
33#include "avfilter.h"
34#include "audio.h"
35#include "formats.h"
36
37#define NUMTAPS 64
38
39static const int8_t filt[NUMTAPS] = {
40/* 30��  330�� */
41    4,   -6,     /* 32 tap stereo FIR filter. */
42    4,  -11,     /* One side filters as if the */
43   -1,   -5,     /* signal was from 30 degrees */
44    3,    3,     /* from the ear, the other as */
45   -2,    5,     /* if 330 degrees. */
46   -5,    0,
47    9,    1,
48    6,    3,     /*                         Input                         */
49   -4,   -1,     /*                   Left         Right                  */
50   -5,   -3,     /*                __________   __________                */
51   -2,   -5,     /*               |          | |          |               */
52   -7,    1,     /*           .---|  Hh,0(f) | |  Hh,0(f) |---.           */
53    6,   -7,     /*          /    |__________| |__________|    \          */
54   30,  -29,     /*         /                \ /                \         */
55   12,   -3,     /*        /                  X                  \        */
56  -11,    4,     /*       /                  / \                  \       */
57   -3,    7,     /*  ____V_____   __________V   V__________   _____V____  */
58  -20,   23,     /* |          | |          |   |          | |          | */
59    2,    0,     /* | Hh,30(f) | | Hh,330(f)|   | Hh,330(f)| | Hh,30(f) | */
60    1,   -6,     /* |__________| |__________|   |__________| |__________| */
61  -14,   -5,     /*      \     ___      /           \      ___     /      */
62   15,  -18,     /*       \   /   \    /    _____    \    /   \   /       */
63    6,    7,     /*        `->| + |<--'    /     \    `-->| + |<-'        */
64   15,  -10,     /*           \___/      _/       \_      \___/           */
65  -14,   22,     /*               \     / \       / \     /               */
66   -7,   -2,     /*                `--->| |       | |<---'                */
67   -4,    9,     /*                     \_/       \_/                     */
68    6,  -12,     /*                                                       */
69    6,   -6,     /*                       Headphones                      */
70    0,  -11,
71    0,   -5,
72    4,    0};
73
74typedef struct {
75    int16_t taps[NUMTAPS * 2];
76} EarwaxContext;
77
78static int query_formats(AVFilterContext *ctx)
79{
80    static const int sample_rates[] = { 44100, -1 };
81
82    AVFilterFormats *formats = NULL;
83    AVFilterChannelLayouts *layout = NULL;
84
85    ff_add_format(&formats, AV_SAMPLE_FMT_S16);
86    ff_set_common_formats(ctx, formats);
87    ff_add_channel_layout(&layout, AV_CH_LAYOUT_STEREO);
88    ff_set_common_channel_layouts(ctx, layout);
89    ff_set_common_samplerates(ctx, ff_make_format_list(sample_rates));
90
91    return 0;
92}
93
94//FIXME: replace with DSPContext.scalarproduct_int16
95static inline int16_t *scalarproduct(const int16_t *in, const int16_t *endin, int16_t *out)
96{
97    int32_t sample;
98    int16_t j;
99
100    while (in < endin) {
101        sample = 0;
102        for (j = 0; j < NUMTAPS; j++)
103            sample += in[j] * filt[j];
104        *out = av_clip_int16(sample >> 6);
105        out++;
106        in++;
107    }
108
109    return out;
110}
111
112static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
113{
114    AVFilterLink *outlink = inlink->dst->outputs[0];
115    int16_t *taps, *endin, *in, *out;
116    AVFrame *outsamples = ff_get_audio_buffer(inlink, insamples->nb_samples);
117    int len;
118
119    if (!outsamples) {
120        av_frame_free(&insamples);
121        return AVERROR(ENOMEM);
122    }
123    av_frame_copy_props(outsamples, insamples);
124
125    taps  = ((EarwaxContext *)inlink->dst->priv)->taps;
126    out   = (int16_t *)outsamples->data[0];
127    in    = (int16_t *)insamples ->data[0];
128
129    len = FFMIN(NUMTAPS, 2*insamples->nb_samples);
130    // copy part of new input and process with saved input
131    memcpy(taps+NUMTAPS, in, len * sizeof(*taps));
132    out   = scalarproduct(taps, taps + len, out);
133
134    // process current input
135    if (2*insamples->nb_samples >= NUMTAPS ){
136        endin = in + insamples->nb_samples * 2 - NUMTAPS;
137        scalarproduct(in, endin, out);
138
139        // save part of input for next round
140        memcpy(taps, endin, NUMTAPS * sizeof(*taps));
141    } else
142        memmove(taps, taps + 2*insamples->nb_samples, NUMTAPS * sizeof(*taps));
143
144    av_frame_free(&insamples);
145    return ff_filter_frame(outlink, outsamples);
146}
147
148static const AVFilterPad earwax_inputs[] = {
149    {
150        .name         = "default",
151        .type         = AVMEDIA_TYPE_AUDIO,
152        .filter_frame = filter_frame,
153    },
154    { NULL }
155};
156
157static const AVFilterPad earwax_outputs[] = {
158    {
159        .name = "default",
160        .type = AVMEDIA_TYPE_AUDIO,
161    },
162    { NULL }
163};
164
165AVFilter ff_af_earwax = {
166    .name           = "earwax",
167    .description    = NULL_IF_CONFIG_SMALL("Widen the stereo image."),
168    .query_formats  = query_formats,
169    .priv_size      = sizeof(EarwaxContext),
170    .inputs         = earwax_inputs,
171    .outputs        = earwax_outputs,
172};
173