[pulseaudio-discuss] [PATCH] add module-virtual-surround-sink

Tanu Kaskinen tanuk at iki.fi
Sun Feb 12 06:17:17 PST 2012


On Sun, 2012-01-22 at 10:53 +0100, Niels Ole Salscheider wrote:
> > 2) Why should we do this advanced downmixing in an optional module?
> > I.e., why not just replace the default downmixing algorighm without
> > creating a module for that?
> 
> Replacing the default downmixing algorithm is not a good idea since this one 
> only works for headphones. And you might want to use a simpler one on a low 
> end machine since folding needs some cpu cycles.
> But if there is a better place to implement this, I am fine with it, too.

For now, a separate filter sink is probably the best solution (or at
least the easiest one). It would be nice to have configurable downmixing
algorithms, though.

> I forgot to add a small fix to my last patch; I have attached a new version.

Thanks for the patch! Sounds like a very cool feature, if it works well
(I haven't tested yet). Code review below. Note that I'm not good at
signal processing algorithms, and I couldn't quite understand what the
actual processing code does (I'm pretty sure that it does a thing called
"convolution", but I couldn't verify that it does it correctly or well).
I trust that it's perfect :)

First complaint: doesn't your git nag about trailing whitespace? When
trying to commit the patch, I get:

src/modules/module-virtual-surround-sink.c:232: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:521: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:524: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:527: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:530: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:533: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:536: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:539: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:542: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:545: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:548: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:551: trailing whitespace.
+    
src/modules/module-virtual-surround-sink.c:554: trailing whitespace.
+    

> From 4e8a09d44ca8df7a7cc32662d0a784c77f09523c Mon Sep 17 00:00:00 2001
> From: Niels Ole Salscheider <niels_ole at salscheider-online.de>
> Date: Sun, 8 Jan 2012 21:22:35 +0100
> Subject: [PATCH] add module-virtual-surround-sink
> 
> It provides a virtual surround sound effect
> 
> v2: Normalize hrir to avoid clipping, some cleanups
> v3: use fabs, not abs
> ---
>  src/Makefile.am                                    |    7 +
>  ...rtual-sink.c => module-virtual-surround-sink.c} |  272
> +++++++++++++++++---
>  2 files changed, 246 insertions(+), 33 deletions(-)
>  copy src/modules/{module-virtual-sink.c =>
> module-virtual-surround-sink.c} (69%)
> 
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 521bf50..8f942f0 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -1009,6 +1009,7 @@ modlibexec_LTLIBRARIES += \
>                 module-loopback.la \
>                 module-virtual-sink.la \
>                 module-virtual-source.la \
> +               module-virtual-surround-sink.la \
>                 module-switch-on-connect.la \
>                 module-filter-apply.la \
>                 module-filter-heuristics.la
> @@ -1309,6 +1310,7 @@ SYMDEF_FILES = \
>                 module-loopback-symdef.h \
>                 module-virtual-sink-symdef.h \
>                 module-virtual-source-symdef.h \
> +               module-virtual-surround-sink-symdef.h \
>                 module-switch-on-connect-symdef.h \
>                 module-filter-apply-symdef.h \
>                 module-filter-heuristics-symdef.h
> @@ -1525,6 +1527,11 @@ module_virtual_source_la_CFLAGS = $(AM_CFLAGS)
> $(SERVER_CFLAGS)
>  module_virtual_source_la_LDFLAGS = $(MODULE_LDFLAGS)
>  module_virtual_source_la_LIBADD = $(MODULE_LIBADD)
>  
> +module_virtual_surround_sink_la_SOURCES =
> modules/module-virtual-surround-sink.c
> +module_virtual_surround_sink_la_CFLAGS = $(AM_CFLAGS)
> $(SERVER_CFLAGS)
> +module_virtual_surround_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
> +module_virtual_surround_sink_la_LIBADD = $(MODULE_LIBADD)
> +
>  # X11
>  
>  module_x11_bell_la_SOURCES = modules/x11/module-x11-bell.c
> diff --git a/src/modules/module-virtual-sink.c
> b/src/modules/module-virtual-surround-sink.c
> similarity index 69%
> copy from src/modules/module-virtual-sink.c
> copy to src/modules/module-virtual-surround-sink.c
> index e7c476e..b3100e4 100644
> --- a/src/modules/module-virtual-sink.c
> +++ b/src/modules/module-virtual-surround-sink.c
> @@ -3,6 +3,7 @@
>  
>      Copyright 2010 Intel Corporation
>      Contributor: Pierre-Louis Bossart
> <pierre-louis.bossart at intel.com>
> +    Copyright 2012 Niels Ole Salscheider
> <niels_ole at salscheider-online.de>
>  
>      PulseAudio is free software; you can redistribute it and/or
> modify
>      it under the terms of the GNU Lesser General Public License as
> published
> @@ -40,11 +41,15 @@
>  #include <pulsecore/rtpoll.h>
>  #include <pulsecore/sample-util.h>
>  #include <pulsecore/ltdl-helper.h>
> +#include <pulsecore/sound-file.h>
> +#include <pulsecore/resampler.h>
>  
> -#include "module-virtual-sink-symdef.h"
> +#include <math.h>
>  
> -PA_MODULE_AUTHOR("Pierre-Louis Bossart");
> -PA_MODULE_DESCRIPTION(_("Virtual sink"));
> +#include "module-virtual-surround-sink-symdef.h"
> +
> +PA_MODULE_AUTHOR("Niels Ole Salscheider");
> +PA_MODULE_DESCRIPTION(_("Virtual surround sink"));
>  PA_MODULE_VERSION(PACKAGE_VERSION);
>  PA_MODULE_LOAD_ONCE(FALSE);
>  PA_MODULE_USAGE(
> @@ -57,6 +62,8 @@ PA_MODULE_USAGE(
>            "channel_map=<channel map> "
>            "use_volume_sharing=<yes or no> "
>            "force_flat_volume=<yes or no> "
> +          "hrir=/path/to/left_hrir.wav "
> +          "hrir_channel_map=<channel map> "
>          ));
>  
>  #define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
> @@ -74,6 +81,21 @@ struct userdata {
>  
>      pa_bool_t auto_desc;
>      unsigned channels;
> +    unsigned hrir_channels;
> +    unsigned master_channels;
> +
> +    unsigned *mapping_left;
> +    unsigned *mapping_right;
> +
> +    unsigned output_left, output_right;
> +
> +    unsigned hrir_samples;
> +    pa_sample_spec hrir_ss;
> +    pa_channel_map hrir_map;
> +    pa_memchunk hrir_chunk;
> +
> +    pa_memchunk input_buffer_chunk;
> +    int input_buffer_offset;
>  };
>  
>  static const char* const valid_modargs[] = {
> @@ -86,6 +108,8 @@ static const char* const valid_modargs[] = {
>      "channel_map",
>      "use_volume_sharing",
>      "force_flat_volume",
> +    "hrir",
> +    "hrir_channel_map",
>      NULL
>  };
>  
> @@ -202,9 +226,15 @@ static int sink_input_pop_cb(pa_sink_input *i,
> size_t nbytes, pa_memchunk *chunk
>      struct userdata *u;
>      float *src, *dst;
>      size_t fs;
> -    unsigned n, c;
> +    unsigned n;
>      pa_memchunk tchunk;
>      pa_usec_t current_latency PA_GCC_UNUSED;

current_latency is in module-virtual-sink only for instructive purposes.
If it's not needed in the filter, it should be removed from the code.

> +    
> +    unsigned j, k, l;
> +    float sum_right, sum_left;
> +    float current_sample;
> +    float *hrir_data;
> +    float *input_buffer_data;
>  
>      pa_sink_input_assert_ref(i);
>      pa_assert(chunk);
> @@ -213,10 +243,6 @@ static int sink_input_pop_cb(pa_sink_input *i,
> size_t nbytes, pa_memchunk *chunk
>      /* Hmm, process any rewind request that might be queued up */
>      pa_sink_process_rewind(u->sink, 0);
>  
> -    /* (1) IF YOU NEED A FIXED BLOCK SIZE USE
> -     * pa_memblockq_peek_fixed_size() HERE INSTEAD. NOTE THAT FILTERS
> -     * WHICH CAN DEAL WITH DYNAMIC BLOCK SIZES ARE HIGHLY
> -     * PREFERRED. */
>      while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) {
>          pa_memchunk nchunk;
>  
> @@ -225,8 +251,6 @@ static int sink_input_pop_cb(pa_sink_input *i,
> size_t nbytes, pa_memchunk *chunk
>          pa_memblock_unref(nchunk.memblock);
>      }
>  
> -    /* (2) IF YOU NEED A FIXED BLOCK SIZE, THIS NEXT LINE IS NOT
> -     * NECESSARY */
>      tchunk.length = PA_MIN(nbytes, tchunk.length);
>      pa_assert(tchunk.length > 0);
>  
> @@ -243,19 +267,43 @@ static int sink_input_pop_cb(pa_sink_input *i,
> size_t nbytes, pa_memchunk *chunk
>  
>      src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) +
> tchunk.index);
>      dst = (float*) pa_memblock_acquire(chunk->memblock);
> +    hrir_data = (float*) pa_memblock_acquire(u->hrir_chunk.memblock);
> +    input_buffer_data = (float*)
> pa_memblock_acquire(u->input_buffer_chunk.memblock);
> +
> +    for (l = 0; l < n; l++) {
> +        memcpy(((char*) input_buffer_data) + u->input_buffer_offset *
> fs, ((char *) src) + l * fs, fs);
> +
> +        sum_right = 0;
> +        sum_left = 0;
>  
> -    /* (3) PUT YOUR CODE HERE TO DO SOMETHING WITH THE DATA */
> +        /* fold the input buffer with the impulse response */
> +        for (j = 0; j < u->hrir_samples; j++) {
> +            for (k = 0; k < u->channels; k++) {
> +                current_sample =
> input_buffer_data[((u->input_buffer_offset + j) % u->hrir_samples) *
> u->channels + k];
>  
> -    /* As an example, copy input to output */
> -    for (c = 0; c < u->channels; c++) {
> -        pa_sample_clamp(PA_SAMPLE_FLOAT32NE,
> -                        dst+c, u->channels * sizeof(float),
> -                        src+c, u->channels * sizeof(float),
> -                        n);
> +                sum_left += current_sample * hrir_data[j *
> u->hrir_channels + u->mapping_left[k]];
> +                sum_right += current_sample * hrir_data[j *
> u->hrir_channels + u->mapping_right[k]];
> +            }
> +        }
> +
> +        for (k = 0; k < u->master_channels; k++) {
> +            if (k == u->output_left)
> +                dst[u->master_channels * l + k] =
> PA_CLAMP_UNLIKELY(sum_left, -1.0f, 1.0f);
> +            else if (k == u->output_right)
> +                dst[u->master_channels * l + k] =
> PA_CLAMP_UNLIKELY(sum_right, -1.0f, 1.0f);
> +            else
> +                dst[u->master_channels * l + k] = 0;
> +        }
> +
> +        u->input_buffer_offset--;
> +        if (u->input_buffer_offset < 0)
> +            u->input_buffer_offset += u->hrir_samples;
>      }
>  
>      pa_memblock_release(tchunk.memblock);
>      pa_memblock_release(chunk->memblock);
> +    pa_memblock_release(u->hrir_chunk.memblock);
> +    pa_memblock_release(u->input_buffer_chunk.memblock);
>  
>      pa_memblock_unref(tchunk.memblock);
>  
> @@ -274,6 +322,7 @@ static int sink_input_pop_cb(pa_sink_input *i,
> size_t nbytes, pa_memchunk *chunk
>  static void sink_input_process_rewind_cb(pa_sink_input *i, size_t
> nbytes) {
>      struct userdata *u;
>      size_t amount = 0;
> +    char *input_buffer;

I'd put this variable inside the innermost if, because it's not used
elsewhere in this function.

>  
>      pa_sink_input_assert_ref(i);
>      pa_assert_se(u = i->userdata);
> @@ -288,7 +337,11 @@ static void
> sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
>          if (amount > 0) {
>              pa_memblockq_seek(u->memblockq, - (int64_t) amount,
> PA_SEEK_RELATIVE, TRUE);
>  
> -            /* (5) PUT YOUR CODE HERE TO RESET YOUR FILTER  */
> +            /* Reset the input buffer */
> +            input_buffer = (char *)
> pa_memblock_acquire(u->input_buffer_chunk.memblock);
> +            memset(input_buffer, 0, u->input_buffer_chunk.length);
> +            pa_memblock_release(u->input_buffer_chunk.memblock);
> +            u->input_buffer_offset = 0;
>          }
>      }
>  
> @@ -314,9 +367,6 @@ static void
> sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
>      pa_sink_input_assert_ref(i);
>      pa_assert_se(u = i->userdata);
>  
> -    /* (6) IF YOU NEED A FIXED BLOCK SIZE ROUND nbytes UP TO
> MULTIPLES
> -     * OF IT HERE. THE PA_ROUND_UP MACRO IS USEFUL FOR THAT. */
> -
>      pa_sink_set_max_request_within_thread(u->sink, nbytes);
>  }
>  
> @@ -337,10 +387,6 @@ static void
> sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
>      pa_sink_input_assert_ref(i);
>      pa_assert_se(u = i->userdata);
>  
> -    /* (7) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
> -     * BLOCK MINUS ONE SAMPLE HERE. pa_usec_to_bytes_round_up() IS
> -     * USEFUL FOR THAT. */
> -
>      pa_sink_set_fixed_latency_within_thread(u->sink,
> i->sink->thread_info.fixed_latency);
>  }
>  
> @@ -366,13 +412,8 @@ static void sink_input_attach_cb(pa_sink_input
> *i) {
>      pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
>      pa_sink_set_latency_range_within_thread(u->sink,
> i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
>  
> -    /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
> -     * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */
>      pa_sink_set_fixed_latency_within_thread(u->sink,
> i->sink->thread_info.fixed_latency);
>  
> -    /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND
> -     * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT
> -     * HERE. SEE (6) */
>      pa_sink_set_max_request_within_thread(u->sink,
> pa_sink_input_get_max_request(i));
>      pa_sink_set_max_rewind_within_thread(u->sink,
> pa_sink_input_get_max_rewind(i));
>  
> @@ -474,6 +515,46 @@ static void
> sink_input_mute_changed_cb(pa_sink_input *i) {
>      pa_sink_mute_changed(u->sink, i->muted);
>  }
>  
> +static pa_channel_position_t mirror_channel(pa_channel_position_t
> channel) {
> +    if (channel == PA_CHANNEL_POSITION_FRONT_LEFT)
> +        return PA_CHANNEL_POSITION_FRONT_RIGHT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_FRONT_RIGHT)
> +        return PA_CHANNEL_POSITION_FRONT_LEFT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_REAR_LEFT)
> +        return PA_CHANNEL_POSITION_REAR_RIGHT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_REAR_RIGHT)
> +        return PA_CHANNEL_POSITION_REAR_LEFT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_SIDE_LEFT)
> +        return PA_CHANNEL_POSITION_SIDE_RIGHT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_SIDE_RIGHT)
> +        return PA_CHANNEL_POSITION_SIDE_LEFT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER)
> +        return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
> +    
> +    if (channel == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER)
> +        return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
> +    
> +    if (channel == PA_CHANNEL_POSITION_TOP_FRONT_LEFT)
> +        return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT)
> +        return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_TOP_REAR_LEFT)
> +        return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
> +    
> +    if (channel == PA_CHANNEL_POSITION_TOP_REAR_RIGHT)
> +        return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
> +    
> +    return channel;
> +}

I think a switch would be nicer than if here.

> +
>  int pa__init(pa_module*m) {
>      struct userdata *u;
>      pa_sample_spec ss;
> @@ -486,6 +567,17 @@ int pa__init(pa_module*m) {
>      pa_bool_t force_flat_volume = FALSE;
>      pa_memchunk silence;
>  
> +    const char *hrir_file;
> +    char *input_buffer;
> +    unsigned i, j, found_channel;
> +    float hrir_sum, hrir_max;
> +    float *hrir_data;
> +
> +    pa_sample_spec hrir_temp_ss;
> +    pa_channel_map hrir_temp_map;
> +    pa_memchunk hrir_temp_chunk;
> +    pa_resampler *resampler;
> +
>      pa_assert(m);
>  
>      if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
> @@ -526,7 +618,8 @@ int pa__init(pa_module*m) {
>      u = pa_xnew0(struct userdata, 1);
>      u->module = m;
>      m->userdata = u;
> -    u->channels = ss.channels;
> +    pa_modargs_get_value_u32(ma, "channels", &u->channels);
> +    u->master_channels = ss.channels;

This seems to be wrong. The "channels" argument is the number of
channels that the filter sink should have. If the user gives the
"channels" argument, it ends up in u->master_channels (because
ss.channels is set by the pa_modargs_get_sample_spec_and_channel_map()
call earlier), and to my understanding u->master_channels should be the
number of channels that the master sink has (or more accurately it
should be the number of channels of the sink input, the master sink
channels don't really matter; a better name for the variable might be
output_channels, or maybe it could even be hardcoded as 2).

If the user doesn't give the "channels" argument, then
u->master_channels is fine, but u->channels gets set to 0. Didn't this
show up in testing? Am I missing something? Messing up either
u->master_channels or u->channels seems like something that should be
very noticeable...

It seems that you create both the sink and the sink input with the same
sample spec and channel map. Shouldn't the sink input always be a stereo
stream, and shouldn't the sink always be a surround sink?

And shouldn't the default channel map be something different than the
master sink's channel map? It probably pretty much never makes sense to
choose the master sink's channel map for the filter sink, because the
target is to enhance stereo headphone output, and therefore the master
sink is going to have a stereo channel map about always, which doesn't
make sense for the filter sink.

>  
>      /* Create sink */
>      pa_sink_new_data_init(&sink_data);
> @@ -614,10 +707,112 @@ int pa__init(pa_module*m) {
>      u->sink->input_to_master = u->sink_input;
>  
>      pa_sink_input_get_silence(u->sink_input, &silence);
> -    u->memblockq = pa_memblockq_new("module-virtual-sink memblockq",
> 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence);
> +    u->memblockq = pa_memblockq_new("module-virtual-surround-sink
> memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence);
>      pa_memblock_unref(silence.memblock);
>  
> -    /* (9) INITIALIZE ANYTHING ELSE YOU NEED HERE */
> +    /* Initialize hrir and input buffer */
> +    /* this is the hrir file for the left ear! */
> +    hrir_file = pa_modargs_get_value(ma, "hrir", NULL);
> +
> +    if (pa_sound_file_load(u->sink->core->mempool, hrir_file,
> &hrir_temp_ss, &hrir_temp_map, &hrir_temp_chunk, NULL) < 0) {
> +        pa_log("Cannot load hrir file.");
> +        goto fail;
> +    }
> +
> +    u->hrir_ss.format = ss.format;
> +    u->hrir_ss.rate = ss.rate;
> +    u->hrir_ss.channels = hrir_temp_ss.channels;
> +
> +    if (pa_modargs_get_channel_map(ma, "hrir_channel_map",
> &u->hrir_map) < 0) {
> +        pa_log("no hrir channel map specified");
> +        if (u->hrir_ss.channels == 6) {
> +            pa_log("assuming default 5.1 hrir channel map");
> +            u->hrir_map.channels = 6;
> +            u->hrir_map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
> +            u->hrir_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
> +            u->hrir_map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
> +            u->hrir_map.map[3] = PA_CHANNEL_POSITION_LFE;
> +            u->hrir_map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
> +            u->hrir_map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
> +        }
> +        else
> +            goto fail;
> +    }

Why are there separate channel maps for the filter sink and the impulse
response?

My preferences for selecting the channel map and sample spec for the
sink input and sink are the following:

Sink input:
Channel map: Always stereo.
Sample format: Always float32ne.
Sample rate: Always match the filter sink.

Sink:
Channel map: Default to the impulse response file channel map. Allow the
user to override that default with the "channels" and/or "channel_map"
module arguments. pa_modargs_get_sample_spec_and_channel_map() will take
care of initializing the channel map appropriately.
Sample format: Always float32ne.
Sample rate: I'm not quite sure about this. I guess defaulting to the
master sink rate would be best. The other alternative would be
defaulting to the impulse response file's rate. The user should be able
to override the default with the "rate" module argument.

> +
> +    /* resample hrir */
> +    resampler = pa_resampler_new(u->sink->core->mempool,
> &hrir_temp_ss, &u->hrir_map, &u->hrir_ss, &u->hrir_map,
> +                                 PA_RESAMPLER_SRC_SINC_BEST_QUALITY,
> PA_RESAMPLER_NO_REMAP);
> +    pa_resampler_run(resampler, &hrir_temp_chunk, &u->hrir_chunk);
> +    pa_resampler_free(resampler);
> +    pa_memblock_unref(hrir_temp_chunk.memblock);
> +
> +    u->hrir_samples =  u->hrir_chunk.length /
> pa_frame_size(&u->hrir_ss);
> +    u->hrir_channels = u->hrir_ss.channels;
> +
> +    if (u->hrir_map.channels != u->hrir_channels) {
> +        pa_log("number of channels in hrir file does not match number
> of channels in hrir channel map");
> +        goto fail;
> +    }
> +
> +    if (u->hrir_map.channels < map.channels) {
> +        pa_log("hrir file does not have enough channels!");
> +        goto fail;
> +    }
> +
> +    /* normalize hrir to avoid clipping */
> +    hrir_data = (float*) pa_memblock_acquire(u->hrir_chunk.memblock);
> +    hrir_max = 0;
> +    for (i = 0; i < u->hrir_samples; i++) {
> +        hrir_sum = 0;
> +        for (j = 0; j < u->hrir_channels; j++)
> +            hrir_sum += fabs(hrir_data[i * u->hrir_channels + j]);
> +
> +        if (hrir_sum > hrir_max)
> +            hrir_max = hrir_sum;
> +    }
> +    if (hrir_max > 1) {
> +        for (i = 0; i < u->hrir_samples; i++) {
> +            for (j = 0; j < u->hrir_channels; j++)
> +                hrir_data[i * u->hrir_channels + j] /= hrir_max;
> +        }
> +    }
> +    pa_memblock_release(u->hrir_chunk.memblock);
> +
> +    /* create mapping between hrir and input */
> +    u->mapping_left = (unsigned *) pa_xnew0(unsigned, u->channels);
> +    u->mapping_right = (unsigned *) pa_xnew0(unsigned, u->channels);
> +    for (i = 0; i < map.channels; i++) {
> +        found_channel = 0;
> +
> +        for (j = 0; j < u->hrir_map.channels; j++) {
> +            if (u->hrir_map.map[j] == map.map[i]) {
> +                u->mapping_left[i] = j;
> +                found_channel = 1;
> +            }
> +
> +            if (u->hrir_map.map[j] == mirror_channel(map.map[i]))
> +                u->mapping_right[i] = j;
> +        }
> +
> +        if (!found_channel) {
> +            pa_log("Cannot find mapping for channel %s",
> pa_channel_position_to_string(map.map[i]));
> +            goto fail;
> +        }
> +
> +        if (map.map[i] == PA_CHANNEL_POSITION_FRONT_LEFT)
> +            u->output_left = i;
> +        if (map.map[i] == PA_CHANNEL_POSITION_FRONT_RIGHT)
> +            u->output_right = i;
> +    }
> +
> +    u->input_buffer_chunk.length = u->hrir_chunk.length;
> +    u->input_buffer_chunk.index = 0;
> +    u->input_buffer_chunk.memblock =
> pa_memblock_new_pool(u->sink->core->mempool,
> u->input_buffer_chunk.length);
> +
> +    input_buffer = (char *)
> pa_memblock_acquire(u->input_buffer_chunk.memblock);
> +    memset(input_buffer, 0, u->input_buffer_chunk.length);
> +    pa_memblock_release(u->input_buffer_chunk.memblock);
> +    u->input_buffer_offset = 0;
>  
>      pa_sink_put(u->sink);
>      pa_sink_input_put(u->sink_input);
> @@ -670,5 +865,16 @@ void pa__done(pa_module*m) {
>      if (u->memblockq)
>          pa_memblockq_free(u->memblockq);
>  
> +    if (u->hrir_chunk.memblock)
> +        pa_memblock_unref(u->hrir_chunk.memblock);
> +
> +    if (u->input_buffer_chunk.memblock)
> +        pa_memblock_unref(u->input_buffer_chunk.memblock);
> +
> +    if (u->mapping_left)
> +        pa_xfree(u->mapping_left);
> +    if (u->mapping_right)
> +        pa_xfree(u->mapping_right);
> +
>      pa_xfree(u);
>  }
> -- 
> 1.7.8.3




More information about the pulseaudio-discuss mailing list