From 94c4cdcd30a874daf7abfcac1f9759748b75cb0a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 8 Feb 2005 20:57:51 +0000 Subject: [PATCH] Add external PCM plugin SDK (draft version) Added the external PCM plugin SDK (draft version). This can be used to create external PCM plugins. Example codes are found in alsa-plugins directory. --- include/Makefile.am | 2 +- include/pcm.h | 4 +- include/pcm_external.h | 16 + include/pcm_ioplug.h | 107 +++++ src/Versions | 11 + src/pcm/Makefile.am | 3 +- src/pcm/pcm_ioplug.c | 913 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1053 insertions(+), 3 deletions(-) create mode 100644 include/pcm_external.h create mode 100644 include/pcm_ioplug.h create mode 100644 src/pcm/pcm_ioplug.c diff --git a/include/Makefile.am b/include/Makefile.am index 3279673a..eeb21fee 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -10,7 +10,7 @@ alsainclude_HEADERS = asoundlib.h asoundef.h \ seq_event.h seq.h seqmid.h seq_midi_event.h \ conv.h instr.h iatomic.h \ pcm_ordinary.h mixer_ordinary.h \ - alisp.h + alisp.h pcm_external.h pcm_ioplug.h noinst_HEADERS = sys.h search.h list.h aserver.h local.h alsa-symbols.h diff --git a/include/pcm.h b/include/pcm.h index cdf5d0d1..1357359a 100644 --- a/include/pcm.h +++ b/include/pcm.h @@ -358,7 +358,9 @@ enum _snd_pcm_type { SND_PCM_TYPE_IEC958, /** Soft volume plugin */ SND_PCM_TYPE_SOFTVOL, - SND_PCM_TYPE_LAST = SND_PCM_TYPE_SOFTVOL + /** External I/O plugin */ + SND_PCM_TYPE_IOPLUG, + SND_PCM_TYPE_LAST = SND_PCM_TYPE_IOPLUG }; /** PCM type */ diff --git a/include/pcm_external.h b/include/pcm_external.h new file mode 100644 index 00000000..92d7c520 --- /dev/null +++ b/include/pcm_external.h @@ -0,0 +1,16 @@ +#ifndef __ALSA_PCM_EXTERNAL_H +#define __ALSA_PCM_EXTERNAL_H + +#include "pcm.h" + +#define SND_PCM_PLUGIN_ENTRY(name) _snd_pcm_##name##_open +#define SND_PCM_PLUGIN_SYMBOL(name) SND_DLSYM_BUILD_VERSION(SND_PCM_PLUGIN_ENTRY(name), SND_PCM_DLSYM_VERSION); + +#define SND_PCM_PLUGIN_DEFINE_FUNC(plugin) \ +int SND_PCM_PLUGIN_ENTRY(plugin) (snd_pcm_t **pcmp, const char *name,\ + snd_config_t *root, snd_config_t *conf, \ + snd_pcm_stream_t stream, int mode) + +#include "pcm_ioplug.h" + +#endif /* __ALSA_PCM_EXTERNAL_H */ diff --git a/include/pcm_ioplug.h b/include/pcm_ioplug.h new file mode 100644 index 00000000..62c064ac --- /dev/null +++ b/include/pcm_ioplug.h @@ -0,0 +1,107 @@ +/* + * ALSA external PCM plugin SDK (draft version) + * + * Copyright (c) 2005 Takashi Iwai + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __ALSA_PCM_IOPLUG_H +#define __ALSA_PCM_IOPLUG_H + +/* hw constraints */ +enum { + SND_PCM_IOPLUG_HW_ACCESS = 0, + SND_PCM_IOPLUG_HW_FORMAT, + SND_PCM_IOPLUG_HW_CHANNELS, + SND_PCM_IOPLUG_HW_RATE, + SND_PCM_IOPLUG_HW_PERIOD_BYTES, + SND_PCM_IOPLUG_HW_BUFFER_BYTES, + SND_PCM_IOPLUG_HW_PERIODS, + SND_PCM_IOPLUG_HW_PARAMS +}; + +typedef struct snd_pcm_ioplug snd_pcm_ioplug_t; +typedef struct snd_pcm_ioplug_callback snd_pcm_ioplug_callback_t; + +/* exported pcm data */ +struct snd_pcm_ioplug { + /* must be filled before calling snd_pcm_ioplug_create() */ + const char *name; + int poll_fd; + unsigned int poll_events; + unsigned int mmap_rw; /* pseudo mmap */ + const snd_pcm_ioplug_callback_t *callback; + void *private_data; + /* filled by snd_pcm_ioplug_open() */ + snd_pcm_t *pcm; + /* read-only status */ + snd_pcm_stream_t stream; + snd_pcm_state_t state; + volatile snd_pcm_uframes_t appl_ptr; + volatile snd_pcm_uframes_t hw_ptr; + int nonblock; + /* filled in hw_params */ + snd_pcm_access_t access; + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; + snd_pcm_uframes_t period_size; + snd_pcm_uframes_t buffer_size; +}; + +/* callback table */ +struct snd_pcm_ioplug_callback { + /* required */ + int (*start)(snd_pcm_ioplug_t *io); + int (*stop)(snd_pcm_ioplug_t *io); + snd_pcm_sframes_t (*pointer)(snd_pcm_ioplug_t *io); + /* optional */ + snd_pcm_sframes_t (*transfer)(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size); + int (*close)(snd_pcm_ioplug_t *io); + int (*hw_params)(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params); + int (*hw_free)(snd_pcm_ioplug_t *io); + int (*sw_params)(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params); + int (*prepare)(snd_pcm_ioplug_t *io); + int (*drain)(snd_pcm_ioplug_t *io); + int (*pause)(snd_pcm_ioplug_t *io, int enable); + int (*resume)(snd_pcm_ioplug_t *io); + int (*poll_revents)(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int nfds, unsigned short *revents); + void (*dump)(snd_pcm_ioplug_t *io, snd_output_t *out); +}; + + +int snd_pcm_ioplug_create(snd_pcm_ioplug_t *io, const char *name, + snd_pcm_stream_t stream, int mode); +int snd_pcm_ioplug_delete(snd_pcm_ioplug_t *io); + +/* update poll_fd and mmap_rw */ +int snd_pcm_ioplug_reinit_status(snd_pcm_ioplug_t *ioplug); + +/* get a mmap area (for mmap_rw only) */ +const snd_pcm_channel_area_t *snd_pcm_ioplug_mmap_areas(snd_pcm_ioplug_t *ioplug); + +/* clear hw_parameter setting */ +void snd_pcm_ioplug_params_reset(snd_pcm_ioplug_t *io); + +/* hw_parameter setting */ +int snd_pcm_ioplug_set_param_minmax(snd_pcm_ioplug_t *io, int type, unsigned int min, unsigned int max); +int snd_pcm_ioplug_set_param_list(snd_pcm_ioplug_t *io, int type, unsigned int num_list, const unsigned int *list); + +#endif /* __ALSA_PCM_IOPLUG_H */ diff --git a/src/Versions b/src/Versions index feb7e680..5ac29f29 100644 --- a/src/Versions +++ b/src/Versions @@ -170,3 +170,14 @@ ALSA_1.0.8 { snd_ctl_elem_add_iec958; snd_ctl_elem_remove; } ALSA_1.0.5; + +ALSA_1.0.9 { + global: + + snd_pcm_ioplug_create; + snd_pcm_ioplug_delete; + snd_pcm_ioplug_reinit_status; + snd_pcm_ioplug_params_reset; + snd_pcm_ioplug_set_param_minmax; + snd_pcm_ioplug_set_param_list; +} ALSA_1.0.5; diff --git a/src/pcm/Makefile.am b/src/pcm/Makefile.am index 19c08820..2ace7912 100644 --- a/src/pcm/Makefile.am +++ b/src/pcm/Makefile.am @@ -11,7 +11,8 @@ libpcm_la_SOURCES = atomic.c mask.c interval.c \ pcm_shm.c pcm_file.c pcm_null.c pcm_share.c \ pcm_meter.c pcm_hooks.c pcm_lfloat.c pcm_ladspa.c \ pcm_direct.c pcm_dmix.c pcm_dsnoop.c pcm_dshare.c \ - pcm_asym.c pcm_iec958.c pcm_softvol.c pcm_symbols.c + pcm_asym.c pcm_iec958.c pcm_softvol.c pcm_symbols.c \ + pcm_ioplug.c EXTRA_SOURCES = pcm_dmix_i386.c pcm_dmix_x86_64.c pcm_dmix_generic.c diff --git a/src/pcm/pcm_ioplug.c b/src/pcm/pcm_ioplug.c new file mode 100644 index 00000000..2a8d9fd2 --- /dev/null +++ b/src/pcm/pcm_ioplug.c @@ -0,0 +1,913 @@ +/** + * \file pcm/pcm_ioplug.c + * \ingroup Plugin_SDK + * \brief I/O Plugin SDK + * \author Takashi Iwai + * \date 2005 + */ +/* + * PCM - External I/O Plugin SDK + * Copyright (c) 2005 by Takashi Iwai + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pcm_local.h" +#include "pcm_ioplug.h" + +/* hw_params */ +struct ioplug_parm { + unsigned int min, max; + unsigned int num_list; + unsigned int *list; + unsigned int active: 1; + unsigned int integer: 1; +}; + +typedef struct snd_pcm_ioplug_priv { + snd_pcm_ioplug_t *data; + struct ioplug_parm params[SND_PCM_IOPLUG_HW_PARAMS]; + unsigned int last_hw; + snd_pcm_uframes_t avail_max; + snd_htimestamp_t trigger_tstamp; +} ioplug_priv_t; + +/* update the hw pointer */ +static void snd_pcm_ioplug_hw_ptr_update(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + snd_pcm_sframes_t hw; + + hw = io->data->callback->pointer(io->data); + if (hw >= 0) { + unsigned int delta; + if ((unsigned int)hw >= io->last_hw) + delta = hw - io->last_hw; + else + delta = pcm->buffer_size + hw - io->last_hw; + io->data->hw_ptr += delta; + io->last_hw = hw; + } else + io->data->state = SNDRV_PCM_STATE_XRUN; +} + +static int snd_pcm_ioplug_info(snd_pcm_t *pcm, snd_pcm_info_t *info) +{ + memset(info, 0, sizeof(*info)); + info->stream = pcm->stream; + info->card = -1; + if (pcm->name) { + strncpy(info->id, pcm->name, sizeof(info->id)); + strncpy(info->name, pcm->name, sizeof(info->name)); + strncpy(info->subname, pcm->name, sizeof(info->subname)); + } + info->subdevices_count = 1; + return 0; +} + +static int snd_pcm_ioplug_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info) +{ + return snd_pcm_channel_info_shm(pcm, info, -1); +} + +static int snd_pcm_ioplug_status(snd_pcm_t *pcm, snd_pcm_status_t * status) +{ + ioplug_priv_t *io = pcm->private_data; + + memset(status, 0, sizeof(*status)); + snd_pcm_ioplug_hw_ptr_update(pcm); + status->state = io->data->state; + status->trigger_tstamp = io->trigger_tstamp; + status->avail = snd_pcm_mmap_avail(pcm); + status->avail_max = io->avail_max; + return 0; +} + +static snd_pcm_state_t snd_pcm_ioplug_state(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + return io->data->state; +} + +static int snd_pcm_ioplug_hwsync(snd_pcm_t *pcm) +{ + snd_pcm_ioplug_hw_ptr_update(pcm); + return 0; +} + +static int snd_pcm_ioplug_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) +{ + snd_pcm_ioplug_hw_ptr_update(pcm); + *delayp = snd_pcm_mmap_hw_avail(pcm); + return 0; +} + +static int snd_pcm_ioplug_reset(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + + io->data->appl_ptr = 0; + io->data->hw_ptr = 0; + io->last_hw = 0; + io->avail_max = 0; + return 0; +} + +static int snd_pcm_ioplug_prepare(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + + io->data->state = SND_PCM_STATE_PREPARED; + snd_pcm_ioplug_reset(pcm); + if (io->data->callback->prepare) + return io->data->callback->prepare(io->data); + return 0; +} + +static inline snd_mask_t *hw_param_mask(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + return ¶ms->masks[var - SND_PCM_HW_PARAM_FIRST_MASK]; +} + +static inline snd_interval_t *hw_param_interval(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + return ¶ms->intervals[var - SND_PCM_HW_PARAM_FIRST_INTERVAL]; +} + +static int hw_params_type[SND_PCM_IOPLUG_HW_PARAMS] = { + [SND_PCM_IOPLUG_HW_ACCESS] = SND_PCM_HW_PARAM_ACCESS, + [SND_PCM_IOPLUG_HW_FORMAT] = SND_PCM_HW_PARAM_FORMAT, + [SND_PCM_IOPLUG_HW_CHANNELS] = SND_PCM_HW_PARAM_CHANNELS, + [SND_PCM_IOPLUG_HW_RATE] = SND_PCM_HW_PARAM_RATE, + [SND_PCM_IOPLUG_HW_PERIOD_BYTES] = SND_PCM_HW_PARAM_PERIOD_BYTES, + [SND_PCM_IOPLUG_HW_BUFFER_BYTES] = SND_PCM_HW_PARAM_BUFFER_BYTES, + [SND_PCM_IOPLUG_HW_PERIODS] = SND_PCM_HW_PARAM_PERIODS, +}; + +static int ioplug_mask_refine(snd_mask_t *mask, struct ioplug_parm *parm) +{ + snd_mask_t bits; + unsigned int i; + + 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); +} + +static 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; +} + +static int ioplug_interval_refine(ioplug_priv_t *io, snd_pcm_hw_params_t *params, int type) +{ + struct ioplug_parm *parm = &io->params[type]; + snd_interval_t *ival; + + if (! parm->active) + return 0; + ival = hw_param_interval(params, hw_params_type[type]); + 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; +} + +/* x = a * b */ +static int rule_mul(snd_pcm_hw_params_t *params, int x, int a, int b) +{ + snd_interval_t t; + + snd_interval_mul(hw_param_interval(params, a), + hw_param_interval(params, b), &t); + return snd_interval_refine(hw_param_interval(params, x), &t); +} + +/* x = a / b */ +static int rule_div(snd_pcm_hw_params_t *params, int x, int a, int b) +{ + snd_interval_t t; + + snd_interval_div(hw_param_interval(params, a), + hw_param_interval(params, b), &t); + return snd_interval_refine(hw_param_interval(params, x), &t); +} + +/* x = a * b / k */ +static int rule_muldivk(snd_pcm_hw_params_t *params, int x, int a, int b, int k) +{ + snd_interval_t t; + + snd_interval_muldivk(hw_param_interval(params, a), + hw_param_interval(params, b), k, &t); + return snd_interval_refine(hw_param_interval(params, x), &t); +} + +/* x = a * k / b */ +static int rule_mulkdiv(snd_pcm_hw_params_t *params, int x, int a, int k, int b) +{ + snd_interval_t t; + + snd_interval_mulkdiv(hw_param_interval(params, a), k, + hw_param_interval(params, b), &t); + return snd_interval_refine(hw_param_interval(params, x), &t); +} + +#if 0 +static void dump_parm(snd_pcm_hw_params_t *params) +{ + snd_output_t *log; + snd_output_stdio_attach(&log, stderr, 0); + snd_pcm_hw_params_dump(params, log); + snd_output_close(log); +} +#endif + +/* refine *_TIME and *_SIZE, then update *_BYTES */ +static int refine_time_and_size(snd_pcm_hw_params_t *params, + int time, int size, int bytes) +{ + int err, change1 = 0; + + /* size = time * rate / 1000000 */ + err = rule_muldivk(params, size, time, + SND_PCM_HW_PARAM_RATE, 1000000); + if (err < 0) + return err; + change1 |= err; + + /* bytes = size * framebits / 8 */ + err = rule_muldivk(params, bytes, size, + SND_PCM_HW_PARAM_FRAME_BITS, 8); + if (err < 0) + return err; + change1 |= err; + return change1; +} + +/* refine *_TIME and *_SIZE from *_BYTES */ +static int refine_back_time_and_size(snd_pcm_hw_params_t *params, + int time, int size, int bytes) +{ + int err; + + /* size = bytes * 8 / framebits */ + err = rule_mulkdiv(params, size, bytes, 8, SND_PCM_HW_PARAM_FRAME_BITS); + if (err < 0) + return err; + /* time = size * 1000000 / rate */ + err = rule_mulkdiv(params, time, size, 1000000, SND_PCM_HW_PARAM_RATE); + if (err < 0) + return err; + return 0; +} + + +static int snd_pcm_ioplug_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) +{ + int change = 0, change1, change2, err; + ioplug_priv_t *io = pcm->private_data; + int i; + + /* access, format */ + for (i = SND_PCM_IOPLUG_HW_ACCESS; i <= SND_PCM_IOPLUG_HW_FORMAT; i++) { + err = ioplug_mask_refine(hw_param_mask(params, hw_params_type[i]), + &io->params[i]); + if (err < 0) + return err; + change |= err; + } + /* channels, rate */ + for (; i <= SND_PCM_IOPLUG_HW_RATE; i++) { + err = ioplug_interval_refine(io, params, i); + if (err < 0) + return err; + change |= err; + } + + if (params->rmask & ((1 << SND_PCM_HW_PARAM_ACCESS) | + (1 << SND_PCM_HW_PARAM_FORMAT) | + (1 << SND_PCM_HW_PARAM_SUBFORMAT) | + (1 << SND_PCM_HW_PARAM_CHANNELS) | + (1 << SND_PCM_HW_PARAM_RATE))) { + err = snd_pcm_hw_refine_soft(pcm, params); + if (err < 0) + return err; + change |= err; + } + + change1 = refine_time_and_size(params, SND_PCM_HW_PARAM_PERIOD_TIME, + SND_PCM_HW_PARAM_PERIOD_SIZE, + SND_PCM_HW_PARAM_PERIOD_BYTES); + if (change1 < 0) + return change1; + err = ioplug_interval_refine(io, params, SND_PCM_IOPLUG_HW_PERIOD_BYTES); + if (err < 0) + return err; + change1 |= err; + if (change1) { + change |= change1; + err = refine_back_time_and_size(params, SND_PCM_HW_PARAM_PERIOD_TIME, + SND_PCM_HW_PARAM_PERIOD_SIZE, + SND_PCM_HW_PARAM_PERIOD_BYTES); + if (err < 0) + return err; + } + + change1 = refine_time_and_size(params, SND_PCM_HW_PARAM_BUFFER_TIME, + SND_PCM_HW_PARAM_BUFFER_SIZE, + SND_PCM_HW_PARAM_BUFFER_BYTES); + if (change1 < 0) + return change1; + change |= change1; + + do { + change2 = 0; + err = ioplug_interval_refine(io, params, SND_PCM_IOPLUG_HW_BUFFER_BYTES); + if (err < 0) + return err; + change2 |= err; + /* periods = buffer_bytes / periods */ + err = rule_div(params, SND_PCM_HW_PARAM_PERIODS, + SND_PCM_HW_PARAM_BUFFER_BYTES, + SND_PCM_HW_PARAM_PERIOD_BYTES); + if (err < 0) + return err; + change2 |= err; + err = ioplug_interval_refine(io, params, SND_PCM_IOPLUG_HW_PERIODS); + if (err < 0) + return err; + change2 |= err; + /* buffer_bytes = periods * period_bytes */ + err = rule_mul(params, SND_PCM_HW_PARAM_BUFFER_BYTES, + SND_PCM_HW_PARAM_PERIOD_BYTES, + SND_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + change2 |= err; + change1 |= change2; + } while (change2); + change |= change1; + + if (change1) { + err = refine_back_time_and_size(params, SND_PCM_HW_PARAM_BUFFER_TIME, + SND_PCM_HW_PARAM_BUFFER_SIZE, + SND_PCM_HW_PARAM_BUFFER_BYTES); + if (err < 0) + return err; + } + +#if 0 + fprintf(stderr, "XXX\n"); + dump_parm(params); +#endif + return change; +} + +static int snd_pcm_ioplug_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) +{ + ioplug_priv_t *io = pcm->private_data; + int err; + + INTERNAL(snd_pcm_hw_params_get_access)(params, &io->data->access); + INTERNAL(snd_pcm_hw_params_get_format)(params, &io->data->format); + INTERNAL(snd_pcm_hw_params_get_channels)(params, &io->data->channels); + INTERNAL(snd_pcm_hw_params_get_rate)(params, &io->data->rate, 0); + INTERNAL(snd_pcm_hw_params_get_period_size)(params, &io->data->period_size, 0); + INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &io->data->buffer_size); + if (io->data->callback->hw_params) { + err = io->data->callback->hw_params(io->data, params); + if (err < 0) + return err; + INTERNAL(snd_pcm_hw_params_get_access)(params, &io->data->access); + INTERNAL(snd_pcm_hw_params_get_format)(params, &io->data->format); + INTERNAL(snd_pcm_hw_params_get_channels)(params, &io->data->channels); + INTERNAL(snd_pcm_hw_params_get_rate)(params, &io->data->rate, 0); + INTERNAL(snd_pcm_hw_params_get_period_size)(params, &io->data->period_size, 0); + INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &io->data->buffer_size); + } + return 0; +} + +static int snd_pcm_ioplug_hw_free(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + + if (io->data->callback->hw_free) + return io->data->callback->hw_free(io->data); + return 0; +} + +static int snd_pcm_ioplug_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) +{ + ioplug_priv_t *io = pcm->private_data; + + if (io->data->callback->sw_params) + return io->data->callback->sw_params(io->data, params); + return 0; +} + + +static int snd_pcm_ioplug_start(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + struct timeval tv; + int err; + + if (io->data->state != SND_PCM_STATE_PREPARED) + return -EBUSY; + + err = io->data->callback->start(io->data); + if (err < 0) + return err; + + gettimeofday(&tv, 0); + io->trigger_tstamp.tv_sec = tv.tv_sec; + io->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L; + io->data->state = SND_PCM_STATE_RUNNING; + + return 0; +} + +static int snd_pcm_ioplug_drop(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + struct timeval tv; + + if (io->data->state == SND_PCM_STATE_OPEN) + return -EBADFD; + + io->data->callback->stop(io->data); + + gettimeofday(&tv, 0); + io->trigger_tstamp.tv_sec = tv.tv_sec; + io->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L; + io->data->state = SND_PCM_STATE_SETUP; + + return 0; +} + +static int snd_pcm_ioplug_drain(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + + if (io->data->state == SND_PCM_STATE_OPEN) + return -EBADFD; + if (io->data->callback->drain) + io->data->callback->drain(io->data); + return snd_pcm_ioplug_drop(pcm); +} + +static int snd_pcm_ioplug_pause(snd_pcm_t *pcm, int enable) +{ + ioplug_priv_t *io = pcm->private_data; + static snd_pcm_state_t states[2] = { + SND_PCM_STATE_PAUSED, SND_PCM_STATE_RUNNING + }; + int prev, err; + + prev = !enable; + enable = !prev; + if (io->data->state != states[prev]) + return -EBADFD; + if (io->data->callback->pause) { + err = io->data->callback->pause(io->data, enable); + if (err < 0) + return err; + } + io->data->state = states[enable]; + return 0; +} + +static snd_pcm_sframes_t snd_pcm_ioplug_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + snd_pcm_mmap_appl_backward(pcm, frames); + return frames; +} + +static snd_pcm_sframes_t snd_pcm_ioplug_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + snd_pcm_mmap_appl_forward(pcm, frames); + return frames; +} + +static int snd_pcm_ioplug_resume(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + + if (io->data->callback->resume) + io->data->callback->resume(io->data); + return 0; +} + +static snd_pcm_sframes_t ioplug_priv_transfer_areas(snd_pcm_t *pcm, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + ioplug_priv_t *io = pcm->private_data; + snd_pcm_sframes_t result; + + if (! size) + return 0; + if (io->data->callback->transfer) + result = io->data->callback->transfer(io->data, areas, offset, size); + else + result = size; + if (result > 0) + snd_pcm_mmap_appl_forward(pcm, result); + return result; +} + +static snd_pcm_sframes_t snd_pcm_ioplug_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) +{ + if (pcm->mmap_rw) + return snd_pcm_mmap_writei(pcm, buffer, size); + else { + snd_pcm_channel_area_t areas[pcm->channels]; + snd_pcm_areas_from_buf(pcm, areas, (void*)buffer); + return snd_pcm_write_areas(pcm, areas, 0, size, + ioplug_priv_transfer_areas); + } +} + +static snd_pcm_sframes_t snd_pcm_ioplug_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) +{ + if (pcm->mmap_rw) + return snd_pcm_mmap_writen(pcm, bufs, size); + else { + snd_pcm_channel_area_t areas[pcm->channels]; + snd_pcm_areas_from_bufs(pcm, areas, bufs); + return snd_pcm_write_areas(pcm, areas, 0, size, + ioplug_priv_transfer_areas); + } +} + +static snd_pcm_sframes_t snd_pcm_ioplug_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) +{ + if (pcm->mmap_rw) + return snd_pcm_mmap_readi(pcm, buffer, size); + else { + snd_pcm_channel_area_t areas[pcm->channels]; + snd_pcm_areas_from_buf(pcm, areas, buffer); + return snd_pcm_read_areas(pcm, areas, 0, size, + ioplug_priv_transfer_areas); + } +} + +static snd_pcm_sframes_t snd_pcm_ioplug_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) +{ + if (pcm->mmap_rw) + return snd_pcm_mmap_readn(pcm, bufs, size); + else { + snd_pcm_channel_area_t areas[pcm->channels]; + snd_pcm_areas_from_bufs(pcm, areas, bufs); + return snd_pcm_read_areas(pcm, areas, 0, size, + ioplug_priv_transfer_areas); + } +} + +static snd_pcm_sframes_t snd_pcm_ioplug_mmap_commit(snd_pcm_t *pcm, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + if (pcm->stream == SND_PCM_STREAM_PLAYBACK && + pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED && + pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) { + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t ofs, frames = size; + + snd_pcm_mmap_begin(pcm, &areas, &ofs, &frames); + if (ofs != offset) + return -EIO; + return ioplug_priv_transfer_areas(pcm, areas, offset, frames); + } + + snd_pcm_mmap_appl_forward(pcm, size); + return size; +} + +static snd_pcm_sframes_t snd_pcm_ioplug_avail_update(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + snd_pcm_uframes_t avail; + + snd_pcm_ioplug_hw_ptr_update(pcm); + if (pcm->stream == SND_PCM_STREAM_CAPTURE && + pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED && + pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) { + if (io->data->callback->transfer) { + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, size = UINT_MAX; + snd_pcm_sframes_t result; + + snd_pcm_mmap_begin(pcm, &areas, &offset, &size); + result = io->data->callback->transfer(io->data, areas, offset, size); + if (result < 0) + return result; + } + } + avail = snd_pcm_mmap_avail(pcm); + if (avail > io->avail_max) + io->avail_max = avail; + return (snd_pcm_sframes_t)avail; +} + +static int snd_pcm_ioplug_nonblock(snd_pcm_t *pcm, int nonblock) +{ + ioplug_priv_t *io = pcm->private_data; + + io->data->nonblock = nonblock; + return 0; +} + +static int snd_pcm_ioplug_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) +{ + ioplug_priv_t *io = pcm->private_data; + + if (io->data->callback->poll_revents) + return io->data->callback->poll_revents(io->data, pfds, nfds, revents); + else + *revents = pfds->revents; + return 0; +} + +static int snd_pcm_ioplug_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_ioplug_async(snd_pcm_t *pcm ATTRIBUTE_UNUSED, + int sig ATTRIBUTE_UNUSED, + pid_t pid ATTRIBUTE_UNUSED) +{ + return -ENOSYS; +} + +static int snd_pcm_ioplug_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static void snd_pcm_ioplug_dump(snd_pcm_t *pcm, snd_output_t *out) +{ + ioplug_priv_t *io = pcm->private_data; + + if (io->data->callback->dump) + io->data->callback->dump(io->data, out); + else { + if (io->data->name) + snd_output_printf(out, "%s\n", io->data->name); + else + snd_output_printf(out, "IO-PCM Plugin\n"); + if (pcm->setup) { + snd_output_printf(out, "Its setup is:\n"); + snd_pcm_dump_setup(pcm, out); + } + } +} + +static void clear_io_params(ioplug_priv_t *io) +{ + int i; + for (i = 0; i < SND_PCM_IOPLUG_HW_PARAMS; i++) { + free(io->params[i].list); + memset(&io->params[i], 0, sizeof(io->params[i])); + } +} + +static int snd_pcm_ioplug_close(snd_pcm_t *pcm) +{ + ioplug_priv_t *io = pcm->private_data; + + clear_io_params(io); + if (io->data->callback->close) + io->data->callback->close(io->data); + free(io); + + return 0; +} + +static snd_pcm_ops_t snd_pcm_ioplug_ops = { + .close = snd_pcm_ioplug_close, + .nonblock = snd_pcm_ioplug_nonblock, + .async = snd_pcm_ioplug_async, + .poll_revents = snd_pcm_ioplug_poll_revents, + .info = snd_pcm_ioplug_info, + .hw_refine = snd_pcm_ioplug_hw_refine, + .hw_params = snd_pcm_ioplug_hw_params, + .hw_free = snd_pcm_ioplug_hw_free, + .sw_params = snd_pcm_ioplug_sw_params, + .channel_info = snd_pcm_ioplug_channel_info, + .dump = snd_pcm_ioplug_dump, + .mmap = snd_pcm_ioplug_mmap, + .munmap = snd_pcm_ioplug_munmap, +}; + +static snd_pcm_fast_ops_t snd_pcm_ioplug_fast_ops = { + .status = snd_pcm_ioplug_status, + .prepare = snd_pcm_ioplug_prepare, + .reset = snd_pcm_ioplug_reset, + .start = snd_pcm_ioplug_start, + .drop = snd_pcm_ioplug_drop, + .drain = snd_pcm_ioplug_drain, + .pause = snd_pcm_ioplug_pause, + .state = snd_pcm_ioplug_state, + .hwsync = snd_pcm_ioplug_hwsync, + .delay = snd_pcm_ioplug_delay, + .resume = snd_pcm_ioplug_resume, + .poll_ask = NULL, + .link_fd = NULL, + .link = NULL, + .unlink = NULL, + .rewind = snd_pcm_ioplug_rewind, + .forward = snd_pcm_ioplug_forward, + .writei = snd_pcm_ioplug_writei, + .writen = snd_pcm_ioplug_writen, + .readi = snd_pcm_ioplug_readi, + .readn = snd_pcm_ioplug_readn, + .avail_update = snd_pcm_ioplug_avail_update, + .mmap_commit = snd_pcm_ioplug_mmap_commit, +}; + +static int ioplug_set_parm_minmax(struct ioplug_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; +} + +static int val_compar(const void *ap, const void *bp) +{ + return *(const unsigned int *)ap - *(const unsigned int *)bp; +} + +static int ioplug_set_parm_list(struct ioplug_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_pcm_ioplug_params_reset(snd_pcm_ioplug_t *ioplug) +{ + ioplug_priv_t *io = ioplug->pcm->private_data; + clear_io_params(io); +} + +int snd_pcm_ioplug_set_param_list(snd_pcm_ioplug_t *ioplug, int type, unsigned int num_list, const unsigned int *list) +{ + ioplug_priv_t *io = ioplug->pcm->private_data; + if (type < 0 && type >= SND_PCM_IOPLUG_HW_PARAMS) { + SNDERR("IOPLUG: invalid parameter type %d", type); + return -EINVAL; + } + if (type == SND_PCM_IOPLUG_HW_PERIODS) + io->params[type].integer = 1; + return ioplug_set_parm_list(&io->params[type], num_list, list); +} + +int snd_pcm_ioplug_set_param_minmax(snd_pcm_ioplug_t *ioplug, int type, unsigned int min, unsigned int max) +{ + ioplug_priv_t *io = ioplug->pcm->private_data; + if (type < 0 && type >= SND_PCM_IOPLUG_HW_PARAMS) { + SNDERR("IOPLUG: invalid parameter type %d", type); + return -EINVAL; + } + if (type == SND_PCM_IOPLUG_HW_ACCESS || type == SND_PCM_IOPLUG_HW_FORMAT) { + SNDERR("IOPLUG: invalid parameter type %d", type); + return -EINVAL; + } + if (type == SND_PCM_IOPLUG_HW_PERIODS) + io->params[type].integer = 1; + return ioplug_set_parm_minmax(&io->params[type], min, max); +} + +int snd_pcm_ioplug_reinit_status(snd_pcm_ioplug_t *ioplug) +{ + ioplug->pcm->poll_fd = ioplug->poll_fd; + ioplug->pcm->poll_events = ioplug->poll_events; + ioplug->pcm->mmap_rw = ioplug->mmap_rw; + return 0; +} + +const snd_pcm_channel_area_t *snd_pcm_ioplug_mmap_areas(snd_pcm_ioplug_t *ioplug) +{ + if (ioplug->mmap_rw) + return snd_pcm_mmap_areas(ioplug->pcm); + return NULL; +} + +int snd_pcm_ioplug_create(snd_pcm_ioplug_t *ioplug, const char *name, + snd_pcm_stream_t stream, int mode) +{ + ioplug_priv_t *io; + int err; + snd_pcm_t *pcm; + snd_pcm_access_t def_access = SND_PCM_ACCESS_RW_INTERLEAVED; + snd_pcm_format_t def_format = SND_PCM_FORMAT_S16; + + assert(ioplug && ioplug->callback); + assert(ioplug->callback->start && + ioplug->callback->stop && + ioplug->callback->pointer); + + io = calloc(1, sizeof(*io)); + if (! io) + return -ENOMEM; + + io->data = ioplug; + ioplug->state = SND_PCM_STATE_OPEN; + ioplug->stream = stream; + + err = snd_pcm_new(&pcm, SND_PCM_TYPE_IOPLUG, name, stream, mode); + if (err < 0) { + free(io); + return err; + } + + ioplug->pcm = pcm; + pcm->ops = &snd_pcm_ioplug_ops; + pcm->fast_ops = &snd_pcm_ioplug_fast_ops; + pcm->private_data = io; + + snd_pcm_set_hw_ptr(pcm, &ioplug->hw_ptr, -1, 0); + snd_pcm_set_appl_ptr(pcm, &ioplug->appl_ptr, -1, 0); + + snd_pcm_ioplug_reinit_status(ioplug); + + return 0; +} + +int snd_pcm_ioplug_delete(snd_pcm_ioplug_t *ioplug) +{ + return snd_pcm_close(ioplug->pcm); +} + -- 2.47.1