tizen 2.3.1 release
[external/alsa-lib.git] / src / pcm / pcm_multi.c
1 /**
2  * \file pcm/pcm_multi.c
3  * \ingroup PCM_Plugins
4  * \brief PCM Multi Streams to One Conversion Plugin Interface
5  * \author Abramo Bagnara <abramo@alsa-project.org>
6  * \date 2000-2001
7  */
8 /*
9  *  PCM - Multi
10  *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
11  *
12  *
13  *   This library is free software; you can redistribute it and/or modify
14  *   it under the terms of the GNU Lesser General Public License as
15  *   published by the Free Software Foundation; either version 2.1 of
16  *   the License, or (at your option) any later version.
17  *
18  *   This program is distributed in the hope that it will be useful,
19  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *   GNU Lesser General Public License for more details.
22  *
23  *   You should have received a copy of the GNU Lesser General Public
24  *   License along with this library; if not, write to the Free Software
25  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
26  *
27  */
28   
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <math.h>
34 #include "pcm_local.h"
35 #include "pcm_generic.h"
36
37 #ifndef PIC
38 /* entry for static linking */
39 const char *_snd_module_pcm_multi = "";
40 #endif
41
42 #ifndef DOC_HIDDEN
43
44 typedef struct {
45         snd_pcm_t *pcm;
46         unsigned int channels_count;
47         int close_slave;
48         snd_pcm_t *linked;
49 } snd_pcm_multi_slave_t;
50
51 typedef struct {
52         int slave_idx;
53         unsigned int slave_channel;
54 } snd_pcm_multi_channel_t;
55
56 typedef struct {
57         unsigned int slaves_count;
58         unsigned int master_slave;
59         snd_pcm_multi_slave_t *slaves;
60         unsigned int channels_count;
61         snd_pcm_multi_channel_t *channels;
62 } snd_pcm_multi_t;
63
64 #endif
65
66 static int snd_pcm_multi_close(snd_pcm_t *pcm)
67 {
68         snd_pcm_multi_t *multi = pcm->private_data;
69         unsigned int i;
70         int ret = 0;
71         for (i = 0; i < multi->slaves_count; ++i) {
72                 snd_pcm_multi_slave_t *slave = &multi->slaves[i];
73                 if (slave->close_slave) {
74                         int err = snd_pcm_close(slave->pcm);
75                         if (err < 0)
76                                 ret = err;
77                 }
78         }
79         free(multi->slaves);
80         free(multi->channels);
81         free(multi);
82         return ret;
83 }
84
85 static int snd_pcm_multi_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
86 {
87         return 0;
88 }
89
90 static int snd_pcm_multi_async(snd_pcm_t *pcm, int sig, pid_t pid)
91 {
92         snd_pcm_multi_t *multi = pcm->private_data;
93         snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
94         return snd_pcm_async(slave_0, sig, pid);
95 }
96
97 static int snd_pcm_multi_poll_descriptors_count(snd_pcm_t *pcm)
98 {
99         snd_pcm_multi_t *multi = pcm->private_data;
100         snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
101         return snd_pcm_poll_descriptors_count(slave_0);
102 }
103
104 static int snd_pcm_multi_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
105 {
106         snd_pcm_multi_t *multi = pcm->private_data;
107         snd_pcm_t *slave;
108         snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
109         int err;
110         unsigned int i;
111
112         for (i = 0; i < multi->slaves_count; ++i) {
113                 slave = multi->slaves[i].pcm;
114                 if (slave == slave_0)
115                         continue;
116                 err = snd_pcm_poll_descriptors(slave, pfds, space);
117                 if (err < 0)
118                         return err;
119         }
120         /* finally overwrite with master's pfds */
121         return snd_pcm_poll_descriptors(slave_0, pfds, space);
122 }
123
124 static int snd_pcm_multi_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
125 {
126         snd_pcm_multi_t *multi = pcm->private_data;
127         snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
128         return snd_pcm_poll_descriptors_revents(slave_0, pfds, nfds, revents);
129 }
130
131 static int snd_pcm_multi_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
132 {
133         snd_pcm_multi_t *multi = pcm->private_data;
134         int err, n;
135         assert(info->subdevice < multi->slaves_count);
136         n = info->subdevice;
137         info->subdevice = 0;
138         err = snd_pcm_info(multi->slaves[n].pcm, info);
139         if (err < 0)
140                 return err;
141         info->subdevices_count = multi->slaves_count;
142         return 0;
143 }
144
145 static int snd_pcm_multi_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
146 {
147         snd_pcm_multi_t *multi = pcm->private_data;
148         snd_pcm_access_mask_t access_mask;
149         int err;
150         snd_pcm_access_mask_any(&access_mask);
151         snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
152         err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
153                                          &access_mask);
154         if (err < 0)
155                 return err;
156         err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS,
157                                     multi->channels_count, 0);
158         if (err < 0)
159                 return err;
160         params->info = ~0U;
161         return 0;
162 }
163
164 static int snd_pcm_multi_hw_refine_sprepare(snd_pcm_t *pcm, unsigned int slave_idx,
165                                             snd_pcm_hw_params_t *sparams)
166 {
167         snd_pcm_multi_t *multi = pcm->private_data;
168         snd_pcm_multi_slave_t *slave = &multi->slaves[slave_idx];
169         snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
170         _snd_pcm_hw_params_any(sparams);
171         _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
172                                    &saccess_mask);
173         _snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS,
174                               slave->channels_count, 0);
175         return 0;
176 }
177
178 static int snd_pcm_multi_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
179                                            unsigned int slave_idx ATTRIBUTE_UNUSED,
180                                            snd_pcm_hw_params_t *params,
181                                            snd_pcm_hw_params_t *sparams)
182 {
183         int err;
184         unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
185                               SND_PCM_HW_PARBIT_SUBFORMAT |
186                               SND_PCM_HW_PARBIT_RATE |
187                               SND_PCM_HW_PARBIT_PERIOD_SIZE |
188                               SND_PCM_HW_PARBIT_PERIOD_TIME |
189                               SND_PCM_HW_PARBIT_PERIODS |
190                               SND_PCM_HW_PARBIT_BUFFER_SIZE |
191                               SND_PCM_HW_PARBIT_BUFFER_TIME |
192                               SND_PCM_HW_PARBIT_TICK_TIME);
193         const snd_pcm_access_mask_t *access_mask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
194         if (!snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_INTERLEAVED) &&
195             !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED) &&
196             !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
197                 snd_pcm_access_mask_t saccess_mask;
198                 snd_pcm_access_mask_any(&saccess_mask);
199                 snd_pcm_access_mask_reset(&saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
200                 err = _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
201                                                  &saccess_mask);
202                 if (err < 0)
203                         return err;
204         }
205         err = _snd_pcm_hw_params_refine(sparams, links, params);
206         if (err < 0)
207                 return err;
208         return 0;
209 }
210         
211 static int snd_pcm_multi_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
212                                            unsigned int slave_idx ATTRIBUTE_UNUSED,
213                                            snd_pcm_hw_params_t *params,
214                                            snd_pcm_hw_params_t *sparams)
215 {
216         int err;
217         unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
218                               SND_PCM_HW_PARBIT_SUBFORMAT |
219                               SND_PCM_HW_PARBIT_RATE |
220                               SND_PCM_HW_PARBIT_PERIOD_SIZE |
221                               SND_PCM_HW_PARBIT_PERIOD_TIME |
222                               SND_PCM_HW_PARBIT_PERIODS |
223                               SND_PCM_HW_PARBIT_BUFFER_SIZE |
224                               SND_PCM_HW_PARBIT_BUFFER_TIME |
225                               SND_PCM_HW_PARBIT_TICK_TIME);
226         snd_pcm_access_mask_t access_mask;
227         const snd_pcm_access_mask_t *saccess_mask = snd_pcm_hw_param_get_mask(sparams, SND_PCM_HW_PARAM_ACCESS);
228         snd_pcm_access_mask_any(&access_mask);
229         snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
230         if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED))
231                 snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
232         if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_COMPLEX) &&
233             !snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED))
234                 snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_COMPLEX);
235         err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
236                                          &access_mask);
237         if (err < 0)
238                 return err;
239         err = _snd_pcm_hw_params_refine(params, links, sparams);
240         if (err < 0)
241                 return err;
242         params->info &= sparams->info;
243         return 0;
244 }
245
246 static int snd_pcm_multi_hw_refine_slave(snd_pcm_t *pcm,
247                                          unsigned int slave_idx,
248                                          snd_pcm_hw_params_t *sparams)
249 {
250         snd_pcm_multi_t *multi = pcm->private_data;
251         snd_pcm_t *slave = multi->slaves[slave_idx].pcm;
252         return snd_pcm_hw_refine(slave, sparams);
253 }
254
255 static int snd_pcm_multi_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
256 {
257         snd_pcm_multi_t *multi = pcm->private_data;
258         unsigned int k;
259         snd_pcm_hw_params_t sparams[multi->slaves_count];
260         int err;
261         unsigned int cmask, changed;
262         err = snd_pcm_multi_hw_refine_cprepare(pcm, params);
263         if (err < 0)
264                 return err;
265         for (k = 0; k < multi->slaves_count; ++k) {
266                 err = snd_pcm_multi_hw_refine_sprepare(pcm, k, &sparams[k]);
267                 if (err < 0) {
268                         SNDERR("Slave PCM #%d not usable", k);
269                         return err;
270                 }
271         }
272         do {
273                 cmask = params->cmask;
274                 params->cmask = 0;
275                 for (k = 0; k < multi->slaves_count; ++k) {
276                         err = snd_pcm_multi_hw_refine_schange(pcm, k, params, &sparams[k]);
277                         if (err >= 0)
278                                 err = snd_pcm_multi_hw_refine_slave(pcm, k, &sparams[k]);
279                         if (err < 0) {
280                                 snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]);
281                                 return err;
282                         }
283                         err = snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]);
284                         if (err < 0)
285                                 return err;
286                 }
287                 err = snd_pcm_hw_refine_soft(pcm, params);
288                 changed = params->cmask;
289                 params->cmask |= cmask;
290                 if (err < 0)
291                         return err;
292         } while (changed);
293         return 0;
294 }
295
296 static int snd_pcm_multi_hw_params_slave(snd_pcm_t *pcm,
297                                          unsigned int slave_idx,
298                                          snd_pcm_hw_params_t *sparams)
299 {
300         snd_pcm_multi_t *multi = pcm->private_data;
301         snd_pcm_t *slave = multi->slaves[slave_idx].pcm;
302         int err = snd_pcm_hw_params(slave, sparams);
303         if (err < 0)
304                 return err;
305         err = snd_pcm_areas_silence(slave->running_areas, 0, slave->channels, slave->buffer_size, slave->format);
306         if (err < 0)
307                 return err;
308         if (slave->stopped_areas) {
309                 err = snd_pcm_areas_silence(slave->stopped_areas, 0, slave->channels, slave->buffer_size, slave->format);
310                 if (err < 0)
311                         return err;
312         }
313         return 0;
314 }
315
316 /* reset links to the normal state
317  * slave #0 = trigger master
318  * slave #1-(N-1) = trigger slaves, linked is set to #0
319  */
320 static void reset_links(snd_pcm_multi_t *multi)
321 {
322         unsigned int i;
323
324         for (i = 0; i < multi->slaves_count; ++i) {
325                 if (multi->slaves[i].linked)
326                         snd_pcm_unlink(multi->slaves[i].linked);
327                 multi->slaves[0].linked = NULL;
328                 if (! i)
329                         continue;
330                 if (snd_pcm_link(multi->slaves[0].pcm, multi->slaves[i].pcm) >= 0)
331                         multi->slaves[i].linked = multi->slaves[0].pcm;
332         }
333 }
334
335 static int snd_pcm_multi_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
336 {
337         snd_pcm_multi_t *multi = pcm->private_data;
338         unsigned int i;
339         snd_pcm_hw_params_t sparams[multi->slaves_count];
340         int err;
341         for (i = 0; i < multi->slaves_count; ++i) {
342                 err = snd_pcm_multi_hw_refine_sprepare(pcm, i, &sparams[i]);
343                 assert(err >= 0);
344                 err = snd_pcm_multi_hw_refine_schange(pcm, i, params, &sparams[i]);
345                 assert(err >= 0);
346                 err = snd_pcm_multi_hw_params_slave(pcm, i, &sparams[i]);
347                 if (err < 0) {
348                         snd_pcm_multi_hw_refine_cchange(pcm, i, params, &sparams[i]);
349                         return err;
350                 }
351         }
352         reset_links(multi);
353         return 0;
354 }
355
356 static int snd_pcm_multi_hw_free(snd_pcm_t *pcm)
357 {
358         snd_pcm_multi_t *multi = pcm->private_data;
359         unsigned int i;
360         int err = 0;
361         for (i = 0; i < multi->slaves_count; ++i) {
362                 snd_pcm_t *slave = multi->slaves[i].pcm;
363                 int e = snd_pcm_hw_free(slave);
364                 if (e < 0)
365                         err = e;
366                 if (!multi->slaves[i].linked)
367                         continue;
368                 e = snd_pcm_unlink(slave);
369                 if (e < 0)
370                         err = e;
371                 multi->slaves[i].linked = NULL;
372         }
373         return err;
374 }
375
376 static int snd_pcm_multi_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
377 {
378         snd_pcm_multi_t *multi = pcm->private_data;
379         unsigned int i;
380         int err;
381         for (i = 0; i < multi->slaves_count; ++i) {
382                 snd_pcm_t *slave = multi->slaves[i].pcm;
383                 err = snd_pcm_sw_params(slave, params);
384                 if (err < 0)
385                         return err;
386         }
387         return 0;
388 }
389
390 static int snd_pcm_multi_status(snd_pcm_t *pcm, snd_pcm_status_t *status)
391 {
392         snd_pcm_multi_t *multi = pcm->private_data;
393         snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
394         return snd_pcm_status(slave, status);
395 }
396
397 static snd_pcm_state_t snd_pcm_multi_state(snd_pcm_t *pcm)
398 {
399         snd_pcm_multi_t *multi = pcm->private_data;
400         snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
401         return snd_pcm_state(slave);
402 }
403
404 static int snd_pcm_multi_hwsync(snd_pcm_t *pcm)
405 {
406         snd_pcm_multi_t *multi = pcm->private_data;
407         snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
408         return snd_pcm_hwsync(slave);
409 }
410
411 static int snd_pcm_multi_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
412 {
413         snd_pcm_multi_t *multi = pcm->private_data;
414         snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
415         return snd_pcm_delay(slave, delayp);
416 }
417
418 static snd_pcm_sframes_t snd_pcm_multi_avail_update(snd_pcm_t *pcm)
419 {
420         snd_pcm_multi_t *multi = pcm->private_data;
421         snd_pcm_sframes_t ret = LONG_MAX;
422         unsigned int i;
423         for (i = 0; i < multi->slaves_count; ++i) {
424                 snd_pcm_sframes_t avail;
425                 avail = snd_pcm_avail_update(multi->slaves[i].pcm);
426                 if (avail < 0)
427                         return avail;
428                 if (ret > avail)
429                         ret = avail;
430         }
431         return ret;
432 }
433
434 static int snd_pcm_multi_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
435                                     snd_htimestamp_t *tstamp)
436 {
437         snd_pcm_multi_t *multi = pcm->private_data;
438         snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
439         return snd_pcm_htimestamp(slave, avail, tstamp);
440 }
441
442 static int snd_pcm_multi_prepare(snd_pcm_t *pcm)
443 {
444         snd_pcm_multi_t *multi = pcm->private_data;
445         int result = 0, err;
446         unsigned int i;
447         for (i = 0; i < multi->slaves_count; ++i) {
448                 /* We call prepare to each slave even if it's linked.
449                  * This is to make sure to sync non-mmaped control/status.
450                  */
451                 err = snd_pcm_prepare(multi->slaves[i].pcm);
452                 if (err < 0)
453                         result = err;
454         }
455         return result;
456 }
457
458 static int snd_pcm_multi_reset(snd_pcm_t *pcm)
459 {
460         snd_pcm_multi_t *multi = pcm->private_data;
461         int result = 0, err;
462         unsigned int i;
463         for (i = 0; i < multi->slaves_count; ++i) {
464                 /* Reset each slave, as well as in prepare */
465                 err = snd_pcm_reset(multi->slaves[i].pcm);
466                 if (err < 0) 
467                         result = err;
468         }
469         return result;
470 }
471
472 /* when the first slave PCM is linked, it means that the whole multi
473  * plugin instance is linked manually to another PCM.  in this case,
474  * we need to trigger the master.
475  */
476 static int snd_pcm_multi_start(snd_pcm_t *pcm)
477 {
478         snd_pcm_multi_t *multi = pcm->private_data;
479         int err = 0;
480         unsigned int i;
481         if (multi->slaves[0].linked)
482                 return snd_pcm_start(multi->slaves[0].linked);
483         for (i = 0; i < multi->slaves_count; ++i) {
484                 if (multi->slaves[i].linked)
485                         continue;
486                 err = snd_pcm_start(multi->slaves[i].pcm);
487                 if (err < 0)
488                         return err;
489         }
490         return err;
491 }
492
493 static int snd_pcm_multi_drop(snd_pcm_t *pcm)
494 {
495         snd_pcm_multi_t *multi = pcm->private_data;
496         int err = 0;
497         unsigned int i;
498         if (multi->slaves[0].linked)
499                 return snd_pcm_drop(multi->slaves[0].linked);
500         for (i = 0; i < multi->slaves_count; ++i) {
501                 if (multi->slaves[i].linked)
502                         continue;
503                 err = snd_pcm_drop(multi->slaves[i].pcm);
504                 if (err < 0)
505                         return err;
506         }
507         return err;
508 }
509
510 static int snd_pcm_multi_drain(snd_pcm_t *pcm)
511 {
512         snd_pcm_multi_t *multi = pcm->private_data;
513         int err = 0;
514         unsigned int i;
515         if (multi->slaves[0].linked)
516                 return snd_pcm_drain(multi->slaves[0].linked);
517         for (i = 0; i < multi->slaves_count; ++i) {
518                 if (multi->slaves[i].linked)
519                         continue;
520                 err = snd_pcm_drain(multi->slaves[i].pcm);
521                 if (err < 0)
522                         return err;
523         }
524         return err;
525 }
526
527 static int snd_pcm_multi_pause(snd_pcm_t *pcm, int enable)
528 {
529         snd_pcm_multi_t *multi = pcm->private_data;
530         int err = 0;
531         unsigned int i;
532         if (multi->slaves[0].linked)
533                 return snd_pcm_pause(multi->slaves[0].linked, enable);
534         for (i = 0; i < multi->slaves_count; ++i) {
535                 if (multi->slaves[i].linked)
536                         continue;
537                 err = snd_pcm_pause(multi->slaves[i].pcm, enable);
538                 if (err < 0)
539                         return err;
540         }
541         return err;
542 }
543
544 static int snd_pcm_multi_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
545 {
546         snd_pcm_multi_t *multi = pcm->private_data;
547         unsigned int channel = info->channel;
548         snd_pcm_multi_channel_t *c = &multi->channels[channel];
549         int err;
550         if (c->slave_idx < 0)
551                 return -ENXIO;
552         info->channel = c->slave_channel;
553         err = snd_pcm_channel_info(multi->slaves[c->slave_idx].pcm, info);
554         info->channel = channel;
555         return err;
556 }
557
558 static snd_pcm_sframes_t snd_pcm_multi_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
559 {
560         snd_pcm_multi_t *multi = pcm->private_data;
561         unsigned int i;
562         snd_pcm_uframes_t pos[multi->slaves_count];
563         memset(pos, 0, sizeof(pos));
564         for (i = 0; i < multi->slaves_count; ++i) {
565                 snd_pcm_t *slave_i = multi->slaves[i].pcm;
566                 snd_pcm_sframes_t f = snd_pcm_rewind(slave_i, frames);
567                 if (f < 0)
568                         return f;
569                 pos[i] = f;
570                 frames = f;
571         }
572         /* Realign the pointers */
573         for (i = 0; i < multi->slaves_count; ++i) {
574                 snd_pcm_t *slave_i = multi->slaves[i].pcm;
575                 snd_pcm_uframes_t f = pos[i] - frames;
576                 snd_pcm_sframes_t result;
577                 if (f > 0) {
578                         result = INTERNAL(snd_pcm_forward)(slave_i, f);
579                         if (result < 0)
580                                 return result;
581                         if ((snd_pcm_uframes_t)result != f)
582                                 return -EIO;
583                 }
584         }
585         return frames;
586 }
587
588 static snd_pcm_sframes_t snd_pcm_multi_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
589 {
590         snd_pcm_multi_t *multi = pcm->private_data;
591         unsigned int i;
592         snd_pcm_uframes_t pos[multi->slaves_count];
593         memset(pos, 0, sizeof(pos));
594         for (i = 0; i < multi->slaves_count; ++i) {
595                 snd_pcm_t *slave_i = multi->slaves[i].pcm;
596                 snd_pcm_sframes_t f = INTERNAL(snd_pcm_forward)(slave_i, frames);
597                 if (f < 0)
598                         return f;
599                 pos[i] = f;
600                 frames = f;
601         }
602         /* Realign the pointers */
603         for (i = 0; i < multi->slaves_count; ++i) {
604                 snd_pcm_t *slave_i = multi->slaves[i].pcm;
605                 snd_pcm_uframes_t f = pos[i] - frames;
606                 snd_pcm_sframes_t result;
607                 if (f > 0) {
608                         result = snd_pcm_rewind(slave_i, f);
609                         if (result < 0)
610                                 return result;
611                         if ((snd_pcm_uframes_t)result != f)
612                                 return -EIO;
613                 }
614         }
615         return frames;
616 }
617
618 static int snd_pcm_multi_resume(snd_pcm_t *pcm)
619 {
620         snd_pcm_multi_t *multi = pcm->private_data;
621         int err = 0;
622         unsigned int i;
623         if (multi->slaves[0].linked)
624                 return snd_pcm_resume(multi->slaves[0].linked);
625         for (i = 0; i < multi->slaves_count; ++i) {
626                 if (multi->slaves[i].linked)
627                         continue;
628                 err = snd_pcm_resume(multi->slaves[i].pcm);
629                 if (err < 0)
630                         return err;
631         }
632         return err;
633 }
634
635 /* if a multi plugin instance is linked as slaves, every slave PCMs
636  * including the first one has to be relinked to the given master.
637  */
638 static int snd_pcm_multi_link_slaves(snd_pcm_t *pcm, snd_pcm_t *master)
639
640         snd_pcm_multi_t *multi = pcm->private_data;
641         unsigned int i;
642         int err;
643
644         for (i = 0; i < multi->slaves_count; ++i) {
645                 snd_pcm_unlink(multi->slaves[i].pcm);
646                 multi->slaves[i].linked = NULL;
647                 err = snd_pcm_link(master, multi->slaves[i].pcm);
648                 if (err < 0) {
649                         reset_links(multi);
650                         return err;
651                 }
652                 multi->slaves[i].linked = master;
653         }
654         return 0;
655 }
656
657 /* linking to a multi as a master is easy - simply link to the first
658  * slave element as its own slaves are already linked.
659  */
660 static int snd_pcm_multi_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2)
661 {
662         snd_pcm_multi_t *multi = pcm1->private_data;
663         if (multi->slaves[0].pcm->fast_ops->link)
664                 return multi->slaves[0].pcm->fast_ops->link(multi->slaves[0].pcm, pcm2);
665         return -ENOSYS;
666 }
667
668 static int snd_pcm_multi_unlink(snd_pcm_t *pcm)
669 {
670         snd_pcm_multi_t *multi = pcm->private_data;
671         unsigned int i;
672
673         for (i = 0; i < multi->slaves_count; ++i) {
674                 if (multi->slaves[i].linked)
675                         snd_pcm_unlink(multi->slaves[i].linked);
676                 multi->slaves[0].linked = NULL;
677         }
678         return 0;
679 }
680
681 static snd_pcm_sframes_t snd_pcm_multi_mmap_commit(snd_pcm_t *pcm,
682                                                    snd_pcm_uframes_t offset,
683                                                    snd_pcm_uframes_t size)
684 {
685         snd_pcm_multi_t *multi = pcm->private_data;
686         snd_pcm_t *slave;
687         unsigned int i;
688         snd_pcm_sframes_t result;
689
690         for (i = 0; i < multi->slaves_count; ++i) {
691                 slave = multi->slaves[i].pcm;
692                 result = snd_pcm_mmap_commit(slave, offset, size);
693                 if (result < 0)
694                         return result;
695                 if ((snd_pcm_uframes_t)result != size)
696                         return -EIO;
697         }
698         return size;
699 }
700
701 static int snd_pcm_multi_munmap(snd_pcm_t *pcm)
702 {
703         free(pcm->mmap_channels);
704         free(pcm->running_areas);
705         pcm->mmap_channels = NULL;
706         pcm->running_areas = NULL;
707         return 0;
708 }
709
710 static int snd_pcm_multi_mmap(snd_pcm_t *pcm)
711 {
712         snd_pcm_multi_t *multi = pcm->private_data;
713         unsigned int c;
714
715         pcm->mmap_channels = calloc(pcm->channels,
716                                     sizeof(pcm->mmap_channels[0]));
717         pcm->running_areas = calloc(pcm->channels,
718                                     sizeof(pcm->running_areas[0]));
719         if (!pcm->mmap_channels || !pcm->running_areas) {
720                 snd_pcm_multi_munmap(pcm);
721                 return -ENOMEM;
722         }
723
724         /* Copy the slave mmapped buffer data */
725         for (c = 0; c < pcm->channels; c++) {
726                 snd_pcm_multi_channel_t *chan = &multi->channels[c];
727                 snd_pcm_t *slave;
728                 if (chan->slave_idx < 0) {
729                         snd_pcm_multi_munmap(pcm);
730                         return -ENXIO;
731                 }
732                 slave = multi->slaves[chan->slave_idx].pcm;
733                 pcm->mmap_channels[c] =
734                         slave->mmap_channels[chan->slave_channel];
735                 pcm->mmap_channels[c].channel = c;
736                 pcm->running_areas[c] =
737                         slave->running_areas[chan->slave_channel];
738         }
739         return 0;
740 }
741
742 static void snd_pcm_multi_dump(snd_pcm_t *pcm, snd_output_t *out)
743 {
744         snd_pcm_multi_t *multi = pcm->private_data;
745         unsigned int k;
746         snd_output_printf(out, "Multi PCM\n");
747         snd_output_printf(out, "  Channel bindings:\n");
748         for (k = 0; k < multi->channels_count; ++k) {
749                 snd_pcm_multi_channel_t *c = &multi->channels[k];
750                 if (c->slave_idx < 0)
751                         continue;
752                 snd_output_printf(out, "    %d: slave %d, channel %d\n", 
753                         k, c->slave_idx, c->slave_channel);
754         }
755         if (pcm->setup) {
756                 snd_output_printf(out, "Its setup is:\n");
757                 snd_pcm_dump_setup(pcm, out);
758         }
759         for (k = 0; k < multi->slaves_count; ++k) {
760                 snd_output_printf(out, "Slave #%d: ", k);
761                 snd_pcm_dump(multi->slaves[k].pcm, out);
762         }
763 }
764
765 static const snd_pcm_ops_t snd_pcm_multi_ops = {
766         .close = snd_pcm_multi_close,
767         .info = snd_pcm_multi_info,
768         .hw_refine = snd_pcm_multi_hw_refine,
769         .hw_params = snd_pcm_multi_hw_params,
770         .hw_free = snd_pcm_multi_hw_free,
771         .sw_params = snd_pcm_multi_sw_params,
772         .channel_info = snd_pcm_multi_channel_info,
773         .dump = snd_pcm_multi_dump,
774         .nonblock = snd_pcm_multi_nonblock,
775         .async = snd_pcm_multi_async,
776         .mmap = snd_pcm_multi_mmap,
777         .munmap = snd_pcm_multi_munmap,
778 };
779
780 static const snd_pcm_fast_ops_t snd_pcm_multi_fast_ops = {
781         .status = snd_pcm_multi_status,
782         .state = snd_pcm_multi_state,
783         .hwsync = snd_pcm_multi_hwsync,
784         .delay = snd_pcm_multi_delay,
785         .prepare = snd_pcm_multi_prepare,
786         .reset = snd_pcm_multi_reset,
787         .start = snd_pcm_multi_start,
788         .drop = snd_pcm_multi_drop,
789         .drain = snd_pcm_multi_drain,
790         .pause = snd_pcm_multi_pause,
791         .writei = snd_pcm_mmap_writei,
792         .writen = snd_pcm_mmap_writen,
793         .readi = snd_pcm_mmap_readi,
794         .readn = snd_pcm_mmap_readn,
795         .rewind = snd_pcm_multi_rewind,
796         .forward = snd_pcm_multi_forward,
797         .resume = snd_pcm_multi_resume,
798         .link = snd_pcm_multi_link,
799         .link_slaves = snd_pcm_multi_link_slaves,
800         .unlink = snd_pcm_multi_unlink,
801         .avail_update = snd_pcm_multi_avail_update,
802         .mmap_commit = snd_pcm_multi_mmap_commit,
803         .htimestamp = snd_pcm_multi_htimestamp,
804         .poll_descriptors_count = snd_pcm_multi_poll_descriptors_count,
805         .poll_descriptors = snd_pcm_multi_poll_descriptors,
806         .poll_revents = snd_pcm_multi_poll_revents,
807 };
808
809 /**
810  * \brief Creates a new Multi PCM
811  * \param pcmp Returns created PCM handle
812  * \param name Name of PCM
813  * \param slaves_count Count of slaves
814  * \param master_slave Master slave number
815  * \param slaves_pcm Array with slave PCMs
816  * \param schannels_count Array with slave channel counts
817  * \param channels_count Count of channels
818  * \param sidxs Array with channels indexes to slaves
819  * \param schannels Array with slave channels
820  * \param close_slaves When set, the slave PCM handle is closed
821  * \retval zero on success otherwise a negative error code
822  * \warning Using of this function might be dangerous in the sense
823  *          of compatibility reasons. The prototype might be freely
824  *          changed in future.
825  */
826 int snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name,
827                        unsigned int slaves_count, unsigned int master_slave,
828                        snd_pcm_t **slaves_pcm, unsigned int *schannels_count,
829                        unsigned int channels_count,
830                        int *sidxs, unsigned int *schannels,
831                        int close_slaves)
832 {
833         snd_pcm_t *pcm;
834         snd_pcm_multi_t *multi;
835         unsigned int i;
836         snd_pcm_stream_t stream;
837         char slave_map[64][64] = { { 0 } };
838         int err;
839
840         assert(pcmp);
841         assert(slaves_count > 0 && slaves_pcm && schannels_count);
842         assert(channels_count > 0 && sidxs && schannels);
843         assert(master_slave < slaves_count);
844
845         multi = calloc(1, sizeof(snd_pcm_multi_t));
846         if (!multi) {
847                 return -ENOMEM;
848         }
849
850         stream = slaves_pcm[0]->stream;
851         
852         multi->slaves_count = slaves_count;
853         multi->master_slave = master_slave;
854         multi->slaves = calloc(slaves_count, sizeof(*multi->slaves));
855         if (!multi->slaves) {
856                 free(multi);
857                 return -ENOMEM;
858         }
859         multi->channels_count = channels_count;
860         multi->channels = calloc(channels_count, sizeof(*multi->channels));
861         if (!multi->channels) {
862                 free(multi->slaves);
863                 free(multi);
864                 return -ENOMEM;
865         }
866         for (i = 0; i < slaves_count; ++i) {
867                 snd_pcm_multi_slave_t *slave = &multi->slaves[i];
868                 assert(slaves_pcm[i]->stream == stream);
869                 slave->pcm = slaves_pcm[i];
870                 slave->channels_count = schannels_count[i];
871                 slave->close_slave = close_slaves;
872         }
873         for (i = 0; i < channels_count; ++i) {
874                 snd_pcm_multi_channel_t *bind = &multi->channels[i];
875                 assert(sidxs[i] < (int)slaves_count);
876                 assert(schannels[i] < schannels_count[sidxs[i]]);
877                 bind->slave_idx = sidxs[i];
878                 bind->slave_channel = schannels[i];
879                 if (sidxs[i] < 0)
880                         continue;
881                 assert(!slave_map[sidxs[i]][schannels[i]]);
882                 slave_map[sidxs[i]][schannels[i]] = 1;
883         }
884         multi->channels_count = channels_count;
885
886         err = snd_pcm_new(&pcm, SND_PCM_TYPE_MULTI, name, stream,
887                           multi->slaves[0].pcm->mode);
888         if (err < 0) {
889                 free(multi->slaves);
890                 free(multi->channels);
891                 free(multi);
892                 return err;
893         }
894         pcm->mmap_rw = 1;
895         pcm->mmap_shadow = 1; /* has own mmap method */
896         pcm->ops = &snd_pcm_multi_ops;
897         pcm->fast_ops = &snd_pcm_multi_fast_ops;
898         pcm->private_data = multi;
899         pcm->poll_fd = multi->slaves[master_slave].pcm->poll_fd;
900         pcm->poll_events = multi->slaves[master_slave].pcm->poll_events;
901         pcm->monotonic = multi->slaves[master_slave].pcm->monotonic;
902         snd_pcm_link_hw_ptr(pcm, multi->slaves[master_slave].pcm);
903         snd_pcm_link_appl_ptr(pcm, multi->slaves[master_slave].pcm);
904         *pcmp = pcm;
905         return 0;
906 }
907
908 /*! \page pcm_plugins
909
910 \section pcm_plugins_multi Plugin: Multiple streams to One
911
912 This plugin converts multiple streams to one.
913
914 \code
915 pcm.name {
916         type multi              # Multiple streams conversion PCM
917         slaves {                # Slaves definition
918                 ID STR          # Slave PCM name
919                 # or
920                 ID {
921                         pcm STR         # Slave PCM name
922                         # or
923                         pcm { }         # Slave PCM definition
924                         channels INT    # Slave channels
925                 }
926         }
927         bindings {              # Bindings table
928                 N {
929                         slave STR       # Slave key
930                         channel INT     # Slave channel
931                 }
932         }
933         [master INT]            # Define the master slave
934 }
935 \endcode
936
937 For example, to bind two PCM streams with two-channel stereo (hw:0,0 and
938 hw:0,1) as one 4-channel stereo PCM stream, define like this:
939 \code
940 pcm.quad {
941         type multi
942
943         slaves.a.pcm "hw:0,0"
944         slaves.a.channels 2
945         slaves.b.pcm "hw:0,1"
946         slaves.b.channels 2
947
948         bindings.0.slave a
949         bindings.0.channel 0
950         bindings.1.slave a
951         bindings.1.channel 1
952         bindings.2.slave b
953         bindings.2.channel 0
954         bindings.3.slave b
955         bindings.3.channel 1
956 }
957 \endcode
958 Note that the resultant pcm "quad" is not in the interleaved format
959 but in the "complex" format.  Hence, it's not accessible by applications
960 which can handle only the interleaved (or the non-interleaved) format.
961 In such a case, wrap this PCM with \ref pcm_plugins_route "route" or
962 \ref pcm_plugins_plug "plug" plugin.
963 \code
964 pcm.quad2 {
965         type route
966         slave.pcm "quad"
967         ttable.0.0 1
968         ttable.1.1 1
969         ttable.2.2 1
970         ttable.3.3 1
971 }
972 \endcode
973
974 \subsection pcm_plugins_multi_funcref Function reference
975
976 <UL>
977   <LI>snd_pcm_multi_open()
978   <LI>_snd_pcm_multi_open()
979 </UL>
980
981 */
982
983 /**
984  * \brief Creates a new Multi PCM
985  * \param pcmp Returns created PCM handle
986  * \param name Name of PCM
987  * \param root Root configuration node
988  * \param conf Configuration node with Multi PCM description
989  * \param stream Stream type
990  * \param mode Stream mode
991  * \retval zero on success otherwise a negative error code
992  * \warning Using of this function might be dangerous in the sense
993  *          of compatibility reasons. The prototype might be freely
994  *          changed in future.
995  */
996 int _snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name,
997                         snd_config_t *root, snd_config_t *conf,
998                         snd_pcm_stream_t stream, int mode)
999 {
1000         snd_config_iterator_t i, inext, j, jnext;
1001         snd_config_t *slaves = NULL;
1002         snd_config_t *bindings = NULL;
1003         int err;
1004         unsigned int idx;
1005         const char **slaves_id = NULL;
1006         snd_config_t **slaves_conf = NULL;
1007         snd_pcm_t **slaves_pcm = NULL;
1008         unsigned int *slaves_channels = NULL;
1009         int *channels_sidx = NULL;
1010         unsigned int *channels_schannel = NULL;
1011         unsigned int slaves_count = 0;
1012         long master_slave = 0;
1013         unsigned int channels_count = 0;
1014         snd_config_for_each(i, inext, conf) {
1015                 snd_config_t *n = snd_config_iterator_entry(i);
1016                 const char *id;
1017                 if (snd_config_get_id(n, &id) < 0)
1018                         continue;
1019                 if (snd_pcm_conf_generic_id(id))
1020                         continue;
1021                 if (strcmp(id, "slaves") == 0) {
1022                         if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
1023                                 SNDERR("Invalid type for %s", id);
1024                                 return -EINVAL;
1025                         }
1026                         slaves = n;
1027                         continue;
1028                 }
1029                 if (strcmp(id, "bindings") == 0) {
1030                         if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
1031                                 SNDERR("Invalid type for %s", id);
1032                                 return -EINVAL;
1033                         }
1034                         bindings = n;
1035                         continue;
1036                 }
1037                 if (strcmp(id, "master") == 0) {
1038                         if (snd_config_get_integer(n, &master_slave) < 0) {
1039                                 SNDERR("Invalid type for %s", id);
1040                                 return -EINVAL;
1041                         }
1042                         continue;
1043                 }
1044                 SNDERR("Unknown field %s", id);
1045                 return -EINVAL;
1046         }
1047         if (!slaves) {
1048                 SNDERR("slaves is not defined");
1049                 return -EINVAL;
1050         }
1051         if (!bindings) {
1052                 SNDERR("bindings is not defined");
1053                 return -EINVAL;
1054         }
1055         snd_config_for_each(i, inext, slaves) {
1056                 ++slaves_count;
1057         }
1058         if (master_slave < 0 || master_slave >= (long)slaves_count) {
1059                 SNDERR("Master slave is out of range (0-%u)\n", slaves_count-1);
1060                 return -EINVAL;
1061         }
1062         snd_config_for_each(i, inext, bindings) {
1063                 long cchannel;
1064                 snd_config_t *m = snd_config_iterator_entry(i);
1065                 const char *id;
1066                 if (snd_config_get_id(m, &id) < 0)
1067                         continue;
1068                 err = safe_strtol(id, &cchannel);
1069                 if (err < 0 || cchannel < 0) {
1070                         SNDERR("Invalid channel number: %s", id);
1071                         return -EINVAL;
1072                 }
1073                 if ((unsigned long)cchannel >= channels_count)
1074                         channels_count = cchannel + 1;
1075         }
1076         if (channels_count == 0) {
1077                 SNDERR("No channels defined");
1078                 return -EINVAL;
1079         }
1080         slaves_id = calloc(slaves_count, sizeof(*slaves_id));
1081         slaves_conf = calloc(slaves_count, sizeof(*slaves_conf));
1082         slaves_pcm = calloc(slaves_count, sizeof(*slaves_pcm));
1083         slaves_channels = calloc(slaves_count, sizeof(*slaves_channels));
1084         channels_sidx = calloc(channels_count, sizeof(*channels_sidx));
1085         channels_schannel = calloc(channels_count, sizeof(*channels_schannel));
1086         if (!slaves_id || !slaves_conf || !slaves_pcm || !slaves_channels ||
1087             !channels_sidx || !channels_schannel) {
1088                 err = -ENOMEM;
1089                 goto _free;
1090         }
1091         idx = 0;
1092         for (idx = 0; idx < channels_count; ++idx)
1093                 channels_sidx[idx] = -1;
1094         idx = 0;
1095         snd_config_for_each(i, inext, slaves) {
1096                 snd_config_t *m = snd_config_iterator_entry(i);
1097                 const char *id;
1098                 int channels;
1099                 if (snd_config_get_id(m, &id) < 0)
1100                         continue;
1101                 slaves_id[idx] = id;
1102                 err = snd_pcm_slave_conf(root, m, &slaves_conf[idx], 1,
1103                                          SND_PCM_HW_PARAM_CHANNELS, SCONF_MANDATORY, &channels);
1104                 if (err < 0)
1105                         goto _free;
1106                 slaves_channels[idx] = channels;
1107                 ++idx;
1108         }
1109
1110         snd_config_for_each(i, inext, bindings) {
1111                 snd_config_t *m = snd_config_iterator_entry(i);
1112                 long cchannel = -1;
1113                 long schannel = -1;
1114                 int slave = -1;
1115                 long val;
1116                 const char *str;
1117                 const char *id;
1118                 if (snd_config_get_id(m, &id) < 0)
1119                         continue;
1120                 err = safe_strtol(id, &cchannel);
1121                 if (err < 0 || cchannel < 0) {
1122                         SNDERR("Invalid channel number: %s", id);
1123                         err = -EINVAL;
1124                         goto _free;
1125                 }
1126                 snd_config_for_each(j, jnext, m) {
1127                         snd_config_t *n = snd_config_iterator_entry(j);
1128                         const char *id;
1129                         if (snd_config_get_id(n, &id) < 0)
1130                                 continue;
1131                         if (strcmp(id, "comment") == 0)
1132                                 continue;
1133                         if (strcmp(id, "slave") == 0) {
1134                                 char buf[32];
1135                                 unsigned int k;
1136                                 err = snd_config_get_string(n, &str);
1137                                 if (err < 0) {
1138                                         err = snd_config_get_integer(n, &val);
1139                                         if (err < 0) {
1140                                                 SNDERR("Invalid value for %s", id);
1141                                                 goto _free;
1142                                         }
1143                                         sprintf(buf, "%ld", val);
1144                                         str = buf;
1145                                 }
1146                                 for (k = 0; k < slaves_count; ++k) {
1147                                         if (strcmp(slaves_id[k], str) == 0)
1148                                                 slave = k;
1149                                 }
1150                                 continue;
1151                         }
1152                         if (strcmp(id, "channel") == 0) {
1153                                 err = snd_config_get_integer(n, &schannel);
1154                                 if (err < 0) {
1155                                         SNDERR("Invalid type for %s", id);
1156                                         goto _free;
1157                                 }
1158                                 continue;
1159                         }
1160                         SNDERR("Unknown field %s", id);
1161                         err = -EINVAL;
1162                         goto _free;
1163                 }
1164                 if (slave < 0 || (unsigned int)slave >= slaves_count) {
1165                         SNDERR("Invalid or missing sidx for channel %s", id);
1166                         err = -EINVAL;
1167                         goto _free;
1168                 }
1169                 if (schannel < 0 || 
1170                     (unsigned int) schannel >= slaves_channels[slave]) {
1171                         SNDERR("Invalid or missing schannel for channel %s", id);
1172                         err = -EINVAL;
1173                         goto _free;
1174                 }
1175                 channels_sidx[cchannel] = slave;
1176                 channels_schannel[cchannel] = schannel;
1177         }
1178         
1179         for (idx = 0; idx < slaves_count; ++idx) {
1180                 err = snd_pcm_open_slave(&slaves_pcm[idx], root,
1181                                          slaves_conf[idx], stream, mode,
1182                                          conf);
1183                 if (err < 0)
1184                         goto _free;
1185                 snd_config_delete(slaves_conf[idx]);
1186                 slaves_conf[idx] = NULL;
1187         }
1188         err = snd_pcm_multi_open(pcmp, name, slaves_count, master_slave,
1189                                  slaves_pcm, slaves_channels,
1190                                  channels_count,
1191                                  channels_sidx, channels_schannel,
1192                                  1);
1193 _free:
1194         if (err < 0) {
1195                 for (idx = 0; idx < slaves_count; ++idx) {
1196                         if (slaves_pcm[idx])
1197                                 snd_pcm_close(slaves_pcm[idx]);
1198                 }
1199         }
1200         if (slaves_conf) {
1201                 for (idx = 0; idx < slaves_count; ++idx) {
1202                         if (slaves_conf[idx])
1203                                 snd_config_delete(slaves_conf[idx]);
1204                 }
1205                 free(slaves_conf);
1206         }
1207         free(slaves_pcm);
1208         free(slaves_channels);
1209         free(channels_sidx);
1210         free(channels_schannel);
1211         free(slaves_id);
1212         return err;
1213 }
1214 #ifndef DOC_HIDDEN
1215 SND_DLSYM_BUILD_VERSION(_snd_pcm_multi_open, SND_PCM_DLSYM_VERSION);
1216 #endif