summaryrefslogtreecommitdiffstats
path: root/mix/pcm_upmix.c
diff options
context:
space:
mode:
Diffstat (limited to 'mix/pcm_upmix.c')
-rw-r--r--mix/pcm_upmix.c397
1 files changed, 397 insertions, 0 deletions
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);