| /** | |
| * \file pcm/pcm_extplug.c | |
| * \ingroup Plugin_SDK | |
| * \brief External Filter Plugin SDK | |
| * \author Takashi Iwai <tiwai@suse.de> | |
| * \date 2005 | |
| */ | |
| /* | |
| * PCM - External Filter Plugin SDK | |
| * Copyright (c) 2005 by Takashi Iwai <tiwai@suse.de> | |
| * | |
| * | |
| * This library is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as | |
| * published by the Free Software Foundation; either version 2.1 of | |
| * the License, or (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #include "pcm_local.h" | |
| #include "pcm_plugin.h" | |
| #include "pcm_extplug.h" | |
| #include "pcm_ext_parm.h" | |
| #ifndef PIC | |
| /* entry for static linking */ | |
| const char *_snd_module_pcm_extplug = ""; | |
| #endif | |
| #ifndef DOC_HIDDEN | |
| typedef struct snd_pcm_extplug_priv { | |
| snd_pcm_plugin_t plug; | |
| snd_pcm_extplug_t *data; | |
| struct snd_ext_parm params[SND_PCM_EXTPLUG_HW_PARAMS]; | |
| struct snd_ext_parm sparams[SND_PCM_EXTPLUG_HW_PARAMS]; | |
| } extplug_priv_t; | |
| static const int hw_params_type[SND_PCM_EXTPLUG_HW_PARAMS] = { | |
| [SND_PCM_EXTPLUG_HW_FORMAT] = SND_PCM_HW_PARAM_FORMAT, | |
| [SND_PCM_EXTPLUG_HW_CHANNELS] = SND_PCM_HW_PARAM_CHANNELS | |
| }; | |
| #define is_mask_type(i) (hw_params_type[i] < SND_PCM_HW_PARAM_FIRST_INTERVAL) | |
| static const unsigned int excl_parbits[SND_PCM_EXTPLUG_HW_PARAMS] = { | |
| [SND_PCM_EXTPLUG_HW_FORMAT] = (SND_PCM_HW_PARBIT_FORMAT| | |
| SND_PCM_HW_PARBIT_SUBFORMAT | | |
| SND_PCM_HW_PARBIT_SAMPLE_BITS), | |
| [SND_PCM_EXTPLUG_HW_CHANNELS] = (SND_PCM_HW_PARBIT_CHANNELS| | |
| SND_PCM_HW_PARBIT_FRAME_BITS), | |
| }; | |
| /* | |
| * set min/max values for the given parameter | |
| */ | |
| int snd_ext_parm_set_minmax(struct snd_ext_parm *parm, unsigned int min, unsigned int max) | |
| { | |
| parm->num_list = 0; | |
| free(parm->list); | |
| parm->list = NULL; | |
| parm->min = min; | |
| parm->max = max; | |
| parm->active = 1; | |
| return 0; | |
| } | |
| /* | |
| * set the list of available values for the given parameter | |
| */ | |
| static int val_compar(const void *ap, const void *bp) | |
| { | |
| return *(const unsigned int *)ap - *(const unsigned int *)bp; | |
| } | |
| int snd_ext_parm_set_list(struct snd_ext_parm *parm, unsigned int num_list, const unsigned int *list) | |
| { | |
| unsigned int *new_list; | |
| new_list = malloc(sizeof(*new_list) * num_list); | |
| if (new_list == NULL) | |
| return -ENOMEM; | |
| memcpy(new_list, list, sizeof(*new_list) * num_list); | |
| qsort(new_list, num_list, sizeof(*new_list), val_compar); | |
| free(parm->list); | |
| parm->num_list = num_list; | |
| parm->list = new_list; | |
| parm->active = 1; | |
| return 0; | |
| } | |
| void snd_ext_parm_clear(struct snd_ext_parm *parm) | |
| { | |
| free(parm->list); | |
| memset(parm, 0, sizeof(*parm)); | |
| } | |
| /* | |
| * limit the interval to the given list | |
| */ | |
| int snd_interval_list(snd_interval_t *ival, int num_list, unsigned int *list) | |
| { | |
| int imin, imax; | |
| int changed = 0; | |
| if (snd_interval_empty(ival)) | |
| return -ENOENT; | |
| for (imin = 0; imin < num_list; imin++) { | |
| if (ival->min == list[imin] && ! ival->openmin) | |
| break; | |
| if (ival->min <= list[imin]) { | |
| ival->min = list[imin]; | |
| ival->openmin = 0; | |
| changed = 1; | |
| break; | |
| } | |
| } | |
| if (imin >= num_list) | |
| return -EINVAL; | |
| for (imax = num_list - 1; imax >= imin; imax--) { | |
| if (ival->max == list[imax] && ! ival->openmax) | |
| break; | |
| if (ival->max >= list[imax]) { | |
| ival->max = list[imax]; | |
| ival->openmax = 0; | |
| changed = 1; | |
| break; | |
| } | |
| } | |
| if (imax < imin) | |
| return -EINVAL; | |
| return changed; | |
| } | |
| /* | |
| * refine the interval parameter | |
| */ | |
| int snd_ext_parm_interval_refine(snd_interval_t *ival, struct snd_ext_parm *parm, int type) | |
| { | |
| parm += type; | |
| if (! parm->active) | |
| return 0; | |
| ival->integer |= parm->integer; | |
| if (parm->num_list) { | |
| return snd_interval_list(ival, parm->num_list, parm->list); | |
| } else if (parm->min || parm->max) { | |
| snd_interval_t t; | |
| memset(&t, 0, sizeof(t)); | |
| snd_interval_set_minmax(&t, parm->min, parm->max); | |
| t.integer = ival->integer; | |
| return snd_interval_refine(ival, &t); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * refine the mask parameter | |
| */ | |
| int snd_ext_parm_mask_refine(snd_mask_t *mask, struct snd_ext_parm *parm, int type) | |
| { | |
| snd_mask_t bits; | |
| unsigned int i; | |
| parm += type; | |
| if (!parm->active) | |
| return 0; | |
| memset(&bits, 0, sizeof(bits)); | |
| for (i = 0; i < parm->num_list; i++) | |
| bits.bits[parm->list[i] / 32] |= 1U << (parm->list[i] % 32); | |
| return snd_mask_refine(mask, &bits); | |
| } | |
| /* | |
| * hw_refine callback | |
| */ | |
| static int extplug_hw_refine(snd_pcm_hw_params_t *hw_params, | |
| struct snd_ext_parm *parm) | |
| { | |
| int i, err, change = 0; | |
| for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) { | |
| int type = hw_params_type[i]; | |
| if (is_mask_type(i)) | |
| err = snd_ext_parm_mask_refine(hw_param_mask(hw_params, type), | |
| parm, i); | |
| else | |
| err = snd_ext_parm_interval_refine(hw_param_interval(hw_params, type), | |
| parm, i); | |
| if (err < 0) | |
| return err; | |
| change |= err; | |
| } | |
| return change; | |
| } | |
| static int snd_pcm_extplug_hw_refine_cprepare(snd_pcm_t *pcm, | |
| snd_pcm_hw_params_t *params) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| int err; | |
| snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM }; | |
| err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS, | |
| &access_mask); | |
| if (err < 0) | |
| return err; | |
| err = extplug_hw_refine(params, ext->params); | |
| if (err < 0) | |
| return err; | |
| params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID); | |
| return 0; | |
| } | |
| static int snd_pcm_extplug_hw_refine_sprepare(snd_pcm_t *pcm, | |
| snd_pcm_hw_params_t *sparams) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| 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); | |
| extplug_hw_refine(sparams, ext->sparams); | |
| return 0; | |
| } | |
| static unsigned int get_links(struct snd_ext_parm *params) | |
| { | |
| int i; | |
| unsigned int links = (SND_PCM_HW_PARBIT_FORMAT | | |
| SND_PCM_HW_PARBIT_SUBFORMAT | | |
| SND_PCM_HW_PARBIT_SAMPLE_BITS | | |
| SND_PCM_HW_PARBIT_CHANNELS | | |
| SND_PCM_HW_PARBIT_FRAME_BITS | | |
| SND_PCM_HW_PARBIT_RATE | | |
| SND_PCM_HW_PARBIT_PERIODS | | |
| SND_PCM_HW_PARBIT_PERIOD_SIZE | | |
| SND_PCM_HW_PARBIT_PERIOD_TIME | | |
| SND_PCM_HW_PARBIT_BUFFER_SIZE | | |
| SND_PCM_HW_PARBIT_BUFFER_TIME | | |
| SND_PCM_HW_PARBIT_TICK_TIME); | |
| for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) { | |
| if (params[i].active && !params[i].keep_link) | |
| links &= ~excl_parbits[i]; | |
| } | |
| return links; | |
| } | |
| static int snd_pcm_extplug_hw_refine_schange(snd_pcm_t *pcm, | |
| snd_pcm_hw_params_t *params, | |
| snd_pcm_hw_params_t *sparams) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| unsigned int links = get_links(ext->sparams); | |
| return _snd_pcm_hw_params_refine(sparams, links, params); | |
| } | |
| static int snd_pcm_extplug_hw_refine_cchange(snd_pcm_t *pcm, | |
| snd_pcm_hw_params_t *params, | |
| snd_pcm_hw_params_t *sparams) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| unsigned int links = get_links(ext->params); | |
| return _snd_pcm_hw_params_refine(params, links, sparams); | |
| } | |
| static int snd_pcm_extplug_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | |
| { | |
| int err = snd_pcm_hw_refine_slave(pcm, params, | |
| snd_pcm_extplug_hw_refine_cprepare, | |
| snd_pcm_extplug_hw_refine_cchange, | |
| snd_pcm_extplug_hw_refine_sprepare, | |
| snd_pcm_extplug_hw_refine_schange, | |
| snd_pcm_generic_hw_refine); | |
| return err; | |
| } | |
| /* | |
| * hw_params callback | |
| */ | |
| static int snd_pcm_extplug_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| snd_pcm_t *slave = ext->plug.gen.slave; | |
| int err = snd_pcm_hw_params_slave(pcm, params, | |
| snd_pcm_extplug_hw_refine_cchange, | |
| snd_pcm_extplug_hw_refine_sprepare, | |
| snd_pcm_extplug_hw_refine_schange, | |
| snd_pcm_generic_hw_params); | |
| if (err < 0) | |
| return err; | |
| ext->data->slave_format = slave->format; | |
| ext->data->slave_subformat = slave->subformat; | |
| ext->data->slave_channels = slave->channels; | |
| ext->data->rate = slave->rate; | |
| INTERNAL(snd_pcm_hw_params_get_format)(params, &ext->data->format); | |
| INTERNAL(snd_pcm_hw_params_get_subformat)(params, &ext->data->subformat); | |
| INTERNAL(snd_pcm_hw_params_get_channels)(params, &ext->data->channels); | |
| if (ext->data->callback->hw_params) { | |
| err = ext->data->callback->hw_params(ext->data, params); | |
| if (err < 0) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * hw_free callback | |
| */ | |
| static int snd_pcm_extplug_hw_free(snd_pcm_t *pcm) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| snd_pcm_hw_free(ext->plug.gen.slave); | |
| if (ext->data->callback->hw_free) | |
| return ext->data->callback->hw_free(ext->data); | |
| return 0; | |
| } | |
| /* | |
| * write_areas skeleton - call transfer callback | |
| */ | |
| static snd_pcm_uframes_t | |
| snd_pcm_extplug_write_areas(snd_pcm_t *pcm, | |
| const snd_pcm_channel_area_t *areas, | |
| snd_pcm_uframes_t offset, | |
| snd_pcm_uframes_t size, | |
| const snd_pcm_channel_area_t *slave_areas, | |
| snd_pcm_uframes_t slave_offset, | |
| snd_pcm_uframes_t *slave_sizep) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| if (size > *slave_sizep) | |
| size = *slave_sizep; | |
| size = ext->data->callback->transfer(ext->data, slave_areas, slave_offset, | |
| areas, offset, size); | |
| *slave_sizep = size; | |
| return size; | |
| } | |
| /* | |
| * read_areas skeleton - call transfer callback | |
| */ | |
| static snd_pcm_uframes_t | |
| snd_pcm_extplug_read_areas(snd_pcm_t *pcm, | |
| const snd_pcm_channel_area_t *areas, | |
| snd_pcm_uframes_t offset, | |
| snd_pcm_uframes_t size, | |
| const snd_pcm_channel_area_t *slave_areas, | |
| snd_pcm_uframes_t slave_offset, | |
| snd_pcm_uframes_t *slave_sizep) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| if (size > *slave_sizep) | |
| size = *slave_sizep; | |
| size = ext->data->callback->transfer(ext->data, areas, offset, | |
| slave_areas, slave_offset, size); | |
| *slave_sizep = size; | |
| return size; | |
| } | |
| /* | |
| * call init callback | |
| */ | |
| static int snd_pcm_extplug_init(snd_pcm_t *pcm) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| return ext->data->callback->init(ext->data); | |
| } | |
| /* | |
| * dump setup | |
| */ | |
| static void snd_pcm_extplug_dump(snd_pcm_t *pcm, snd_output_t *out) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| if (ext->data->callback->dump) | |
| ext->data->callback->dump(ext->data, out); | |
| else { | |
| if (ext->data->name) | |
| snd_output_printf(out, "%s\n", ext->data->name); | |
| else | |
| snd_output_printf(out, "External PCM Plugin\n"); | |
| if (pcm->setup) { | |
| snd_output_printf(out, "Its setup is:\n"); | |
| snd_pcm_dump_setup(pcm, out); | |
| } | |
| } | |
| snd_output_printf(out, "Slave: "); | |
| snd_pcm_dump(ext->plug.gen.slave, out); | |
| } | |
| static void clear_ext_params(extplug_priv_t *ext) | |
| { | |
| int i; | |
| for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) { | |
| snd_ext_parm_clear(&ext->params[i]); | |
| snd_ext_parm_clear(&ext->sparams[i]); | |
| } | |
| } | |
| static int snd_pcm_extplug_close(snd_pcm_t *pcm) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| snd_pcm_close(ext->plug.gen.slave); | |
| clear_ext_params(ext); | |
| if (ext->data->callback->close) | |
| ext->data->callback->close(ext->data); | |
| free(ext); | |
| return 0; | |
| } | |
| static snd_pcm_chmap_query_t **snd_pcm_extplug_query_chmaps(snd_pcm_t *pcm) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| if (ext->data->version >= 0x010002 && | |
| ext->data->callback->query_chmaps) | |
| return ext->data->callback->query_chmaps(ext->data); | |
| return snd_pcm_generic_query_chmaps(pcm); | |
| } | |
| static snd_pcm_chmap_t *snd_pcm_extplug_get_chmap(snd_pcm_t *pcm) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| if (ext->data->version >= 0x010002 && | |
| ext->data->callback->get_chmap) | |
| return ext->data->callback->get_chmap(ext->data); | |
| return snd_pcm_generic_get_chmap(pcm); | |
| } | |
| static int snd_pcm_extplug_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map) | |
| { | |
| extplug_priv_t *ext = pcm->private_data; | |
| if (ext->data->version >= 0x010002 && | |
| ext->data->callback->set_chmap) | |
| return ext->data->callback->set_chmap(ext->data, map); | |
| return snd_pcm_generic_set_chmap(pcm, map); | |
| } | |
| static const snd_pcm_ops_t snd_pcm_extplug_ops = { | |
| .close = snd_pcm_extplug_close, | |
| .info = snd_pcm_generic_info, | |
| .hw_refine = snd_pcm_extplug_hw_refine, | |
| .hw_params = snd_pcm_extplug_hw_params, | |
| .hw_free = snd_pcm_extplug_hw_free, | |
| .sw_params = snd_pcm_generic_sw_params, | |
| .channel_info = snd_pcm_generic_channel_info, | |
| .dump = snd_pcm_extplug_dump, | |
| .nonblock = snd_pcm_generic_nonblock, | |
| .async = snd_pcm_generic_async, | |
| .mmap = snd_pcm_generic_mmap, | |
| .munmap = snd_pcm_generic_munmap, | |
| .query_chmaps = snd_pcm_extplug_query_chmaps, | |
| .get_chmap = snd_pcm_extplug_get_chmap, | |
| .set_chmap = snd_pcm_extplug_set_chmap, | |
| }; | |
| #endif /* !DOC_HIDDEN */ | |
| /* | |
| * Exported functions | |
| */ | |
| /*! \page pcm_external_plugins PCM External Plugin SDK | |
| \section pcm_externals External Plugins | |
| The external plugins are implemented in a shared object file located | |
| at /usr/lib/alsa-lib (the exact location depends on the build option | |
| and asoundrc configuration). It has to be the file like | |
| libasound_module_pcm_MYPLUGIN.so, where MYPLUGIN corresponds to your | |
| own plugin name. | |
| The entry point of the plugin is defined via | |
| #SND_PCM_PLUGIN_DEFINE_FUNC() macro. This macro defines the function | |
| with a proper name to be referred from alsa-lib. The function takes | |
| the following 6 arguments: | |
| \code | |
| int (snd_pcm_t **pcmp, const char *name, snd_config_t *root, | |
| snd_config_t *conf, snd_pcm_stream_t stream, int mode) | |
| \endcode | |
| The first argument, pcmp, is the pointer to store the resultant PCM | |
| handle. The arguments name, root, stream and mode are the parameters | |
| to be passed to the plugin constructor. The conf is the configuration | |
| tree for the plugin. The arguments above are defined in the macro | |
| itself, so don't use variables with the same names to shadow | |
| parameters. | |
| After parsing the configuration parameters in the given conf tree, | |
| usually you will call the external plugin API function, | |
| #snd_pcm_extplug_create() or #snd_pcm_ioplug_create(), depending | |
| on the plugin type. The PCM handle must be filled *pcmp in return. | |
| Then this function must return either a value 0 when succeeded, or a | |
| negative value as the error code. | |
| Finally, add #SND_PCM_PLUGIN_SYMBOL() with the name of your | |
| plugin as the argument at the end. This defines the proper versioned | |
| symbol as the reference. | |
| The typical code would look like below: | |
| \code | |
| struct myplug_info { | |
| snd_pcm_extplug_t ext; | |
| int my_own_data; | |
| ... | |
| }; | |
| SND_PCM_PLUGIN_DEFINE_FUNC(myplug) | |
| { | |
| snd_config_iterator_t i, next; | |
| snd_config_t *slave = NULL; | |
| struct myplug_info *myplug; | |
| int err; | |
| snd_config_for_each(i, next, conf) { | |
| snd_config_t *n = snd_config_iterator_entry(i); | |
| const char *id; | |
| if (snd_config_get_id(n, &id) < 0) | |
| continue; | |
| if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0) | |
| continue; | |
| if (strcmp(id, "slave") == 0) { | |
| slave = n; | |
| continue; | |
| } | |
| if (strcmp(id, "my_own_parameter") == 0) { | |
| .... | |
| continue; | |
| } | |
| SNDERR("Unknown field %s", id); | |
| return -EINVAL; | |
| } | |
| if (! slave) { | |
| SNDERR("No slave defined for myplug"); | |
| return -EINVAL; | |
| } | |
| myplug = calloc(1, sizeof(*myplug)); | |
| if (myplug == NULL) | |
| return -ENOMEM; | |
| myplug->ext.version = SND_PCM_EXTPLUG_VERSION; | |
| myplug->ext.name = "My Own Plugin"; | |
| myplug->ext.callback = &my_own_callback; | |
| myplug->ext.private_data = myplug; | |
| .... | |
| err = snd_pcm_extplug_create(&myplug->ext, name, root, conf, stream, mode); | |
| if (err < 0) { | |
| myplug_free(myplug); | |
| return err; | |
| } | |
| *pcmp = myplug->ext.pcm; | |
| return 0; | |
| } | |
| SND_PCM_PLUGIN_SYMBOL(myplug); | |
| \endcode | |
| Read the codes in alsa-plugins package for the real examples. | |
| \section pcm_extplug External Plugin: Filter-Type Plugin | |
| The filter-type plugin is a plugin to convert the PCM signals from the input | |
| and feeds to the output. Thus, this plugin always needs a slave PCM as its output. | |
| The plugin can modify the format and the channels of the input/output PCM. | |
| It can <i>not</i> modify the sample rate (because of simplicity reason). | |
| The following fields have to be filled in extplug record before calling | |
| #snd_pcm_extplug_create() : version, name, callback. | |
| Otherfields are optional and should be initialized with zero. | |
| The constant #SND_PCM_EXTPLUG_VERSION must be passed to the version | |
| field for the version check in alsa-lib. A non-NULL ASCII string | |
| has to be passed to the name field. The callback field contains the | |
| table of callback functions for this plugin (defined as | |
| #snd_pcm_extplug_callback_t). | |
| The driver can set an arbitrary value (pointer) to private_data | |
| field to refer its own data in the callbacks. | |
| The rest fields are filled by #snd_pcm_extplug_create(). The pcm field | |
| is the resultant PCM handle. The others are the current status of the | |
| PCM. | |
| The callback functions in #snd_pcm_extplug_callback_t define the real | |
| behavior of the driver. | |
| At least, transfer callback must be given. This callback is called | |
| at each time certain size of data block is transfered to the slave | |
| PCM. Other callbacks are optional. | |
| The close callback is called when the PCM is closed. If the plugin | |
| allocates private resources, this is the place to release them | |
| again. The hw_params and hw_free callbacks are called at | |
| #snd_pcm_hw_params() and #snd_pcm_hw_free() API calls, | |
| respectively. The last, dump callback, is called for printing the | |
| information of the given plugin. | |
| The init callback is called when the PCM is at prepare state or any | |
| initialization is issued. Use this callback to reset the PCM instance | |
| to a sane initial state. | |
| The hw_params constraints can be defined via either | |
| #snd_pcm_extplug_set_param_minmax() and #snd_pcm_extplug_set_param_list() | |
| functions after calling #snd_pcm_extplug_create(). | |
| The former defines the minimal and maximal acceptable values for the | |
| given hw_params parameter (SND_PCM_EXTPLUG_HW_XXX). | |
| This function can't be used for the format parameter. The latter | |
| function specifies the available parameter values as the list. | |
| As mentioned above, the rate can't be changed. Only changeable | |
| parameters are sample format and channels. | |
| To define the constraints of the slave PCM configuration, use | |
| either #snd_pcm_extplug_set_slave_param_minmax() and | |
| #snd_pcm_extplug_set_slave_param_list(). The arguments are as same | |
| as former functions. | |
| To clear the parameter constraints, call #snd_pcm_extplug_params_reset() | |
| function. | |
| When using snd_pcm_extplug_set_param_*() or snd_pcm_extplug_set_slave_param_*() | |
| for any parameter. This parameter is no longer linked between the client and | |
| slave PCM. Therefore it could differ and the extplug has to support conversion | |
| between all valid parameter configurations. To keep the client and slave | |
| parameter linked #snd_pcm_extplug_set_param_link() can be used for the | |
| corresponding parameter. For example if the extplug does not support channel nor | |
| format conversion the supported client parameters can be limited with | |
| snd_pcm_extplug_set_param_*() and afterwards | |
| snd_pcm_extplug_set_param_link(ext, SND_PCM_EXTPLUG_HW_FORMAT, 1) and | |
| snd_pcm_extplug_set_param_link(ext, SND_PCM_EXTPLUG_HW_CHANNELS, 1) should be | |
| called to keep the client and slave parameters the same. | |
| */ | |
| /** | |
| * \brief Create an extplug instance | |
| * \param extplug the extplug handle | |
| * \param name name of the PCM | |
| * \param root configuration tree root | |
| * \param slave_conf slave configuration root | |
| * \param stream stream direction | |
| * \param mode PCM open mode | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Creates the extplug instance based on the given handle. | |
| * The slave_conf argument is mandatory, and usually taken from the config tree of the | |
| * PCM plugin as "slave" config value. | |
| * name, root, stream and mode arguments are the values used for opening the PCM. | |
| * | |
| * The callback is the mandatory field of extplug handle. At least, start, stop and | |
| * pointer callbacks must be set before calling this function. | |
| */ | |
| int snd_pcm_extplug_create(snd_pcm_extplug_t *extplug, const char *name, | |
| snd_config_t *root, snd_config_t *slave_conf, | |
| snd_pcm_stream_t stream, int mode) | |
| { | |
| extplug_priv_t *ext; | |
| int err; | |
| snd_pcm_t *spcm, *pcm; | |
| snd_config_t *sconf; | |
| assert(root); | |
| assert(extplug && extplug->callback); | |
| assert(extplug->callback->transfer); | |
| assert(slave_conf); | |
| /* We support 1.0.0 to current */ | |
| if (extplug->version < 0x010000 || | |
| extplug->version > SND_PCM_EXTPLUG_VERSION) { | |
| SNDERR("extplug: Plugin version mismatch: 0x%x", | |
| extplug->version); | |
| return -ENXIO; | |
| } | |
| err = snd_pcm_slave_conf(root, slave_conf, &sconf, 0); | |
| if (err < 0) | |
| return err; | |
| err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, NULL); | |
| snd_config_delete(sconf); | |
| if (err < 0) | |
| return err; | |
| ext = calloc(1, sizeof(*ext)); | |
| if (! ext) | |
| return -ENOMEM; | |
| ext->data = extplug; | |
| extplug->stream = stream; | |
| snd_pcm_plugin_init(&ext->plug); | |
| ext->plug.read = snd_pcm_extplug_read_areas; | |
| ext->plug.write = snd_pcm_extplug_write_areas; | |
| ext->plug.undo_read = snd_pcm_plugin_undo_read_generic; | |
| ext->plug.undo_write = snd_pcm_plugin_undo_write_generic; | |
| ext->plug.gen.slave = spcm; | |
| ext->plug.gen.close_slave = 1; | |
| if (extplug->version >= 0x010001 && extplug->callback->init) | |
| ext->plug.init = snd_pcm_extplug_init; | |
| err = snd_pcm_new(&pcm, SND_PCM_TYPE_EXTPLUG, name, stream, mode); | |
| if (err < 0) { | |
| free(ext); | |
| return err; | |
| } | |
| extplug->pcm = pcm; | |
| pcm->ops = &snd_pcm_extplug_ops; | |
| pcm->fast_ops = &snd_pcm_plugin_fast_ops; | |
| pcm->private_data = ext; | |
| pcm->poll_fd = spcm->poll_fd; | |
| pcm->poll_events = spcm->poll_events; | |
| pcm->tstamp_type = spcm->tstamp_type; | |
| snd_pcm_set_hw_ptr(pcm, &ext->plug.hw_ptr, -1, 0); | |
| snd_pcm_set_appl_ptr(pcm, &ext->plug.appl_ptr, -1, 0); | |
| return 0; | |
| } | |
| /** | |
| * \brief Delete the extplug instance | |
| * \param extplug the extplug handle to delete | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * The destructor of extplug instance. | |
| * Closes the PCM and deletes the associated resources. | |
| */ | |
| int snd_pcm_extplug_delete(snd_pcm_extplug_t *extplug) | |
| { | |
| return snd_pcm_close(extplug->pcm); | |
| } | |
| /** | |
| * \brief Reset extplug parameters | |
| * \param extplug the extplug handle | |
| * | |
| * Resets the all parameters for the given extplug handle. | |
| */ | |
| void snd_pcm_extplug_params_reset(snd_pcm_extplug_t *extplug) | |
| { | |
| extplug_priv_t *ext = extplug->pcm->private_data; | |
| clear_ext_params(ext); | |
| } | |
| /** | |
| * \brief Set slave parameter as the list | |
| * \param extplug the extplug handle | |
| * \param type parameter type | |
| * \param num_list number of available values | |
| * \param list the list of available values | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Sets the slave parameter as the list. | |
| * The available values of the given parameter type of the slave PCM is restricted | |
| * to the ones of the given list. | |
| */ | |
| int snd_pcm_extplug_set_slave_param_list(snd_pcm_extplug_t *extplug, int type, unsigned int num_list, const unsigned int *list) | |
| { | |
| extplug_priv_t *ext = extplug->pcm->private_data; | |
| if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) { | |
| SNDERR("EXTPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| return snd_ext_parm_set_list(&ext->sparams[type], num_list, list); | |
| } | |
| /** | |
| * \brief Set slave parameter as the min/max values | |
| * \param extplug the extplug handle | |
| * \param type parameter type | |
| * \param min the minimum value | |
| * \param max the maximum value | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Sets the slave parameter as the min/max values. | |
| * The available values of the given parameter type of the slave PCM is restricted | |
| * between the given minimum and maximum values. | |
| */ | |
| int snd_pcm_extplug_set_slave_param_minmax(snd_pcm_extplug_t *extplug, int type, unsigned int min, unsigned int max) | |
| { | |
| extplug_priv_t *ext = extplug->pcm->private_data; | |
| if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) { | |
| SNDERR("EXTPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| if (is_mask_type(type)) { | |
| SNDERR("EXTPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| return snd_ext_parm_set_minmax(&ext->sparams[type], min, max); | |
| } | |
| /** | |
| * \brief Set master parameter as the list | |
| * \param extplug the extplug handle | |
| * \param type parameter type | |
| * \param num_list number of available values | |
| * \param list the list of available values | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Sets the master parameter as the list. | |
| * The available values of the given parameter type of this PCM (as input) is restricted | |
| * to the ones of the given list. | |
| */ | |
| int snd_pcm_extplug_set_param_list(snd_pcm_extplug_t *extplug, int type, unsigned int num_list, const unsigned int *list) | |
| { | |
| extplug_priv_t *ext = extplug->pcm->private_data; | |
| if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) { | |
| SNDERR("EXTPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| return snd_ext_parm_set_list(&ext->params[type], num_list, list); | |
| } | |
| /** | |
| * \brief Set master parameter as the min/max values | |
| * \param extplug the extplug handle | |
| * \param type parameter type | |
| * \param min the minimum value | |
| * \param max the maximum value | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Sets the master parameter as the min/max values. | |
| * The available values of the given parameter type of this PCM (as input) is restricted | |
| * between the given minimum and maximum values. | |
| */ | |
| int snd_pcm_extplug_set_param_minmax(snd_pcm_extplug_t *extplug, int type, unsigned int min, unsigned int max) | |
| { | |
| extplug_priv_t *ext = extplug->pcm->private_data; | |
| if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) { | |
| SNDERR("EXTPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| if (is_mask_type(type)) { | |
| SNDERR("EXTPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| return snd_ext_parm_set_minmax(&ext->params[type], min, max); | |
| } | |
| /** | |
| * @brief Keep the client and slave format/channels the same if requested. This | |
| * is for example useful if this extplug does not support any channel | |
| * conversion. | |
| * @param extplug the extplug handle | |
| * @param type parameter type | |
| * @param keep_link if 1 the parameter identified by type will be kept the same | |
| * for the client and slave PCM of this extplug | |
| * @return 0 if successful, or a negative error code | |
| */ | |
| int snd_pcm_extplug_set_param_link(snd_pcm_extplug_t *extplug, int type, | |
| int keep_link) | |
| { | |
| extplug_priv_t *ext = extplug->pcm->private_data; | |
| if (type < 0 || type >= SND_PCM_EXTPLUG_HW_PARAMS) { | |
| SNDERR("EXTPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| ext->params[type].keep_link = keep_link ? 1 : 0; | |
| ext->sparams[type].keep_link = keep_link ? 1 : 0; | |
| return 0; | |
| } |