summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2006-03-21 11:55:36 +0000
committerTakashi Iwai <tiwai@suse.de>2006-03-21 11:55:36 +0000
commitb59cf0e288b82fca2e0715fca20ba3ede5dae3dc (patch)
treeca353c9d7bfc50393287bce4d8ca3bf06a3efc0e
parenta86da2532c4072bccaeb380099a90b63262bf14d (diff)
Add upmix and vdownmix plugins
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.
-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);