This is the combined diff of the first three patches for easier review. diff --git a/LICENSE b/LICENSE index 226c4ce..6932317 100644 --- a/LICENSE +++ b/LICENSE @@ -29,6 +29,9 @@ considered too small and stable to be considered as an external library) use the more permissive MIT license. This include the device reservation DBus protocol and realtime kit implementations. +A more permissive BSD-style license is used for LFE filters, see +src/pulsecore/filter/LICENSE.WEBKIT for details. + Additionally, a more permissive Sun license is used for code that performs u-law, A-law and linear PCM conversions. diff --git a/src/Makefile.am b/src/Makefile.am index 67f8627..302c532 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -911,6 +911,9 @@ lib_LTLIBRARIES += libpulsecore- at PA_MAJORMINOR@.la # Pure core stuff libpulsecore_ at PA_MAJORMINOR@_la_SOURCES = \ + pulsecore/filter/lfe-filter.c pulsecore/filter/lfe-filter.h \ + pulsecore/filter/biquad.c pulsecore/filter/biquad.h \ + pulsecore/filter/crossover.c pulsecore/filter/crossover.h \ pulsecore/asyncmsgq.c pulsecore/asyncmsgq.h \ pulsecore/asyncq.c pulsecore/asyncq.h \ pulsecore/auth-cookie.c pulsecore/auth-cookie.h \ diff --git a/src/pulsecore/filter/LICENSE.WEBKIT b/src/pulsecore/filter/LICENSE.WEBKIT new file mode 100644 index 0000000..2f69d9f --- /dev/null +++ b/src/pulsecore/filter/LICENSE.WEBKIT @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/src/pulsecore/filter/biquad.c b/src/pulsecore/filter/biquad.c new file mode 100644 index 0000000..a6121f4 --- /dev/null +++ b/src/pulsecore/filter/biquad.c @@ -0,0 +1,126 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Copyright (C) 2010 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE.WEBKIT file. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> + +#include <math.h> +#include "biquad.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, + double a0, double a1, double a2) +{ + double a0_inv = 1 / a0; + bq->b0 = b0 * a0_inv; + bq->b1 = b1 * a0_inv; + bq->b2 = b2 * a0_inv; + bq->a1 = a1 * a0_inv; + bq->a2 = a2 * a0_inv; +} + +static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = PA_MIN(cutoff, 1.0); + cutoff = PA_MAX(0.0, cutoff); + + if (cutoff >= 1.0) { + /* When cutoff is 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } else if (cutoff > 0) { + /* Compute biquad coefficients for lowpass filter */ + double r = PA_MAX(0.0, resonance); /* can't go negative */ + double g = pow(10.0, 0.05 * r); + double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); + + double theta = M_PI * cutoff; + double sn = 0.5 * d * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta - gamma); + + double b0 = 2 * alpha; + double b1 = 2 * 2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); + } else { + /* When cutoff is zero, nothing gets through the filter, so set + * coefficients up correctly. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } +} + +static void biquad_highpass(struct biquad *bq, double cutoff, double resonance) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = PA_MIN(cutoff, 1.0); + cutoff = PA_MAX(0.0, cutoff); + + if (cutoff >= 1.0) { + /* The z-transform is 0. */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } else if (cutoff > 0) { + /* Compute biquad coefficients for highpass filter */ + double r = PA_MAX(0.0, resonance); /* can't go negative */ + double g = pow(10.0, 0.05 * r); + double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); + + double theta = M_PI * cutoff; + double sn = 0.5 * d * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta + gamma); + + double b0 = 2 * alpha; + double b1 = 2 * -2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); + } else { + /* When cutoff is zero, we need to be careful because the above + * gives a quadratic divided by the same quadratic, with poles + * and zeros on the unit circle in the same place. When cutoff + * is zero, the z-transform is 1. + */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } +} + +void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, + double gain) +{ + + switch (type) { + case BQ_LOWPASS: + biquad_lowpass(bq, freq, Q); + break; + case BQ_HIGHPASS: + biquad_highpass(bq, freq, Q); + break; + case BQ_NONE: + /* Identity filter. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + break; + } +} diff --git a/src/pulsecore/filter/biquad.h b/src/pulsecore/filter/biquad.h new file mode 100644 index 0000000..4a84b61 --- /dev/null +++ b/src/pulsecore/filter/biquad.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef BIQUAD_H_ +#define BIQUAD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) + * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs + * are stored in x1 and x2, and the previous two outputs are stored in y1 and + * y2. + * + * We use double during the coefficients calculation for better accurary, but + * float is used during the actual filtering for faster computation. + */ +struct biquad { + float b0, b1, b2; + float a1, a2; +}; + +/* The type of the biquad filters */ +enum biquad_type { + BQ_NONE, + BQ_LOWPASS, + BQ_HIGHPASS, +}; + +/* Initialize a biquad filter parameters from its type and parameters. + * Args: + * bq - The biquad filter we want to set. + * type - The type of the biquad filter. + * frequency - The value should be in the range [0, 1]. It is relative to + * half of the sampling rate. + * Q - Quality factor. See Web Audio API for details. + * gain - The value is in dB. See Web Audio API for details. + */ +void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, + double gain); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* BIQUAD_H_ */ diff --git a/src/pulsecore/filter/crossover.c b/src/pulsecore/filter/crossover.c new file mode 100644 index 0000000..c2a584b --- /dev/null +++ b/src/pulsecore/filter/crossover.c @@ -0,0 +1,97 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> + +#include "crossover.h" + +void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq) +{ + biquad_set(&lr4->bq, type, freq, 0, 0); + lr4->x1 = 0; + lr4->x2 = 0; + lr4->y1 = 0; + lr4->y2 = 0; + lr4->z1 = 0; + lr4->z2 = 0; +} + +void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest) +{ + float lx1 = lr4->x1; + float lx2 = lr4->x2; + float ly1 = lr4->y1; + float ly2 = lr4->y2; + float lz1 = lr4->z1; + float lz2 = lr4->z2; + float lb0 = lr4->bq.b0; + float lb1 = lr4->bq.b1; + float lb2 = lr4->bq.b2; + float la1 = lr4->bq.a1; + float la2 = lr4->bq.a2; + + int i; + for (i = 0; i < samples; i += channels) { + float x, y, z; + x = src[i]; + y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2; + z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2; + lx2 = lx1; + lx1 = x; + ly2 = ly1; + ly1 = y; + lz2 = lz1; + lz1 = z; + dest[i] = PA_CLAMP_UNLIKELY((int) z, -0x8000, 0x7fff); + } + + lr4->x1 = lx1; + lr4->x2 = lx2; + lr4->y1 = ly1; + lr4->y2 = ly2; + lr4->z1 = lz1; + lr4->z2 = lz2; +} + +void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest) +{ + float lx1 = lr4->x1; + float lx2 = lr4->x2; + float ly1 = lr4->y1; + float ly2 = lr4->y2; + float lz1 = lr4->z1; + float lz2 = lr4->z2; + float lb0 = lr4->bq.b0; + float lb1 = lr4->bq.b1; + float lb2 = lr4->bq.b2; + float la1 = lr4->bq.a1; + float la2 = lr4->bq.a2; + + int i; + for (i = 0; i < samples; i += channels) { + float x, y, z; + x = src[i]; + y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2; + z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2; + lx2 = lx1; + lx1 = x; + ly2 = ly1; + ly1 = y; + lz2 = lz1; + lz1 = z; + dest[i] = z; + } + + lr4->x1 = lx1; + lr4->x2 = lx2; + lr4->y1 = ly1; + lr4->y2 = ly2; + lr4->z1 = lz1; + lr4->z2 = lz2; +} diff --git a/src/pulsecore/filter/crossover.h b/src/pulsecore/filter/crossover.h new file mode 100644 index 0000000..c5c9765 --- /dev/null +++ b/src/pulsecore/filter/crossover.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef CROSSOVER_H_ +#define CROSSOVER_H_ + +#include "biquad.h" +/* An LR4 filter is two biquads with the same parameters connected in series: + * + * x -- [BIQUAD] -- y -- [BIQUAD] -- z + * + * Both biquad filter has the same parameter b[012] and a[12], + * The variable [xyz][12] keep the history values. + */ +struct lr4 { + struct biquad bq; + float x1, x2; + float y1, y2; + float z1, z2; +}; + +void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq); + +void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest); +void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest); + +#endif /* CROSSOVER_H_ */ diff --git a/src/pulsecore/filter/lfe-filter.c b/src/pulsecore/filter/lfe-filter.c new file mode 100644 index 0000000..9b238fe --- /dev/null +++ b/src/pulsecore/filter/lfe-filter.c @@ -0,0 +1,103 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 David Henningsson, Canonical Ltd. + + PulseAudio 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. + + PulseAudio 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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "lfe-filter.h" +#include <pulse/xmalloc.h> +#include <pulsecore/filter/biquad.h> +#include <pulsecore/filter/crossover.h> + +/* An LR4 filter, implemented as a chain of two Butterworth filters. + + Currently the channel map is fixed so that a highpass filter is applied to all + channels except for the LFE channel, where a lowpass filter is applied. + This works well for e g stereo to 2.1/5.1/7.1 scenarios, where the remap engine + has calculated the LFE channel to be the average of all source channels. +*/ + +struct pa_lfe_filter { + float crossover; + pa_channel_map cm; + pa_sample_spec ss; + bool active; + struct lr4 lr4[PA_CHANNELS_MAX]; +}; + +pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq) { + + pa_lfe_filter_t *f = pa_xnew0(struct pa_lfe_filter, 1); + f->crossover = crossover_freq; + f->cm = *cm; + f->ss = *ss; + pa_lfe_filter_update_rate(f, ss->rate); + return f; +} + +void pa_lfe_filter_free(pa_lfe_filter_t *f) { + pa_xfree(f); +} + +void pa_lfe_filter_reset(pa_lfe_filter_t *f) { + pa_lfe_filter_update_rate(f, f->ss.rate); +} + +pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) { + int samples = buf->length / pa_sample_size_of_format(f->ss.format); + + if (!f->active) + return buf; + if (f->ss.format == PA_SAMPLE_FLOAT32NE) { + int i; + float *data = pa_memblock_acquire_chunk(buf); + for (i = 0; i < f->cm.channels; i++) + lr4_process_float32(&f->lr4[i], samples, f->cm.channels, &data[i], &data[i]); + pa_memblock_release(buf->memblock); + } + else if (f->ss.format == PA_SAMPLE_S16NE) { + int i; + short *data = pa_memblock_acquire_chunk(buf); + for (i = 0; i < f->cm.channels; i++) + lr4_process_s16(&f->lr4[i], samples, f->cm.channels, &data[i], &data[i]); + pa_memblock_release(buf->memblock); + } + else pa_assert_not_reached(); + return buf; +} + +void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) { + int i; + float biquad_freq = f->crossover / (new_rate / 2); + + f->ss.rate = new_rate; + if (biquad_freq <= 0 || biquad_freq >= 1) { + pa_log_warn("Crossover frequency (%f) outside range for sample rate %d", f->crossover, new_rate); + f->active = false; + return; + } + + for (i = 0; i < f->cm.channels; i++) + lr4_set(&f->lr4[i], f->cm.map[i] == PA_CHANNEL_POSITION_LFE ? BQ_LOWPASS : BQ_HIGHPASS, biquad_freq); + + f->active = true; +} diff --git a/src/pulsecore/filter/lfe-filter.h b/src/pulsecore/filter/lfe-filter.h new file mode 100644 index 0000000..25db8a0 --- /dev/null +++ b/src/pulsecore/filter/lfe-filter.h @@ -0,0 +1,38 @@ +#ifndef foolfefilterhfoo +#define foolfefilterhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 David Henningsson, Canonical Ltd. + + PulseAudio 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. + + PulseAudio 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 + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulsecore/memchunk.h> + + +typedef struct pa_lfe_filter pa_lfe_filter_t; + +pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq); +void pa_lfe_filter_free(pa_lfe_filter_t *); +void pa_lfe_filter_reset(pa_lfe_filter_t *); +pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *filter, pa_memchunk *buf); +void pa_lfe_filter_update_rate(pa_lfe_filter_t *, uint32_t new_rate); + +#endif diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c index 0d7cc20..157671d 100644 --- a/src/pulsecore/resampler.c +++ b/src/pulsecore/resampler.c @@ -40,7 +40,7 @@ struct ffmpeg_data { /* data specific to ffmpeg */ static int copy_init(pa_resampler *r); -static void setup_remap(const pa_resampler *r, pa_remap_t *m); +static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_filter_required); static void free_remap(pa_remap_t *m); static int (* const init_table[])(pa_resampler *r) = { @@ -324,6 +324,7 @@ pa_resampler* pa_resampler_new( pa_resample_flags_t flags) { pa_resampler *r = NULL; + bool lfe_filter_required = false; pa_assert(pool); pa_assert(a); @@ -412,7 +413,15 @@ pa_resampler* pa_resampler_new( /* set up the remap structure */ if (r->map_required) - setup_remap(r, &r->remap); + setup_remap(r, &r->remap, &lfe_filter_required); + + if (lfe_filter_required) { + pa_sample_spec wss = r->o_ss; + wss.format = r->work_format; + /* TODO: Temporary code that sets crossover freq to 120 Hz. This should be a parameter */ + r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, 120.0f); + pa_log_debug(" lfe filter activated (LR4 type)"); + } /* initialize implementation */ if (init_table[method](r) < 0) @@ -434,6 +443,9 @@ void pa_resampler_free(pa_resampler *r) { else pa_xfree(r->impl.data); + if (r->lfe_filter) + pa_lfe_filter_free(r->lfe_filter); + if (r->to_work_format_buf.memblock) pa_memblock_unref(r->to_work_format_buf.memblock); if (r->remap_buf.memblock) @@ -472,6 +484,9 @@ void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) { r->o_ss.rate = rate; r->impl.update_rates(r); + + if (r->lfe_filter) + pa_lfe_filter_update_rate(r->lfe_filter, rate); } size_t pa_resampler_request(pa_resampler *r, size_t out_length) { @@ -556,6 +571,9 @@ void pa_resampler_reset(pa_resampler *r) { if (r->impl.reset) r->impl.reset(r); + if (r->lfe_filter) + pa_lfe_filter_reset(r->lfe_filter); + *r->have_leftover = false; } @@ -756,7 +774,7 @@ static int front_rear_side(pa_channel_position_t p) { return ON_OTHER; } -static void setup_remap(const pa_resampler *r, pa_remap_t *m) { +static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_filter_required) { unsigned oc, ic; unsigned n_oc, n_ic; bool ic_connected[PA_CHANNELS_MAX]; @@ -765,6 +783,7 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m) { pa_assert(r); pa_assert(m); + pa_assert(lfe_filter_required); n_oc = r->o_ss.channels; n_ic = r->i_ss.channels; @@ -777,6 +796,7 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m) { memset(m->map_table_i, 0, sizeof(m->map_table_i)); memset(ic_connected, 0, sizeof(ic_connected)); + *lfe_filter_required = false; if (r->flags & PA_RESAMPLER_NO_REMAP) { for (oc = 0; oc < PA_MIN(n_ic, n_oc); oc++) @@ -888,6 +908,9 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m) { oc_connected = true; ic_connected[ic] = true; + + if (a == PA_CHANNEL_POSITION_MONO && on_lfe(b) && !(r->flags & PA_RESAMPLER_NO_LFE)) + *lfe_filter_required = true; } else if (b == PA_CHANNEL_POSITION_MONO) { m->map_table_f[oc][ic] = 1.0f / (float) n_ic; @@ -970,6 +993,8 @@ static void setup_remap(const pa_resampler *r, pa_remap_t *m) { /* Please note that a channel connected to LFE doesn't * really count as connected. */ + + *lfe_filter_required = true; } } } @@ -1340,6 +1365,9 @@ void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out) buf = remap_channels(r, buf); } + if (r->lfe_filter) + buf = pa_lfe_filter_process(r->lfe_filter, buf); + if (buf->length) { buf = convert_from_work_format(r, buf); *out = *buf; diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h index 4840a5e..45ad75a 100644 --- a/src/pulsecore/resampler.h +++ b/src/pulsecore/resampler.h @@ -26,6 +26,7 @@ #include <pulsecore/memchunk.h> #include <pulsecore/sconv.h> #include <pulsecore/remap.h> +#include <pulsecore/filter/lfe-filter.h> typedef struct pa_resampler pa_resampler; typedef struct pa_resampler_impl pa_resampler_impl; @@ -106,6 +107,8 @@ struct pa_resampler { pa_remap_t remap; bool map_required; + pa_lfe_filter_t *lfe_filter; + pa_resampler_impl impl; };