From: Takashi Iwai Date: Tue, 8 Feb 2005 20:57:51 +0000 (+0000) Subject: Add external PCM plugin SDK (draft version) X-Git-Tag: v1.0.9rc1~30 X-Git-Url: https://git.alsa-project.org/?a=commitdiff_plain;h=94c4cdcd30a874daf7abfcac1f9759748b75cb0a;p=alsa-lib.git 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. --- 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); +} +