| /** | |
| * \file pcm/pcm_multi.c | |
| * \ingroup PCM_Plugins | |
| * \brief PCM Multi Streams to One Conversion Plugin Interface | |
| * \author Abramo Bagnara <abramo@alsa-project.org> | |
| * \date 2000-2001 | |
| */ | |
| /* | |
| * PCM - Multi | |
| * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> | |
| * | |
| * | |
| * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #include "pcm_local.h" | |
| #include "pcm_generic.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <math.h> | |
| #ifndef PIC | |
| /* entry for static linking */ | |
| const char *_snd_module_pcm_multi = ""; | |
| #endif | |
| #ifndef DOC_HIDDEN | |
| typedef struct { | |
| snd_pcm_t *pcm; | |
| unsigned int channels_count; | |
| int close_slave; | |
| snd_pcm_t *linked; | |
| } snd_pcm_multi_slave_t; | |
| typedef struct { | |
| int slave_idx; | |
| unsigned int slave_channel; | |
| } snd_pcm_multi_channel_t; | |
| typedef struct { | |
| snd_pcm_uframes_t appl_ptr, hw_ptr; | |
| unsigned int slaves_count; | |
| unsigned int master_slave; | |
| snd_pcm_multi_slave_t *slaves; | |
| unsigned int channels_count; | |
| snd_pcm_multi_channel_t *channels; | |
| } snd_pcm_multi_t; | |
| #endif | |
| static int snd_pcm_multi_close(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| int ret = 0; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_multi_slave_t *slave = &multi->slaves[i]; | |
| if (slave->close_slave) { | |
| int err = snd_pcm_close(slave->pcm); | |
| if (err < 0) | |
| ret = err; | |
| } | |
| } | |
| free(multi->slaves); | |
| free(multi->channels); | |
| free(multi); | |
| return ret; | |
| } | |
| static int snd_pcm_multi_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int snd_pcm_multi_async(snd_pcm_t *pcm, int sig, pid_t pid) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm; | |
| return snd_pcm_async(slave_0, sig, pid); | |
| } | |
| static int snd_pcm_multi_poll_descriptors_count(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm; | |
| return snd_pcm_poll_descriptors_count(slave_0); | |
| } | |
| static int snd_pcm_multi_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave; | |
| snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm; | |
| int err; | |
| unsigned int i; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| slave = multi->slaves[i].pcm; | |
| if (slave == slave_0) | |
| continue; | |
| err = snd_pcm_poll_descriptors(slave, pfds, space); | |
| if (err < 0) | |
| return err; | |
| } | |
| /* finally overwrite with master's pfds */ | |
| return snd_pcm_poll_descriptors(slave_0, pfds, space); | |
| } | |
| static int snd_pcm_multi_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm; | |
| return snd_pcm_poll_descriptors_revents(slave_0, pfds, nfds, revents); | |
| } | |
| static int snd_pcm_multi_info(snd_pcm_t *pcm, snd_pcm_info_t *info) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| int err, n; | |
| assert(info->subdevice < multi->slaves_count); | |
| n = info->subdevice; | |
| info->subdevice = 0; | |
| err = snd_pcm_info(multi->slaves[n].pcm, info); | |
| if (err < 0) | |
| return err; | |
| info->subdevices_count = multi->slaves_count; | |
| return 0; | |
| } | |
| static int snd_pcm_multi_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_access_mask_t access_mask; | |
| int err; | |
| snd_pcm_access_mask_any(&access_mask); | |
| snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); | |
| err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS, | |
| &access_mask); | |
| if (err < 0) | |
| return err; | |
| err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS, | |
| multi->channels_count, 0); | |
| if (err < 0) | |
| return err; | |
| params->info = ~0U; | |
| return 0; | |
| } | |
| static int snd_pcm_multi_hw_refine_sprepare(snd_pcm_t *pcm, unsigned int slave_idx, | |
| snd_pcm_hw_params_t *sparams) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_multi_slave_t *slave = &multi->slaves[slave_idx]; | |
| snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP }; | |
| _snd_pcm_hw_params_any(sparams); | |
| _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS, | |
| &saccess_mask); | |
| _snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS, | |
| slave->channels_count, 0); | |
| return 0; | |
| } | |
| static int snd_pcm_multi_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, | |
| unsigned int slave_idx ATTRIBUTE_UNUSED, | |
| snd_pcm_hw_params_t *params, | |
| snd_pcm_hw_params_t *sparams) | |
| { | |
| int err; | |
| unsigned int links = (SND_PCM_HW_PARBIT_FORMAT | | |
| SND_PCM_HW_PARBIT_SUBFORMAT | | |
| SND_PCM_HW_PARBIT_RATE | | |
| SND_PCM_HW_PARBIT_PERIOD_SIZE | | |
| SND_PCM_HW_PARBIT_PERIOD_TIME | | |
| SND_PCM_HW_PARBIT_PERIODS | | |
| SND_PCM_HW_PARBIT_BUFFER_SIZE | | |
| SND_PCM_HW_PARBIT_BUFFER_TIME | | |
| SND_PCM_HW_PARBIT_TICK_TIME); | |
| const snd_pcm_access_mask_t *access_mask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS); | |
| if (!snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_INTERLEAVED) && | |
| !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED) && | |
| !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) { | |
| snd_pcm_access_mask_t saccess_mask; | |
| snd_pcm_access_mask_any(&saccess_mask); | |
| snd_pcm_access_mask_reset(&saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); | |
| err = _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS, | |
| &saccess_mask); | |
| if (err < 0) | |
| return err; | |
| } | |
| err = _snd_pcm_hw_params_refine(sparams, links, params); | |
| if (err < 0) | |
| return err; | |
| return 0; | |
| } | |
| static int snd_pcm_multi_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, | |
| unsigned int slave_idx ATTRIBUTE_UNUSED, | |
| snd_pcm_hw_params_t *params, | |
| snd_pcm_hw_params_t *sparams) | |
| { | |
| int err; | |
| unsigned int links = (SND_PCM_HW_PARBIT_FORMAT | | |
| SND_PCM_HW_PARBIT_SUBFORMAT | | |
| SND_PCM_HW_PARBIT_RATE | | |
| SND_PCM_HW_PARBIT_PERIOD_SIZE | | |
| SND_PCM_HW_PARBIT_PERIOD_TIME | | |
| SND_PCM_HW_PARBIT_PERIODS | | |
| SND_PCM_HW_PARBIT_BUFFER_SIZE | | |
| SND_PCM_HW_PARBIT_BUFFER_TIME | | |
| SND_PCM_HW_PARBIT_TICK_TIME); | |
| snd_pcm_access_mask_t access_mask; | |
| const snd_pcm_access_mask_t *saccess_mask = snd_pcm_hw_param_get_mask(sparams, SND_PCM_HW_PARAM_ACCESS); | |
| snd_pcm_access_mask_any(&access_mask); | |
| snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); | |
| if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) | |
| snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); | |
| if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_COMPLEX) && | |
| !snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED)) | |
| snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_COMPLEX); | |
| err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS, | |
| &access_mask); | |
| if (err < 0) | |
| return err; | |
| err = _snd_pcm_hw_params_refine(params, links, sparams); | |
| if (err < 0) | |
| return err; | |
| params->info &= sparams->info; | |
| return 0; | |
| } | |
| static int snd_pcm_multi_hw_refine_slave(snd_pcm_t *pcm, | |
| unsigned int slave_idx, | |
| snd_pcm_hw_params_t *sparams) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave = multi->slaves[slave_idx].pcm; | |
| return snd_pcm_hw_refine(slave, sparams); | |
| } | |
| static int snd_pcm_multi_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int k; | |
| snd_pcm_hw_params_t sparams[multi->slaves_count]; | |
| int err; | |
| unsigned int cmask, changed; | |
| err = snd_pcm_multi_hw_refine_cprepare(pcm, params); | |
| if (err < 0) | |
| return err; | |
| for (k = 0; k < multi->slaves_count; ++k) { | |
| err = snd_pcm_multi_hw_refine_sprepare(pcm, k, &sparams[k]); | |
| if (err < 0) { | |
| SNDERR("Slave PCM #%d not usable", k); | |
| return err; | |
| } | |
| } | |
| do { | |
| cmask = params->cmask; | |
| params->cmask = 0; | |
| for (k = 0; k < multi->slaves_count; ++k) { | |
| err = snd_pcm_multi_hw_refine_schange(pcm, k, params, &sparams[k]); | |
| if (err >= 0) | |
| err = snd_pcm_multi_hw_refine_slave(pcm, k, &sparams[k]); | |
| if (err < 0) { | |
| snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]); | |
| return err; | |
| } | |
| err = snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]); | |
| if (err < 0) | |
| return err; | |
| } | |
| err = snd_pcm_hw_refine_soft(pcm, params); | |
| changed = params->cmask; | |
| params->cmask |= cmask; | |
| if (err < 0) | |
| return err; | |
| } while (changed); | |
| return 0; | |
| } | |
| static int snd_pcm_multi_hw_params_slave(snd_pcm_t *pcm, | |
| unsigned int slave_idx, | |
| snd_pcm_hw_params_t *sparams) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave = multi->slaves[slave_idx].pcm; | |
| int err = snd_pcm_hw_params(slave, sparams); | |
| if (err < 0) | |
| return err; | |
| err = snd_pcm_areas_silence(slave->running_areas, 0, slave->channels, slave->buffer_size, slave->format); | |
| if (err < 0) | |
| return err; | |
| if (slave->stopped_areas) { | |
| err = snd_pcm_areas_silence(slave->stopped_areas, 0, slave->channels, slave->buffer_size, slave->format); | |
| if (err < 0) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| /* reset links to the normal state | |
| * slave #0 = trigger master | |
| * slave #1-(N-1) = trigger slaves, linked is set to #0 | |
| */ | |
| static void reset_links(snd_pcm_multi_t *multi) | |
| { | |
| unsigned int i; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| if (multi->slaves[i].linked) | |
| snd_pcm_unlink(multi->slaves[i].linked); | |
| multi->slaves[0].linked = NULL; | |
| if (! i) | |
| continue; | |
| if (snd_pcm_link(multi->slaves[0].pcm, multi->slaves[i].pcm) >= 0) | |
| multi->slaves[i].linked = multi->slaves[0].pcm; | |
| } | |
| } | |
| static int snd_pcm_multi_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| snd_pcm_hw_params_t sparams[multi->slaves_count]; | |
| int err; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| err = snd_pcm_multi_hw_refine_sprepare(pcm, i, &sparams[i]); | |
| assert(err >= 0); | |
| err = snd_pcm_multi_hw_refine_schange(pcm, i, params, &sparams[i]); | |
| assert(err >= 0); | |
| err = snd_pcm_multi_hw_params_slave(pcm, i, &sparams[i]); | |
| if (err < 0) { | |
| snd_pcm_multi_hw_refine_cchange(pcm, i, params, &sparams[i]); | |
| return err; | |
| } | |
| } | |
| reset_links(multi); | |
| return 0; | |
| } | |
| static int snd_pcm_multi_hw_free(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| int err = 0; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_t *slave = multi->slaves[i].pcm; | |
| int e = snd_pcm_hw_free(slave); | |
| if (e < 0) | |
| err = e; | |
| if (!multi->slaves[i].linked) | |
| continue; | |
| e = snd_pcm_unlink(slave); | |
| if (e < 0) | |
| err = e; | |
| multi->slaves[i].linked = NULL; | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_multi_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| int err; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_t *slave = multi->slaves[i].pcm; | |
| err = snd_pcm_sw_params(slave, params); | |
| if (err < 0) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_multi_avail_update(snd_pcm_t *pcm); | |
| static int snd_pcm_multi_status(snd_pcm_t *pcm, snd_pcm_status_t *status) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm; | |
| int err = snd_pcm_status(slave, status); | |
| if (err < 0) | |
| return err; | |
| snd_pcm_sframes_t avail = snd_pcm_multi_avail_update(pcm); | |
| if (avail < 0) | |
| return avail; | |
| status->hw_ptr = *pcm->hw.ptr; | |
| status->avail = avail; | |
| return 0; | |
| } | |
| static snd_pcm_state_t snd_pcm_multi_state(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm; | |
| return snd_pcm_state(slave); | |
| } | |
| static void snd_pcm_multi_hwptr_update(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_uframes_t hw_ptr = 0, slave_hw_ptr, avail, last_avail; | |
| unsigned int i; | |
| /* the logic is really simple, choose the lowest hw_ptr from slaves */ | |
| if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { | |
| last_avail = 0; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| slave_hw_ptr = *multi->slaves[i].pcm->hw.ptr; | |
| avail = __snd_pcm_playback_avail(pcm, multi->hw_ptr, slave_hw_ptr); | |
| if (avail > last_avail) { | |
| hw_ptr = slave_hw_ptr; | |
| last_avail = avail; | |
| } | |
| } | |
| } else { | |
| last_avail = LONG_MAX; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| slave_hw_ptr = *multi->slaves[i].pcm->hw.ptr; | |
| avail = __snd_pcm_capture_avail(pcm, multi->hw_ptr, slave_hw_ptr); | |
| if (avail < last_avail) { | |
| hw_ptr = slave_hw_ptr; | |
| last_avail = avail; | |
| } | |
| } | |
| } | |
| multi->hw_ptr = hw_ptr; | |
| } | |
| static int snd_pcm_multi_hwsync(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| int err; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| err = snd_pcm_hwsync(multi->slaves[i].pcm); | |
| if (err < 0) | |
| return err; | |
| } | |
| snd_pcm_multi_hwptr_update(pcm); | |
| return 0; | |
| } | |
| static int snd_pcm_multi_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_sframes_t d, dr = 0; | |
| unsigned int i; | |
| int err; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| err = snd_pcm_delay(multi->slaves[i].pcm, &d); | |
| if (err < 0) | |
| return err; | |
| if (dr < d) | |
| dr = d; | |
| } | |
| *delayp = dr; | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_multi_avail_update(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_sframes_t ret = LONG_MAX; | |
| unsigned int i; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_sframes_t avail; | |
| avail = snd_pcm_avail_update(multi->slaves[i].pcm); | |
| if (avail < 0) | |
| return avail; | |
| if (ret > avail) | |
| ret = avail; | |
| } | |
| snd_pcm_multi_hwptr_update(pcm); | |
| return ret; | |
| } | |
| static int snd_pcm_multi_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail, | |
| snd_htimestamp_t *tstamp) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm; | |
| return snd_pcm_htimestamp(slave, avail, tstamp); | |
| } | |
| static int snd_pcm_multi_prepare(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| int result = 0, err; | |
| unsigned int i; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| /* We call prepare to each slave even if it's linked. | |
| * This is to make sure to sync non-mmaped control/status. | |
| */ | |
| err = snd_pcm_prepare(multi->slaves[i].pcm); | |
| if (err < 0) | |
| result = err; | |
| } | |
| multi->hw_ptr = multi->appl_ptr = 0; | |
| return result; | |
| } | |
| static int snd_pcm_multi_reset(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| int result = 0, err; | |
| unsigned int i; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| /* Reset each slave, as well as in prepare */ | |
| err = snd_pcm_reset(multi->slaves[i].pcm); | |
| if (err < 0) | |
| result = err; | |
| } | |
| multi->hw_ptr = multi->appl_ptr = 0; | |
| return result; | |
| } | |
| /* when the first slave PCM is linked, it means that the whole multi | |
| * plugin instance is linked manually to another PCM. in this case, | |
| * we need to trigger the master. | |
| */ | |
| static int snd_pcm_multi_start(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| int err = 0; | |
| unsigned int i; | |
| if (multi->slaves[0].linked) | |
| return snd_pcm_start(multi->slaves[0].linked); | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| if (multi->slaves[i].linked) | |
| continue; | |
| err = snd_pcm_start(multi->slaves[i].pcm); | |
| if (err < 0) | |
| return err; | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_multi_drop(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| int err = 0; | |
| unsigned int i; | |
| if (multi->slaves[0].linked) | |
| return snd_pcm_drop(multi->slaves[0].linked); | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| if (multi->slaves[i].linked) | |
| continue; | |
| err = snd_pcm_drop(multi->slaves[i].pcm); | |
| if (err < 0) | |
| return err; | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_multi_drain(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| int err = 0; | |
| unsigned int i; | |
| if (multi->slaves[0].linked) | |
| return snd_pcm_drain(multi->slaves[0].linked); | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| if (multi->slaves[i].linked) | |
| continue; | |
| err = snd_pcm_drain(multi->slaves[i].pcm); | |
| if (err < 0) | |
| return err; | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_multi_pause(snd_pcm_t *pcm, int enable) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| int err = 0; | |
| unsigned int i; | |
| if (multi->slaves[0].linked) | |
| return snd_pcm_pause(multi->slaves[0].linked, enable); | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| if (multi->slaves[i].linked) | |
| continue; | |
| err = snd_pcm_pause(multi->slaves[i].pcm, enable); | |
| if (err < 0) | |
| return err; | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_multi_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int channel = info->channel; | |
| snd_pcm_multi_channel_t *c = &multi->channels[channel]; | |
| int err; | |
| if (c->slave_idx < 0) | |
| return -ENXIO; | |
| info->channel = c->slave_channel; | |
| err = snd_pcm_channel_info(multi->slaves[c->slave_idx].pcm, info); | |
| info->channel = channel; | |
| return err; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_multi_rewindable(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| snd_pcm_sframes_t frames = LONG_MAX; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_sframes_t f = snd_pcm_rewindable(multi->slaves[i].pcm); | |
| if (f <= 0) | |
| return f; | |
| if (f < frames) | |
| frames = f; | |
| } | |
| return frames; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_multi_forwardable(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| snd_pcm_sframes_t frames = LONG_MAX; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_sframes_t f = snd_pcm_forwardable(multi->slaves[i].pcm); | |
| if (f <= 0) | |
| return f; | |
| if (f < frames) | |
| frames = f; | |
| } | |
| return frames; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_multi_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| snd_pcm_uframes_t pos[multi->slaves_count]; | |
| memset(pos, 0, sizeof(pos)); | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_t *slave_i = multi->slaves[i].pcm; | |
| snd_pcm_sframes_t f = snd_pcm_rewind(slave_i, frames); | |
| if (f < 0) | |
| return f; | |
| pos[i] = f; | |
| frames = f; | |
| } | |
| /* Realign the pointers */ | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_t *slave_i = multi->slaves[i].pcm; | |
| snd_pcm_uframes_t f = pos[i] - frames; | |
| snd_pcm_sframes_t result; | |
| if (f > 0) { | |
| result = INTERNAL(snd_pcm_forward)(slave_i, f); | |
| if (result < 0) | |
| return result; | |
| if ((snd_pcm_uframes_t)result != f) | |
| return -EIO; | |
| } | |
| } | |
| snd_pcm_mmap_appl_backward(pcm, frames); | |
| return frames; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_multi_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| snd_pcm_uframes_t pos[multi->slaves_count]; | |
| memset(pos, 0, sizeof(pos)); | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_t *slave_i = multi->slaves[i].pcm; | |
| snd_pcm_sframes_t f = INTERNAL(snd_pcm_forward)(slave_i, frames); | |
| if (f < 0) | |
| return f; | |
| pos[i] = f; | |
| frames = f; | |
| } | |
| /* Realign the pointers */ | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_t *slave_i = multi->slaves[i].pcm; | |
| snd_pcm_uframes_t f = pos[i] - frames; | |
| snd_pcm_sframes_t result; | |
| if (f > 0) { | |
| result = snd_pcm_rewind(slave_i, f); | |
| if (result < 0) | |
| return result; | |
| if ((snd_pcm_uframes_t)result != f) | |
| return -EIO; | |
| } | |
| } | |
| snd_pcm_mmap_appl_forward(pcm, frames); | |
| return frames; | |
| } | |
| static int snd_pcm_multi_resume(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| int err = 0; | |
| unsigned int i; | |
| if (multi->slaves[0].linked) | |
| return snd_pcm_resume(multi->slaves[0].linked); | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| if (multi->slaves[i].linked) | |
| continue; | |
| err = snd_pcm_resume(multi->slaves[i].pcm); | |
| if (err < 0) | |
| return err; | |
| } | |
| return err; | |
| } | |
| /* if a multi plugin instance is linked as slaves, every slave PCMs | |
| * including the first one has to be relinked to the given master. | |
| */ | |
| static int snd_pcm_multi_link_slaves(snd_pcm_t *pcm, snd_pcm_t *master) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| int err; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| snd_pcm_unlink(multi->slaves[i].pcm); | |
| multi->slaves[i].linked = NULL; | |
| err = snd_pcm_link(master, multi->slaves[i].pcm); | |
| if (err < 0) { | |
| reset_links(multi); | |
| return err; | |
| } | |
| multi->slaves[i].linked = master; | |
| } | |
| return 0; | |
| } | |
| /* linking to a multi as a master is easy - simply link to the first | |
| * slave element as its own slaves are already linked. | |
| */ | |
| static int snd_pcm_multi_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2) | |
| { | |
| snd_pcm_multi_t *multi = pcm1->private_data; | |
| snd_pcm_t *main_pcm = multi->slaves[0].pcm; | |
| if (main_pcm->fast_ops->link) | |
| return main_pcm->fast_ops->link(main_pcm->fast_op_arg, pcm2); | |
| return -ENOSYS; | |
| } | |
| static int snd_pcm_multi_unlink(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| if (multi->slaves[i].linked) | |
| snd_pcm_unlink(multi->slaves[i].linked); | |
| multi->slaves[0].linked = NULL; | |
| } | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_multi_mmap_commit(snd_pcm_t *pcm, | |
| snd_pcm_uframes_t offset, | |
| snd_pcm_uframes_t size) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_t *slave; | |
| unsigned int i; | |
| snd_pcm_sframes_t result; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| slave = multi->slaves[i].pcm; | |
| result = snd_pcm_mmap_commit(slave, offset, size); | |
| if (result < 0) | |
| return result; | |
| if ((snd_pcm_uframes_t)result != size) | |
| return -EIO; | |
| } | |
| snd_pcm_mmap_appl_forward(pcm, size); | |
| return size; | |
| } | |
| static int snd_pcm_multi_munmap(snd_pcm_t *pcm) | |
| { | |
| free(pcm->mmap_channels); | |
| free(pcm->running_areas); | |
| pcm->mmap_channels = NULL; | |
| pcm->running_areas = NULL; | |
| return 0; | |
| } | |
| static int snd_pcm_multi_mmap(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int c; | |
| pcm->mmap_channels = calloc(pcm->channels, | |
| sizeof(pcm->mmap_channels[0])); | |
| pcm->running_areas = calloc(pcm->channels, | |
| sizeof(pcm->running_areas[0])); | |
| if (!pcm->mmap_channels || !pcm->running_areas) { | |
| snd_pcm_multi_munmap(pcm); | |
| return -ENOMEM; | |
| } | |
| /* Copy the slave mmapped buffer data */ | |
| for (c = 0; c < pcm->channels; c++) { | |
| snd_pcm_multi_channel_t *chan = &multi->channels[c]; | |
| snd_pcm_t *slave; | |
| if (chan->slave_idx < 0) { | |
| snd_pcm_multi_munmap(pcm); | |
| return -ENXIO; | |
| } | |
| slave = multi->slaves[chan->slave_idx].pcm; | |
| pcm->mmap_channels[c] = | |
| slave->mmap_channels[chan->slave_channel]; | |
| pcm->mmap_channels[c].channel = c; | |
| pcm->running_areas[c] = | |
| slave->running_areas[chan->slave_channel]; | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_multi_may_wait_for_avail_min(snd_pcm_t *pcm, snd_pcm_uframes_t avail) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int i; | |
| for (i = 0; i < multi->slaves_count; ++i) { | |
| if (snd_pcm_may_wait_for_avail_min(multi->slaves[i].pcm, avail)) | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static snd_pcm_chmap_query_t **snd_pcm_multi_query_chmaps(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_chmap_query_t **slave_maps[multi->slaves_count]; | |
| snd_pcm_chmap_query_t **maps; | |
| unsigned int i; | |
| int err = -ENOMEM; | |
| memset(slave_maps, 0, sizeof(slave_maps)); | |
| maps = calloc(2, sizeof(*maps)); | |
| if (!maps) | |
| return NULL; | |
| maps[0] = calloc(multi->channels_count + 2, sizeof(int *)); | |
| if (!maps[0]) | |
| goto error; | |
| maps[0]->type = SND_CHMAP_TYPE_FIXED; | |
| maps[0]->map.channels = multi->channels_count; | |
| for (i = 0; i < multi->slaves_count; i++) { | |
| slave_maps[i] = snd_pcm_query_chmaps(multi->slaves[i].pcm); | |
| if (!slave_maps[i]) | |
| goto error; | |
| } | |
| for (i = 0; i < multi->channels_count; i++) { | |
| snd_pcm_multi_channel_t *bind = &multi->channels[i]; | |
| unsigned int slave_channels = | |
| multi->slaves[bind->slave_idx].channels_count; | |
| snd_pcm_chmap_query_t **p; | |
| for (p = slave_maps[bind->slave_idx]; *p; p++) { | |
| if ((*p)->map.channels == slave_channels) { | |
| maps[0]->map.pos[i] = | |
| (*p)->map.pos[bind->slave_channel]; | |
| break; | |
| } | |
| } | |
| } | |
| err = 0; | |
| error: | |
| for (i = 0; i < multi->slaves_count; i++) { | |
| if (slave_maps[i]) | |
| snd_pcm_free_chmaps(slave_maps[i]); | |
| } | |
| if (err) { | |
| snd_pcm_free_chmaps(maps); | |
| return NULL; | |
| } | |
| return maps; | |
| } | |
| static snd_pcm_chmap_t *snd_pcm_multi_get_chmap(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_chmap_t *map; | |
| snd_pcm_chmap_t *slave_maps[multi->slaves_count]; | |
| unsigned int i; | |
| int err = -ENOMEM; | |
| memset(slave_maps, 0, sizeof(slave_maps)); | |
| map = calloc(multi->channels_count + 1, sizeof(int)); | |
| if (!map) | |
| return NULL; | |
| for (i = 0; i < multi->slaves_count; i++) { | |
| slave_maps[i] = snd_pcm_get_chmap(multi->slaves[i].pcm); | |
| if (!slave_maps[i]) | |
| goto error; | |
| } | |
| map->channels = multi->channels_count; | |
| for (i = 0; i < multi->channels_count; i++) { | |
| snd_pcm_multi_channel_t *bind = &multi->channels[i]; | |
| map->pos[i] = slave_maps[bind->slave_idx]->pos[bind->slave_channel]; | |
| } | |
| err = 0; | |
| error: | |
| for (i = 0; i < multi->slaves_count; i++) | |
| free(slave_maps[i]); | |
| if (err) { | |
| free(map); | |
| return NULL; | |
| } | |
| return map; | |
| } | |
| static int snd_pcm_multi_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| snd_pcm_chmap_t *slave_maps[multi->slaves_count]; | |
| unsigned int i; | |
| int err = 0; | |
| if (map->channels != multi->channels_count) | |
| return -EINVAL; | |
| for (i = 0; i < multi->slaves_count; i++) { | |
| slave_maps[i] = calloc(multi->slaves[i].channels_count + 1, | |
| sizeof(int)); | |
| if (!slave_maps[i]) { | |
| for (i++; i < multi->slaves_count; i++) | |
| slave_maps[i] = NULL; | |
| err = -ENOMEM; | |
| goto error; | |
| } | |
| } | |
| for (i = 0; i < multi->channels_count; i++) { | |
| snd_pcm_multi_channel_t *bind = &multi->channels[i]; | |
| slave_maps[bind->slave_idx]->pos[bind->slave_channel] = | |
| map->pos[i]; | |
| } | |
| for (i = 0; i < multi->slaves_count; i++) { | |
| err = snd_pcm_set_chmap(multi->slaves[i].pcm, slave_maps[i]); | |
| if (err < 0) | |
| goto error; | |
| } | |
| error: | |
| for (i = 0; i < multi->slaves_count; i++) | |
| free(slave_maps[i]); | |
| return err; | |
| } | |
| static void snd_pcm_multi_dump(snd_pcm_t *pcm, snd_output_t *out) | |
| { | |
| snd_pcm_multi_t *multi = pcm->private_data; | |
| unsigned int k; | |
| snd_output_printf(out, "Multi PCM\n"); | |
| snd_output_printf(out, " Channel bindings:\n"); | |
| for (k = 0; k < multi->channels_count; ++k) { | |
| snd_pcm_multi_channel_t *c = &multi->channels[k]; | |
| if (c->slave_idx < 0) | |
| continue; | |
| snd_output_printf(out, " %d: slave %d, channel %d\n", | |
| k, c->slave_idx, c->slave_channel); | |
| } | |
| if (pcm->setup) { | |
| snd_output_printf(out, "Its setup is:\n"); | |
| snd_pcm_dump_setup(pcm, out); | |
| } | |
| for (k = 0; k < multi->slaves_count; ++k) { | |
| snd_output_printf(out, "Slave #%d: ", k); | |
| snd_pcm_dump(multi->slaves[k].pcm, out); | |
| } | |
| } | |
| static const snd_pcm_ops_t snd_pcm_multi_ops = { | |
| .close = snd_pcm_multi_close, | |
| .info = snd_pcm_multi_info, | |
| .hw_refine = snd_pcm_multi_hw_refine, | |
| .hw_params = snd_pcm_multi_hw_params, | |
| .hw_free = snd_pcm_multi_hw_free, | |
| .sw_params = snd_pcm_multi_sw_params, | |
| .channel_info = snd_pcm_multi_channel_info, | |
| .dump = snd_pcm_multi_dump, | |
| .nonblock = snd_pcm_multi_nonblock, | |
| .async = snd_pcm_multi_async, | |
| .mmap = snd_pcm_multi_mmap, | |
| .munmap = snd_pcm_multi_munmap, | |
| .query_chmaps = snd_pcm_multi_query_chmaps, | |
| .get_chmap = snd_pcm_multi_get_chmap, | |
| .set_chmap = snd_pcm_multi_set_chmap, | |
| }; | |
| static const snd_pcm_fast_ops_t snd_pcm_multi_fast_ops = { | |
| .status = snd_pcm_multi_status, | |
| .state = snd_pcm_multi_state, | |
| .hwsync = snd_pcm_multi_hwsync, | |
| .delay = snd_pcm_multi_delay, | |
| .prepare = snd_pcm_multi_prepare, | |
| .reset = snd_pcm_multi_reset, | |
| .start = snd_pcm_multi_start, | |
| .drop = snd_pcm_multi_drop, | |
| .drain = snd_pcm_multi_drain, | |
| .pause = snd_pcm_multi_pause, | |
| .writei = snd_pcm_mmap_writei, | |
| .writen = snd_pcm_mmap_writen, | |
| .readi = snd_pcm_mmap_readi, | |
| .readn = snd_pcm_mmap_readn, | |
| .rewindable = snd_pcm_multi_rewindable, | |
| .rewind = snd_pcm_multi_rewind, | |
| .forwardable = snd_pcm_multi_forwardable, | |
| .forward = snd_pcm_multi_forward, | |
| .resume = snd_pcm_multi_resume, | |
| .link = snd_pcm_multi_link, | |
| .link_slaves = snd_pcm_multi_link_slaves, | |
| .unlink = snd_pcm_multi_unlink, | |
| .avail_update = snd_pcm_multi_avail_update, | |
| .mmap_commit = snd_pcm_multi_mmap_commit, | |
| .htimestamp = snd_pcm_multi_htimestamp, | |
| .poll_descriptors_count = snd_pcm_multi_poll_descriptors_count, | |
| .poll_descriptors = snd_pcm_multi_poll_descriptors, | |
| .poll_revents = snd_pcm_multi_poll_revents, | |
| .may_wait_for_avail_min = snd_pcm_multi_may_wait_for_avail_min, | |
| }; | |
| /** | |
| * \brief Creates a new Multi PCM | |
| * \param pcmp Returns created PCM handle | |
| * \param name Name of PCM | |
| * \param slaves_count Count of slaves | |
| * \param master_slave Master slave number | |
| * \param slaves_pcm Array with slave PCMs | |
| * \param schannels_count Array with slave channel counts | |
| * \param channels_count Count of channels | |
| * \param sidxs Array with channels indexes to slaves | |
| * \param schannels Array with slave channels | |
| * \param close_slaves When set, the slave PCM handle is closed | |
| * \retval zero on success otherwise a negative error code | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| int snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name, | |
| unsigned int slaves_count, unsigned int master_slave, | |
| snd_pcm_t **slaves_pcm, unsigned int *schannels_count, | |
| unsigned int channels_count, | |
| int *sidxs, unsigned int *schannels, | |
| int close_slaves) | |
| { | |
| snd_pcm_t *pcm; | |
| snd_pcm_multi_t *multi; | |
| unsigned int i; | |
| snd_pcm_stream_t stream; | |
| int err; | |
| assert(pcmp); | |
| assert(slaves_count > 0 && slaves_pcm && schannels_count); | |
| assert(channels_count > 0 && sidxs && schannels); | |
| assert(master_slave < slaves_count); | |
| multi = calloc(1, sizeof(snd_pcm_multi_t)); | |
| if (!multi) { | |
| return -ENOMEM; | |
| } | |
| stream = slaves_pcm[0]->stream; | |
| multi->slaves_count = slaves_count; | |
| multi->master_slave = master_slave; | |
| multi->slaves = calloc(slaves_count, sizeof(*multi->slaves)); | |
| if (!multi->slaves) { | |
| free(multi); | |
| return -ENOMEM; | |
| } | |
| multi->channels_count = channels_count; | |
| multi->channels = calloc(channels_count, sizeof(*multi->channels)); | |
| if (!multi->channels) { | |
| free(multi->slaves); | |
| free(multi); | |
| return -ENOMEM; | |
| } | |
| for (i = 0; i < slaves_count; ++i) { | |
| snd_pcm_multi_slave_t *slave = &multi->slaves[i]; | |
| assert(slaves_pcm[i]->stream == stream); | |
| slave->pcm = slaves_pcm[i]; | |
| slave->channels_count = schannels_count[i]; | |
| slave->close_slave = close_slaves; | |
| } | |
| for (i = 0; i < channels_count; ++i) { | |
| snd_pcm_multi_channel_t *bind = &multi->channels[i]; | |
| assert(sidxs[i] < (int)slaves_count); | |
| assert(schannels[i] < schannels_count[sidxs[i]]); | |
| bind->slave_idx = sidxs[i]; | |
| bind->slave_channel = schannels[i]; | |
| if (sidxs[i] < 0) | |
| continue; | |
| } | |
| multi->channels_count = channels_count; | |
| err = snd_pcm_new(&pcm, SND_PCM_TYPE_MULTI, name, stream, | |
| multi->slaves[0].pcm->mode); | |
| if (err < 0) { | |
| free(multi->slaves); | |
| free(multi->channels); | |
| free(multi); | |
| return err; | |
| } | |
| pcm->mmap_rw = 1; | |
| pcm->mmap_shadow = 1; /* has own mmap method */ | |
| pcm->ops = &snd_pcm_multi_ops; | |
| pcm->fast_ops = &snd_pcm_multi_fast_ops; | |
| pcm->private_data = multi; | |
| pcm->poll_fd = multi->slaves[master_slave].pcm->poll_fd; | |
| pcm->poll_events = multi->slaves[master_slave].pcm->poll_events; | |
| pcm->tstamp_type = multi->slaves[master_slave].pcm->tstamp_type; | |
| snd_pcm_set_hw_ptr(pcm, &multi->hw_ptr, -1, 0); | |
| snd_pcm_set_appl_ptr(pcm, &multi->appl_ptr, -1, 0); | |
| *pcmp = pcm; | |
| return 0; | |
| } | |
| /*! \page pcm_plugins | |
| \section pcm_plugins_multi Plugin: Multiple streams to One | |
| This plugin converts multiple streams to one. | |
| \code | |
| pcm.name { | |
| type multi # Multiple streams conversion PCM | |
| slaves { # Slaves definition | |
| ID STR # Slave PCM name | |
| # or | |
| ID { | |
| pcm STR # Slave PCM name | |
| # or | |
| pcm { } # Slave PCM definition | |
| channels INT # Slave channels | |
| } | |
| } | |
| bindings { # Bindings table | |
| N { | |
| slave STR # Slave key | |
| channel INT # Slave channel | |
| } | |
| } | |
| [master INT] # Define the master slave | |
| } | |
| \endcode | |
| For example, to bind two PCM streams with two-channel stereo (hw:0,0 and | |
| hw:0,1) as one 4-channel stereo PCM stream, define like this: | |
| \code | |
| pcm.quad { | |
| type multi | |
| slaves.a.pcm "hw:0,0" | |
| slaves.a.channels 2 | |
| slaves.b.pcm "hw:0,1" | |
| slaves.b.channels 2 | |
| bindings.0.slave a | |
| bindings.0.channel 0 | |
| bindings.1.slave a | |
| bindings.1.channel 1 | |
| bindings.2.slave b | |
| bindings.2.channel 0 | |
| bindings.3.slave b | |
| bindings.3.channel 1 | |
| } | |
| \endcode | |
| Note that the resultant pcm "quad" is not in the interleaved format | |
| but in the "complex" format. Hence, it's not accessible by applications | |
| which can handle only the interleaved (or the non-interleaved) format. | |
| In such a case, wrap this PCM with \ref pcm_plugins_route "route" or | |
| \ref pcm_plugins_plug "plug" plugin. | |
| \code | |
| pcm.quad2 { | |
| type route | |
| slave.pcm "quad" | |
| ttable.0.0 1 | |
| ttable.1.1 1 | |
| ttable.2.2 1 | |
| ttable.3.3 1 | |
| } | |
| \endcode | |
| \subsection pcm_plugins_multi_funcref Function reference | |
| <UL> | |
| <LI>snd_pcm_multi_open() | |
| <LI>_snd_pcm_multi_open() | |
| </UL> | |
| */ | |
| /** | |
| * \brief Creates a new Multi PCM | |
| * \param pcmp Returns created PCM handle | |
| * \param name Name of PCM | |
| * \param root Root configuration node | |
| * \param conf Configuration node with Multi PCM description | |
| * \param stream Stream type | |
| * \param mode Stream mode | |
| * \retval zero on success otherwise a negative error code | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| int _snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name, | |
| snd_config_t *root, snd_config_t *conf, | |
| snd_pcm_stream_t stream, int mode) | |
| { | |
| snd_config_iterator_t i, inext, j, jnext; | |
| snd_config_t *slaves = NULL; | |
| snd_config_t *bindings = NULL; | |
| int err; | |
| unsigned int idx; | |
| const char **slaves_id = NULL; | |
| snd_config_t **slaves_conf = NULL; | |
| snd_pcm_t **slaves_pcm = NULL; | |
| unsigned int *slaves_channels = NULL; | |
| int *channels_sidx = NULL; | |
| unsigned int *channels_schannel = NULL; | |
| unsigned int slaves_count = 0; | |
| long master_slave = 0; | |
| unsigned int channels_count = 0; | |
| snd_config_for_each(i, inext, conf) { | |
| snd_config_t *n = snd_config_iterator_entry(i); | |
| const char *id; | |
| if (snd_config_get_id(n, &id) < 0) | |
| continue; | |
| if (snd_pcm_conf_generic_id(id)) | |
| continue; | |
| if (strcmp(id, "slaves") == 0) { | |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { | |
| SNDERR("Invalid type for %s", id); | |
| return -EINVAL; | |
| } | |
| slaves = n; | |
| continue; | |
| } | |
| if (strcmp(id, "bindings") == 0) { | |
| if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { | |
| SNDERR("Invalid type for %s", id); | |
| return -EINVAL; | |
| } | |
| bindings = n; | |
| continue; | |
| } | |
| if (strcmp(id, "master") == 0) { | |
| if (snd_config_get_integer(n, &master_slave) < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| return -EINVAL; | |
| } | |
| continue; | |
| } | |
| SNDERR("Unknown field %s", id); | |
| return -EINVAL; | |
| } | |
| if (!slaves) { | |
| SNDERR("slaves is not defined"); | |
| return -EINVAL; | |
| } | |
| if (!bindings) { | |
| SNDERR("bindings is not defined"); | |
| return -EINVAL; | |
| } | |
| snd_config_for_each(i, inext, slaves) { | |
| ++slaves_count; | |
| } | |
| if (master_slave < 0 || master_slave >= (long)slaves_count) { | |
| SNDERR("Master slave is out of range (0-%u)", slaves_count-1); | |
| return -EINVAL; | |
| } | |
| snd_config_for_each(i, inext, bindings) { | |
| long cchannel; | |
| snd_config_t *m = snd_config_iterator_entry(i); | |
| const char *id; | |
| if (snd_config_get_id(m, &id) < 0) | |
| continue; | |
| err = safe_strtol(id, &cchannel); | |
| if (err < 0 || cchannel < 0) { | |
| SNDERR("Invalid channel number: %s", id); | |
| return -EINVAL; | |
| } | |
| if ((unsigned long)cchannel >= channels_count) | |
| channels_count = cchannel + 1; | |
| } | |
| if (channels_count == 0) { | |
| SNDERR("No channels defined"); | |
| return -EINVAL; | |
| } | |
| slaves_id = calloc(slaves_count, sizeof(*slaves_id)); | |
| slaves_conf = calloc(slaves_count, sizeof(*slaves_conf)); | |
| slaves_pcm = calloc(slaves_count, sizeof(*slaves_pcm)); | |
| slaves_channels = calloc(slaves_count, sizeof(*slaves_channels)); | |
| channels_sidx = calloc(channels_count, sizeof(*channels_sidx)); | |
| channels_schannel = calloc(channels_count, sizeof(*channels_schannel)); | |
| if (!slaves_id || !slaves_conf || !slaves_pcm || !slaves_channels || | |
| !channels_sidx || !channels_schannel) { | |
| err = -ENOMEM; | |
| goto _free; | |
| } | |
| for (idx = 0; idx < channels_count; ++idx) | |
| channels_sidx[idx] = -1; | |
| idx = 0; | |
| snd_config_for_each(i, inext, slaves) { | |
| snd_config_t *m = snd_config_iterator_entry(i); | |
| const char *id; | |
| int channels; | |
| if (snd_config_get_id(m, &id) < 0) | |
| continue; | |
| slaves_id[idx] = id; | |
| err = snd_pcm_slave_conf(root, m, &slaves_conf[idx], 1, | |
| SND_PCM_HW_PARAM_CHANNELS, SCONF_MANDATORY, &channels); | |
| if (err < 0) | |
| goto _free; | |
| slaves_channels[idx] = channels; | |
| ++idx; | |
| } | |
| snd_config_for_each(i, inext, bindings) { | |
| snd_config_t *m = snd_config_iterator_entry(i); | |
| long cchannel = -1; | |
| long schannel = -1; | |
| int slave = -1; | |
| long val; | |
| const char *str; | |
| const char *id; | |
| if (snd_config_get_id(m, &id) < 0) | |
| continue; | |
| err = safe_strtol(id, &cchannel); | |
| if (err < 0 || cchannel < 0) { | |
| SNDERR("Invalid channel number: %s", id); | |
| err = -EINVAL; | |
| goto _free; | |
| } | |
| snd_config_for_each(j, jnext, m) { | |
| snd_config_t *n = snd_config_iterator_entry(j); | |
| const char *id; | |
| if (snd_config_get_id(n, &id) < 0) | |
| continue; | |
| if (strcmp(id, "comment") == 0) | |
| continue; | |
| if (strcmp(id, "slave") == 0) { | |
| char buf[32]; | |
| unsigned int k; | |
| err = snd_config_get_string(n, &str); | |
| if (err < 0) { | |
| err = snd_config_get_integer(n, &val); | |
| if (err < 0) { | |
| SNDERR("Invalid value for %s", id); | |
| goto _free; | |
| } | |
| sprintf(buf, "%ld", val); | |
| str = buf; | |
| } | |
| for (k = 0; k < slaves_count; ++k) { | |
| if (strcmp(slaves_id[k], str) == 0) | |
| slave = k; | |
| } | |
| continue; | |
| } | |
| if (strcmp(id, "channel") == 0) { | |
| err = snd_config_get_integer(n, &schannel); | |
| if (err < 0) { | |
| SNDERR("Invalid type for %s", id); | |
| goto _free; | |
| } | |
| continue; | |
| } | |
| SNDERR("Unknown field %s", id); | |
| err = -EINVAL; | |
| goto _free; | |
| } | |
| if (slave < 0 || (unsigned int)slave >= slaves_count) { | |
| SNDERR("Invalid or missing sidx for channel %s", id); | |
| err = -EINVAL; | |
| goto _free; | |
| } | |
| if (schannel < 0 || | |
| (unsigned int) schannel >= slaves_channels[slave]) { | |
| SNDERR("Invalid or missing schannel for channel %s", id); | |
| err = -EINVAL; | |
| goto _free; | |
| } | |
| channels_sidx[cchannel] = slave; | |
| channels_schannel[cchannel] = schannel; | |
| } | |
| for (idx = 0; idx < slaves_count; ++idx) { | |
| err = snd_pcm_open_slave(&slaves_pcm[idx], root, | |
| slaves_conf[idx], stream, mode, | |
| conf); | |
| if (err < 0) | |
| goto _free; | |
| snd_config_delete(slaves_conf[idx]); | |
| slaves_conf[idx] = NULL; | |
| } | |
| err = snd_pcm_multi_open(pcmp, name, slaves_count, master_slave, | |
| slaves_pcm, slaves_channels, | |
| channels_count, | |
| channels_sidx, channels_schannel, | |
| 1); | |
| _free: | |
| if (err < 0) { | |
| for (idx = 0; idx < slaves_count; ++idx) { | |
| if (slaves_pcm[idx]) | |
| snd_pcm_close(slaves_pcm[idx]); | |
| } | |
| } | |
| if (slaves_conf) { | |
| for (idx = 0; idx < slaves_count; ++idx) { | |
| if (slaves_conf[idx]) | |
| snd_config_delete(slaves_conf[idx]); | |
| } | |
| free(slaves_conf); | |
| } | |
| free(slaves_pcm); | |
| free(slaves_channels); | |
| free(channels_sidx); | |
| free(channels_schannel); | |
| free(slaves_id); | |
| return err; | |
| } | |
| #ifndef DOC_HIDDEN | |
| SND_DLSYM_BUILD_VERSION(_snd_pcm_multi_open, SND_PCM_DLSYM_VERSION); | |
| #endif |