| 1 | /* |
| 2 | * This file is part of FFmpeg. |
| 3 | * |
| 4 | * FFmpeg is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU Lesser General Public |
| 6 | * License as published by the Free Software Foundation; either |
| 7 | * version 2.1 of the License, or (at your option) any later version. |
| 8 | * |
| 9 | * FFmpeg is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | * Lesser General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU Lesser General Public |
| 15 | * License along with FFmpeg; if not, write to the Free Software |
| 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 17 | */ |
| 18 | |
| 19 | /** |
| 20 | * @file |
| 21 | * Bauer stereo-to-binaural filter |
| 22 | */ |
| 23 | |
| 24 | #include <bs2b.h> |
| 25 | |
| 26 | #include "libavutil/channel_layout.h" |
| 27 | #include "libavutil/common.h" |
| 28 | #include "libavutil/opt.h" |
| 29 | |
| 30 | #include "audio.h" |
| 31 | #include "avfilter.h" |
| 32 | #include "formats.h" |
| 33 | #include "internal.h" |
| 34 | |
| 35 | typedef struct Bs2bContext { |
| 36 | const AVClass *class; |
| 37 | |
| 38 | int profile; |
| 39 | int fcut; |
| 40 | int feed; |
| 41 | |
| 42 | t_bs2bdp bs2bp; |
| 43 | |
| 44 | void (*filter)(t_bs2bdp bs2bdp, uint8_t *sample, int n); |
| 45 | } Bs2bContext; |
| 46 | |
| 47 | #define OFFSET(x) offsetof(Bs2bContext, x) |
| 48 | #define A AV_OPT_FLAG_AUDIO_PARAM |
| 49 | |
| 50 | static const AVOption options[] = { |
| 51 | { "profile", "Apply a pre-defined crossfeed level", |
| 52 | OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = BS2B_DEFAULT_CLEVEL }, 0, INT_MAX, A, "profile" }, |
| 53 | { "default", "default profile", 0, AV_OPT_TYPE_CONST, { .i64 = BS2B_DEFAULT_CLEVEL }, 0, 0, A, "profile" }, |
| 54 | { "cmoy", "Chu Moy circuit", 0, AV_OPT_TYPE_CONST, { .i64 = BS2B_CMOY_CLEVEL }, 0, 0, A, "profile" }, |
| 55 | { "jmeier", "Jan Meier circuit", 0, AV_OPT_TYPE_CONST, { .i64 = BS2B_JMEIER_CLEVEL }, 0, 0, A, "profile" }, |
| 56 | { "fcut", "Set cut frequency (in Hz)", |
| 57 | OFFSET(fcut), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BS2B_MAXFCUT, A }, |
| 58 | { "feed", "Set feed level (in Hz)", |
| 59 | OFFSET(feed), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BS2B_MAXFEED, A }, |
| 60 | { NULL }, |
| 61 | }; |
| 62 | |
| 63 | static const AVClass bs2b_class = { |
| 64 | .class_name = "bs2b filter", |
| 65 | .item_name = av_default_item_name, |
| 66 | .option = options, |
| 67 | .version = LIBAVUTIL_VERSION_INT, |
| 68 | }; |
| 69 | |
| 70 | static av_cold int init(AVFilterContext *ctx) |
| 71 | { |
| 72 | Bs2bContext *bs2b = ctx->priv; |
| 73 | |
| 74 | if (!(bs2b->bs2bp = bs2b_open())) |
| 75 | return AVERROR(ENOMEM); |
| 76 | |
| 77 | bs2b_set_level(bs2b->bs2bp, bs2b->profile); |
| 78 | |
| 79 | if (bs2b->fcut) |
| 80 | bs2b_set_level_fcut(bs2b->bs2bp, bs2b->fcut); |
| 81 | |
| 82 | if (bs2b->feed) |
| 83 | bs2b_set_level_feed(bs2b->bs2bp, bs2b->feed); |
| 84 | |
| 85 | return 0; |
| 86 | } |
| 87 | |
| 88 | static av_cold void uninit(AVFilterContext *ctx) |
| 89 | { |
| 90 | Bs2bContext *bs2b = ctx->priv; |
| 91 | |
| 92 | if (bs2b->bs2bp) |
| 93 | bs2b_close(bs2b->bs2bp); |
| 94 | } |
| 95 | |
| 96 | static int query_formats(AVFilterContext *ctx) |
| 97 | { |
| 98 | AVFilterFormats *formats = NULL; |
| 99 | AVFilterChannelLayouts *layouts = NULL; |
| 100 | |
| 101 | static const enum AVSampleFormat sample_fmts[] = { |
| 102 | AV_SAMPLE_FMT_U8, |
| 103 | AV_SAMPLE_FMT_S16, |
| 104 | AV_SAMPLE_FMT_S32, |
| 105 | AV_SAMPLE_FMT_FLT, |
| 106 | AV_SAMPLE_FMT_DBL, |
| 107 | AV_SAMPLE_FMT_NONE, |
| 108 | }; |
| 109 | |
| 110 | if (ff_add_channel_layout(&layouts, AV_CH_LAYOUT_STEREO) != 0) |
| 111 | return AVERROR(ENOMEM); |
| 112 | ff_set_common_channel_layouts(ctx, layouts); |
| 113 | |
| 114 | formats = ff_make_format_list(sample_fmts); |
| 115 | if (!formats) |
| 116 | return AVERROR(ENOMEM); |
| 117 | ff_set_common_formats(ctx, formats); |
| 118 | |
| 119 | formats = ff_all_samplerates(); |
| 120 | if (!formats) |
| 121 | return AVERROR(ENOMEM); |
| 122 | ff_set_common_samplerates(ctx, formats); |
| 123 | |
| 124 | return 0; |
| 125 | } |
| 126 | |
| 127 | static int filter_frame(AVFilterLink *inlink, AVFrame *frame) |
| 128 | { |
| 129 | int ret; |
| 130 | AVFrame *out_frame; |
| 131 | |
| 132 | Bs2bContext *bs2b = inlink->dst->priv; |
| 133 | AVFilterLink *outlink = inlink->dst->outputs[0]; |
| 134 | |
| 135 | if (av_frame_is_writable(frame)) { |
| 136 | out_frame = frame; |
| 137 | } else { |
| 138 | out_frame = ff_get_audio_buffer(inlink, frame->nb_samples); |
| 139 | if (!out_frame) |
| 140 | return AVERROR(ENOMEM); |
| 141 | av_frame_copy(out_frame, frame); |
| 142 | ret = av_frame_copy_props(out_frame, frame); |
| 143 | if (ret < 0) { |
| 144 | av_frame_free(&out_frame); |
| 145 | av_frame_free(&frame); |
| 146 | return ret; |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | bs2b->filter(bs2b->bs2bp, out_frame->extended_data[0], out_frame->nb_samples); |
| 151 | |
| 152 | if (frame != out_frame) |
| 153 | av_frame_free(&frame); |
| 154 | |
| 155 | return ff_filter_frame(outlink, out_frame); |
| 156 | } |
| 157 | |
| 158 | static int config_output(AVFilterLink *outlink) |
| 159 | { |
| 160 | AVFilterContext *ctx = outlink->src; |
| 161 | Bs2bContext *bs2b = ctx->priv; |
| 162 | AVFilterLink *inlink = ctx->inputs[0]; |
| 163 | |
| 164 | int srate = inlink->sample_rate; |
| 165 | |
| 166 | switch (inlink->format) { |
| 167 | case AV_SAMPLE_FMT_U8: |
| 168 | bs2b->filter = bs2b_cross_feed_u8; |
| 169 | break; |
| 170 | case AV_SAMPLE_FMT_S16: |
| 171 | bs2b->filter = (void*)bs2b_cross_feed_s16; |
| 172 | break; |
| 173 | case AV_SAMPLE_FMT_S32: |
| 174 | bs2b->filter = (void*)bs2b_cross_feed_s32; |
| 175 | break; |
| 176 | case AV_SAMPLE_FMT_FLT: |
| 177 | bs2b->filter = (void*)bs2b_cross_feed_f; |
| 178 | break; |
| 179 | case AV_SAMPLE_FMT_DBL: |
| 180 | bs2b->filter = (void*)bs2b_cross_feed_d; |
| 181 | break; |
| 182 | default: |
| 183 | return AVERROR_BUG; |
| 184 | } |
| 185 | |
| 186 | if ((srate < BS2B_MINSRATE) || (srate > BS2B_MAXSRATE)) |
| 187 | return AVERROR(ENOSYS); |
| 188 | |
| 189 | bs2b_set_srate(bs2b->bs2bp, srate); |
| 190 | |
| 191 | return 0; |
| 192 | } |
| 193 | |
| 194 | static const AVFilterPad bs2b_inputs[] = { |
| 195 | { |
| 196 | .name = "default", |
| 197 | .type = AVMEDIA_TYPE_AUDIO, |
| 198 | .filter_frame = filter_frame, |
| 199 | }, |
| 200 | { NULL } |
| 201 | }; |
| 202 | |
| 203 | static const AVFilterPad bs2b_outputs[] = { |
| 204 | { |
| 205 | .name = "default", |
| 206 | .type = AVMEDIA_TYPE_AUDIO, |
| 207 | .config_props = config_output, |
| 208 | }, |
| 209 | { NULL } |
| 210 | }; |
| 211 | |
| 212 | AVFilter ff_af_bs2b = { |
| 213 | .name = "bs2b", |
| 214 | .description = NULL_IF_CONFIG_SMALL("Bauer stereo-to-binaural filter."), |
| 215 | .query_formats = query_formats, |
| 216 | .priv_size = sizeof(Bs2bContext), |
| 217 | .priv_class = &bs2b_class, |
| 218 | .init = init, |
| 219 | .uninit = uninit, |
| 220 | .inputs = bs2b_inputs, |
| 221 | .outputs = bs2b_outputs, |
| 222 | }; |