[pulseaudio-discuss] [PATCH 6/7] lfe-filter: Add rewind support

David Henningsson david.henningsson at canonical.com
Tue Feb 17 04:29:23 PST 2015


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



More information about the pulseaudio-discuss mailing list