Store current filter state at every normal block process. When a rewind happens, rewind back to the nearest saved state, then calculate forward to the actual sample position. Signed-off-by: David Henningsson <david.henningsson at canonical.com> --- src/pulsecore/filter/lfe-filter.c | 101 ++++++++++++++++++++++++++++++-- src/pulsecore/filter/lfe-filter.c.orig | 103 +++++++++++++++++++++++++++++++++ src/pulsecore/filter/lfe-filter.c.rej | 58 +++++++++++++++++++ src/pulsecore/filter/lfe-filter.h | 5 +- 4 files changed, 259 insertions(+), 8 deletions(-) create mode 100644 src/pulsecore/filter/lfe-filter.c.orig create mode 100644 src/pulsecore/filter/lfe-filter.c.rej diff --git a/src/pulsecore/filter/lfe-filter.c b/src/pulsecore/filter/lfe-filter.c index 9b238fe..2953657 100644 --- a/src/pulsecore/filter/lfe-filter.c +++ b/src/pulsecore/filter/lfe-filter.c @@ -25,9 +25,20 @@ #include "lfe-filter.h" #include <pulse/xmalloc.h> +#include <pulsecore/flist.h> +#include <pulsecore/llist.h> #include <pulsecore/filter/biquad.h> #include <pulsecore/filter/crossover.h> +struct saved_state { + PA_LLIST_FIELDS(struct saved_state); + pa_memchunk chunk; + int64_t index; + struct lr4 lr4[PA_CHANNELS_MAX]; +}; + +PA_STATIC_FLIST_DECLARE(lfe_state, 0, pa_xfree); + /* 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 @@ -37,24 +48,37 @@ */ struct pa_lfe_filter { + int64_t index; + PA_LLIST_HEAD(struct saved_state, saved); float crossover; pa_channel_map cm; pa_sample_spec ss; + size_t maxrewind; 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) { +static void remove_state(pa_lfe_filter_t *f, struct saved_state *s) { + PA_LLIST_REMOVE(struct saved_state, f->saved, s); + pa_memblock_unref(s->chunk.memblock); + pa_xfree(s); +} + +pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind) { pa_lfe_filter_t *f = pa_xnew0(struct pa_lfe_filter, 1); f->crossover = crossover_freq; f->cm = *cm; f->ss = *ss; + f->maxrewind = maxrewind; pa_lfe_filter_update_rate(f, ss->rate); return f; } void pa_lfe_filter_free(pa_lfe_filter_t *f) { + while (f->saved) + remove_state(f, f->saved); + pa_xfree(f); } @@ -62,26 +86,53 @@ 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) { +static void process_block(pa_lfe_filter_t *f, pa_memchunk *buf, bool store_result) { int samples = buf->length / pa_sample_size_of_format(f->ss.format); - if (!f->active) - return buf; + void *garbage = store_result ? NULL : pa_xmalloc(buf->length); + 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]); + lr4_process_float32(&f->lr4[i], samples, f->cm.channels, &data[i], garbage ? garbage : &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]); + lr4_process_s16(&f->lr4[i], samples, f->cm.channels, &data[i], garbage ? garbage : &data[i]); pa_memblock_release(buf->memblock); } else pa_assert_not_reached(); + + pa_xfree(garbage); + f->index += samples; +} + +pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) { + struct saved_state *s, *s2; + + if (!f->active) + return buf; + + // Remove old states (FIXME: we could do better than searching the entire array here?) + PA_LLIST_FOREACH_SAFE(s, s2, f->saved) + if (s->index + (int64_t) (s->chunk.length + f->maxrewind) < f->index) + remove_state(f, s); + + // Insert our existing state into the flist + if ((s = pa_flist_pop(PA_STATIC_FLIST_GET(lfe_state))) == NULL) + s = pa_xnew(struct saved_state, 1); + PA_LLIST_INIT(struct saved_state, s); + s->index = f->index; + s->chunk = *buf; + pa_memblock_ref(buf->memblock); + memcpy(s->lr4, f->lr4, sizeof(struct lr4) * f->cm.channels); + PA_LLIST_PREPEND(struct saved_state, f->saved, s); + + process_block(f, buf, true); return buf; } @@ -89,6 +140,10 @@ 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); + while (f->saved) + remove_state(f, f->saved); + + f->index = 0; 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); @@ -101,3 +156,37 @@ void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) { f->active = true; } + +void pa_lfe_filter_rewind(pa_lfe_filter_t *f, size_t amount) { + struct saved_state *i, *s = NULL; + size_t samples = amount / pa_sample_size_of_format(f->ss.format); + f->index -= samples; + + // Find the closest saved position + PA_LLIST_FOREACH(i, f->saved) { + if (i->index > f->index) + continue; + if (s == NULL || i->index > s->index) + s = i; + } + if (s == NULL) { + pa_log_debug("Rewinding LFE filter %lu samples to position %lli. No saved state found", samples, (long long) f->index); + pa_lfe_filter_update_rate(f, f->ss.rate); + return; + } + pa_log_debug("Rewinding LFE filter %lu samples to position %lli. Found saved state at position %lli", + samples, (long long) f->index, (long long) s->index); + memcpy(f->lr4, s->lr4, sizeof(struct lr4) * f->cm.channels); + + // now fast forward to the actual position + if (f->index > s->index) { + pa_memchunk x = s->chunk; + size_t left = f->index - s->index; + if (left > s->chunk.length) { + pa_log_error("Hole in stream, cannot fast forward LFE filter"); + return; + } + f->index = s->index; + process_block(f, &x, false); + } +} diff --git a/src/pulsecore/filter/lfe-filter.c.orig b/src/pulsecore/filter/lfe-filter.c.orig new file mode 100644 index 0000000..9b238fe --- /dev/null +++ b/src/pulsecore/filter/lfe-filter.c.orig @@ -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.c.rej b/src/pulsecore/filter/lfe-filter.c.rej new file mode 100644 index 0000000..7dec57f --- /dev/null +++ b/src/pulsecore/filter/lfe-filter.c.rej @@ -0,0 +1,58 @@ +--- src/pulsecore/filter/lfe-filter.c ++++ src/pulsecore/filter/lfe-filter.c +@@ -25,30 +25,54 @@ + + #include "lfe-filter.h" + #include <pulse/xmalloc.h> ++#include <pulsecore/flist.h> ++#include <pulsecore/llist.h> + #include <pulsecore/filter/biquad.h> + #include <pulsecore/filter/crossover.h> + ++struct saved_state { ++ PA_LLIST_FIELDS(struct saved_state); ++ pa_memchunk chunk; ++ int64_t index; ++ struct lr4 lr4[PA_CHANNELS_MAX]; ++}; ++ ++PA_STATIC_FLIST_DECLARE(lfe_state, 0, pa_xfree); ++ + // An LR4 filter, implemented as a chain of two LR2 filters. + + struct pa_lfe_filter { ++ int64_t index; ++ PA_LLIST_HEAD(struct saved_state, saved); + float crossover; + pa_channel_map cm; + pa_sample_spec ss; ++ size_t maxrewind; + 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) { ++static void remove_state(pa_lfe_filter_t *f, struct saved_state *s) { ++ PA_LLIST_REMOVE(struct saved_state, f->saved, s); ++ pa_memblock_unref(s->chunk.memblock); ++ pa_xfree(s); ++} ++ ++pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind) { + + pa_lfe_filter_t *f = pa_xnew0(struct pa_lfe_filter, 1); + f->crossover = crossover_freq; + f->cm = *cm; + f->ss = *ss; ++ f->maxrewind = maxrewind; + pa_lfe_filter_update_rate(f, ss->rate); + return f; + } + + void pa_lfe_filter_free(pa_lfe_filter_t *f) { ++ while (f->saved) ++ remove_state(f, f->saved); ++ + pa_xfree(f); + } + diff --git a/src/pulsecore/filter/lfe-filter.h b/src/pulsecore/filter/lfe-filter.h index 25db8a0..54d695b 100644 --- a/src/pulsecore/filter/lfe-filter.h +++ b/src/pulsecore/filter/lfe-filter.h @@ -25,13 +25,14 @@ #include <pulse/sample.h> #include <pulse/channelmap.h> #include <pulsecore/memchunk.h> - +#include <pulsecore/memblockq.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); +pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind); void pa_lfe_filter_free(pa_lfe_filter_t *); void pa_lfe_filter_reset(pa_lfe_filter_t *); +void pa_lfe_filter_rewind(pa_lfe_filter_t *, size_t amount); 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); -- 1.9.1