summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--configure.in1
-rw-r--r--doc/Makefile.am3
-rw-r--r--doc/upmix.txt34
-rw-r--r--doc/vdownmix.txt23
-rw-r--r--mix/Makefile.am14
-rw-r--r--mix/pcm_upmix.c397
-rw-r--r--mix/pcm_vdownmix.c341
8 files changed, 813 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am
index b5adc0b..5de61a3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,7 +5,7 @@ if HAVE_POLYP
POLYPDIR = polyp
endif
-SUBDIRS = oss $(JACKDIR) $(POLYPDIR) doc
+SUBDIRS = oss mix $(JACKDIR) $(POLYPDIR) doc
EXTRA_DIST = cvscompile version
AUTOMAKE_OPTIONS = foreign
diff --git a/configure.in b/configure.in
index 33d6205..0a4445c 100644
--- a/configure.in
+++ b/configure.in
@@ -25,5 +25,6 @@ AC_OUTPUT([
oss/Makefile
jack/Makefile
polyp/Makefile
+ mix/Makefile
doc/Makefile
])
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 70bcc3e..dfb40b3 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1 +1,2 @@
-EXTRA_DIST = README-pcm-oss README-jack README-polyp
+EXTRA_DIST = README-pcm-oss README-jack README-polyp \
+ upmix.txt vdownmix.txt
diff --git a/doc/upmix.txt b/doc/upmix.txt
new file mode 100644
index 0000000..b8a6da4
--- /dev/null
+++ b/doc/upmix.txt
@@ -0,0 +1,34 @@
+UPMIX PLUGIN
+============
+
+The upmix plugin is an easy-to-use plugin for upmixing from 1 or 2
+channel stream to 4 or 6-channel stream. The number of channels to be
+expanded is determined by the slave PCM. For example, the following
+PCM defines upmixing to 5.1 from 2-6 channels input:
+
+ pcm.upmix51 {
+ type upmix
+ slave.pcm "surround51"
+ }
+
+You can use this PCM as a default one by defining below:
+
+ pcm.!default "plug:upmix51"
+
+The upmix plugin copies left and right channels to rear left and right
+with a certain delay. The delay size can be specified by "delay" PCM
+option in msec. For example, to set 10ms delay in the above case:
+
+ pcm.upmix51 {
+ type upmix
+ slave.pcm "surround51"
+ channels 6
+ delay 10
+ }
+
+As default, 15ms delay is used.
+
+The center and LFE channels are the average of sum of left and right
+signals.
+
+The accepted format is currently only S16.
diff --git a/doc/vdownmix.txt b/doc/vdownmix.txt
new file mode 100644
index 0000000..0e1403f
--- /dev/null
+++ b/doc/vdownmix.txt
@@ -0,0 +1,23 @@
+VDOWNMIX PLUGIN
+===============
+
+The vdownmix plugin is a downmixer from 4-6 channels to 2-channel
+stereo headphone output. This plugin processes the input signals with
+a simple spacialization, so the output sounds like a kind of "virtual
+surround".
+
+For example, define the below:
+
+ pcm.!surround51 {
+ type vdownmix
+ slave.pcm "default"
+ }
+ pcm.!surround40 {
+ type vdownmix
+ slave.pcm "default"
+ }
+
+and the outputs from video player to these PCMs are converted to the
+default 2.0 output with a proper downmix.
+
+The accepted format is currently only S16.
diff --git a/mix/Makefile.am b/mix/Makefile.am
new file mode 100644
index 0000000..c830043
--- /dev/null
+++ b/mix/Makefile.am
@@ -0,0 +1,14 @@
+asound_module_pcm_upmix_LTLIBRARIES = libasound_module_pcm_upmix.la
+asound_module_pcm_vdownmix_LTLIBRARIES = libasound_module_pcm_vdownmix.la
+
+asound_module_pcm_upmixdir = $(libdir)/alsa-lib
+asound_module_pcm_vdownmixdir = $(libdir)/alsa-lib
+
+AM_CFLAGS = -Wall -g @ALSA_CFLAGS@
+AM_LDFLAGS = -module -avoid-version -export-dynamic
+
+libasound_module_pcm_upmix_la_SOURCES = pcm_upmix.c
+libasound_module_pcm_upmix_la_LIBADD = @ALSA_LIBS@
+libasound_module_pcm_vdownmix_la_SOURCES = pcm_vdownmix.c
+libasound_module_pcm_vdownmix_la_LIBADD = @ALSA_LIBS@
+
diff --git a/mix/pcm_upmix.c b/mix/pcm_upmix.c
new file mode 100644
index 0000000..ed8d041
--- /dev/null
+++ b/mix/pcm_upmix.c
@@ -0,0 +1,397 @@
+/*
+ * Automatic upmix plugin
+ *
+ * Copyright (c) 2006 by Takashi Iwai <tiwai@suse.de>
+ *
+ * This library 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.
+ *
+ * This program 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <alsa/asoundlib.h>
+#include <alsa/pcm_external.h>
+
+typedef struct snd_pcm_upmix snd_pcm_upmix_t;
+
+typedef void (*upmixer_t)(snd_pcm_upmix_t *mix,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size);
+
+struct snd_pcm_upmix {
+ snd_pcm_extplug_t ext;
+ /* setup */
+ int delay_ms;
+ /* privates */
+ upmixer_t upmix;
+ unsigned int curpos;
+ int delay;
+ short *delayline[2];
+};
+
+static inline void *area_addr(const snd_pcm_channel_area_t *area,
+ snd_pcm_uframes_t offset)
+{
+ unsigned int bitofs = area->first + area->step * offset;
+ return (char *) area->addr + bitofs / 8;
+}
+
+static inline unsigned int area_step(const snd_pcm_channel_area_t *area)
+{
+ return area->step / 8;
+}
+
+/* Delayed copy SL & SR */
+static void delayed_copy(snd_pcm_upmix_t *mix,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ unsigned int size)
+{
+ unsigned int i, p, delay, curpos, dst_step, src_step;
+ short *dst, *src;
+
+ if (! mix->delay_ms) {
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 2, size, SND_PCM_FORMAT_S16);
+ return;
+ }
+
+ delay = mix->delay;
+ if (delay > size)
+ delay = size;
+ for (i = 0; i < 2; i++) {
+ dst = (short *)area_addr(dst_areas + i, dst_offset);
+ dst_step = area_step(dst_areas + i) / 2;
+ curpos = mix->curpos;
+ for (p = 0; p < delay; p++) {
+ *dst = mix->delayline[i][curpos];
+ dst += dst_step;
+ curpos = (curpos + 1) % mix->delay;
+ }
+ snd_pcm_area_copy(dst_areas + i, dst_offset + delay,
+ src_areas + i, src_offset,
+ size - delay, SND_PCM_FORMAT_S16);
+ src = (short *)area_addr(src_areas + i,
+ src_offset + size - delay);
+ src_step = area_step(src_areas + i) / 2;
+ curpos = mix->curpos;
+ for (p = 0; p < delay; p++) {
+ mix->delayline[i][curpos] = *src;
+ src += src_step;
+ curpos = (curpos + 1) % mix->delay;
+ }
+ }
+ mix->curpos = curpos;
+}
+
+/* Average of L+R -> C and LFE */
+static void average_copy(const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ unsigned int nchns,
+ unsigned int size)
+{
+ short *dst[2], *src[2];
+ unsigned int i, dst_step[2], src_step[2];
+
+ for (i = 0; i < nchns; i++) {
+ dst[i] = (short *)area_addr(dst_areas + i, dst_offset);
+ dst_step[i] = area_step(dst_areas + i) / 2;
+ }
+ for (i = 0; i < 2; i++) {
+ src[i] = (short *)area_addr(src_areas + i, src_offset);
+ src_step[i] = area_step(src_areas + i) / 2;
+ }
+ while (size--) {
+ short val = (*src[0] >> 1) + (*src[1] >> 1);
+ for (i = 0; i < nchns; i++) {
+ *dst[i] = val;
+ dst[i] += dst_step[i];
+ }
+ src[0] += src_step[0];
+ src[1] += src_step[1];
+ }
+}
+
+static void upmix_1_to_51(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ int i;
+ for (i = 0; i < 6; i++)
+ snd_pcm_area_copy(dst_areas + i, dst_offset,
+ src_areas, src_offset,
+ size, SND_PCM_FORMAT_S16);
+}
+
+static void upmix_1_to_40(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ int i;
+ for (i = 0; i < 4; i++)
+ snd_pcm_area_copy(dst_areas + i, dst_offset,
+ src_areas, src_offset,
+ size, SND_PCM_FORMAT_S16);
+}
+
+static void upmix_2_to_51(snd_pcm_upmix_t *mix,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 2, size, SND_PCM_FORMAT_S16);
+ delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
+ size);
+ average_copy(dst_areas + 4, dst_offset, src_areas, src_offset,
+ 2, size);
+}
+
+static void upmix_2_to_40(snd_pcm_upmix_t *mix,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 2, size, SND_PCM_FORMAT_S16);
+ delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
+ size);
+}
+
+static void upmix_3_to_51(snd_pcm_upmix_t *mix,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 2, size, SND_PCM_FORMAT_S16);
+ delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
+ size);
+ snd_pcm_areas_copy(dst_areas + 4, dst_offset, src_areas, src_offset,
+ 2, size, SND_PCM_FORMAT_S16);
+}
+
+static void upmix_3_to_40(snd_pcm_upmix_t *mix,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 2, size, SND_PCM_FORMAT_S16);
+ delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
+ size);
+}
+
+static void upmix_4_to_51(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 4, size, SND_PCM_FORMAT_S16);
+ snd_pcm_areas_copy(dst_areas + 4, dst_offset, src_areas, src_offset,
+ 2, size, SND_PCM_FORMAT_S16);
+}
+
+static void upmix_4_to_40(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 4, size, SND_PCM_FORMAT_S16);
+}
+
+static void upmix_5_to_51(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 5, size, SND_PCM_FORMAT_S16);
+ snd_pcm_area_copy(dst_areas + 5, dst_offset, src_areas + 4, src_offset,
+ size, SND_PCM_FORMAT_S16);
+}
+
+static void upmix_6_to_51(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+ 6, size, SND_PCM_FORMAT_S16);
+}
+
+static upmixer_t do_upmix[6][2] = {
+ { upmix_1_to_40, upmix_1_to_51 },
+ { upmix_2_to_40, upmix_2_to_51 },
+ { upmix_3_to_40, upmix_3_to_51 },
+ { upmix_4_to_40, upmix_4_to_51 },
+ { upmix_4_to_40, upmix_5_to_51 },
+ { upmix_4_to_40, upmix_6_to_51 },
+};
+
+static snd_pcm_sframes_t
+upmix_transfer(snd_pcm_extplug_t *ext,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_upmix_t *mix = (snd_pcm_upmix_t *)ext;
+ mix->upmix(mix, dst_areas, dst_offset,
+ src_areas, src_offset, size);
+ return size;
+}
+
+static int upmix_init(snd_pcm_extplug_t *ext)
+{
+ snd_pcm_upmix_t *mix = (snd_pcm_upmix_t *)ext;
+ int ctype, stype;
+
+ stype = (ext->slave_channels == 6) ? 1 : 0;
+ ctype = ext->channels - 1;
+ if (ctype < 0 || ctype >= 5)
+ return -EINVAL;
+ mix->upmix = do_upmix[ctype][stype];
+
+ if (mix->delay_ms) {
+ free(mix->delayline[0]);
+ free(mix->delayline[1]);
+ mix->delay = ext->rate * mix->delay_ms / 1000;
+ mix->delayline[0] = calloc(2, mix->delay);
+ mix->delayline[1] = calloc(2, mix->delay);
+ if (! mix->delayline[0] || ! mix->delayline[1])
+ return -ENOMEM;
+ mix->curpos = 0;
+ }
+ return 0;
+}
+
+static int upmix_close(snd_pcm_extplug_t *ext)
+{
+ snd_pcm_upmix_t *mix = (snd_pcm_upmix_t *)ext;
+ free(mix->delayline[0]);
+ free(mix->delayline[1]);
+ return 0;
+}
+
+static snd_pcm_extplug_callback_t upmix_callback = {
+ .transfer = upmix_transfer,
+ .init = upmix_init,
+ .close = upmix_close,
+};
+
+SND_PCM_PLUGIN_DEFINE_FUNC(upmix)
+{
+ snd_config_iterator_t i, next;
+ snd_pcm_upmix_t *mix;
+ snd_config_t *sconf = NULL;
+ static unsigned int chlist[2] = {4, 6};
+ int delay = 10;
+ int err;
+
+ snd_config_for_each(i, next, conf) {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+ continue;
+ if (strcmp(id, "slave") == 0) {
+ sconf = n;
+ continue;
+ }
+ if (strcmp(id, "delay") == 0) {
+ long val;
+ err = snd_config_get_integer(n, &val);
+ if (err < 0) {
+ SNDERR("Invalid value for %s", id);
+ return err;
+ }
+ delay = val;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ if (! sconf) {
+ SNDERR("No slave configuration for filrmix pcm");
+ return -EINVAL;
+ }
+
+ mix = calloc(1, sizeof(*mix));
+ if (mix == NULL)
+ return -ENOMEM;
+
+ mix->ext.version = SND_PCM_EXTPLUG_VERSION;
+ mix->ext.name = "Upmix Plugin";
+ mix->ext.callback = &upmix_callback;
+ mix->ext.private_data = mix;
+ if (delay < 0)
+ delay = 0;
+ else if (delay > 1000)
+ delay = 1000;
+ mix->delay_ms = delay;
+
+ err = snd_pcm_extplug_create(&mix->ext, name, root, sconf, stream, mode);
+ if (err < 0) {
+ free(mix);
+ return err;
+ }
+
+ snd_pcm_extplug_set_param_minmax(&mix->ext,
+ SND_PCM_EXTPLUG_HW_CHANNELS,
+ 1, 6);
+ snd_pcm_extplug_set_slave_param_list(&mix->ext,
+ SND_PCM_EXTPLUG_HW_CHANNELS,
+ 2, chlist);
+ snd_pcm_extplug_set_param(&mix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
+ SND_PCM_FORMAT_S16);
+ snd_pcm_extplug_set_slave_param(&mix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
+ SND_PCM_FORMAT_S16);
+
+ *pcmp = mix->ext.pcm;
+ return 0;
+}
+
+SND_PCM_PLUGIN_SYMBOL(upmix);
diff --git a/mix/pcm_vdownmix.c b/mix/pcm_vdownmix.c
new file mode 100644
index 0000000..a083ae2
--- /dev/null
+++ b/mix/pcm_vdownmix.c
@@ -0,0 +1,341 @@
+/*
+ * 4/5/6 -> 2 downmix with a simple spacialization
+ *
+ * Copyright (c) 2006 by Takashi Iwai <tiwai@suse.de>
+ *
+ * This library 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.
+ *
+ * This program 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <alsa/asoundlib.h>
+#include <alsa/pcm_external.h>
+
+/* #define I_AM_POWERFUL */
+
+#ifdef I_AM_POWERFUL
+#define RINGBUF_SIZE (1 << 9)
+#else
+#define RINGBUF_SIZE (1 << 7)
+#endif
+#define RINGBUF_MASK (RINGBUF_SIZE - 1)
+
+struct vdownmix_tap {
+ int delay;
+ int weight;
+};
+
+#define MAX_TAPS 30
+
+struct vdownmix_filter {
+ int taps;
+ struct vdownmix_tap tap[MAX_TAPS];
+};
+
+typedef struct {
+ snd_pcm_extplug_t ext;
+ int channels;
+ unsigned int curpos;
+ short rbuf[RINGBUF_SIZE][5];
+} snd_pcm_vdownmix_t;
+
+static struct vdownmix_filter tap_filters[5] = {
+ {
+#ifdef I_AM_POWERFUL
+ 18,
+#else
+ 14,
+#endif
+ {{ 0, 0xfffffd0a },
+ { 1, 0x41d },
+ { 2, 0xffffe657 },
+ { 3, 0x6eb5 },
+ { 4, 0xffffe657 },
+ { 5, 0x41d },
+ { 6, 0xfffffd0a },
+ { 71, 0xffffff1c },
+ { 72, 0x12e },
+ { 73, 0xfffff81a },
+ { 74, 0x24de },
+ { 75, 0xfffff81a },
+ { 76, 0x12e },
+ { 77, 0xffffff1c },
+ { 265, 0xfffffc65 },
+ { 266, 0xee1 },
+ { 267, 0xfffffc65 },
+ { 395, 0x46a }},
+ },
+
+ {
+#ifdef I_AM_POWERFUL
+ 17,
+#else
+ 10,
+#endif
+ {{ 8, 0xcf },
+ { 9, 0xa7b },
+ { 10, 0xcd7 },
+ { 11, 0x5b3 },
+ { 12, 0x859 },
+ { 13, 0xaf },
+ { 80, 0x38b },
+ { 81, 0x454 },
+ { 82, 0x218 },
+ { 83, 0x2c1 },
+ { 268, 0x58b },
+ { 275, 0xc2 },
+ { 397, 0xbd },
+ { 398, 0x1e8 },
+ { 506, 0xfffffeac },
+ { 507, 0x636 },
+ { 508, 0xfffffeac }},
+ },
+
+ {
+#ifdef I_AM_POWERFUL
+ 11,
+#else
+ 1,
+#endif
+ {{ 3, 0x4000 },
+ { 125, 0x12a },
+ { 126, 0xda1 },
+ { 127, 0x12a },
+ { 193, 0xfffffed3 },
+ { 194, 0xdb9 },
+ { 195, 0xfffffed3 },
+ { 454, 0x10a },
+ { 483, 0xfffffe97 },
+ { 484, 0x698 },
+ { 485, 0xfffffe97 }},
+ },
+
+ {
+#ifdef I_AM_POWERFUL
+ 25,
+#else
+ 10,
+#endif
+ {{ 5, 0x1cb },
+ { 6, 0x9c5 },
+ { 7, 0x117e },
+ { 8, 0x200 },
+ { 9, 0x533 },
+ { 10, 0x1c6 },
+ { 11, 0x167 },
+ { 12, 0x5ff },
+ { 13, 0x425 },
+ { 14, 0xd9 },
+ { 128, 0x247 },
+ { 129, 0x5e1 },
+ { 130, 0xb7 },
+ { 131, 0x122 },
+ { 135, 0x10a },
+ { 200, 0x1b6 },
+ { 201, 0xa7 },
+ { 202, 0x188 },
+ { 203, 0x1d9 },
+ { 445, 0xffffff44 },
+ { 446, 0x5e2 },
+ { 447, 0xffffff44 },
+ { 484, 0xffffff51 },
+ { 485, 0x449 },
+ { 486, 0xffffff51 }},
+ },
+
+ {
+#ifdef I_AM_POWERFUL
+ 21,
+#else
+ 7,
+#endif
+ {{ 0, 0xfffffdee },
+ { 1, 0x28b },
+ { 2, 0xffffed1e },
+ { 3, 0x6336 },
+ { 4, 0xffffed1e },
+ { 5, 0x28b },
+ { 6, 0xfffffdee },
+ { 51, 0xffffff2c },
+ { 52, 0x105 },
+ { 53, 0xfffff86b },
+ { 54, 0x27d9 },
+ { 55, 0xfffff86b },
+ { 56, 0x105 },
+ { 57, 0xffffff2c },
+ { 333, 0xfffffd69 },
+ { 334, 0xb2f },
+ { 335, 0xfffffd69 },
+ { 339, 0xdf },
+ { 340, 0x168 },
+ { 342, 0xa6 },
+ { 343, 0xba }},
+ },
+};
+
+static int tap_index[5][2] = {
+ /* left */
+ { 0, 1 },
+ /* right */
+ { 1, 0 },
+ /* rear left */
+ { 2, 3 },
+ /* rear right */
+ { 3, 2 },
+ /* center */
+ { 4, 4 },
+};
+
+static inline void *area_addr(const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset)
+{
+ unsigned int bitofs = area->first + area->step * offset;
+ return (char *) area->addr + bitofs / 8;
+}
+
+static inline unsigned int area_step(const snd_pcm_channel_area_t *area)
+{
+ return area->step / 8;
+}
+
+static snd_pcm_sframes_t
+vdownmix_transfer(snd_pcm_extplug_t *ext,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_vdownmix_t *mix = (snd_pcm_vdownmix_t *)ext;
+ short *src[mix->channels], *ptr[2];
+ unsigned int src_step[mix->channels], step[2];
+ int i, ch, curpos, p, idx;
+ int acc[2];
+ int fr;
+
+ ptr[0] = area_addr(dst_areas, dst_offset);
+ step[0] = area_step(dst_areas) / 2;
+ ptr[1] = area_addr(dst_areas + 1, dst_offset);
+ step[1] = area_step(dst_areas + 1) / 2;
+ for (ch = 0; ch < mix->channels; ch++) {
+ const snd_pcm_channel_area_t *src_area = &src_areas[ch];
+ src[ch] = area_addr(src_area, src_offset);
+ src_step[ch] = area_step(src_area) / 2;
+ }
+ curpos = mix->curpos;
+ fr = size;
+ while (fr--) {
+ acc[0] = acc[1] = 0;
+ for (ch = 0; ch < mix->channels; ch++) {
+ mix->rbuf[curpos][ch] = *src[ch];
+ for (idx = 0; idx < 2; idx++) {
+ int f = tap_index[ch][idx];
+ const struct vdownmix_filter *filter;
+ filter = &tap_filters[f];
+ for (i = 0; i < filter->taps; i++) {
+ p = (curpos + RINGBUF_SIZE - filter->tap[i].delay)
+ & RINGBUF_MASK;
+ acc[idx] += mix->rbuf[p][ch] * filter->tap[i].weight;
+ }
+ }
+ src[ch] += src_step[ch];
+ }
+ for (idx = 0; idx < 2; idx++) {
+ acc[idx] >>= 14;
+ if (acc[idx] < -32768)
+ *ptr[idx] = -32768;
+ else if (acc[idx] > 32767)
+ *ptr[idx] = 32767;
+ else
+ *ptr[idx] = acc[idx];
+ ptr[idx] += step[idx];
+ }
+ curpos = (curpos + 1) & RINGBUF_MASK;
+ }
+ mix->curpos = curpos;
+ return size;
+}
+
+
+static int vdownmix_init(snd_pcm_extplug_t *ext)
+{
+ snd_pcm_vdownmix_t *mix = (snd_pcm_vdownmix_t *)ext;
+ mix->channels = ext->channels;
+ if (mix->channels > 5) /* ignore LFE */
+ mix->channels = 5;
+ mix->curpos = 0;
+ memset(mix->rbuf, 0, sizeof(mix->rbuf));
+ return 0;
+}
+
+static snd_pcm_extplug_callback_t vdownmix_callback = {
+ .transfer = vdownmix_transfer,
+ .init = vdownmix_init,
+ /* .dump = filr_dump, */
+};
+
+SND_PCM_PLUGIN_DEFINE_FUNC(vdownmix)
+{
+ snd_config_iterator_t i, next;
+ snd_pcm_vdownmix_t *mix;
+ snd_config_t *sconf = NULL;
+ int err;
+
+ snd_config_for_each(i, next, conf) {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+ continue;
+ if (strcmp(id, "slave") == 0) {
+ sconf = n;
+ continue;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ if (! sconf) {
+ SNDERR("No slave configuration for vdownmix pcm");
+ return -EINVAL;
+ }
+
+ mix = calloc(1, sizeof(*mix));
+ if (mix == NULL)
+ return -ENOMEM;
+
+ mix->ext.version = SND_PCM_EXTPLUG_VERSION;
+ mix->ext.name = "Vdownmix Plugin";
+ mix->ext.callback = &vdownmix_callback;
+ mix->ext.private_data = mix;
+
+ err = snd_pcm_extplug_create(&mix->ext, name, root, sconf, stream, mode);
+ if (err < 0) {
+ free(mix);
+ return err;
+ }
+
+ /* 4/5/6 -> 2 downmix */
+ snd_pcm_extplug_set_param_minmax(&mix->ext, SND_PCM_EXTPLUG_HW_CHANNELS,
+ 4, 6);
+ snd_pcm_extplug_set_slave_param(&mix->ext, SND_PCM_EXTPLUG_HW_CHANNELS, 2);
+ snd_pcm_extplug_set_param(&mix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
+ SND_PCM_FORMAT_S16);
+ snd_pcm_extplug_set_slave_param(&mix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
+ SND_PCM_FORMAT_S16);
+
+ *pcmp = mix->ext.pcm;
+ return 0;
+}
+
+SND_PCM_PLUGIN_SYMBOL(vdownmix);