[pulseaudio-discuss] [PATCH 2/6] lfe-filter: Enable LFE filter in the resampler

David Henningsson david.henningsson at canonical.com
Mon Feb 9 13:27:06 PST 2015



On 2015-02-01 16:15, Alexander E. Patrakov wrote:
> 29.01.2015 03:14, David Henningsson wrote:
>> When enable-lfe-remixing is set, an LFE channel is present in the
>> resampler's destination channel map but not in the source channel map,
>> we insert a low-pass filter instead of just averaging the channels.
>> Other channels will get a high-pass filter.
>
> That's a policy decision. Essentially, your code assumes that the LFE
> channel, if present, is correct for the target speakers. Other possible
> policies would be to add it to center and then always regenerate it, or
> to add the regenerated content to the original LFE content.
>
> But, for the time being, let's proceed with this policy.
>
>>
>> In this patch, the crossover frequency is hardcoded to 120Hz (to be fixed
>> in later patches).
>>
>> Note that in current state the LFE filter is
>>   - not very optimised
>>   - not rewind friendly (rewinding can cause audible artifacts)
>>
>> Signed-off-by: David Henningsson <david.henningsson at canonical.com>
>> ---
>>   src/Makefile.am                   |  3 ++
>>   src/pulsecore/filter/crossover.c  | 79 ++++++++++++++++++++++++++++++-
>>   src/pulsecore/filter/crossover.h  |  6 +++
>>   src/pulsecore/filter/lfe-filter.c | 97
>> +++++++++++++++++++++++++++++++++++++++
>>   src/pulsecore/filter/lfe-filter.h | 38 +++++++++++++++
>>   src/pulsecore/resampler.c         | 34 ++++++++++++--
>>   src/pulsecore/resampler.h         |  3 ++
>>   7 files changed, 255 insertions(+), 5 deletions(-)
>>   create mode 100644 src/pulsecore/filter/lfe-filter.c
>>   create mode 100644 src/pulsecore/filter/lfe-filter.h
>>
>> diff --git a/src/Makefile.am b/src/Makefile.am
>> index e37a22a..d200271 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/crossover.c
>> b/src/pulsecore/filter/crossover.c
>> index 11a8c6e..c902099 100644
>> --- a/src/pulsecore/filter/crossover.c
>> +++ b/src/pulsecore/filter/crossover.c
>> @@ -3,10 +3,10 @@
>>    * found in the LICENSE file.
>>    */
>>
>> -#include "crossover.h"
>>   #include "biquad.h"
>> +#include "crossover.h"
>>
>> -static void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq)
>> +void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq)
>>   {
>>       struct biquad q;
>>       biquad_set(&q, type, freq, 0, 0);
>> @@ -23,6 +23,81 @@ static void lr4_set(struct lr4 *lr4, enum
>> biquad_type type, float freq)
>>       lr4->z2 = 0;
>>   }
>>
>> +void lr4_process(struct lr4 *lr4, int samples, int channels, float
>> *data0)
>> +{
>> +    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->b0;
>> +    float lb1 = lr4->b1;
>> +    float lb2 = lr4->b2;
>> +    float la1 = lr4->a1;
>> +    float la2 = lr4->a2;
>> +
>> +    int i;
>> +    for (i = 0; i < samples; i += channels) {
>> +        float x, y, z;
>> +        x = data0[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;
>> +        data0[i] = z;
>
> In-place modification. Why?
>
>> +    }
>> +
>> +    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 *data0)
>> +{
>> +    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->b0;
>> +    float lb1 = lr4->b1;
>> +    float lb2 = lr4->b2;
>> +    float la1 = lr4->a1;
>> +    float la2 = lr4->a2;
>> +
>> +    int i;
>> +    for (i = 0; i < samples; i += channels) {
>> +        float x, y, z;
>> +        x = data0[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;
>> +        data0[i] = z;
>
> Filters can overshoot. Please clamp to the range of short.

Is this really true here - as Butterworth and LR4 filters never boost 
any frequency, only attenuate, can this filter still overshoot?

>
>> +    }
>> +
>> +    lr4->x1 = lx1;
>> +    lr4->x2 = lx2;
>> +    lr4->y1 = ly1;
>> +    lr4->y2 = ly2;
>> +    lr4->z1 = lz1;
>> +    lr4->z2 = lz2;
>> +}
>> +
>> +
>>   /* Split input data using two LR4 filters, put the result into the
>> input array
>>    * and another array.
>>    *
>> diff --git a/src/pulsecore/filter/crossover.h
>> b/src/pulsecore/filter/crossover.h
>> index 99a601c..aa1140f 100644
>> --- a/src/pulsecore/filter/crossover.h
>> +++ b/src/pulsecore/filter/crossover.h
>> @@ -25,6 +25,12 @@ struct lr4 {
>>       float z1, z2;
>>   };
>>
>> +void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq);
>> +
>> +void lr4_process(struct lr4 *lr4, int samples, int channels, float
>> *data0);
>> +void lr4_process_s16(struct lr4 *lr4, int samples, int channels,
>> short *data0);
>> +
>> +
>>   /* Three bands crossover filter:
>>    *
>>    * INPUT --+-- lp0 --+-- lp1 --+---> LOW (0)
>> diff --git a/src/pulsecore/filter/lfe-filter.c
>> b/src/pulsecore/filter/lfe-filter.c
>> new file mode 100644
>> index 0000000..1597eca
>> --- /dev/null
>> +++ b/src/pulsecore/filter/lfe-filter.c
>> @@ -0,0 +1,97 @@
>> +/***
>> +  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 LR2 filters.
>
> As a chain of two Butterworth filters. LR2 is not Butterworth.
> Butterworth filter has Q == 0.707, LR2 has Q == 0.5.

Ack

>
>> +
>> +struct pa_lfe_filter {
>> +    float crossover;
>> +    pa_channel_map cm;
>> +    pa_sample_spec ss;
>> +    bool active;
>> +    struct lr4 lr4[PA_CHANNELS_MAX];
>
> The allocation (and the fact that there is one filter per output
> channel) is non-obvious here and should be commented better. For each
> non-LFE channel, a trivial implementation would have one struct lr4 for
> highpass filter and one struct lr4 for lowpass filter. Here you reduced
> the number of the needed filters by pre-averaging non-LFE input channels
> and running them through a single filter.

The existing remap algorithm - or policy, if you prefer - is to average 
all channels and put it on the LFE channel.

It seems to me that if you wanted another remap algorithm that should be 
a comment in setup_remap(), not here. I wouldn't say that this filter 
"expects" anything specific, it just removes frequencies according to 
the crossover, and it's the job of setup_remap() to set things up correctly.

I could even add a channel bitmask to make it more generic and allow 
other channels to be high- or lowpass than the current fixed "LFE is 
lowpass everything else is highpass", but I thought it was unnecessary 
complexity at this point.

>> +};
>> +
>> +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) {
>
> Maybe a comment is needed that this function expects an average of all
> non-LFE channels in the would-be-LFE channel? Not sure how this would
> mix with the alternative LFE channel generation policies.

See above.

-- 
David Henningsson, Canonical Ltd.
https://launchpad.net/~diwic


More information about the pulseaudio-discuss mailing list