mirror of
				https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
				synced 2025-11-03 09:01:50 -05:00 
			
		
		
		
	lfe-filter: Add rewind support
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@canonical.com>
This commit is contained in:
		
							parent
							
								
									d0e8b0fe07
								
							
						
					
					
						commit
						defc2b702b
					
				
					 3 changed files with 108 additions and 9 deletions
				
			
		| 
						 | 
					@ -23,9 +23,20 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "lfe-filter.h"
 | 
					#include "lfe-filter.h"
 | 
				
			||||||
#include <pulse/xmalloc.h>
 | 
					#include <pulse/xmalloc.h>
 | 
				
			||||||
 | 
					#include <pulsecore/flist.h>
 | 
				
			||||||
 | 
					#include <pulsecore/llist.h>
 | 
				
			||||||
#include <pulsecore/filter/biquad.h>
 | 
					#include <pulsecore/filter/biquad.h>
 | 
				
			||||||
#include <pulsecore/filter/crossover.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.
 | 
					/* 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
 | 
					   Currently the channel map is fixed so that a highpass filter is applied to all
 | 
				
			||||||
| 
						 | 
					@ -35,24 +46,37 @@
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct pa_lfe_filter {
 | 
					struct pa_lfe_filter {
 | 
				
			||||||
 | 
					    int64_t index;
 | 
				
			||||||
 | 
					    PA_LLIST_HEAD(struct saved_state, saved);
 | 
				
			||||||
    float crossover;
 | 
					    float crossover;
 | 
				
			||||||
    pa_channel_map cm;
 | 
					    pa_channel_map cm;
 | 
				
			||||||
    pa_sample_spec ss;
 | 
					    pa_sample_spec ss;
 | 
				
			||||||
 | 
					    size_t maxrewind;
 | 
				
			||||||
    bool active;
 | 
					    bool active;
 | 
				
			||||||
    struct lr4 lr4[PA_CHANNELS_MAX];
 | 
					    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);
 | 
					    pa_lfe_filter_t *f = pa_xnew0(struct pa_lfe_filter, 1);
 | 
				
			||||||
    f->crossover = crossover_freq;
 | 
					    f->crossover = crossover_freq;
 | 
				
			||||||
    f->cm = *cm;
 | 
					    f->cm = *cm;
 | 
				
			||||||
    f->ss = *ss;
 | 
					    f->ss = *ss;
 | 
				
			||||||
 | 
					    f->maxrewind = maxrewind;
 | 
				
			||||||
    pa_lfe_filter_update_rate(f, ss->rate);
 | 
					    pa_lfe_filter_update_rate(f, ss->rate);
 | 
				
			||||||
    return f;
 | 
					    return f;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void pa_lfe_filter_free(pa_lfe_filter_t *f) {
 | 
					void pa_lfe_filter_free(pa_lfe_filter_t *f) {
 | 
				
			||||||
 | 
					    while (f->saved)
 | 
				
			||||||
 | 
					        remove_state(f, f->saved);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pa_xfree(f);
 | 
					    pa_xfree(f);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,26 +84,61 @@ void pa_lfe_filter_reset(pa_lfe_filter_t *f) {
 | 
				
			||||||
    pa_lfe_filter_update_rate(f, f->ss.rate);
 | 
					    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_frame_size(&f->ss);
 | 
					    int samples = buf->length / pa_frame_size(&f->ss);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!f->active)
 | 
					    void *garbage = store_result ? NULL : pa_xmalloc(buf->length);
 | 
				
			||||||
        return buf;
 | 
					
 | 
				
			||||||
    if (f->ss.format == PA_SAMPLE_FLOAT32NE) {
 | 
					    if (f->ss.format == PA_SAMPLE_FLOAT32NE) {
 | 
				
			||||||
        int i;
 | 
					        int i;
 | 
				
			||||||
        float *data = pa_memblock_acquire_chunk(buf);
 | 
					        float *data = pa_memblock_acquire_chunk(buf);
 | 
				
			||||||
        for (i = 0; i < f->cm.channels; i++)
 | 
					        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);
 | 
					        pa_memblock_release(buf->memblock);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else if (f->ss.format == PA_SAMPLE_S16NE) {
 | 
					    else if (f->ss.format == PA_SAMPLE_S16NE) {
 | 
				
			||||||
        int i;
 | 
					        int i;
 | 
				
			||||||
        short *data = pa_memblock_acquire_chunk(buf);
 | 
					        short *data = pa_memblock_acquire_chunk(buf);
 | 
				
			||||||
        for (i = 0; i < f->cm.channels; i++)
 | 
					        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);
 | 
					        pa_memblock_release(buf->memblock);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else pa_assert_not_reached();
 | 
					    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;
 | 
				
			||||||
 | 
					    void *data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 / pa_frame_size(&f->ss) + 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* TODO: This actually memcpys the entire chunk into a new allocation, because we need to retain the original
 | 
				
			||||||
 | 
					       in case of rewinding. Investigate whether this can be avoided. */
 | 
				
			||||||
 | 
					    data = pa_memblock_acquire_chunk(buf);
 | 
				
			||||||
 | 
					    s->chunk.memblock = pa_memblock_new_malloced(pa_memblock_get_pool(buf->memblock), pa_xmemdup(data, buf->length), buf->length);
 | 
				
			||||||
 | 
					    s->chunk.length = buf->length;
 | 
				
			||||||
 | 
					    s->chunk.index = 0;
 | 
				
			||||||
 | 
					    pa_memblock_release(buf->memblock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    s->index = f->index;
 | 
				
			||||||
 | 
					    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;
 | 
					    return buf;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,6 +146,10 @@ void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) {
 | 
				
			||||||
    int i;
 | 
					    int i;
 | 
				
			||||||
    float biquad_freq = f->crossover / (new_rate / 2);
 | 
					    float biquad_freq = f->crossover / (new_rate / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (f->saved)
 | 
				
			||||||
 | 
					        remove_state(f, f->saved);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    f->index = 0;
 | 
				
			||||||
    f->ss.rate = new_rate;
 | 
					    f->ss.rate = new_rate;
 | 
				
			||||||
    if (biquad_freq <= 0 || biquad_freq >= 1) {
 | 
					    if (biquad_freq <= 0 || biquad_freq >= 1) {
 | 
				
			||||||
        pa_log_warn("Crossover frequency (%f) outside range for sample rate %d", f->crossover, new_rate);
 | 
					        pa_log_warn("Crossover frequency (%f) outside range for sample rate %d", f->crossover, new_rate);
 | 
				
			||||||
| 
						 | 
					@ -99,3 +162,37 @@ void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    f->active = true;
 | 
					    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_frame_size(&f->ss);
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					        x.length = (f->index - s->index) * pa_frame_size(&f->ss);
 | 
				
			||||||
 | 
					        if (x.length > s->chunk.length) {
 | 
				
			||||||
 | 
					            pa_log_error("Hole in stream, cannot fast forward LFE filter");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        f->index = s->index;
 | 
				
			||||||
 | 
					        process_block(f, &x, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,13 +25,14 @@
 | 
				
			||||||
#include <pulse/sample.h>
 | 
					#include <pulse/sample.h>
 | 
				
			||||||
#include <pulse/channelmap.h>
 | 
					#include <pulse/channelmap.h>
 | 
				
			||||||
#include <pulsecore/memchunk.h>
 | 
					#include <pulsecore/memchunk.h>
 | 
				
			||||||
 | 
					#include <pulsecore/memblockq.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct pa_lfe_filter pa_lfe_filter_t;
 | 
					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_free(pa_lfe_filter_t *);
 | 
				
			||||||
void pa_lfe_filter_reset(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);
 | 
					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);
 | 
					void pa_lfe_filter_update_rate(pa_lfe_filter_t *, uint32_t new_rate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -419,7 +419,8 @@ pa_resampler* pa_resampler_new(
 | 
				
			||||||
    if (lfe_filter_required) {
 | 
					    if (lfe_filter_required) {
 | 
				
			||||||
        pa_sample_spec wss = r->o_ss;
 | 
					        pa_sample_spec wss = r->o_ss;
 | 
				
			||||||
        wss.format = r->work_format;
 | 
					        wss.format = r->work_format;
 | 
				
			||||||
        r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, (float)crossover_freq);
 | 
					        /* FIXME: For now just hardcode maxrewind to 3 seconds */
 | 
				
			||||||
 | 
					        r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, (float)crossover_freq, b->rate * 3);
 | 
				
			||||||
        pa_log_debug("  lfe filter activated (LR4 type), the crossover_freq = %uHz", crossover_freq);
 | 
					        pa_log_debug("  lfe filter activated (LR4 type), the crossover_freq = %uHz", crossover_freq);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue