Change FSF address (Franklin Street)
[alsa-plugins.git] / mix / pcm_upmix.c
1 /*
2  * Automatic upmix plugin
3  *
4  * Copyright (c) 2006 by Takashi Iwai <tiwai@suse.de>
5  *
6  * This library is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include <alsa/asoundlib.h>
22 #include <alsa/pcm_external.h>
23
24 typedef struct snd_pcm_upmix snd_pcm_upmix_t;
25
26 typedef void (*upmixer_t)(snd_pcm_upmix_t *mix,
27                           const snd_pcm_channel_area_t *dst_areas,
28                           snd_pcm_uframes_t dst_offset,
29                           const snd_pcm_channel_area_t *src_areas,
30                           snd_pcm_uframes_t src_offset,
31                           snd_pcm_uframes_t size);
32
33 struct snd_pcm_upmix {
34         snd_pcm_extplug_t ext;
35         /* setup */
36         int delay_ms;
37         /* privates */
38         upmixer_t upmix;
39         unsigned int curpos;
40         int delay;
41         short *delayline[2];
42 };
43
44 static inline void *area_addr(const snd_pcm_channel_area_t *area,
45                               snd_pcm_uframes_t offset)
46 {
47         unsigned int bitofs = area->first + area->step * offset;
48         return (char *) area->addr + bitofs / 8;
49 }
50
51 static inline unsigned int area_step(const snd_pcm_channel_area_t *area)
52 {
53         return area->step / 8;
54 }
55
56 /* Delayed copy SL & SR */
57 static void delayed_copy(snd_pcm_upmix_t *mix,
58                          const snd_pcm_channel_area_t *dst_areas,
59                          snd_pcm_uframes_t dst_offset,
60                          const snd_pcm_channel_area_t *src_areas,
61                          snd_pcm_uframes_t src_offset,
62                          unsigned int size)
63 {
64         unsigned int i, p, delay, curpos, dst_step, src_step;
65         short *dst, *src;
66
67         if (! mix->delay_ms) {
68                 snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
69                                    2, size, SND_PCM_FORMAT_S16);
70                 return;
71         }
72
73         delay = mix->delay;
74         if (delay > size)
75                 delay = size;
76         for (i = 0; i < 2; i++) {
77                 dst = (short *)area_addr(dst_areas + i, dst_offset);
78                 dst_step = area_step(dst_areas + i) / 2;
79                 curpos = mix->curpos;
80                 for (p = 0; p < delay; p++) {
81                         *dst = mix->delayline[i][curpos];
82                         dst += dst_step;
83                         curpos = (curpos + 1) % mix->delay;
84                 }
85                 snd_pcm_area_copy(dst_areas + i, dst_offset + delay,
86                                   src_areas + i, src_offset,
87                                   size - delay, SND_PCM_FORMAT_S16);
88                 src = (short *)area_addr(src_areas + i,
89                                          src_offset + size - delay);
90                 src_step = area_step(src_areas + i) / 2;
91                 curpos = mix->curpos;
92                 for (p = 0; p < delay; p++) {
93                         mix->delayline[i][curpos] = *src;
94                         src += src_step;
95                         curpos = (curpos + 1) % mix->delay;
96                 }
97         }
98         mix->curpos = curpos;
99 }
100
101 /* Average of L+R -> C and LFE */
102 static void average_copy(const snd_pcm_channel_area_t *dst_areas,
103                          snd_pcm_uframes_t dst_offset,
104                          const snd_pcm_channel_area_t *src_areas,
105                          snd_pcm_uframes_t src_offset,
106                          unsigned int nchns,
107                          unsigned int size)
108 {
109         short *dst[2], *src[2];
110         unsigned int i, dst_step[2], src_step[2];
111
112         for (i = 0; i < nchns; i++) {
113                 dst[i] = (short *)area_addr(dst_areas + i, dst_offset);
114                 dst_step[i] = area_step(dst_areas + i) / 2;
115         }
116         for (i = 0; i < 2; i++) {
117                 src[i] = (short *)area_addr(src_areas + i, src_offset);
118                 src_step[i] = area_step(src_areas + i) / 2;
119         }
120         while (size--) {
121                 short val = (*src[0] >> 1) + (*src[1] >> 1);
122                 for (i = 0; i < nchns; i++) {
123                         *dst[i] = val;
124                         dst[i] += dst_step[i];
125                 }
126                 src[0] += src_step[0];
127                 src[1] += src_step[1];
128         }
129 }
130
131 static void upmix_1_to_71(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
132                           const snd_pcm_channel_area_t *dst_areas,
133                           snd_pcm_uframes_t dst_offset,
134                           const snd_pcm_channel_area_t *src_areas,
135                           snd_pcm_uframes_t src_offset,
136                           snd_pcm_uframes_t size)
137 {
138         int i;
139         for (i = 0; i < 8; i++)
140                 snd_pcm_area_copy(dst_areas + i, dst_offset,
141                                   src_areas, src_offset,
142                                   size, SND_PCM_FORMAT_S16);
143 }
144
145 static void upmix_1_to_51(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
146                           const snd_pcm_channel_area_t *dst_areas,
147                           snd_pcm_uframes_t dst_offset,
148                           const snd_pcm_channel_area_t *src_areas,
149                           snd_pcm_uframes_t src_offset,
150                           snd_pcm_uframes_t size)
151 {
152         int i;
153         for (i = 0; i < 6; i++)
154                 snd_pcm_area_copy(dst_areas + i, dst_offset,
155                                   src_areas, src_offset,
156                                   size, SND_PCM_FORMAT_S16);
157 }
158
159 static void upmix_1_to_40(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
160                           const snd_pcm_channel_area_t *dst_areas,
161                           snd_pcm_uframes_t dst_offset,
162                           const snd_pcm_channel_area_t *src_areas,
163                           snd_pcm_uframes_t src_offset,
164                           snd_pcm_uframes_t size)
165 {
166         int i;
167         for (i = 0; i < 4; i++)
168                 snd_pcm_area_copy(dst_areas + i, dst_offset,
169                                   src_areas, src_offset,
170                                   size, SND_PCM_FORMAT_S16);
171 }
172
173 static void upmix_2_to_71(snd_pcm_upmix_t *mix,
174                           const snd_pcm_channel_area_t *dst_areas,
175                           snd_pcm_uframes_t dst_offset,
176                           const snd_pcm_channel_area_t *src_areas,
177                           snd_pcm_uframes_t src_offset,
178                           snd_pcm_uframes_t size)
179 {
180         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
181                            2, size, SND_PCM_FORMAT_S16);
182         delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
183                      size);
184         average_copy(dst_areas + 4, dst_offset, src_areas, src_offset,
185                      2, size);
186         snd_pcm_areas_copy(dst_areas + 6, dst_offset, src_areas, src_offset,
187                            2, size, SND_PCM_FORMAT_S16);
188         
189 }
190
191 static void upmix_2_to_51(snd_pcm_upmix_t *mix,
192                           const snd_pcm_channel_area_t *dst_areas,
193                           snd_pcm_uframes_t dst_offset,
194                           const snd_pcm_channel_area_t *src_areas,
195                           snd_pcm_uframes_t src_offset,
196                           snd_pcm_uframes_t size)
197 {
198         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
199                            2, size, SND_PCM_FORMAT_S16);
200         delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
201                      size);
202         average_copy(dst_areas + 4, dst_offset, src_areas, src_offset,
203                      2, size);
204 }
205
206 static void upmix_2_to_40(snd_pcm_upmix_t *mix,
207                           const snd_pcm_channel_area_t *dst_areas,
208                           snd_pcm_uframes_t dst_offset,
209                           const snd_pcm_channel_area_t *src_areas,
210                           snd_pcm_uframes_t src_offset,
211                           snd_pcm_uframes_t size)
212 {
213         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
214                            2, size, SND_PCM_FORMAT_S16);
215         delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
216                      size);
217 }
218
219 static void upmix_3_to_51(snd_pcm_upmix_t *mix,
220                           const snd_pcm_channel_area_t *dst_areas,
221                           snd_pcm_uframes_t dst_offset,
222                           const snd_pcm_channel_area_t *src_areas,
223                           snd_pcm_uframes_t src_offset,
224                           snd_pcm_uframes_t size)
225 {
226         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
227                            2, size, SND_PCM_FORMAT_S16);
228         delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
229                      size);
230         snd_pcm_areas_copy(dst_areas + 4, dst_offset, src_areas, src_offset,
231                            2, size, SND_PCM_FORMAT_S16);
232 }
233
234 static void upmix_3_to_40(snd_pcm_upmix_t *mix,
235                           const snd_pcm_channel_area_t *dst_areas,
236                           snd_pcm_uframes_t dst_offset,
237                           const snd_pcm_channel_area_t *src_areas,
238                           snd_pcm_uframes_t src_offset,
239                           snd_pcm_uframes_t size)
240 {
241         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
242                            2, size, SND_PCM_FORMAT_S16);
243         delayed_copy(mix, dst_areas + 2, dst_offset, src_areas, src_offset,
244                      size);
245 }
246
247 static void upmix_4_to_51(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
248                           const snd_pcm_channel_area_t *dst_areas,
249                           snd_pcm_uframes_t dst_offset,
250                           const snd_pcm_channel_area_t *src_areas,
251                           snd_pcm_uframes_t src_offset,
252                           snd_pcm_uframes_t size)
253 {
254         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
255                            4, size, SND_PCM_FORMAT_S16);
256         snd_pcm_areas_copy(dst_areas + 4, dst_offset, src_areas, src_offset,
257                            2, size, SND_PCM_FORMAT_S16);
258 }
259
260 static void upmix_4_to_40(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
261                           const snd_pcm_channel_area_t *dst_areas,
262                           snd_pcm_uframes_t dst_offset,
263                           const snd_pcm_channel_area_t *src_areas,
264                           snd_pcm_uframes_t src_offset,
265                           snd_pcm_uframes_t size)
266 {
267         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
268                            4, size, SND_PCM_FORMAT_S16);
269 }
270
271 static void upmix_5_to_51(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
272                           const snd_pcm_channel_area_t *dst_areas,
273                           snd_pcm_uframes_t dst_offset,
274                           const snd_pcm_channel_area_t *src_areas,
275                           snd_pcm_uframes_t src_offset,
276                           snd_pcm_uframes_t size)
277 {
278         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
279                            5, size, SND_PCM_FORMAT_S16);
280         snd_pcm_area_copy(dst_areas + 5, dst_offset, src_areas + 4, src_offset,
281                           size, SND_PCM_FORMAT_S16);
282 }
283
284 static void upmix_6_to_51(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
285                           const snd_pcm_channel_area_t *dst_areas,
286                           snd_pcm_uframes_t dst_offset,
287                           const snd_pcm_channel_area_t *src_areas,
288                           snd_pcm_uframes_t src_offset,
289                           snd_pcm_uframes_t size)
290 {
291         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
292                            6, size, SND_PCM_FORMAT_S16);
293 }
294
295 static void upmix_8_to_71(snd_pcm_upmix_t *mix ATTRIBUTE_UNUSED,
296                           const snd_pcm_channel_area_t *dst_areas,
297                           snd_pcm_uframes_t dst_offset,
298                           const snd_pcm_channel_area_t *src_areas,
299                           snd_pcm_uframes_t src_offset,
300                           snd_pcm_uframes_t size)
301 {
302         snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
303                            8, size, SND_PCM_FORMAT_S16);
304 }
305
306 static const upmixer_t do_upmix[8][3] = {
307         { upmix_1_to_40, upmix_1_to_51, upmix_1_to_71 },
308         { upmix_2_to_40, upmix_2_to_51, upmix_2_to_71 },
309         { upmix_3_to_40, upmix_3_to_51, upmix_3_to_51 },
310         { upmix_4_to_40, upmix_4_to_51, upmix_4_to_51 },
311         { upmix_4_to_40, upmix_5_to_51, upmix_5_to_51 },
312         { upmix_4_to_40, upmix_6_to_51, upmix_6_to_51 },
313         { upmix_4_to_40, upmix_6_to_51, upmix_6_to_51 },
314         { upmix_4_to_40, upmix_6_to_51, upmix_8_to_71 },
315 };
316
317 static snd_pcm_sframes_t
318 upmix_transfer(snd_pcm_extplug_t *ext,
319                const snd_pcm_channel_area_t *dst_areas,
320                snd_pcm_uframes_t dst_offset,
321                const snd_pcm_channel_area_t *src_areas,
322                snd_pcm_uframes_t src_offset,
323                snd_pcm_uframes_t size)
324 {
325         snd_pcm_upmix_t *mix = (snd_pcm_upmix_t *)ext;
326         mix->upmix(mix, dst_areas, dst_offset,
327                    src_areas, src_offset, size);
328         return size;
329 }
330
331 static int upmix_init(snd_pcm_extplug_t *ext)
332 {
333         snd_pcm_upmix_t *mix = (snd_pcm_upmix_t *)ext;
334         int ctype, stype;
335
336         switch (ext->slave_channels) {
337                 case    6:
338                         stype = 1;
339                         break;
340                 case 8:
341                         stype = 2;
342                         break;
343                 default:
344                         stype = 0;
345         }
346         ctype = ext->channels - 1;
347         if (ctype < 0 || ctype > 7) {
348                 SNDERR("Invalid channel numbers for upmix: %d", ctype + 1);
349                 return -EINVAL;
350         }
351         mix->upmix = do_upmix[ctype][stype];
352
353         if (mix->delay_ms) {
354                 free(mix->delayline[0]);
355                 free(mix->delayline[1]);
356                 mix->delay = ext->rate * mix->delay_ms / 1000;
357                 mix->delayline[0] = calloc(2, mix->delay);
358                 mix->delayline[1] = calloc(2, mix->delay);
359                 if (! mix->delayline[0] || ! mix->delayline[1])
360                         return -ENOMEM;
361                 mix->curpos = 0;
362         }
363         return 0;
364 }
365
366 static int upmix_close(snd_pcm_extplug_t *ext)
367 {
368         snd_pcm_upmix_t *mix = (snd_pcm_upmix_t *)ext;
369         free(mix->delayline[0]);
370         free(mix->delayline[1]);
371         return 0;
372 }
373
374 #if SND_PCM_EXTPLUG_VERSION >= 0x10002
375 static unsigned int chmap[8][8] = {
376         { SND_CHMAP_MONO },
377         { SND_CHMAP_FL, SND_CHMAP_FR },
378         { SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_FC },
379         { SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR },
380         { SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC },
381         { SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC, SND_CHMAP_LFE },
382         { SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_UNKNOWN },
383         { SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_SL, SND_CHMAP_SR },
384 };
385
386 static snd_pcm_chmap_query_t **upmix_query_chmaps(snd_pcm_extplug_t *ext ATTRIBUTE_UNUSED)
387 {
388         snd_pcm_chmap_query_t **maps;
389         int i;
390
391         maps = calloc(9, sizeof(void *));
392         if (!maps)
393                 return NULL;
394         for (i = 0; i < 8; i++) {
395                 snd_pcm_chmap_query_t *p;
396                 p = maps[i] = calloc(i + 1 + 2, sizeof(int));
397                 if (!p) {
398                         snd_pcm_free_chmaps(maps);
399                         return NULL;
400                 }
401                 p->type = SND_CHMAP_TYPE_FIXED;
402                 p->map.channels = i + 1;
403                 memcpy(p->map.pos, &chmap[i][0], (i + 1) * sizeof(int));
404         }
405         return maps;
406 }
407
408 static snd_pcm_chmap_t *upmix_get_chmap(snd_pcm_extplug_t *ext)
409 {
410         snd_pcm_chmap_t *map;
411
412         if (ext->channels < 1 || ext->channels > 8)
413                 return NULL;
414         map = malloc((ext->channels + 1) * sizeof(int));
415         if (!map)
416                 return NULL;
417         map->channels = ext->channels;
418         memcpy(map->pos, &chmap[ext->channels - 1][0], ext->channels * sizeof(int));
419         return map;
420 }
421 #endif /* SND_PCM_EXTPLUG_VERSION >= 0x10002 */
422
423 static const snd_pcm_extplug_callback_t upmix_callback = {
424         .transfer = upmix_transfer,
425         .init = upmix_init,
426         .close = upmix_close,
427 #if SND_PCM_EXTPLUG_VERSION >= 0x10002
428         .query_chmaps = upmix_query_chmaps,
429         .get_chmap = upmix_get_chmap,
430 #endif
431 };
432
433 SND_PCM_PLUGIN_DEFINE_FUNC(upmix)
434 {
435         snd_config_iterator_t i, next;
436         snd_pcm_upmix_t *mix;
437         snd_config_t *sconf = NULL;
438         static const unsigned int chlist[3] = {4, 6, 8};
439         unsigned int channels = 0;
440         int delay = 10;
441         int err;
442
443         snd_config_for_each(i, next, conf) {
444                 snd_config_t *n = snd_config_iterator_entry(i);
445                 const char *id;
446                 if (snd_config_get_id(n, &id) < 0)
447                         continue;
448                 if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "hint") == 0)
449                         continue;
450                 if (strcmp(id, "slave") == 0) {
451                         sconf = n;
452                         continue;
453                 }
454                 if (strcmp(id, "delay") == 0) {
455                         long val;
456                         err = snd_config_get_integer(n, &val);
457                         if (err < 0) {
458                                 SNDERR("Invalid value for %s", id);
459                                 return err;
460                         }
461                         delay = val;
462                         continue;
463                 }
464                 if (strcmp(id, "channels") == 0) {
465                         long val;
466                         err = snd_config_get_integer(n, &val);
467                         if (err < 0) {
468                                 SNDERR("Invalid value for %s", id);
469                                 return err;
470                         }
471                         channels = val;
472                         if (channels != 4 && channels != 6 && channels != 0 && channels != 8) {
473                                 SNDERR("channels must be 4, 6, 8 or 0");
474                                 return -EINVAL;
475                         }
476                         continue;
477                 }
478                 SNDERR("Unknown field %s", id);
479                 return -EINVAL;
480         }
481
482         if (! sconf) {
483                 SNDERR("No slave configuration for filrmix pcm");
484                 return -EINVAL;
485         }
486
487         mix = calloc(1, sizeof(*mix));
488         if (mix == NULL)
489                 return -ENOMEM;
490
491         mix->ext.version = SND_PCM_EXTPLUG_VERSION;
492         mix->ext.name = "Upmix Plugin";
493         mix->ext.callback = &upmix_callback;
494         mix->ext.private_data = mix;
495         if (delay < 0)
496                 delay = 0;
497         else if (delay > 1000)
498                 delay = 1000;
499         mix->delay_ms = delay;
500
501         err = snd_pcm_extplug_create(&mix->ext, name, root, sconf, stream, mode);
502         if (err < 0) {
503                 free(mix);
504                 return err;
505         }
506
507         snd_pcm_extplug_set_param_minmax(&mix->ext,
508                                          SND_PCM_EXTPLUG_HW_CHANNELS,
509                                          1, 8);
510         if (channels)
511                 snd_pcm_extplug_set_slave_param_minmax(&mix->ext,
512                                                        SND_PCM_EXTPLUG_HW_CHANNELS,
513                                                        channels, channels);
514         else
515                 snd_pcm_extplug_set_slave_param_list(&mix->ext,
516                                                      SND_PCM_EXTPLUG_HW_CHANNELS,
517                                                      3, chlist);
518         snd_pcm_extplug_set_param(&mix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
519                                   SND_PCM_FORMAT_S16);
520         snd_pcm_extplug_set_slave_param(&mix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
521                                         SND_PCM_FORMAT_S16);
522
523         *pcmp = mix->ext.pcm;
524         return 0;
525 }
526
527 SND_PCM_PLUGIN_SYMBOL(upmix);