diff options
Diffstat (limited to 'mix/pcm_vdownmix.c')
-rw-r--r-- | mix/pcm_vdownmix.c | 341 |
1 files changed, 341 insertions, 0 deletions
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); |