]> git.alsa-project.org Git - alsa-plugins.git/commitdiff
Add upmix and vdownmix plugins
authorTakashi Iwai <tiwai@suse.de>
Tue, 21 Mar 2006 11:55:36 +0000 (11:55 +0000)
committerTakashi Iwai <tiwai@suse.de>
Tue, 21 Mar 2006 11:55:36 +0000 (11:55 +0000)
Added PCM upmix and vdownmix plugins.

The upmix plugin is for upmixing to 4.0 or 5.1 surrounds
by simple copying (and delay line for rear).

The vdownmix plugin is a kind of "virtual surround", which
downmixes 4.0 or 5.1 input to 2.0 output with some effect.

Makefile.am
configure.in
doc/Makefile.am
doc/upmix.txt [new file with mode: 0644]
doc/vdownmix.txt [new file with mode: 0644]
mix/Makefile.am [new file with mode: 0644]
mix/pcm_upmix.c [new file with mode: 0644]
mix/pcm_vdownmix.c [new file with mode: 0644]

index b5adc0bc8fa5ff8fe2aff460e01040501964098e..5de61a38252b62ce29f2c34c4956a6b956610e16 100644 (file)
@@ -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
 
index 33d6205e6070921a4e15f98e936e7bc9e549c46d..0a4445cb3c2a399682f272f131fa3df8640fbcc6 100644 (file)
@@ -25,5 +25,6 @@ AC_OUTPUT([
        oss/Makefile
        jack/Makefile
        polyp/Makefile
+       mix/Makefile
        doc/Makefile
 ])
index 70bcc3eefabd6010dc53fb805e290255230156a1..dfb40b30a7134286a34fa529ea1b54b7e1a1e3e1 100644 (file)
@@ -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 (file)
index 0000000..b8a6da4
--- /dev/null
@@ -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 (file)
index 0000000..0e1403f
--- /dev/null
@@ -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 (file)
index 0000000..c830043
--- /dev/null
@@ -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 (file)
index 0000000..ed8d041
--- /dev/null
@@ -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 (file)
index 0000000..a083ae2
--- /dev/null
@@ -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);