| /* pcm.c | 
 | ** | 
 | ** Copyright 2011, The Android Open Source Project | 
 | ** | 
 | ** Redistribution and use in source and binary forms, with or without | 
 | ** modification, are permitted provided that the following conditions are met: | 
 | **     * Redistributions of source code must retain the above copyright | 
 | **       notice, this list of conditions and the following disclaimer. | 
 | **     * Redistributions in binary form must reproduce the above copyright | 
 | **       notice, this list of conditions and the following disclaimer in the | 
 | **       documentation and/or other materials provided with the distribution. | 
 | **     * Neither the name of The Android Open Source Project nor the names of | 
 | **       its contributors may be used to endorse or promote products derived | 
 | **       from this software without specific prior written permission. | 
 | ** | 
 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND | 
 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE | 
 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 
 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | 
 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH | 
 | ** DAMAGE. | 
 | */ | 
 |  | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <fcntl.h> | 
 | #include <stdarg.h> | 
 | #include <string.h> | 
 | #include <errno.h> | 
 | #include <unistd.h> | 
 | #include <poll.h> | 
 |  | 
 | #include <sys/ioctl.h> | 
 | #include <sys/mman.h> | 
 | #include <sys/time.h> | 
 | #include <limits.h> | 
 |  | 
 | #include <linux/ioctl.h> | 
 | #define __force | 
 | #define __bitwise | 
 | #define __user | 
 | #include <sound/asound.h> | 
 |  | 
 | #include <tinyalsa/asoundlib.h> | 
 |  | 
 | #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL | 
 |  | 
 | /* Logs information into a string; follows snprintf() in that | 
 |  * offset may be greater than size, and though no characters are copied | 
 |  * into string, characters are still counted into offset. */ | 
 | #define STRLOG(string, offset, size, ...) \ | 
 |     do { int temp, clipoffset = offset > size ? size : offset; \ | 
 |          temp = snprintf(string + clipoffset, size - clipoffset, __VA_ARGS__); \ | 
 |          if (temp > 0) offset += temp; } while (0) | 
 |  | 
 | #ifndef ARRAY_SIZE | 
 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) | 
 | #endif | 
 |  | 
 | /* refer to SNDRV_PCM_ACCESS_##index in sound/asound.h. */ | 
 | static const char * const access_lookup[] = { | 
 |         "MMAP_INTERLEAVED", | 
 |         "MMAP_NONINTERLEAVED", | 
 |         "MMAP_COMPLEX", | 
 |         "RW_INTERLEAVED", | 
 |         "RW_NONINTERLEAVED", | 
 | }; | 
 |  | 
 | /* refer to SNDRV_PCM_FORMAT_##index in sound/asound.h. */ | 
 | static const char * const format_lookup[] = { | 
 |         /*[0] =*/ "S8", | 
 |         "U8", | 
 |         "S16_LE", | 
 |         "S16_BE", | 
 |         "U16_LE", | 
 |         "U16_BE", | 
 |         "S24_LE", | 
 |         "S24_BE", | 
 |         "U24_LE", | 
 |         "U24_BE", | 
 |         "S32_LE", | 
 |         "S32_BE", | 
 |         "U32_LE", | 
 |         "U32_BE", | 
 |         "FLOAT_LE", | 
 |         "FLOAT_BE", | 
 |         "FLOAT64_LE", | 
 |         "FLOAT64_BE", | 
 |         "IEC958_SUBFRAME_LE", | 
 |         "IEC958_SUBFRAME_BE", | 
 |         "MU_LAW", | 
 |         "A_LAW", | 
 |         "IMA_ADPCM", | 
 |         "MPEG", | 
 |         /*[24] =*/ "GSM", | 
 |         /* gap */ | 
 |         [31] = "SPECIAL", | 
 |         "S24_3LE", | 
 |         "S24_3BE", | 
 |         "U24_3LE", | 
 |         "U24_3BE", | 
 |         "S20_3LE", | 
 |         "S20_3BE", | 
 |         "U20_3LE", | 
 |         "U20_3BE", | 
 |         "S18_3LE", | 
 |         "S18_3BE", | 
 |         "U18_3LE", | 
 |         /*[43] =*/ "U18_3BE", | 
 | #if 0 | 
 |         /* recent additions, may not be present on local asound.h */ | 
 |         "G723_24", | 
 |         "G723_24_1B", | 
 |         "G723_40", | 
 |         "G723_40_1B", | 
 |         "DSD_U8", | 
 |         "DSD_U16_LE", | 
 | #endif | 
 | }; | 
 |  | 
 | /* refer to SNDRV_PCM_SUBFORMAT_##index in sound/asound.h. */ | 
 | static const char * const subformat_lookup[] = { | 
 |         "STD", | 
 | }; | 
 |  | 
 | static inline int param_is_mask(int p) | 
 | { | 
 |     return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && | 
 |         (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); | 
 | } | 
 |  | 
 | static inline int param_is_interval(int p) | 
 | { | 
 |     return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && | 
 |         (p <= SNDRV_PCM_HW_PARAM_LAST_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]); | 
 | } | 
 |  | 
 | static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n) | 
 | { | 
 |     return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); | 
 | } | 
 |  | 
 | static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit) | 
 | { | 
 |     if (bit >= SNDRV_MASK_MAX) | 
 |         return; | 
 |     if (param_is_mask(n)) { | 
 |         struct snd_mask *m = param_to_mask(p, n); | 
 |         m->bits[0] = 0; | 
 |         m->bits[1] = 0; | 
 |         m->bits[bit >> 5] |= (1 << (bit & 31)); | 
 |     } | 
 | } | 
 |  | 
 | static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val) | 
 | { | 
 |     if (param_is_interval(n)) { | 
 |         struct snd_interval *i = param_to_interval(p, n); | 
 |         i->min = val; | 
 |     } | 
 | } | 
 |  | 
 | static unsigned int param_get_min(struct snd_pcm_hw_params *p, int n) | 
 | { | 
 |     if (param_is_interval(n)) { | 
 |         struct snd_interval *i = param_to_interval(p, n); | 
 |         return i->min; | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | static void param_set_max(struct snd_pcm_hw_params *p, int n, unsigned int val) | 
 | { | 
 |     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); | 
 |         return i->max; | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val) | 
 | { | 
 |     if (param_is_interval(n)) { | 
 |         struct snd_interval *i = param_to_interval(p, n); | 
 |         i->min = val; | 
 |         i->max = val; | 
 |         i->integer = 1; | 
 |     } | 
 | } | 
 |  | 
 | static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n) | 
 | { | 
 |     if (param_is_interval(n)) { | 
 |         struct snd_interval *i = param_to_interval(p, n); | 
 |         if (i->integer) | 
 |             return i->max; | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | static void param_init(struct snd_pcm_hw_params *p) | 
 | { | 
 |     int n; | 
 |  | 
 |     memset(p, 0, sizeof(*p)); | 
 |     for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK; | 
 |          n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) { | 
 |             struct snd_mask *m = param_to_mask(p, n); | 
 |             m->bits[0] = ~0; | 
 |             m->bits[1] = ~0; | 
 |     } | 
 |     for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; | 
 |          n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { | 
 |             struct snd_interval *i = param_to_interval(p, n); | 
 |             i->min = 0; | 
 |             i->max = ~0; | 
 |     } | 
 |     p->rmask = ~0U; | 
 |     p->cmask = 0; | 
 |     p->info = ~0U; | 
 | } | 
 |  | 
 | #define PCM_ERROR_MAX 128 | 
 |  | 
 | struct pcm { | 
 |     int fd; | 
 |     unsigned int flags; | 
 |     int running:1; | 
 |     int prepared:1; | 
 |     int underruns; | 
 |     unsigned int buffer_size; | 
 |     unsigned int boundary; | 
 |     char error[PCM_ERROR_MAX]; | 
 |     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; | 
 | }; | 
 |  | 
 | unsigned int pcm_get_buffer_size(struct pcm *pcm) | 
 | { | 
 |     return pcm->buffer_size; | 
 | } | 
 |  | 
 | const char* pcm_get_error(struct pcm *pcm) | 
 | { | 
 |     return pcm->error; | 
 | } | 
 |  | 
 | static int oops(struct pcm *pcm, int e, const char *fmt, ...) | 
 | { | 
 |     va_list ap; | 
 |     int sz; | 
 |  | 
 |     va_start(ap, fmt); | 
 |     vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap); | 
 |     va_end(ap); | 
 |     sz = strlen(pcm->error); | 
 |  | 
 |     if (errno) | 
 |         snprintf(pcm->error + sz, PCM_ERROR_MAX - sz, | 
 |                  ": %s", strerror(e)); | 
 |     return -1; | 
 | } | 
 |  | 
 | static unsigned int pcm_format_to_alsa(enum pcm_format format) | 
 | { | 
 |     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; | 
 |     }; | 
 | } | 
 |  | 
 | unsigned int pcm_format_to_bits(enum pcm_format format) | 
 | { | 
 |     switch (format) { | 
 |     case PCM_FORMAT_S32_LE: | 
 |     case PCM_FORMAT_S24_LE: | 
 |         return 32; | 
 |     case PCM_FORMAT_S24_3LE: | 
 |         return 24; | 
 |     default: | 
 |     case PCM_FORMAT_S16_LE: | 
 |         return 16; | 
 |     }; | 
 | } | 
 |  | 
 | unsigned int pcm_bytes_to_frames(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) | 
 | { | 
 |     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) { | 
 |         pcm->sync_ptr->flags = flags; | 
 |         if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) | 
 |             return -1; | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | 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); | 
 |     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); | 
 |     if (pcm->mmap_control == MAP_FAILED) | 
 |         pcm->mmap_control = NULL; | 
 |     if (!pcm->mmap_control) { | 
 |         munmap(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; | 
 |  | 
 | mmap_error: | 
 |  | 
 |     pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr)); | 
 |     if (!pcm->sync_ptr) | 
 |         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; | 
 | } | 
 |  | 
 | static void pcm_hw_munmap_status(struct pcm *pcm) { | 
 |     if (pcm->sync_ptr) { | 
 |         free(pcm->sync_ptr); | 
 |         pcm->sync_ptr = NULL; | 
 |     } else { | 
 |         int page_size = sysconf(_SC_PAGE_SIZE); | 
 |         if (pcm->mmap_status) | 
 |             munmap(pcm->mmap_status, page_size); | 
 |         if (pcm->mmap_control) | 
 |             munmap(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, commit, "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_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, | 
 | }; | 
 |  | 
 | struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, | 
 |                                   unsigned int flags) | 
 | { | 
 |     struct snd_pcm_hw_params *params; | 
 |     char fn[256]; | 
 |     int fd; | 
 |  | 
 |     snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, | 
 |              flags & PCM_IN ? 'c' : 'p'); | 
 |  | 
 |     fd = open(fn, O_RDWR); | 
 |     if (fd < 0) { | 
 |         fprintf(stderr, "cannot open device '%s'\n", fn); | 
 |         goto err_open; | 
 |     } | 
 |  | 
 |     params = calloc(1, sizeof(struct snd_pcm_hw_params)); | 
 |     if (!params) | 
 |         goto err_calloc; | 
 |  | 
 |     param_init(params); | 
 |     if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) { | 
 |         fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno); | 
 |         goto err_hw_refine; | 
 |     } | 
 |  | 
 |     close(fd); | 
 |  | 
 |     return (struct pcm_params *)params; | 
 |  | 
 | err_hw_refine: | 
 |     free(params); | 
 | err_calloc: | 
 |     close(fd); | 
 | err_open: | 
 |     return NULL; | 
 | } | 
 |  | 
 | void pcm_params_free(struct pcm_params *pcm_params) | 
 | { | 
 |     struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; | 
 |  | 
 |     if (params) | 
 |         free(params); | 
 | } | 
 |  | 
 | static int pcm_param_to_alsa(enum pcm_param param) | 
 | { | 
 |     switch (param) { | 
 |     case PCM_PARAM_ACCESS: | 
 |         return SNDRV_PCM_HW_PARAM_ACCESS; | 
 |     case PCM_PARAM_FORMAT: | 
 |         return SNDRV_PCM_HW_PARAM_FORMAT; | 
 |     case PCM_PARAM_SUBFORMAT: | 
 |         return SNDRV_PCM_HW_PARAM_SUBFORMAT; | 
 |     case PCM_PARAM_SAMPLE_BITS: | 
 |         return SNDRV_PCM_HW_PARAM_SAMPLE_BITS; | 
 |         break; | 
 |     case PCM_PARAM_FRAME_BITS: | 
 |         return SNDRV_PCM_HW_PARAM_FRAME_BITS; | 
 |         break; | 
 |     case PCM_PARAM_CHANNELS: | 
 |         return SNDRV_PCM_HW_PARAM_CHANNELS; | 
 |         break; | 
 |     case PCM_PARAM_RATE: | 
 |         return SNDRV_PCM_HW_PARAM_RATE; | 
 |         break; | 
 |     case PCM_PARAM_PERIOD_TIME: | 
 |         return SNDRV_PCM_HW_PARAM_PERIOD_TIME; | 
 |         break; | 
 |     case PCM_PARAM_PERIOD_SIZE: | 
 |         return SNDRV_PCM_HW_PARAM_PERIOD_SIZE; | 
 |         break; | 
 |     case PCM_PARAM_PERIOD_BYTES: | 
 |         return SNDRV_PCM_HW_PARAM_PERIOD_BYTES; | 
 |         break; | 
 |     case PCM_PARAM_PERIODS: | 
 |         return SNDRV_PCM_HW_PARAM_PERIODS; | 
 |         break; | 
 |     case PCM_PARAM_BUFFER_TIME: | 
 |         return SNDRV_PCM_HW_PARAM_BUFFER_TIME; | 
 |         break; | 
 |     case PCM_PARAM_BUFFER_SIZE: | 
 |         return SNDRV_PCM_HW_PARAM_BUFFER_SIZE; | 
 |         break; | 
 |     case PCM_PARAM_BUFFER_BYTES: | 
 |         return SNDRV_PCM_HW_PARAM_BUFFER_BYTES; | 
 |         break; | 
 |     case PCM_PARAM_TICK_TIME: | 
 |         return SNDRV_PCM_HW_PARAM_TICK_TIME; | 
 |         break; | 
 |  | 
 |     default: | 
 |         return -1; | 
 |     } | 
 | } | 
 |  | 
 | struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params, | 
 |                                      enum pcm_param param) | 
 | { | 
 |     int p; | 
 |     struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; | 
 |     if (params == NULL) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     p = pcm_param_to_alsa(param); | 
 |     if (p < 0 || !param_is_mask(p)) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     return (struct pcm_mask *)param_to_mask(params, p); | 
 | } | 
 |  | 
 | unsigned int pcm_params_get_min(struct pcm_params *pcm_params, | 
 |                                 enum pcm_param param) | 
 | { | 
 |     struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; | 
 |     int p; | 
 |  | 
 |     if (!params) | 
 |         return 0; | 
 |  | 
 |     p = pcm_param_to_alsa(param); | 
 |     if (p < 0) | 
 |         return 0; | 
 |  | 
 |     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); | 
 | } | 
 |  | 
 | unsigned int pcm_params_get_max(struct pcm_params *pcm_params, | 
 |                                 enum pcm_param param) | 
 | { | 
 |     struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params; | 
 |     int p; | 
 |  | 
 |     if (!params) | 
 |         return 0; | 
 |  | 
 |     p = pcm_param_to_alsa(param); | 
 |     if (p < 0) | 
 |         return 0; | 
 |  | 
 |     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) | 
 | { | 
 |     const unsigned int bitshift = 5; /* for 32 bit integer */ | 
 |     const unsigned int bitmask = (1 << bitshift) - 1; | 
 |     unsigned int element; | 
 |  | 
 |     element = index >> bitshift; | 
 |     if (element >= ARRAY_SIZE(m->bits)) | 
 |         return 0; /* for safety, but should never occur */ | 
 |     return (m->bits[element] >> (index & bitmask)) & 1; | 
 | } | 
 |  | 
 | static int pcm_mask_to_string(struct pcm_mask *m, char *string, unsigned int size, | 
 |                               char *mask_name, | 
 |                               const char * const *bit_array_name, size_t bit_array_size) | 
 | { | 
 |     unsigned int i; | 
 |     unsigned int offset = 0; | 
 |  | 
 |     if (m == NULL) | 
 |         return 0; | 
 |     if (bit_array_size < 32) { | 
 |         STRLOG(string, offset, size, "%12s:\t%#08x\n", mask_name, m->bits[0]); | 
 |     } else { /* spans two or more bitfields, print with an array index */ | 
 |         for (i = 0; i < (bit_array_size + 31) >> 5; ++i) { | 
 |             STRLOG(string, offset, size, "%9s[%d]:\t%#08x\n", | 
 |                    mask_name, i, m->bits[i]); | 
 |         } | 
 |     } | 
 |     for (i = 0; i < bit_array_size; ++i) { | 
 |         if (pcm_mask_test(m, i)) { | 
 |             STRLOG(string, offset, size, "%12s \t%s\n", "", bit_array_name[i]); | 
 |         } | 
 |     } | 
 |     return offset; | 
 | } | 
 |  | 
 | int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size) | 
 | { | 
 |     struct pcm_mask *m; | 
 |     unsigned int min, max; | 
 |     unsigned int clipoffset, offset; | 
 |  | 
 |     m = pcm_params_get_mask(params, PCM_PARAM_ACCESS); | 
 |     offset = pcm_mask_to_string(m, string, size, | 
 |                                  "Access", access_lookup, ARRAY_SIZE(access_lookup)); | 
 |     m = pcm_params_get_mask(params, PCM_PARAM_FORMAT); | 
 |     clipoffset = offset > size ? size : offset; | 
 |     offset += pcm_mask_to_string(m, string + clipoffset, size - clipoffset, | 
 |                                  "Format", format_lookup, ARRAY_SIZE(format_lookup)); | 
 |     m = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT); | 
 |     clipoffset = offset > size ? size : offset; | 
 |     offset += pcm_mask_to_string(m, string + clipoffset, size - clipoffset, | 
 |                                  "Subformat", subformat_lookup, ARRAY_SIZE(subformat_lookup)); | 
 |     min = pcm_params_get_min(params, PCM_PARAM_RATE); | 
 |     max = pcm_params_get_max(params, PCM_PARAM_RATE); | 
 |     STRLOG(string, offset, size, "        Rate:\tmin=%uHz\tmax=%uHz\n", min, max); | 
 |     min = pcm_params_get_min(params, PCM_PARAM_CHANNELS); | 
 |     max = pcm_params_get_max(params, PCM_PARAM_CHANNELS); | 
 |     STRLOG(string, offset, size, "    Channels:\tmin=%u\t\tmax=%u\n", min, max); | 
 |     min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS); | 
 |     max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS); | 
 |     STRLOG(string, offset, size, " Sample bits:\tmin=%u\t\tmax=%u\n", min, max); | 
 |     min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE); | 
 |     max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE); | 
 |     STRLOG(string, offset, size, " Period size:\tmin=%u\t\tmax=%u\n", min, max); | 
 |     min = pcm_params_get_min(params, PCM_PARAM_PERIODS); | 
 |     max = pcm_params_get_max(params, PCM_PARAM_PERIODS); | 
 |     STRLOG(string, offset, size, "Period count:\tmin=%u\t\tmax=%u\n", min, max); | 
 |     return offset; | 
 | } | 
 |  | 
 | int pcm_params_format_test(struct pcm_params *params, enum pcm_format format) | 
 | { | 
 |     unsigned int alsa_format = pcm_format_to_alsa(format); | 
 |  | 
 |     if (alsa_format == SNDRV_PCM_FORMAT_S16_LE && format != PCM_FORMAT_S16_LE) | 
 |         return 0; /* caution: format not recognized is equivalent to S16_LE */ | 
 |     return pcm_mask_test(pcm_params_get_mask(params, PCM_PARAM_FORMAT), alsa_format); | 
 | } | 
 |  | 
 | int pcm_close(struct pcm *pcm) | 
 | { | 
 |     if (pcm == &bad_pcm) | 
 |         return 0; | 
 |  | 
 |     pcm_hw_munmap_status(pcm); | 
 |  | 
 |     if (pcm->flags & PCM_MMAP) { | 
 |         pcm_stop(pcm); | 
 |         munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); | 
 |     } | 
 |  | 
 |     if (pcm->fd >= 0) | 
 |         close(pcm->fd); | 
 |     pcm->prepared = 0; | 
 |     pcm->running = 0; | 
 |     pcm->buffer_size = 0; | 
 |     pcm->fd = -1; | 
 |     free(pcm); | 
 |     return 0; | 
 | } | 
 |  | 
 | struct pcm *pcm_open(unsigned int card, unsigned int device, | 
 |                      unsigned int flags, 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; | 
 |  | 
 |     pcm = calloc(1, sizeof(struct pcm)); | 
 |     if (!pcm || !config) | 
 |         return &bad_pcm; /* TODO: could support default config here */ | 
 |  | 
 |     pcm->config = *config; | 
 |  | 
 |     snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, | 
 |              flags & PCM_IN ? 'c' : 'p'); | 
 |  | 
 |     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)) { | 
 |         oops(pcm, errno, "cannot get info"); | 
 |         goto fail_close; | 
 |     } | 
 |  | 
 |     param_init(¶ms); | 
 |     param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT, | 
 |                    pcm_format_to_alsa(config->format)); | 
 |     param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT, | 
 |                    SNDRV_PCM_SUBFORMAT_STD); | 
 |     param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size); | 
 |     param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, | 
 |                   pcm_format_to_bits(config->format)); | 
 |     param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS, | 
 |                   pcm_format_to_bits(config->format) * config->channels); | 
 |     param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS, | 
 |                   config->channels); | 
 |     param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count); | 
 |     param_set_int(¶ms, 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(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, | 
 |                        SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); | 
 |     else | 
 |         param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, | 
 |                        SNDRV_PCM_ACCESS_RW_INTERLEAVED); | 
 |  | 
 |     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { | 
 |         oops(pcm, errno, "cannot set hw params"); | 
 |         goto fail_close; | 
 |     } | 
 |  | 
 |     /* get our refined hw_params */ | 
 |     config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); | 
 |     config->period_count = param_get_int(¶ms, 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) { | 
 |         oops(pcm, rc, "mmap status failed"); | 
 |         goto fail; | 
 |     } | 
 |  | 
 | #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); | 
 |         if (rc < 0) { | 
 |             oops(pcm, rc, "cannot set timestamp type"); | 
 |             goto fail; | 
 |         } | 
 |     } | 
 | #endif | 
 |  | 
 |     pcm->underruns = 0; | 
 |     return pcm; | 
 |  | 
 | fail: | 
 |     if (flags & PCM_MMAP) | 
 |         munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); | 
 | fail_close: | 
 |     close(pcm->fd); | 
 |     pcm->fd = -1; | 
 |     return pcm; | 
 | } | 
 |  | 
 | int pcm_is_ready(struct pcm *pcm) | 
 | { | 
 |     return pcm->fd >= 0; | 
 | } | 
 |  | 
 | int pcm_prepare(struct pcm *pcm) | 
 | { | 
 |     if (pcm->prepared) | 
 |         return 0; | 
 |  | 
 |     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0) | 
 |         return oops(pcm, errno, "cannot prepare channel"); | 
 |  | 
 |     pcm->prepared = 1; | 
 |     return 0; | 
 | } | 
 |  | 
 | int pcm_start(struct pcm *pcm) | 
 | { | 
 |     int prepare_error = pcm_prepare(pcm); | 
 |     if (prepare_error) | 
 |         return prepare_error; | 
 |  | 
 |     if (pcm->flags & PCM_MMAP) | 
 | 	    pcm_sync_ptr(pcm, 0); | 
 |  | 
 |     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0) | 
 |         return oops(pcm, errno, "cannot start channel"); | 
 |  | 
 |     pcm->running = 1; | 
 |     return 0; | 
 | } | 
 |  | 
 | int pcm_stop(struct pcm *pcm) | 
 | { | 
 |     if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) | 
 |         return oops(pcm, errno, "cannot stop channel"); | 
 |  | 
 |     pcm->prepared = 0; | 
 |     pcm->running = 0; | 
 |     return 0; | 
 | } | 
 |  | 
 | static inline int pcm_mmap_playback_avail(struct pcm *pcm) | 
 | { | 
 |     int avail; | 
 |  | 
 |     avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr; | 
 |  | 
 |     if (avail < 0) | 
 |         avail += pcm->boundary; | 
 |     else if (avail > (int)pcm->boundary) | 
 |         avail -= pcm->boundary; | 
 |  | 
 |     return avail; | 
 | } | 
 |  | 
 | static inline int pcm_mmap_capture_avail(struct pcm *pcm) | 
 | { | 
 |     int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr; | 
 |     if (avail < 0) | 
 |         avail += pcm->boundary; | 
 |     return avail; | 
 | } | 
 |  | 
 | 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 | 
 |         return pcm_mmap_playback_avail(pcm); | 
 | } | 
 |  | 
 | static void pcm_mmap_appl_forward(struct pcm *pcm, int frames) | 
 | { | 
 |     unsigned int appl_ptr = pcm->mmap_control->appl_ptr; | 
 |     appl_ptr += frames; | 
 |  | 
 |     /* check for boundary wrap */ | 
 |     if (appl_ptr > pcm->boundary) | 
 |          appl_ptr -= pcm->boundary; | 
 |     pcm->mmap_control->appl_ptr = appl_ptr; | 
 | } | 
 |  | 
 | int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, | 
 |                    unsigned int *frames) | 
 | { | 
 |     unsigned int continuous, copy_frames, avail; | 
 |  | 
 |     /* return the mmap buffer */ | 
 |     *areas = pcm->mmap_buffer; | 
 |  | 
 |     /* and the application offset in frames */ | 
 |     *offset = pcm->mmap_control->appl_ptr % pcm->buffer_size; | 
 |  | 
 |     avail = pcm_mmap_avail(pcm); | 
 |     if (avail > pcm->buffer_size) | 
 |         avail = pcm->buffer_size; | 
 |     continuous = pcm->buffer_size - *offset; | 
 |  | 
 |     /* we can only copy frames if the are availabale and continuos */ | 
 |     copy_frames = *frames; | 
 |     if (copy_frames > avail) | 
 |         copy_frames = avail; | 
 |     if (copy_frames > continuous) | 
 |         copy_frames = continuous; | 
 |     *frames = copy_frames; | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | int pcm_mmap_commit(struct pcm *pcm, unsigned int offset __attribute__((unused)), unsigned int frames) | 
 | { | 
 |     /* update the application pointer in userspace and kernel */ | 
 |     pcm_mmap_appl_forward(pcm, frames); | 
 |     pcm_sync_ptr(pcm, 0); | 
 |  | 
 |     return frames; | 
 | } | 
 |  | 
 | int pcm_avail_update(struct pcm *pcm) | 
 | { | 
 |     pcm_sync_ptr(pcm, 0); | 
 |     return pcm_mmap_avail(pcm); | 
 | } | 
 |  | 
 | int pcm_state(struct pcm *pcm) | 
 | { | 
 |     int err = pcm_sync_ptr(pcm, 0); | 
 |     if (err < 0) | 
 |         return err; | 
 |  | 
 |     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; | 
 | } | 
 |  | 
 | int pcm_wait(struct pcm *pcm, int timeout) | 
 | { | 
 |     struct pollfd pfd; | 
 |     int err; | 
 |  | 
 |     pfd.fd = pcm->fd; | 
 |     pfd.events = POLLOUT | POLLERR | POLLNVAL; | 
 |  | 
 |     do { | 
 |         /* let's wait for avail or timeout */ | 
 |         err = poll(&pfd, 1, timeout); | 
 |         if (err < 0) | 
 |             return -errno; | 
 |  | 
 |         /* timeout ? */ | 
 |         if (err == 0) | 
 |             return 0; | 
 |  | 
 |         /* have we been interrupted ? */ | 
 |         if (errno == -EINTR) | 
 |             continue; | 
 |  | 
 |         /* check for any errors */ | 
 |         if (pfd.revents & (POLLERR | POLLNVAL)) { | 
 |             switch (pcm_state(pcm)) { | 
 |             case PCM_STATE_XRUN: | 
 |                 return -EPIPE; | 
 |             case PCM_STATE_SUSPENDED: | 
 |                 return -ESTRPIPE; | 
 |             case PCM_STATE_DISCONNECTED: | 
 |                 return -ENODEV; | 
 |             default: | 
 |                 return -EIO; | 
 |             } | 
 |         } | 
 |     /* poll again if fd not ready for IO */ | 
 |     } while (!(pfd.revents & (POLLIN | POLLOUT))); | 
 |  | 
 |     return 1; | 
 | } | 
 |  | 
 | int pcm_get_poll_fd(struct pcm *pcm) | 
 | { | 
 |     return pcm->fd; | 
 | } | 
 |  | 
 | int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes) | 
 | { | 
 |     int err = 0, frames, avail; | 
 |     unsigned int offset = 0, count; | 
 |  | 
 |     if (bytes == 0) | 
 |         return 0; | 
 |  | 
 |     count = pcm_bytes_to_frames(pcm, bytes); | 
 |  | 
 |     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, err, "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; | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count) | 
 | { | 
 |     if ((~pcm->flags) & (PCM_OUT | PCM_MMAP)) | 
 |         return -ENOSYS; | 
 |  | 
 |     return pcm_mmap_transfer(pcm, (void *)data, count); | 
 | } | 
 |  | 
 | int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count) | 
 | { | 
 |     if ((~pcm->flags) & (PCM_IN | PCM_MMAP)) | 
 |         return -ENOSYS; | 
 |  | 
 |     return pcm_mmap_transfer(pcm, data, count); | 
 | } | 
 |  | 
 | int pcm_ioctl(struct pcm *pcm, int request, ...) | 
 | { | 
 |     va_list ap; | 
 |     void * arg; | 
 |  | 
 |     if (!pcm_is_ready(pcm)) | 
 |         return -1; | 
 |  | 
 |     va_start(ap, request); | 
 |     arg = va_arg(ap, void *); | 
 |     va_end(ap); | 
 |  | 
 |     return ioctl(pcm->fd, request, arg); | 
 | } |