diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | configure.in | 1 | ||||
-rw-r--r-- | doc/Makefile.am | 3 | ||||
-rw-r--r-- | doc/upmix.txt | 34 | ||||
-rw-r--r-- | doc/vdownmix.txt | 23 | ||||
-rw-r--r-- | mix/Makefile.am | 14 | ||||
-rw-r--r-- | mix/pcm_upmix.c | 397 | ||||
-rw-r--r-- | mix/pcm_vdownmix.c | 341 |
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); |