]> git.alsa-project.org Git - alsa-lib.git/commitdiff
Add external PCM plugin SDK (draft version)
authorTakashi Iwai <tiwai@suse.de>
Tue, 8 Feb 2005 20:57:51 +0000 (20:57 +0000)
committerTakashi Iwai <tiwai@suse.de>
Tue, 8 Feb 2005 20:57:51 +0000 (20:57 +0000)
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
include/pcm.h
include/pcm_external.h [new file with mode: 0644]
include/pcm_ioplug.h [new file with mode: 0644]
src/Versions
src/pcm/Makefile.am
src/pcm/pcm_ioplug.c [new file with mode: 0644]

index 3279673af277fb59944c47d91cabb19f032af9a9..eeb21fee70c7ba018a3c7aa1fa1ba2828d1aeeae 100644 (file)
@@ -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
 
index cdf5d0d1feecb9c0bae463bc951973b7cd1cd69d..1357359a343a5b57e70bee3700e27c05938e3d01 100644 (file)
@@ -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 (file)
index 0000000..92d7c52
--- /dev/null
@@ -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 (file)
index 0000000..62c064a
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * ALSA external PCM plugin SDK (draft version)
+ *
+ * Copyright (c) 2005 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., 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 */
index feb7e6807f40b7d75f6f58c1a91e22cd64203b27..5ac29f291a31fc400dd84521b8f14a13b422c944 100644 (file)
@@ -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;
index 19c0882058c7c1f09fd1b890a484ae99006d8927..2ace7912e5d51307a6971d0f649c13e28cce5897 100644 (file)
@@ -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 (file)
index 0000000..2a8d9fd
--- /dev/null
@@ -0,0 +1,913 @@
+/**
+ * \file pcm/pcm_ioplug.c
+ * \ingroup Plugin_SDK
+ * \brief I/O Plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2005
+ */
+/*
+ *  PCM - External I/O 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., 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 &params->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 &params->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);
+}
+