Sync from GitHub

fb0ab77c 2020-11-02 modify the buffer size to the period size to avoid underrun
16fee33d 2020-11-02 calcuate the thresholds after options parsing
ee63fc9a 2020-10-29 check whether the audio data are still enough to play
6125aaf4 2020-09-04 tinymix: Fix get/set for tlv based mixer controls
f29b8df6 2020-08-19 tinyalsa: add plugin support for mmap/poll ops
8718ec97 2020-07-19 Quick fix for #168
d86996de 2020-06-03 tinyplay: replace manual option parsing with optparse
758a1124 2020-06-03 tinypcminfo: replace manual option parsing with optparse
1e144025 2020-06-03 tinymix: replace getopt_long with optparse
f6ab1276 2020-06-03 tinycap: replace manual option parsing with optparse
ab224a09 2020-06-03 Fix memory leak
6b105acd 2020-04-22 Removed whitespace
859adb2d 2020-04-18 pcm_hw_close: avoid SIGSEGV when pcm_hw_open fail
8fca97e3 2020-04-10 pcm: do not negate errno
00f5aa13 2020-04-10 pcm: avoid calling pcm_hw->close() twice
05c64c3d 2020-02-06 pcm: Set SW params.avail_min to period size
405bef3f 2019-09-16 utils: Fix spelling in help text
986b8e33 2019-09-04 tinyalsa: add support for PCM plugins
e7c627dd 2019-06-19 tinyalsa: add support for mixer plugins
b38b6a67 2019-01-07 pcm: Check for error after setting appl_ptr
1804d158 2019-01-04 pcm: Replace 'underruns' with 'xruns'
7ff9cde3 2018-12-31 pcm: Remove prepare before start
4ee09a97 2018-12-18 pcm: Rewrite pcm_mmap_transfer() for compatibility
c1446758 2018-12-18 pcm: Call HWSYNC ioctl when status is mmaped
cf5f063f 2018-07-17 Fixed pcm_start when pcm is linked to other pcm device
f8f7ef29 2017-09-08 Update output format for enumerated controls
9ed7df6b 2017-09-07 Pull mixer_ctl_get_value out of loop in tinymix_print_enum
e40758bb 2017-09-07 Fixup some minor code formatting issues
25976dc2 2017-04-10 removed tab characters
861da7ab 2017-04-10 moved code in pcm_open() to pcm_set_config()
08bb5909 2017-04-10 added pcm_get_config()
a6947c62 2017-01-19 tinymix: fix segfaults with set command
e1c6374e 2017-01-14 updated read handling for pcm_readi
a94295b6 2016-12-01 Added mixer_get_ctl_const
77979a88 2016-12-01 Added format related getters for PCM
147d7ade 2016-12-01 Added const specifier in several functions
cac43a20 2016-12-01 Added const specifier for several mixer functions
4e55719a 2016-11-23 finished mixer API documentation
17a10242 2016-11-23 added some documentation
d265c27d 2016-11-23 added parameter doc
8e1b1029 2016-11-19 renamed doxygen group, tinyalsa-pcm to libtinyalsa-pcm
8ae5d11b 2016-11-19 renamed doxygen group, tinyalsa-mixer to libtinyalsa-mixer
888bc69f 2016-11-19 make tinymix command oriented, based on amixer
969ba791 2016-11-19 created cmd and ctx structures for tinyplay
474ab244 2016-11-19 added tinyalsa version header
b7a28579 2016-11-19 added some documentation
91cf5e2b 2016-11-07 I work on platforms based on the Qualcomm SnapDragon chip. There can be hundreds of paths on the audio side of this chip.
04952ee1 2016-10-05 included only necessary headers
6a38d5fa 2016-10-03 Merge branch 'defer-enum-inspection' of https://github.com/dawagner/tinyalsa into dawagner-defer-enum-inspection
93b544ec 2016-10-01 seperated pcm and mixer APIs
c01d4a38 2016-10-01 added support for more signed types
7c8b20a6 2016-10-01 added some documentation to mixer interface
6d58e01d 2016-10-01 added basic pcm documentation
4f556060 2016-09-16 fixed bracket placement
bb402603 2016-08-03 Added get_file_descriptor()
c7328362 2016-01-24 mixer: fix possible null pointer dereference
9d3cdd0e 2016-01-24 fix format specifier for unsigned int
8eaad667 2016-01-22 tinycap: add capture time parameter
d131cf24 2016-01-09 tinymix: don't get byte array if size is zero
7cab1ef9 2015-04-02 tinymix: Support more that 512 bytes in byte control
899ceceb 2014-09-09 mixer: remove separate info list
fd329035 2014-09-09 mixer: add ability to update control list with new controls
4cddf19d 2014-04-02 tinyalsa: add pcm_get_subdevice()
4a484e11 2013-09-12 Tinyalsa: standardize error message for wrong input value
5741cc04 2013-08-12 tinycap: support streaming captured PCM to stdout
8b772cc5 2013-07-15 tinymix: Improve detect of integer values
9b42396a 2013-06-03 Defer filling enum-type mixers information
bad2b79b 2012-11-07 tinymix: Add support for passing control name
b29ac1ab 2012-03-08 mixer: simplify string get APIs
6be28f1b 2011-10-13 pcm: add mmap playback and no periodic IRQ support.
193b1c3b 2011-06-07 mixer: make error handling more consistent
066c9f67 2011-06-05 Add enum support to mixer
d2cb5030 2011-06-04 Improve mixer support
a0ef465b 2011-06-02 Add tinyplay utility to play PCM riff/wave files

Test: n/a
Change-Id: I296a47fcb3da962626e6a0fc731a5e32d0138cf8
diff --git a/src/pcm.c b/src/pcm.c
index 735fb1d..352ddc6 100644
--- a/src/pcm.c
+++ b/src/pcm.c
@@ -38,17 +38,37 @@
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/time.h>
+#include <time.h>
 #include <limits.h>
 
 #include <linux/ioctl.h>
+
+#ifndef __force
 #define __force
+#endif
+
+#ifndef __bitwise
 #define __bitwise
+#endif
+
+#ifndef __user
 #define __user
+#endif
+
 #include <sound/asound.h>
 
-#include <tinyalsa/asoundlib.h>
+#include <tinyalsa/pcm.h>
+#include <tinyalsa/limits.h>
+#include "pcm_io.h"
+#include "snd_card_plugin.h"
 
+#ifndef PARAM_MAX
 #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL
+#endif /* PARAM_MAX */
+
+#ifndef SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP
+#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2)
+#endif /* SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP */
 
 /* Logs information into a string; follows snprintf() in that
  * offset may be greater than size, and though no characters are copied
@@ -140,6 +160,11 @@
         (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL);
 }
 
+static inline const struct snd_interval *param_get_interval(const struct snd_pcm_hw_params *p, int n)
+{
+    return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
+}
+
 static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n)
 {
     return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
@@ -170,27 +195,19 @@
     }
 }
 
-static unsigned int param_get_min(struct snd_pcm_hw_params *p, int n)
+static unsigned int param_get_min(const struct snd_pcm_hw_params *p, int n)
 {
     if (param_is_interval(n)) {
-        struct snd_interval *i = param_to_interval(p, n);
+        const struct snd_interval *i = param_get_interval(p, n);
         return i->min;
     }
     return 0;
 }
 
-static void param_set_max(struct snd_pcm_hw_params *p, int n, unsigned int val)
+static unsigned int param_get_max(const struct snd_pcm_hw_params *p, int n)
 {
     if (param_is_interval(n)) {
-        struct snd_interval *i = param_to_interval(p, n);
-        i->max = val;
-    }
-}
-
-static unsigned int param_get_max(struct snd_pcm_hw_params *p, int n)
-{
-    if (param_is_interval(n)) {
-        struct snd_interval *i = param_to_interval(p, n);
+        const struct snd_interval *i = param_get_interval(p, n);
         return i->max;
     }
     return 0;
@@ -238,42 +255,73 @@
     p->info = ~0U;
 }
 
+static unsigned int pcm_format_to_alsa(enum pcm_format format)
+{
+    switch (format) {
+
+    case PCM_FORMAT_S8:
+        return SNDRV_PCM_FORMAT_S8;
+
+    default:
+    case PCM_FORMAT_S16_LE:
+        return SNDRV_PCM_FORMAT_S16_LE;
+    case PCM_FORMAT_S16_BE:
+        return SNDRV_PCM_FORMAT_S16_BE;
+
+    case PCM_FORMAT_S24_LE:
+        return SNDRV_PCM_FORMAT_S24_LE;
+    case PCM_FORMAT_S24_BE:
+        return SNDRV_PCM_FORMAT_S24_BE;
+
+    case PCM_FORMAT_S24_3LE:
+        return SNDRV_PCM_FORMAT_S24_3LE;
+    case PCM_FORMAT_S24_3BE:
+        return SNDRV_PCM_FORMAT_S24_3BE;
+
+    case PCM_FORMAT_S32_LE:
+        return SNDRV_PCM_FORMAT_S32_LE;
+    case PCM_FORMAT_S32_BE:
+        return SNDRV_PCM_FORMAT_S32_BE;
+    };
+}
+
 #define PCM_ERROR_MAX 128
 
+/** A PCM handle.
+ * @ingroup libtinyalsa-pcm
+ */
 struct pcm {
+    /** The PCM's file descriptor */
     int fd;
+    /** Flags that were passed to @ref pcm_open */
     unsigned int flags;
-    int running:1;
-    int prepared:1;
-    int underruns;
+    /** The number of (under/over)runs that have occured */
+    int xruns;
+    /** Size of the buffer */
     unsigned int buffer_size;
+    /** The boundary for ring buffer pointers */
     unsigned int boundary;
+    /** Description of the last error that occured */
     char error[PCM_ERROR_MAX];
+    /** Configuration that was passed to @ref pcm_open */
     struct pcm_config config;
     struct snd_pcm_mmap_status *mmap_status;
     struct snd_pcm_mmap_control *mmap_control;
     struct snd_pcm_sync_ptr *sync_ptr;
     void *mmap_buffer;
     unsigned int noirq_frames_per_msec;
-    int wait_for_avail_min;
+    /** The delay of the PCM, in terms of frames */
+    long pcm_delay;
+    /** The subdevice corresponding to the PCM */
     unsigned int subdevice;
+    /** Pointer to the pcm ops, either hw or plugin */
+    const struct pcm_ops *ops;
+    /** Private data for pcm_hw or pcm_plugin */
+    void *data;
+    /** Pointer to the pcm node from snd card definition */
+    struct snd_node *snd_node;
 };
 
-unsigned int pcm_get_buffer_size(struct pcm *pcm)
-{
-    return pcm->buffer_size;
-}
-
-const char* pcm_get_error(struct pcm *pcm)
-{
-    return pcm->error;
-}
-
-unsigned int pcm_get_subdevice(struct pcm *pcm)
-{
-    return pcm->subdevice;
-}
-
 static int oops(struct pcm *pcm, int e, const char *fmt, ...)
 {
     va_list ap;
@@ -290,84 +338,306 @@
     return -1;
 }
 
-static unsigned int pcm_format_to_alsa(enum pcm_format format)
+/** Gets the buffer size of the PCM.
+ * @param pcm A PCM handle.
+ * @return The buffer size of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_get_buffer_size(const struct pcm *pcm)
 {
-    switch (format) {
-    case PCM_FORMAT_S32_LE:
-        return SNDRV_PCM_FORMAT_S32_LE;
-    case PCM_FORMAT_S8:
-        return SNDRV_PCM_FORMAT_S8;
-    case PCM_FORMAT_S24_3LE:
-        return SNDRV_PCM_FORMAT_S24_3LE;
-    case PCM_FORMAT_S24_LE:
-        return SNDRV_PCM_FORMAT_S24_LE;
-    default:
-    case PCM_FORMAT_S16_LE:
-        return SNDRV_PCM_FORMAT_S16_LE;
-    };
+    return pcm->buffer_size;
 }
 
+/** Gets the channel count of the PCM.
+ * @param pcm A PCM handle.
+ * @return The channel count of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_get_channels(const struct pcm *pcm)
+{
+    return pcm->config.channels;
+}
+
+/** Gets the PCM configuration.
+ * @param pcm A PCM handle.
+ * @return The PCM configuration.
+ *  This function only returns NULL if
+ *  @p pcm is NULL.
+ * @ingroup libtinyalsa-pcm
+ * */
+const struct pcm_config * pcm_get_config(const struct pcm *pcm)
+{
+    if (pcm == NULL)
+        return NULL;
+    return &pcm->config;
+}
+
+/** Gets the rate of the PCM.
+ * The rate is given in frames per second.
+ * @param pcm A PCM handle.
+ * @return The rate of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_get_rate(const struct pcm *pcm)
+{
+    return pcm->config.rate;
+}
+
+/** Gets the format of the PCM.
+ * @param pcm A PCM handle.
+ * @return The format of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+enum pcm_format pcm_get_format(const struct pcm *pcm)
+{
+    return pcm->config.format;
+}
+
+/** Gets the file descriptor of the PCM.
+ * Useful for extending functionality of the PCM when needed.
+ * @param pcm A PCM handle.
+ * @return The file descriptor of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_get_file_descriptor(const struct pcm *pcm)
+{
+    return pcm->fd;
+}
+
+/** Gets the error message for the last error that occured.
+ * If no error occured and this function is called, the results are undefined.
+ * @param pcm A PCM handle.
+ * @return The error message of the last error that occured.
+ * @ingroup libtinyalsa-pcm
+ */
+const char* pcm_get_error(const struct pcm *pcm)
+{
+    return pcm->error;
+}
+
+/** Sets the PCM configuration.
+ * @param pcm A PCM handle.
+ * @param config The configuration to use for the
+ *  PCM. This parameter may be NULL, in which case
+ *  the default configuration is used.
+ * @returns Zero on success, a negative errno value
+ *  on failure.
+ * @ingroup libtinyalsa-pcm
+ * */
+int pcm_set_config(struct pcm *pcm, const struct pcm_config *config)
+{
+    if (pcm == NULL)
+        return -EFAULT;
+    else if (config == NULL) {
+        config = &pcm->config;
+        pcm->config.channels = 2;
+        pcm->config.rate = 48000;
+        pcm->config.period_size = 1024;
+        pcm->config.period_count = 4;
+        pcm->config.format = PCM_FORMAT_S16_LE;
+        pcm->config.start_threshold = config->period_count * config->period_size;
+        pcm->config.stop_threshold = config->period_count * config->period_size;
+        pcm->config.silence_threshold = 0;
+        pcm->config.silence_size = 0;
+    } else
+        pcm->config = *config;
+
+    struct snd_pcm_hw_params params;
+    param_init(&params);
+    param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
+                   pcm_format_to_alsa(config->format));
+    param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
+    param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
+                  config->channels);
+    param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
+    param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);
+
+    if (pcm->flags & PCM_NOIRQ) {
+
+        if (!(pcm->flags & PCM_MMAP)) {
+            oops(pcm, EINVAL, "noirq only currently supported with mmap().");
+            return -EINVAL;
+        }
+
+        params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
+        pcm->noirq_frames_per_msec = config->rate / 1000;
+    }
+
+    if (pcm->flags & PCM_MMAP)
+        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
+                   SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
+    else
+        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
+                   SNDRV_PCM_ACCESS_RW_INTERLEAVED);
+
+    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
+        int errno_copy = errno;
+        oops(pcm, errno, "cannot set hw params");
+        return -errno_copy;
+    }
+
+    /* get our refined hw_params */
+    pcm->config.period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+    pcm->config.period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);
+    pcm->buffer_size = config->period_count * config->period_size;
+
+    if (pcm->flags & PCM_MMAP) {
+        pcm->mmap_buffer = pcm->ops->mmap(pcm->data, NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
+                                PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+        if (pcm->mmap_buffer == MAP_FAILED) {
+            int errno_copy = errno;
+            oops(pcm, errno, "failed to mmap buffer %d bytes\n",
+                 pcm_frames_to_bytes(pcm, pcm->buffer_size));
+            return -errno_copy;
+        }
+    }
+
+    struct snd_pcm_sw_params sparams;
+    memset(&sparams, 0, sizeof(sparams));
+    sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
+    sparams.period_step = 1;
+    sparams.avail_min = config->period_size;
+
+    if (!config->start_threshold) {
+        if (pcm->flags & PCM_IN)
+            pcm->config.start_threshold = sparams.start_threshold = 1;
+        else
+            pcm->config.start_threshold = sparams.start_threshold =
+                config->period_count * config->period_size / 2;
+    } else
+        sparams.start_threshold = config->start_threshold;
+
+    /* pick a high stop threshold - todo: does this need further tuning */
+    if (!config->stop_threshold) {
+        if (pcm->flags & PCM_IN)
+            pcm->config.stop_threshold = sparams.stop_threshold =
+                config->period_count * config->period_size * 10;
+        else
+            pcm->config.stop_threshold = sparams.stop_threshold =
+                config->period_count * config->period_size;
+    }
+    else
+        sparams.stop_threshold = config->stop_threshold;
+
+    sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
+    sparams.silence_size = config->silence_size;
+    sparams.silence_threshold = config->silence_threshold;
+    pcm->boundary = sparams.boundary = pcm->buffer_size;
+
+    while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
+        pcm->boundary *= 2;
+
+    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
+        int errno_copy = errno;
+        oops(pcm, errno, "cannot set sw params");
+        return -errno_copy;
+    }
+
+    return 0;
+}
+
+/** Gets the subdevice on which the pcm has been opened.
+ * @param pcm A PCM handle.
+ * @return The subdevice on which the pcm has been opened */
+unsigned int pcm_get_subdevice(const struct pcm *pcm)
+{
+    return pcm->subdevice;
+}
+
+/** Determines the number of bits occupied by a @ref pcm_format.
+ * @param format A PCM format.
+ * @return The number of bits associated with @p format
+ * @ingroup libtinyalsa-pcm
+ */
 unsigned int pcm_format_to_bits(enum pcm_format format)
 {
     switch (format) {
     case PCM_FORMAT_S32_LE:
+    case PCM_FORMAT_S32_BE:
     case PCM_FORMAT_S24_LE:
+    case PCM_FORMAT_S24_BE:
         return 32;
     case PCM_FORMAT_S24_3LE:
+    case PCM_FORMAT_S24_3BE:
         return 24;
     default:
     case PCM_FORMAT_S16_LE:
+    case PCM_FORMAT_S16_BE:
         return 16;
+    case PCM_FORMAT_S8:
+        return 8;
     };
 }
 
-unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes)
+/** Determines how many frames of a PCM can fit into a number of bytes.
+ * @param pcm A PCM handle.
+ * @param bytes The number of bytes.
+ * @return The number of frames that may fit into @p bytes
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_bytes_to_frames(const struct pcm *pcm, unsigned int bytes)
 {
     return bytes / (pcm->config.channels *
         (pcm_format_to_bits(pcm->config.format) >> 3));
 }
 
-unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames)
+/** Determines how many bytes are occupied by a number of frames of a PCM.
+ * @param pcm A PCM handle.
+ * @param frames The number of frames of a PCM.
+ * @return The bytes occupied by @p frames.
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_frames_to_bytes(const struct pcm *pcm, unsigned int frames)
 {
     return frames * pcm->config.channels *
         (pcm_format_to_bits(pcm->config.format) >> 3);
 }
 
-static int pcm_sync_ptr(struct pcm *pcm, int flags) {
-    if (pcm->sync_ptr) {
+static int pcm_sync_ptr(struct pcm *pcm, int flags)
+{
+    if (pcm->sync_ptr == NULL) {
+        /* status and control are mmaped */
+
+        if (flags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
+            if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HWSYNC) == -1) {
+                oops(pcm, errno, "failed to sync hardware pointer");
+                return -1;
+            }
+        }
+    } else {
         pcm->sync_ptr->flags = flags;
-        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0)
+        if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SYNC_PTR,
+                            pcm->sync_ptr) < 0) {
+            oops(pcm, errno, "failed to sync mmap ptr");
             return -1;
+        }
     }
+
     return 0;
 }
 
-static int pcm_hw_mmap_status(struct pcm *pcm) {
-
+static int pcm_hw_mmap_status(struct pcm *pcm)
+{
     if (pcm->sync_ptr)
         return 0;
 
     int page_size = sysconf(_SC_PAGE_SIZE);
-    pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED,
-                            pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS);
+    pcm->mmap_status = pcm->ops->mmap(pcm->data, NULL, page_size, PROT_READ, MAP_SHARED,
+                            SNDRV_PCM_MMAP_OFFSET_STATUS);
     if (pcm->mmap_status == MAP_FAILED)
         pcm->mmap_status = NULL;
     if (!pcm->mmap_status)
         goto mmap_error;
 
-    pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
-                             MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
+    pcm->mmap_control = pcm->ops->mmap(pcm->data, NULL, page_size, PROT_READ | PROT_WRITE,
+                             MAP_SHARED, SNDRV_PCM_MMAP_OFFSET_CONTROL);
     if (pcm->mmap_control == MAP_FAILED)
         pcm->mmap_control = NULL;
     if (!pcm->mmap_control) {
-        munmap(pcm->mmap_status, page_size);
+        pcm->ops->munmap(pcm->data, pcm->mmap_status, page_size);
         pcm->mmap_status = NULL;
         goto mmap_error;
     }
-    if (pcm->flags & PCM_MMAP)
-        pcm->mmap_control->avail_min = pcm->config.avail_min;
-    else
-        pcm->mmap_control->avail_min = 1;
 
     return 0;
 
@@ -378,12 +648,6 @@
         return -ENOMEM;
     pcm->mmap_status = &pcm->sync_ptr->s.status;
     pcm->mmap_control = &pcm->sync_ptr->c.control;
-    if (pcm->flags & PCM_MMAP)
-        pcm->mmap_control->avail_min = pcm->config.avail_min;
-    else
-        pcm->mmap_control->avail_min = 1;
-
-    pcm_sync_ptr(pcm, 0);
 
     return 0;
 }
@@ -395,215 +659,58 @@
     } else {
         int page_size = sysconf(_SC_PAGE_SIZE);
         if (pcm->mmap_status)
-            munmap(pcm->mmap_status, page_size);
+            pcm->ops->munmap(pcm->data, pcm->mmap_status, page_size);
         if (pcm->mmap_control)
-            munmap(pcm->mmap_control, page_size);
+            pcm->ops->munmap(pcm->data, pcm->mmap_control, page_size);
     }
     pcm->mmap_status = NULL;
     pcm->mmap_control = NULL;
 }
 
-static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,
-                          char *buf, unsigned int src_offset,
-                          unsigned int frames)
-{
-    int size_bytes = pcm_frames_to_bytes(pcm, frames);
-    int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset);
-    int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset);
-
-    /* interleaved only atm */
-    if (pcm->flags & PCM_IN)
-        memcpy(buf + src_offset_bytes,
-               (char*)pcm->mmap_buffer + pcm_offset_bytes,
-               size_bytes);
-    else
-        memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,
-               buf + src_offset_bytes,
-               size_bytes);
-    return 0;
-}
-
-static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf,
-                                unsigned int offset, unsigned int size)
-{
-    void *pcm_areas;
-    int commit;
-    unsigned int pcm_offset, frames, count = 0;
-
-    while (size > 0) {
-        frames = size;
-        pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
-        pcm_areas_copy(pcm, pcm_offset, buf, offset, frames);
-        commit = pcm_mmap_commit(pcm, pcm_offset, frames);
-        if (commit < 0) {
-            oops(pcm, errno, "failed to commit %d frames\n", frames);
-            return commit;
-        }
-
-        offset += commit;
-        count += commit;
-        size -= commit;
-    }
-    return count;
-}
-
-int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,
-                       struct timespec *tstamp)
-{
-    int frames;
-    int rc;
-    snd_pcm_uframes_t hw_ptr;
-
-    if (!pcm_is_ready(pcm))
-        return -1;
-
-    rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC);
-    if (rc < 0)
-        return -1;
-
-    if ((pcm->mmap_status->state != PCM_STATE_RUNNING) &&
-            (pcm->mmap_status->state != PCM_STATE_DRAINING))
-        return -1;
-
-    *tstamp = pcm->mmap_status->tstamp;
-    if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0)
-        return -1;
-
-    hw_ptr = pcm->mmap_status->hw_ptr;
-    if (pcm->flags & PCM_IN)
-        frames = hw_ptr - pcm->mmap_control->appl_ptr;
-    else
-        frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
-
-    if (frames < 0)
-        frames += pcm->boundary;
-    else if (frames > (int)pcm->boundary)
-        frames -= pcm->boundary;
-
-    *avail = (unsigned int)frames;
-
-    return 0;
-}
-
-int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr, struct timespec *tstamp)
-{
-    int frames;
-    int rc;
-
-    if (pcm == NULL || hw_ptr == NULL || tstamp == NULL)
-        return oops(pcm, EINVAL, "pcm %p, hw_ptr %p, tstamp %p", pcm, hw_ptr, tstamp);
-
-    if (!pcm_is_ready(pcm))
-        return oops(pcm, errno, "pcm_is_ready failed");
-
-    rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);
-    if (rc < 0)
-        return oops(pcm, errno, "pcm_sync_ptr failed");
-
-    if (pcm->mmap_status == NULL)
-        return oops(pcm, EINVAL, "pcm %p, mmap_status is NULL", pcm);
-
-    if ((pcm->mmap_status->state != PCM_STATE_RUNNING) &&
-            (pcm->mmap_status->state != PCM_STATE_DRAINING))
-        return oops(pcm, ENOSYS, "invalid stream state %d", pcm->mmap_status->state);
-
-    *tstamp = pcm->mmap_status->tstamp;
-    if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0)
-        return oops(pcm, errno, "invalid time stamp");
-
-    *hw_ptr = pcm->mmap_status->hw_ptr;
-
-    return 0;
-}
-
-int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
-{
-    struct snd_xferi x;
-
-    if (pcm->flags & PCM_IN)
-        return -EINVAL;
-
-    x.buf = (void*)data;
-    x.frames = count / (pcm->config.channels *
-                        pcm_format_to_bits(pcm->config.format) / 8);
-
-    for (;;) {
-        if (!pcm->running) {
-            int prepare_error = pcm_prepare(pcm);
-            if (prepare_error)
-                return prepare_error;
-            if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
-                return oops(pcm, errno, "cannot write initial data");
-            pcm->running = 1;
-            return 0;
-        }
-        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
-            pcm->prepared = 0;
-            pcm->running = 0;
-            if (errno == EPIPE) {
-                /* we failed to make our window -- try to restart if we are
-                 * allowed to do so.  Otherwise, simply allow the EPIPE error to
-                 * propagate up to the app level */
-                pcm->underruns++;
-                if (pcm->flags & PCM_NORESTART)
-                    return -EPIPE;
-                continue;
-            }
-            return oops(pcm, errno, "cannot write stream data");
-        }
-        return 0;
-    }
-}
-
-int pcm_read(struct pcm *pcm, void *data, unsigned int count)
-{
-    struct snd_xferi x;
-
-    if (!(pcm->flags & PCM_IN))
-        return -EINVAL;
-
-    x.buf = data;
-    x.frames = count / (pcm->config.channels *
-                        pcm_format_to_bits(pcm->config.format) / 8);
-
-    for (;;) {
-        if (!pcm->running) {
-            if (pcm_start(pcm) < 0) {
-                fprintf(stderr, "start error");
-                return -errno;
-            }
-        }
-        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
-            pcm->prepared = 0;
-            pcm->running = 0;
-            if (errno == EPIPE) {
-                    /* we failed to make our window -- try to restart */
-                pcm->underruns++;
-                continue;
-            }
-            return oops(pcm, errno, "cannot read stream data");
-        }
-        return 0;
-    }
-}
-
 static struct pcm bad_pcm = {
     .fd = -1,
 };
 
+/** Gets the hardware parameters of a PCM, without created a PCM handle.
+ * @param card The card of the PCM.
+ *  The default card is zero.
+ * @param device The device of the PCM.
+ *  The default device is zero.
+ * @param flags Specifies whether the PCM is an input or output.
+ *  May be one of the following:
+ *   - @ref PCM_IN
+ *   - @ref PCM_OUT
+ * @return On success, the hardware parameters of the PCM; on failure, NULL.
+ * @ingroup libtinyalsa-pcm
+ */
 struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
                                   unsigned int flags)
 {
     struct snd_pcm_hw_params *params;
-    char fn[256];
+    void *snd_node = NULL, *data;
+    const struct pcm_ops *ops;
     int fd;
 
-    snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
-             flags & PCM_IN ? 'c' : 'p');
+    ops = &hw_ops;
+    fd = ops->open(card, device, flags, &data, snd_node);
 
-    fd = open(fn, O_RDWR);
+#ifdef TINYALSA_USES_PLUGINS
     if (fd < 0) {
-        fprintf(stderr, "cannot open device '%s'\n", fn);
+        int pcm_type;
+        snd_node = snd_utils_open_pcm(card, device);
+        pcm_type = snd_utils_get_node_type(snd_node);
+        if (!snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) {
+            fprintf(stderr, "no device (hw/plugin) for card(%u), device(%u)",
+                 card, device);
+            goto err_open;
+        }
+        ops = &plug_ops;
+        fd = ops->open(card, device, flags, &data, snd_node);
+    }
+#endif
+    if (fd < 0) {
+        fprintf(stderr, "cannot open card(%d) device (%d): %s\n",
+                card, device, strerror(errno));
         goto err_open;
     }
 
@@ -612,23 +719,36 @@
         goto err_calloc;
 
     param_init(params);
-    if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) {
+    if (ops->ioctl(data, SNDRV_PCM_IOCTL_HW_REFINE, params)) {
         fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno);
         goto err_hw_refine;
     }
 
-    close(fd);
+#ifdef TINYALSA_USES_PLUGINS
+    if (snd_node)
+        snd_utils_close_dev_node(snd_node);
+#endif
+    ops->close(data);
 
     return (struct pcm_params *)params;
 
 err_hw_refine:
     free(params);
 err_calloc:
-    close(fd);
+#ifdef TINYALSA_USES_PLUGINS
+    if (snd_node)
+        snd_utils_close_dev_node(snd_node);
+#endif
+    ops->close(data);
 err_open:
     return NULL;
 }
 
+/** Frees the hardware parameters returned by @ref pcm_params_get.
+ * @param pcm_params Hardware parameters of a PCM.
+ *  May be NULL.
+ * @ingroup libtinyalsa-pcm
+ */
 void pcm_params_free(struct pcm_params *pcm_params)
 {
     struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
@@ -688,7 +808,14 @@
     }
 }
 
-struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params,
+/** Gets a mask from a PCM's hardware parameters.
+ * @param pcm_params A PCM's hardware parameters.
+ * @param param The parameter to get.
+ * @return If @p pcm_params is NULL or @p param is not a mask, NULL is returned.
+ *  Otherwise, the mask associated with @p param is returned.
+ * @ingroup libtinyalsa-pcm
+ */
+const struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params,
                                      enum pcm_param param)
 {
     int p;
@@ -702,9 +829,15 @@
         return NULL;
     }
 
-    return (struct pcm_mask *)param_to_mask(params, p);
+    return (const struct pcm_mask *)param_to_mask(params, p);
 }
 
+/** Get the minimum of a specified PCM parameter.
+ * @param pcm_params A PCM parameters structure.
+ * @param param The specified parameter to get the minimum of.
+ * @returns On success, the parameter minimum.
+ *  On failure, zero.
+ */
 unsigned int pcm_params_get_min(const struct pcm_params *pcm_params,
                                 enum pcm_param param)
 {
@@ -721,26 +854,16 @@
     return param_get_min(params, p);
 }
 
-void pcm_params_set_min(struct pcm_params *pcm_params,
-                                enum pcm_param param, unsigned int val)
-{
-    struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
-    int p;
-
-    if (!params)
-        return;
-
-    p = pcm_param_to_alsa(param);
-    if (p < 0)
-        return;
-
-    param_set_min(params, p, val);
-}
-
+/** Get the maximum of a specified PCM parameter.
+ * @param pcm_params A PCM parameters structure.
+ * @param param The specified parameter to get the maximum of.
+ * @returns On success, the parameter maximum.
+ *  On failure, zero.
+ */
 unsigned int pcm_params_get_max(const struct pcm_params *pcm_params,
                                 enum pcm_param param)
 {
-    struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
+    const struct snd_pcm_hw_params *params = (const struct snd_pcm_hw_params *)pcm_params;
     int p;
 
     if (!params)
@@ -753,23 +876,7 @@
     return param_get_max(params, p);
 }
 
-void pcm_params_set_max(struct pcm_params *pcm_params,
-                                enum pcm_param param, unsigned int val)
-{
-    struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
-    int p;
-
-    if (!params)
-        return;
-
-    p = pcm_param_to_alsa(param);
-    if (p < 0)
-        return;
-
-    param_set_max(params, p, val);
-}
-
-static int pcm_mask_test(struct pcm_mask *m, unsigned int index)
+static int pcm_mask_test(const struct pcm_mask *m, unsigned int index)
 {
     const unsigned int bitshift = 5; /* for 32 bit integer */
     const unsigned int bitmask = (1 << bitshift) - 1;
@@ -781,7 +888,7 @@
     return (m->bits[element] >> (index & bitmask)) & 1;
 }
 
-static int pcm_mask_to_string(struct pcm_mask *m, char *string, unsigned int size,
+static int pcm_mask_to_string(const struct pcm_mask *m, char *string, unsigned int size,
                               char *mask_name,
                               const char * const *bit_array_name, size_t bit_array_size)
 {
@@ -808,7 +915,7 @@
 
 int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size)
 {
-    struct pcm_mask *m;
+    const struct pcm_mask *m;
     unsigned int min, max;
     unsigned int clipoffset, offset;
 
@@ -850,6 +957,12 @@
     return pcm_mask_test(pcm_params_get_mask(params, PCM_PARAM_FORMAT), alsa_format);
 }
 
+/** Closes a PCM returned by @ref pcm_open.
+ * @param pcm A PCM returned by @ref pcm_open.
+ *  May not be NULL.
+ * @return Always returns zero.
+ * @ingroup libtinyalsa-pcm
+ */
 int pcm_close(struct pcm *pcm)
 {
     if (pcm == &bad_pcm)
@@ -859,157 +972,113 @@
 
     if (pcm->flags & PCM_MMAP) {
         pcm_stop(pcm);
-        munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
+        pcm->ops->munmap(pcm->data, pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
     }
 
-    if (pcm->fd >= 0)
-        close(pcm->fd);
-    pcm->prepared = 0;
-    pcm->running = 0;
+    snd_utils_close_dev_node(pcm->snd_node);
+    pcm->ops->close(pcm->data);
     pcm->buffer_size = 0;
     pcm->fd = -1;
     free(pcm);
     return 0;
 }
 
+/** Opens a PCM by it's name.
+ * @param name The name of the PCM.
+ *  The name is given in the format: <i>hw</i>:<b>card</b>,<b>device</b>
+ * @param flags Specify characteristics and functionality about the pcm.
+ *  May be a bitwise AND of the following:
+ *   - @ref PCM_IN
+ *   - @ref PCM_OUT
+ *   - @ref PCM_MMAP
+ *   - @ref PCM_NOIRQ
+ *   - @ref PCM_MONOTONIC
+ * @param config The hardware and software parameters to open the PCM with.
+ * @returns A PCM structure.
+ *  If an error occurs allocating memory for the PCM, NULL is returned.
+ *  Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready.
+ *  If @ref pcm_is_ready, check @ref pcm_get_error for more information.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm *pcm_open_by_name(const char *name,
+                             unsigned int flags,
+                             const struct pcm_config *config)
+{
+  unsigned int card, device;
+  if ((name[0] != 'h')
+   || (name[1] != 'w')
+   || (name[2] != ':')) {
+    return NULL;
+  } else if (sscanf(&name[3], "%u,%u", &card, &device) != 2) {
+    return NULL;
+  }
+  return pcm_open(card, device, flags, config);
+}
+
+/** Opens a PCM.
+ * @param card The card that the pcm belongs to.
+ *  The default card is zero.
+ * @param device The device that the pcm belongs to.
+ *  The default device is zero.
+ * @param flags Specify characteristics and functionality about the pcm.
+ *  May be a bitwise AND of the following:
+ *   - @ref PCM_IN
+ *   - @ref PCM_OUT
+ *   - @ref PCM_MMAP
+ *   - @ref PCM_NOIRQ
+ *   - @ref PCM_MONOTONIC
+ * @param config The hardware and software parameters to open the PCM with.
+ * @returns A PCM structure.
+ *  If an error occurs allocating memory for the PCM, NULL is returned.
+ *  Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready.
+ *  If @ref pcm_is_ready, check @ref pcm_get_error for more information.
+ * @ingroup libtinyalsa-pcm
+ */
 struct pcm *pcm_open(unsigned int card, unsigned int device,
-                     unsigned int flags, struct pcm_config *config)
+                     unsigned int flags, const struct pcm_config *config)
 {
     struct pcm *pcm;
     struct snd_pcm_info info;
-    struct snd_pcm_hw_params params;
-    struct snd_pcm_sw_params sparams;
-    char fn[256];
     int rc;
 
-    if (!config) {
-        return &bad_pcm; /* TODO: could support default config here */
-    }
     pcm = calloc(1, sizeof(struct pcm));
     if (!pcm)
-        return &bad_pcm; /* TODO: could support default config here */
+        return &bad_pcm;
 
-    pcm->config = *config;
+    /* Default to hw_ops, attemp plugin open only if hw (/dev/snd/pcm*) open fails */
+    pcm->ops = &hw_ops;
+    pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, NULL);
 
-    snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
-             flags & PCM_IN ? 'c' : 'p');
+#ifdef TINYALSA_USES_PLUGINS
+    if (pcm->fd < 0) {
+        int pcm_type;
+        pcm->snd_node = snd_utils_open_pcm(card, device);
+        pcm_type = snd_utils_get_node_type(pcm->snd_node);
+        if (!pcm->snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) {
+            oops(pcm, -ENODEV, "no device (hw/plugin) for card(%u), device(%u)",
+                 card, device);
+            goto fail_close_dev_node;
+        }
+        pcm->ops = &plug_ops;
+        pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);
+    }
+#endif
+    if (pcm->fd < 0) {
+        oops(pcm, errno, "cannot open device (%u) for card (%u)",
+             device, card);
+        goto fail_close_dev_node;
+    }
 
     pcm->flags = flags;
-    pcm->fd = open(fn, O_RDWR|O_NONBLOCK);
-    if (pcm->fd < 0) {
-        oops(pcm, errno, "cannot open device '%s'", fn);
-        return pcm;
-    }
 
-    if (fcntl(pcm->fd, F_SETFL, fcntl(pcm->fd, F_GETFL) &
-              ~O_NONBLOCK) < 0) {
-        oops(pcm, errno, "failed to reset blocking mode '%s'", fn);
-        goto fail_close;
-    }
-
-    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
+    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) {
         oops(pcm, errno, "cannot get info");
         goto fail_close;
     }
     pcm->subdevice = info.subdevice;
 
-    param_init(&params);
-    param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
-                   pcm_format_to_alsa(config->format));
-    param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
-                   SNDRV_PCM_SUBFORMAT_STD);
-    param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
-    param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
-                  pcm_format_to_bits(config->format));
-    param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
-                  pcm_format_to_bits(config->format) * config->channels);
-    param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
-                  config->channels);
-    param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
-    param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);
-
-    if (flags & PCM_NOIRQ) {
-        if (!(flags & PCM_MMAP)) {
-            oops(pcm, EINVAL, "noirq only currently supported with mmap().");
-            goto fail_close;
-        }
-
-        params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
-        pcm->noirq_frames_per_msec = config->rate / 1000;
-    }
-
-    if (flags & PCM_MMAP)
-        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
-                       SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
-    else
-        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
-                       SNDRV_PCM_ACCESS_RW_INTERLEAVED);
-
-    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
-        oops(pcm, errno, "cannot set hw params");
+    if (pcm_set_config(pcm, config) != 0)
         goto fail_close;
-    }
-
-    /* get our refined hw_params */
-    config->period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
-    config->period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);
-    pcm->buffer_size = config->period_count * config->period_size;
-
-    if (flags & PCM_MMAP) {
-        pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
-                                PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
-        if (pcm->mmap_buffer == MAP_FAILED) {
-            oops(pcm, errno, "failed to mmap buffer %d bytes\n",
-                 pcm_frames_to_bytes(pcm, pcm->buffer_size));
-            goto fail_close;
-        }
-    }
-
-    memset(&sparams, 0, sizeof(sparams));
-    sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
-    sparams.period_step = 1;
-
-    if (!config->start_threshold) {
-        if (pcm->flags & PCM_IN)
-            pcm->config.start_threshold = sparams.start_threshold = 1;
-        else
-            pcm->config.start_threshold = sparams.start_threshold =
-                config->period_count * config->period_size / 2;
-    } else
-        sparams.start_threshold = config->start_threshold;
-
-    /* pick a high stop threshold - todo: does this need further tuning */
-    if (!config->stop_threshold) {
-        if (pcm->flags & PCM_IN)
-            pcm->config.stop_threshold = sparams.stop_threshold =
-                config->period_count * config->period_size * 10;
-        else
-            pcm->config.stop_threshold = sparams.stop_threshold =
-                config->period_count * config->period_size;
-    }
-    else
-        sparams.stop_threshold = config->stop_threshold;
-
-    if (!pcm->config.avail_min) {
-        if (pcm->flags & PCM_MMAP)
-            pcm->config.avail_min = sparams.avail_min = pcm->config.period_size;
-        else
-            pcm->config.avail_min = sparams.avail_min = 1;
-    } else
-        sparams.avail_min = config->avail_min;
-
-    sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
-    sparams.silence_threshold = config->silence_threshold;
-    sparams.silence_size = config->silence_size;
-    pcm->boundary = sparams.boundary = pcm->buffer_size;
-
-    while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
-        pcm->boundary *= 2;
-
-    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
-        oops(pcm, errno, "cannot set sw params");
-        goto fail;
-    }
 
     rc = pcm_hw_mmap_status(pcm);
     if (rc < 0) {
@@ -1020,7 +1089,7 @@
 #ifdef SNDRV_PCM_IOCTL_TTSTAMP
     if (pcm->flags & PCM_MONOTONIC) {
         int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
-        rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
+        rc = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
         if (rc < 0) {
             oops(pcm, errno, "cannot set timestamp type");
             goto fail;
@@ -1028,58 +1097,120 @@
     }
 #endif
 
-    pcm->underruns = 0;
+    /* prepare here so the user does not need to do this later */
+    if (pcm_prepare(pcm))
+        goto fail;
+
+    pcm->xruns = 0;
     return pcm;
 
 fail:
+    pcm_hw_munmap_status(pcm);
     if (flags & PCM_MMAP)
-        munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
+        pcm->ops->munmap(pcm->data, pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
 fail_close:
-    close(pcm->fd);
-    pcm->fd = -1;
-    return pcm;
+    pcm->ops->close(pcm->data);
+fail_close_dev_node:
+#ifdef TINYALSA_USES_PLUGINS
+    if (pcm->snd_node)
+        snd_utils_close_dev_node(pcm->snd_node);
+#endif
+    free(pcm);
+    return &bad_pcm;
 }
 
-int pcm_is_ready(struct pcm *pcm)
+/** Checks if a PCM file has been opened without error.
+ * @param pcm A PCM handle.
+ *  May be NULL.
+ * @return If a PCM's file descriptor is not valid or the pointer is NULL, it returns zero.
+ *  Otherwise, the function returns one.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_is_ready(const struct pcm *pcm)
 {
-    return pcm->fd >= 0;
+    if (pcm != NULL) {
+        return pcm->fd >= 0;
+    }
+    return 0;
 }
 
+/** Links two PCMs.
+ * After this function is called, the two PCMs will prepare, start and stop in sync (at the same time).
+ * If an error occurs, the error message will be written to @p pcm1.
+ * @param pcm1 A PCM handle.
+ * @param pcm2 Another PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_link(struct pcm *pcm1, struct pcm *pcm2)
+{
+    int err = ioctl(pcm1->fd, SNDRV_PCM_IOCTL_LINK, pcm2->fd);
+    if (err == -1) {
+        return oops(pcm1, errno, "cannot link PCM");
+    }
+    return 0;
+}
+
+/** Unlinks a PCM.
+ * @see @ref pcm_link
+ * @param pcm A PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_unlink(struct pcm *pcm)
+{
+    int err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_UNLINK);
+    if (err == -1) {
+        return oops(pcm, errno, "cannot unlink PCM");
+    }
+    return 0;
+}
+
+/** Prepares a PCM, if it has not been prepared already.
+ * @param pcm A PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
 int pcm_prepare(struct pcm *pcm)
 {
-    if (pcm->prepared)
-        return 0;
-
-    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0)
+    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_PREPARE) < 0)
         return oops(pcm, errno, "cannot prepare channel");
 
-    pcm->prepared = 1;
+    /* get appl_ptr and avail_min from kernel */
+    pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
+
     return 0;
 }
 
+/** Starts a PCM.
+ * @param pcm A PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
 int pcm_start(struct pcm *pcm)
 {
-    int prepare_error = pcm_prepare(pcm);
-    if (prepare_error)
-        return prepare_error;
+    /* set appl_ptr and avail_min in kernel */
+    if (pcm_sync_ptr(pcm, 0) < 0)
+        return -1;
 
-    if (pcm->flags & PCM_MMAP)
-	    pcm_sync_ptr(pcm, 0);
+    if (pcm->mmap_status->state != PCM_STATE_RUNNING) {
+        if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START) < 0)
+            return oops(pcm, errno, "cannot start channel");
+    }
 
-    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0)
-        return oops(pcm, errno, "cannot start channel");
-
-    pcm->running = 1;
     return 0;
 }
 
+/** Stops a PCM.
+ * @param pcm A PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
 int pcm_stop(struct pcm *pcm)
 {
-    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0)
+    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_DROP) < 0)
         return oops(pcm, errno, "cannot stop channel");
 
-    pcm->prepared = 0;
-    pcm->running = 0;
     return 0;
 }
 
@@ -1091,7 +1222,7 @@
 
     if (avail < 0)
         avail += pcm->boundary;
-    else if (avail > (int)pcm->boundary)
+    else if (avail >= (int)pcm->boundary)
         avail -= pcm->boundary;
 
     return avail;
@@ -1107,7 +1238,6 @@
 
 int pcm_mmap_avail(struct pcm *pcm)
 {
-    pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);
     if (pcm->flags & PCM_IN)
         return pcm_mmap_capture_avail(pcm);
     else
@@ -1152,21 +1282,125 @@
     return 0;
 }
 
-int pcm_mmap_commit(struct pcm *pcm, unsigned int offset __attribute__((unused)), unsigned int frames)
+static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,
+                          char *buf, unsigned int src_offset,
+                          unsigned int frames)
 {
+    int size_bytes = pcm_frames_to_bytes(pcm, frames);
+    int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset);
+    int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset);
+
+    /* interleaved only atm */
+    if (pcm->flags & PCM_IN)
+        memcpy(buf + src_offset_bytes,
+               (char*)pcm->mmap_buffer + pcm_offset_bytes,
+               size_bytes);
+    else
+        memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,
+               buf + src_offset_bytes,
+               size_bytes);
+    return 0;
+}
+
+int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames)
+{
+    int ret;
+
+    /* not used */
+    (void) offset;
+
     /* update the application pointer in userspace and kernel */
     pcm_mmap_appl_forward(pcm, frames);
-    pcm_sync_ptr(pcm, 0);
+    ret = pcm_sync_ptr(pcm, 0);
+    if (ret != 0){
+        printf("%d\n", ret);
+        return ret;
+    }
 
     return frames;
 }
 
+static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf,
+                                unsigned int offset, unsigned int size)
+{
+    void *pcm_areas;
+    int commit;
+    unsigned int pcm_offset, frames, count = 0;
+
+    while (pcm_mmap_avail(pcm) && size) {
+        frames = size;
+        pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
+        pcm_areas_copy(pcm, pcm_offset, buf, offset, frames);
+        commit = pcm_mmap_commit(pcm, pcm_offset, frames);
+        if (commit < 0) {
+            oops(pcm, commit, "failed to commit %d frames\n", frames);
+            return commit;
+        }
+
+        offset += commit;
+        count += commit;
+        size -= commit;
+    }
+    return count;
+}
+
+int pcm_get_poll_fd(struct pcm *pcm)
+{
+    return pcm->fd;
+}
+
 int pcm_avail_update(struct pcm *pcm)
 {
-    pcm_sync_ptr(pcm, 0);
+    pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
     return pcm_mmap_avail(pcm);
 }
 
+/** Returns available frames in pcm buffer and corresponding time stamp.
+ * The clock is CLOCK_MONOTONIC if flag @ref PCM_MONOTONIC was specified in @ref pcm_open,
+ * otherwise the clock is CLOCK_REALTIME.
+ * For an input stream, frames available are frames ready for the application to read.
+ * For an output stream, frames available are the number of empty frames available for the application to write.
+ * @param pcm A PCM handle.
+ * @param avail The number of available frames
+ * @param tstamp The timestamp
+ * @return On success, zero is returned; on failure, negative one.
+ */
+int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,
+                       struct timespec *tstamp)
+{
+    int checking;
+    int tmp;
+
+    if (!pcm_is_ready(pcm))
+        return -1;
+
+    checking = 0;
+
+again:
+
+    tmp = pcm_avail_update(pcm);
+    if (tmp < 0)
+        return tmp; /* error */
+
+    if (checking && (unsigned int) tmp == *avail)
+        return 0;
+
+    *avail = (unsigned int) tmp;
+    *tstamp = pcm->mmap_status->tstamp;
+
+    /*
+     * When status is mmaped, get avail again to ensure
+     * valid timestamp.
+     */
+    if (!pcm->sync_ptr) {
+        checking = 1;
+        goto again;
+    }
+
+    /* SYNC_PTR ioctl was used, no need to check avail */
+    return 0;
+}
+
 int pcm_state(struct pcm *pcm)
 {
     int err = pcm_sync_ptr(pcm, 0);
@@ -1176,26 +1410,25 @@
     return pcm->mmap_status->state;
 }
 
-int pcm_set_avail_min(struct pcm *pcm, int avail_min)
-{
-    if ((~pcm->flags) & (PCM_MMAP | PCM_NOIRQ))
-        return -ENOSYS;
-
-    pcm->config.avail_min = avail_min;
-    return 0;
-}
-
+/** Waits for frames to be available for read or write operations.
+ * @param pcm A PCM handle.
+ * @param timeout The maximum amount of time to wait for, in terms of milliseconds.
+ * @returns If frames became available, one is returned.
+ *  If a timeout occured, zero is returned.
+ *  If an error occured, a negative number is returned.
+ * @ingroup libtinyalsa-pcm
+ */
 int pcm_wait(struct pcm *pcm, int timeout)
 {
     struct pollfd pfd;
     int err;
 
     pfd.fd = pcm->fd;
-    pfd.events = POLLOUT | POLLERR | POLLNVAL;
+    pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
 
     do {
         /* let's wait for avail or timeout */
-        err = poll(&pfd, 1, timeout);
+        err = pcm->ops->poll(pcm->data, &pfd, 1, timeout);
         if (err < 0)
             return -errno;
 
@@ -1226,97 +1459,88 @@
     return 1;
 }
 
-int pcm_get_poll_fd(struct pcm *pcm)
+/*
+ * Transfer data to/from mmaped buffer. This imitates the
+ * behavior of read/write system calls.
+ *
+ * However, this doesn't seems to offer any advantage over
+ * the read/write syscalls. Should it be removed?
+ */
+int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames)
 {
-    return pcm->fd;
-}
+    int is_playback;
 
-int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes)
-{
-    int err = 0, frames, avail;
-    unsigned int offset = 0, count;
+    int state;
+    unsigned int avail;
+    unsigned int user_offset;
 
-    if (bytes == 0)
+    int err;
+    int tmp;
+
+    is_playback = !(pcm->flags & PCM_IN);
+
+    if (frames == 0)
         return 0;
 
-    count = pcm_bytes_to_frames(pcm, bytes);
+    /* update hardware pointer and get state */
+    err = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC |
+                            SNDRV_PCM_SYNC_PTR_APPL |
+                            SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
+    if (err == -1)
+        return -1;
+    state = pcm->mmap_status->state;
 
-    while (count > 0) {
-
-        /* get the available space for writing new frames */
-        avail = pcm_avail_update(pcm);
-        if (avail < 0) {
-            fprintf(stderr, "cannot determine available mmap frames");
-            return err;
-        }
-
-        /* start the audio if we reach the threshold */
-	    if (!pcm->running &&
-            (pcm->buffer_size - avail) >= pcm->config.start_threshold) {
-            if (pcm_start(pcm) < 0) {
-               fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n",
-                    (unsigned int)pcm->mmap_status->hw_ptr,
-                    (unsigned int)pcm->mmap_control->appl_ptr,
-                    avail);
-                return -errno;
-            }
-            pcm->wait_for_avail_min = 0;
-        }
-
-        /* sleep until we have space to write new frames */
-        if (pcm->running) {
-            /* enable waiting for avail_min threshold when less frames than we have to write
-             * are available. */
-            if (!pcm->wait_for_avail_min && (count > (unsigned int)avail))
-                pcm->wait_for_avail_min = 1;
-
-            if (pcm->wait_for_avail_min && (avail < pcm->config.avail_min)) {
-                int time = -1;
-
-                /* disable waiting for avail_min threshold to allow small amounts of data to be
-                 * written without waiting as long as there is enough room in buffer. */
-                pcm->wait_for_avail_min = 0;
-
-                if (pcm->flags & PCM_NOIRQ)
-                    time = (pcm->config.avail_min - avail) / pcm->noirq_frames_per_msec;
-
-                err = pcm_wait(pcm, time);
-                if (err < 0) {
-                    pcm->prepared = 0;
-                    pcm->running = 0;
-                    oops(pcm, errno, "wait error: hw 0x%x app 0x%x avail 0x%x\n",
-                        (unsigned int)pcm->mmap_status->hw_ptr,
-                        (unsigned int)pcm->mmap_control->appl_ptr,
-                        avail);
-                    pcm->mmap_control->appl_ptr = 0;
-                    return err;
-                }
-                continue;
-            }
-        }
-
-        frames = count;
-        if (frames > avail)
-            frames = avail;
-
-        if (!frames)
-            break;
-
-        /* copy frames from buffer */
-        frames = pcm_mmap_transfer_areas(pcm, (void *)buffer, offset, frames);
-        if (frames < 0) {
-            fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n",
-                    (unsigned int)pcm->mmap_status->hw_ptr,
-                    (unsigned int)pcm->mmap_control->appl_ptr,
-                    avail);
-            return frames;
-        }
-
-        offset += frames;
-        count -= frames;
+    /*
+     * If frames < start_threshold, wait indefinitely.
+     * Another thread may start capture
+     */
+    if (!is_playback && state == PCM_STATE_PREPARED &&
+        frames >= pcm->config.start_threshold) {
+            err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START);
+        if (err == -1)
+            return -1;
+        /* state = PCM_STATE_RUNNING */
     }
 
-    return 0;
+    avail = pcm_mmap_avail(pcm);
+    user_offset = 0;
+
+    while (frames) {
+        if (!avail) {
+            if (pcm->flags & PCM_NONBLOCK) {
+                errno = EAGAIN;
+                break;
+            }
+
+            /* wait for interrupt */
+            err = pcm_wait(pcm, -1);
+            if (err < 0) {
+                errno = -err;
+                break;
+            }
+
+            /* get hardware pointer */
+            avail = pcm_avail_update(pcm);
+        }
+
+        tmp = pcm_mmap_transfer_areas(pcm, buffer, user_offset, frames);
+        if (tmp < 0)
+            break;
+
+        user_offset += tmp;
+        frames -= tmp;
+        avail -= tmp;
+
+        /* start playback if written >= start_threshold */
+        if (is_playback && state == PCM_STATE_PREPARED &&
+            pcm->buffer_size - avail >= pcm->config.start_threshold) {
+            err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START);
+            if (err == -1)
+                break;
+        }
+    }
+
+    return user_offset ? (int) user_offset : -1;
 }
 
 int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count)
@@ -1324,7 +1548,8 @@
     if ((~pcm->flags) & (PCM_OUT | PCM_MMAP))
         return -ENOSYS;
 
-    return pcm_mmap_transfer(pcm, (void *)data, count);
+    return pcm_mmap_transfer(pcm, (void *)data,
+                             pcm_bytes_to_frames(pcm, count));
 }
 
 int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count)
@@ -1332,20 +1557,196 @@
     if ((~pcm->flags) & (PCM_IN | PCM_MMAP))
         return -ENOSYS;
 
-    return pcm_mmap_transfer(pcm, data, count);
+    return pcm_mmap_transfer(pcm, data, pcm_bytes_to_frames(pcm, count));
 }
 
-int pcm_ioctl(struct pcm *pcm, int request, ...)
+/* Returns current read/write position in the mmap buffer with associated time stamp. */
+int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr, struct timespec *tstamp)
 {
-    va_list ap;
-    void * arg;
+    int rc;
+
+    if (pcm == NULL || hw_ptr == NULL || tstamp == NULL)
+        return oops(pcm, EINVAL, "pcm %p, hw_ptr %p, tstamp %p", pcm, hw_ptr, tstamp);
 
     if (!pcm_is_ready(pcm))
+        return oops(pcm, errno, "pcm_is_ready failed");
+
+    rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);
+    if (rc < 0)
+        return oops(pcm, errno, "pcm_sync_ptr failed");
+
+    if (pcm->mmap_status == NULL)
+        return oops(pcm, EINVAL, "pcm %p, mmap_status is NULL", pcm);
+
+    if ((pcm->mmap_status->state != PCM_STATE_RUNNING) &&
+            (pcm->mmap_status->state != PCM_STATE_DRAINING))
+        return oops(pcm, ENOSYS, "invalid stream state %d", pcm->mmap_status->state);
+
+    *tstamp = pcm->mmap_status->tstamp;
+    if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0)
+        return oops(pcm, errno, "invalid time stamp");
+
+    *hw_ptr = pcm->mmap_status->hw_ptr;
+
+    return 0;
+}
+
+static int pcm_rw_transfer(struct pcm *pcm, void *data, unsigned int frames)
+{
+    int is_playback;
+
+    struct snd_xferi transfer;
+    int res;
+
+    is_playback = !(pcm->flags & PCM_IN);
+
+    transfer.buf = data;
+    transfer.frames = frames;
+    transfer.result = 0;
+
+    res = pcm->ops->ioctl(pcm->data, is_playback
+                          ? SNDRV_PCM_IOCTL_WRITEI_FRAMES
+                          : SNDRV_PCM_IOCTL_READI_FRAMES, &transfer);
+
+    return res == 0 ? (int) transfer.result : -1;
+}
+
+static int pcm_generic_transfer(struct pcm *pcm, void *data,
+                                unsigned int frames)
+{
+    int res;
+
+#if UINT_MAX > TINYALSA_FRAMES_MAX
+    if (frames > TINYALSA_FRAMES_MAX)
+        return -EINVAL;
+#endif
+    if (frames > INT_MAX)
+        return -EINVAL;
+
+again:
+
+    if (pcm->flags & PCM_MMAP)
+        res = pcm_mmap_transfer(pcm, data, frames);
+    else
+        res = pcm_rw_transfer(pcm, data, frames);
+
+    if (res < 0) {
+        switch (errno) {
+        case EPIPE:
+            pcm->xruns++;
+            /* fallthrough */
+        case ESTRPIPE:
+            /*
+             * Try to restart if we are allowed to do so.
+             * Otherwise, return error.
+             */
+            if (pcm->flags & PCM_NORESTART || pcm_prepare(pcm))
+                return -1;
+            goto again;
+        case EAGAIN:
+            if (pcm->flags & PCM_NONBLOCK)
+                return -1;
+            /* fallthrough */
+        default:
+            return oops(pcm, errno, "cannot read/write stream data");
+        }
+    }
+
+    return res;
+}
+
+/** Writes audio samples to PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_OUT flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param frame_count The number of frames occupied by the sample array.
+ *  This value should not be greater than @ref TINYALSA_FRAMES_MAX
+ *  or INT_MAX.
+ * @return On success, this function returns the number of frames written; otherwise, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count)
+{
+    if (pcm->flags & PCM_IN)
+        return -EINVAL;
+
+    return pcm_generic_transfer(pcm, (void*) data, frame_count);
+}
+
+/** Reads audio samples from PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_IN flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param frame_count The number of frames occupied by the sample array.
+ *  This value should not be greater than @ref TINYALSA_FRAMES_MAX
+ *  or INT_MAX.
+ * @return On success, this function returns the number of frames written; otherwise, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count)
+{
+    if (!(pcm->flags & PCM_IN))
+        return -EINVAL;
+
+    return pcm_generic_transfer(pcm, data, frame_count);
+}
+
+/** Writes audio samples to PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_OUT flag.
+ * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param count The number of bytes occupied by the sample array.
+ * @return On success, this function returns zero; otherwise, a negative number.
+ * @deprecated
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
+{
+    unsigned int requested_frames = pcm_bytes_to_frames(pcm, count);
+    int ret = pcm_writei(pcm, data, requested_frames);
+
+    if (ret < 0)
+        return ret;
+
+    return ((unsigned int )ret == requested_frames) ? 0 : -EIO;
+}
+
+/** Reads audio samples from PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_IN flag.
+ * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param count The number of bytes occupied by the sample array.
+ * @return On success, this function returns zero; otherwise, a negative number.
+ * @deprecated
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_read(struct pcm *pcm, void *data, unsigned int count)
+{
+    unsigned int requested_frames = pcm_bytes_to_frames(pcm, count);
+    int ret = pcm_readi(pcm, data, requested_frames);
+
+    if (ret < 0)
+        return ret;
+
+    return ((unsigned int )ret == requested_frames) ? 0 : -EIO;
+}
+
+/** Gets the delay of the PCM, in terms of frames.
+ * @param pcm A PCM handle.
+ * @returns On success, the delay of the PCM.
+ *  On failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+long pcm_get_delay(struct pcm *pcm)
+{
+    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DELAY, &pcm->pcm_delay) < 0)
         return -1;
 
-    va_start(ap, request);
-    arg = va_arg(ap, void *);
-    va_end(ap);
-
-    return ioctl(pcm->fd, request, arg);
+    return pcm->pcm_delay;
 }