Hello,

this is the first version of a patch to add module-virtual-surround-sink.

This module provides a virtual surround sound effect by folding the audio 
signal with a measured impulse response.

What do you think?

I have uploaded suitable impulse responses to:
http://stuff.salscheider-online.de/hrir_kemar.tar.gz
http://stuff.salscheider-online.de/hrir_listen.tar.gz

The first archive contains an impulse response for the KEMAR dummy head and is 
quite small. If that impulse response does not work well for you, you can try 
the listen database. This archive contains a folder "demos" that should help 
you to find a suitable impulse response.

Regards,

Ole
>From c391c1ef13e9d6b67303ee56680e2dbadd33ed9f 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
---
 src/Makefile.am                                    |    7 +
 ...rtual-sink.c => module-virtual-surround-sink.c} |  246 +++++++++++++++++---
 2 files changed, 216 insertions(+), 37 deletions(-)
 copy src/modules/{module-virtual-sink.c => module-virtual-surround-sink.c} (71%)

diff --git a/src/Makefile.am b/src/Makefile.am
index 02635fa..187a6bb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1016,6 +1016,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
@@ -1316,6 +1317,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
@@ -1532,6 +1534,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 71%
copy from src/modules/module-virtual-sink.c
copy to src/modules/module-virtual-surround-sink.c
index e7c476e..a5ca837 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,13 @@
 #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 "module-virtual-surround-sink-symdef.h"
 
-PA_MODULE_AUTHOR("Pierre-Louis Bossart");
-PA_MODULE_DESCRIPTION(_("Virtual sink"));
+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 +60,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 +79,19 @@ struct userdata {
 
     pa_bool_t auto_desc;
     unsigned channels;
+    unsigned hrir_channels;
+    unsigned master_channels;
+    
+    unsigned *mapping_left;
+    unsigned *mapping_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 +104,8 @@ static const char* const valid_modargs[] = {
     "channel_map",
     "use_volume_sharing",
     "force_flat_volume",
+    "hrir",
+    "hrir_channel_map",
     NULL
 };
 
@@ -202,9 +222,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 +239,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 +247,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,20 +263,40 @@ 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);
-
-    /* (3) PUT YOUR CODE HERE TO DO SOMETHING WITH THE DATA */
-
-    /* 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);
+    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;
+        
+        /* 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];
+                
+                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]];
+            }
+        }
+        
+        dst[u->master_channels * l] = PA_CLAMP_UNLIKELY(sum_left, -1.0f, 1.0f);
+        dst[u->master_channels * l + 1] = PA_CLAMP_UNLIKELY(sum_right, -1.0f, 1.0f);
+        for (k = 2; k < u->master_channels; k++)
+            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);
 
     /* (4) IF YOU NEED THE LATENCY FOR SOMETHING ACQUIRE IT LIKE THIS: */
@@ -274,6 +314,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 +329,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 +359,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 +379,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 +404,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 +507,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;
@@ -485,6 +558,15 @@ int pa__init(pa_module*m) {
     pa_bool_t use_volume_sharing = TRUE;
     pa_bool_t force_flat_volume = FALSE;
     pa_memchunk silence;
+    
+    const char *hrir_file;
+    char *input_buffer;
+    unsigned i, j, found_channel;
+    
+    pa_sample_spec hrir_temp_ss;
+    pa_channel_map hrir_temp_map;
+    pa_memchunk hrir_temp_chunk;
+    pa_resampler *resampler;
 
     pa_assert(m);
 
@@ -526,7 +608,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 +697,88 @@ 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;
+    }
+    
+    /* 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;
+        }
+    }
+    
+    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);
@@ -669,6 +830,17 @@ 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