Hello,

> 1) Is the license of the HRTF dataset that you use compatible with GPL?

The Listen HRIR dataset is public domain so it is GPL compatible.

The HRIR based on the Kemar dummy head "is provided free with no restrictions 
on use, provided the authors are cited when the data is used in any research 
or commercial application."
But there is no need to distribute all HRIRs with pulseaudio. I think a few 
good that are GPL compatible are enough.

> 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.

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

Regards,

Ole
>From 4e8a09d44ca8df7a7cc32662d0a784c77f09523c Mon Sep 17 00:00:00 2001
From: Niels Ole Salscheider <niels_...@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.boss...@intel.com>
+    Copyright 2012 Niels Ole Salscheider <niels_...@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;
+    
+    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;
 
     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;
+}
+
 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;
 
     /* 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;
+    }
+
+    /* 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

Attachment: signature.asc
Description: This is a digitally signed message part.

_______________________________________________
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss

Reply via email to