History

avfilter/af_crossfeed: add option for linear phase filtering

But at expense of additional delay.

Paul B Mahol committed 4 years ago

Commit 5ccd8f41 parent 13098672 files · +167 −14Browse files

Unified · Split

2 files
  1. doc/filters.texi +7 −0
  2. libavfilter/af_crossfeed.c +160 −14
doc/filters.texi+7−0
@@ -4205,6 +4205,13 @@ Set input gain. Default is 0.9. · 4204 unmodified lines
4205 4205
4206@item level_out4206@item level_out
4207Set output gain. Default is 1.4207Set output gain. Default is 1.
4208
4209@item block_size
4210Set block size used for reverse IIR processing. If this value is set to high enough
4211value (higher than impulse response length truncated when reaches near zero values) filtering
4212will become linear phase otherwise if not big enough it will just produce nasty artifacts.
4213
4214Note that filter delay will be exactly this many samples when set to non-zero value.
4208@end table4215@end table
4209 4216
4210@subsection Commands4217@subsection Commands
libavfilter/af_crossfeed.c+160−14
@@ -21,6 +21,7 @@ · 20 unmodified lines
21#include "libavutil/opt.h"21#include "libavutil/opt.h"
22#include "avfilter.h"22#include "avfilter.h"
23#include "audio.h"23#include "audio.h"
24#include "filters.h"
24#include "formats.h"25#include "formats.h"
25 26
26typedef struct CrossfeedContext {27typedef struct CrossfeedContext {
@@ -31,11 +32,16 @@ typedef struct CrossfeedContext { · 4 unmodified lines
31 double slope;32 double slope;
32 double level_in;33 double level_in;
33 double level_out;34 double level_out;
35 int block_samples;
36 int block_size;
34 37
35 double a0, a1, a2;38 double a0, a1, a2;
36 double b0, b1, b2;39 double b0, b1, b2;
37 40
38 double w1, w2;41 double w1, w2;
42
43 double *mid;
44 double *side[3];
39} CrossfeedContext;45} CrossfeedContext;
40 46
41static int query_formats(AVFilterContext *ctx)47static int query_formats(AVFilterContext *ctx)
@@ -77,9 +83,49 @@ static int config_input(AVFilterLink *inlink) · 35 unmodified lines
77 s->b1 /= s->a0;83 s->b1 /= s->a0;
78 s->b2 /= s->a0;84 s->b2 /= s->a0;
79 85
86 if (s->block_samples == 0 && s->block_size > 0) {
87 s->block_samples = s->block_size;
88 s->mid = av_calloc(s->block_samples * 2, sizeof(*s->mid));
89 for (int i = 0; i < 3; i++) {
90 s->side[i] = av_calloc(s->block_samples * 2, sizeof(*s->side[0]));
91 if (!s->side[i])
92 return AVERROR(ENOMEM);
93 }
94 }
95
80 return 0;96 return 0;
81}97}
82 98
99static void reverse_samples(double *dst, const double *src,
100 int nb_samples)
101{
102 for (int i = 0, j = nb_samples - 1; i < nb_samples; i++, j--)
103 dst[i] = src[j];
104}
105
106static void filter_samples(double *dst, const double *src,
107 int nb_samples,
108 double b0, double b1, double b2,
109 double a1, double a2,
110 double *sw1, double *sw2)
111{
112 double w1 = *sw1;
113 double w2 = *sw2;
114
115 for (int n = 0; n < nb_samples; n++) {
116 double side = src[n];
117 double oside = side * b0 + w1;
118
119 w1 = b1 * side + w2 + a1 * oside;
120 w2 = b2 * side + a2 * oside;
121
122 dst[n] = oside;
123 }
124
125 *sw1 = w1;
126 *sw2 = w2;
127}
128
83static int filter_frame(AVFilterLink *inlink, AVFrame *in)129static int filter_frame(AVFilterLink *inlink, AVFrame *in)
84{130{
85 AVFilterContext *ctx = inlink->dst;131 AVFilterContext *ctx = inlink->dst;
@@ -95,7 +141,6 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) · 9 unmodified lines
95 const double a2 = -s->a2;141 const double a2 = -s->a2;
96 AVFrame *out;142 AVFrame *out;
97 double *dst;143 double *dst;
98 int n;
99 144
100 if (av_frame_is_writable(in)) {145 if (av_frame_is_writable(in)) {
101 out = in;146 out = in;
@@ -109,21 +154,79 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) · 7 unmodified lines
109 }154 }
110 dst = (double *)out->data[0];155 dst = (double *)out->data[0];
111 156
112 for (n = 0; n < out->nb_samples; n++, src += 2, dst += 2) {157 if (s->block_samples == 0) {
113 double mid = (src[0] + src[1]) * level_in * .5;158 double w1 = s->w1;
114 double side = (src[0] - src[1]) * level_in * .5;159 double w2 = s->w2;
115 double oside = side * b0 + s->w1;160
161 for (int n = 0; n < out->nb_samples; n++, src += 2, dst += 2) {
162 double mid = (src[0] + src[1]) * level_in * .5;
163 double side = (src[0] - src[1]) * level_in * .5;
164 double oside = side * b0 + w1;
165
166 w1 = b1 * side + w2 + a1 * oside;
167 w2 = b2 * side + a2 * oside;
168
169 if (ctx->is_disabled) {
170 dst[0] = src[0];
171 dst[1] = src[1];
172 } else {
173 dst[0] = (mid + oside) * level_out;
174 dst[1] = (mid - oside) * level_out;
175 }
176 }
177
178 s->w1 = w1;
179 s->w2 = w2;
180 } else {
181 double *mdst = s->mid + s->block_samples;
182 double *sdst = s->side[0] + s->block_samples;
183 double *ssrc = s->side[0];
184 double *msrc = s->mid;
185 double w1 = s->w1;
186 double w2 = s->w2;
187
188 for (int n = 0; n < out->nb_samples; n++, src += 2) {
189 mdst[n] = (src[0] + src[1]) * level_in * .5;
190 sdst[n] = (src[0] - src[1]) * level_in * .5;
191 }
192
193 sdst = s->side[1];
194 filter_samples(sdst, ssrc, s->block_samples,
195 b0, b1, b2, a1, a2,
196 &w1, &w2);
197 s->w1 = w1;
198 s->w2 = w2;
116 199
117 s->w1 = b1 * side + s->w2 + a1 * oside;200 ssrc = s->side[0] + s->block_samples;
118 s->w2 = b2 * side + a2 * oside;201 sdst = s->side[1] + s->block_samples;
202 filter_samples(sdst, ssrc, s->block_samples,
203 b0, b1, b2, a1, a2,
204 &w1, &w2);
119 205
120 if (ctx->is_disabled) {206 reverse_samples(s->side[2], s->side[1], s->block_samples * 2);
121 dst[0] = src[0];207 w1 = w2 = 0.;
122 dst[1] = src[1];208 filter_samples(s->side[2], s->side[2], s->block_samples * 2,
123 } else {209 b0, b1, b2, a1, a2,
124 dst[0] = (mid + oside) * level_out;210 &w1, &w2);
125 dst[1] = (mid - oside) * level_out;211
212 reverse_samples(s->side[1], s->side[2] + s->block_samples, s->block_samples);
213
214 src = (const double *)in->data[0];
215 ssrc = s->side[1];
216 for (int n = 0; n < out->nb_samples; n++, src += 2, dst += 2) {
217 if (ctx->is_disabled) {
218 dst[0] = src[0];
219 dst[1] = src[1];
220 } else {
221 dst[0] = (msrc[n] + ssrc[n]) * level_out;
222 dst[1] = (msrc[n] - ssrc[n]) * level_out;
223 }
126 }224 }
225
226 memmove(s->mid, s->mid + s->block_samples,
227 s->block_samples * sizeof(*s->mid));
228 memmove(s->side[0], s->side[0] + s->block_samples,
229 s->block_samples * sizeof(*s->side[0]));
127 }230 }
128 231
129 if (out != in)232 if (out != in)
@@ -131,6 +234,37 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) · 1 unmodified line
131 return ff_filter_frame(outlink, out);234 return ff_filter_frame(outlink, out);
132}235}
133 236
237static int activate(AVFilterContext *ctx)
238{
239 AVFilterLink *inlink = ctx->inputs[0];
240 AVFilterLink *outlink = ctx->outputs[0];
241 CrossfeedContext *s = ctx->priv;
242 AVFrame *in = NULL;
243 int ret;
244
245 FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
246
247 if (s->block_samples > 0) {
248 ret = ff_inlink_consume_samples(inlink, s->block_samples, s->block_samples, &in);
249 } else {
250 ret = ff_inlink_consume_frame(inlink, &in);
251 }
252 if (ret < 0)
253 return ret;
254 if (ret > 0)
255 return filter_frame(inlink, in);
256
257 if (s->block_samples > 0 && ff_inlink_queued_samples(inlink) >= s->block_samples) {
258 ff_filter_set_ready(ctx, 10);
259 return 0;
260 }
261
262 FF_FILTER_FORWARD_STATUS(inlink, outlink);
263 FF_FILTER_FORWARD_WANTED(outlink, inlink);
264
265 return FFERROR_NOT_READY;
266}
267
134static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,268static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
135 char *res, int res_len, int flags)269 char *res, int res_len, int flags)
136{270{
@@ -143,8 +277,18 @@ static int process_command(AVFilterContext *ctx, const char *cmd, const char *ar · 6 unmodified lines
143 return config_input(ctx->inputs[0]);277 return config_input(ctx->inputs[0]);
144}278}
145 279
280static av_cold void uninit(AVFilterContext *ctx)
281{
282 CrossfeedContext *s = ctx->priv;
283
284 av_freep(&s->mid);
285 for (int i = 0; i < 3; i++)
286 av_freep(&s->side[i]);
287}
288
146#define OFFSET(x) offsetof(CrossfeedContext, x)289#define OFFSET(x) offsetof(CrossfeedContext, x)
147#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM290#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
291#define AF AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
148 292
149static const AVOption crossfeed_options[] = {293static const AVOption crossfeed_options[] = {
150 { "strength", "set crossfeed strength", OFFSET(strength), AV_OPT_TYPE_DOUBLE, {.dbl=.2}, 0, 1, FLAGS },294 { "strength", "set crossfeed strength", OFFSET(strength), AV_OPT_TYPE_DOUBLE, {.dbl=.2}, 0, 1, FLAGS },
@@ -152,6 +296,7 @@ static const AVOption crossfeed_options[] = { · 1 unmodified line
152 { "slope", "set curve slope", OFFSET(slope), AV_OPT_TYPE_DOUBLE, {.dbl=.5}, .01, 1, FLAGS },296 { "slope", "set curve slope", OFFSET(slope), AV_OPT_TYPE_DOUBLE, {.dbl=.5}, .01, 1, FLAGS },
153 { "level_in", "set level in", OFFSET(level_in), AV_OPT_TYPE_DOUBLE, {.dbl=.9}, 0, 1, FLAGS },297 { "level_in", "set level in", OFFSET(level_in), AV_OPT_TYPE_DOUBLE, {.dbl=.9}, 0, 1, FLAGS },
154 { "level_out", "set level out", OFFSET(level_out), AV_OPT_TYPE_DOUBLE, {.dbl=1.}, 0, 1, FLAGS },298 { "level_out", "set level out", OFFSET(level_out), AV_OPT_TYPE_DOUBLE, {.dbl=1.}, 0, 1, FLAGS },
299 { "block_size", "set the block size", OFFSET(block_size),AV_OPT_TYPE_INT, {.i64=0}, 0, 32768, AF },
155 { NULL }300 { NULL }
156};301};
157 302
@@ -161,7 +306,6 @@ static const AVFilterPad inputs[] = { · 3 unmodified lines
161 {306 {
162 .name = "default",307 .name = "default",
163 .type = AVMEDIA_TYPE_AUDIO,308 .type = AVMEDIA_TYPE_AUDIO,
164 .filter_frame = filter_frame,
165 .config_props = config_input,309 .config_props = config_input,
166 },310 },
167};311};
@@ -178,6 +322,8 @@ const AVFilter ff_af_crossfeed = { · 10 unmodified lines
178 .description = NULL_IF_CONFIG_SMALL("Apply headphone crossfeed filter."),322 .description = NULL_IF_CONFIG_SMALL("Apply headphone crossfeed filter."),
179 .priv_size = sizeof(CrossfeedContext),323 .priv_size = sizeof(CrossfeedContext),
180 .priv_class = &crossfeed_class,324 .priv_class = &crossfeed_class,
325 .activate = activate,
326 .uninit = uninit,
181 FILTER_INPUTS(inputs),327 FILTER_INPUTS(inputs),
182 FILTER_OUTPUTS(outputs),328 FILTER_OUTPUTS(outputs),
183 FILTER_QUERY_FUNC(query_formats),329 FILTER_QUERY_FUNC(query_formats),

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t