Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/next/external/cache/sources/tinyalsa_hub/pcm.c
Views: 3959
/* pcm.c1**2** Copyright 2011, The Android Open Source Project3**4** Redistribution and use in source and binary forms, with or without5** modification, are permitted provided that the following conditions are met:6** * Redistributions of source code must retain the above copyright7** notice, this list of conditions and the following disclaimer.8** * Redistributions in binary form must reproduce the above copyright9** notice, this list of conditions and the following disclaimer in the10** documentation and/or other materials provided with the distribution.11** * Neither the name of The Android Open Source Project nor the names of12** its contributors may be used to endorse or promote products derived13** from this software without specific prior written permission.14**15** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND16** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE17** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE18** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE19** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL20** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR21** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER22** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT23** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY24** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH25** DAMAGE.26*/2728#include <stdio.h>29#include <stdlib.h>30#include <fcntl.h>31#include <stdarg.h>32#include <string.h>33#include <errno.h>34#include <unistd.h>35#include <poll.h>3637#include <sys/ioctl.h>38#include <sys/mman.h>39#include <sys/time.h>40#include <limits.h>4142#include <linux/ioctl.h>43#define __force44#define __bitwise45#define __user46#include <sound/asound.h>4748#include <tinyalsa/asoundlib.h>4950#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL51#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2)5253static inline int param_is_mask(int p)54{55return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) &&56(p <= SNDRV_PCM_HW_PARAM_LAST_MASK);57}5859static inline int param_is_interval(int p)60{61return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) &&62(p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL);63}6465static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n)66{67return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);68}6970static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n)71{72return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]);73}7475static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit)76{77if (bit >= SNDRV_MASK_MAX)78return;79if (param_is_mask(n)) {80struct snd_mask *m = param_to_mask(p, n);81m->bits[0] = 0;82m->bits[1] = 0;83m->bits[bit >> 5] |= (1 << (bit & 31));84}85}8687static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val)88{89if (param_is_interval(n)) {90struct snd_interval *i = param_to_interval(p, n);91i->min = val;92}93}9495static unsigned int param_get_min(struct snd_pcm_hw_params *p, int n)96{97if (param_is_interval(n)) {98struct snd_interval *i = param_to_interval(p, n);99return i->min;100}101return 0;102}103104static unsigned int param_get_max(struct snd_pcm_hw_params *p, int n)105{106if (param_is_interval(n)) {107struct snd_interval *i = param_to_interval(p, n);108return i->max;109}110return 0;111}112113static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val)114{115if (param_is_interval(n)) {116struct snd_interval *i = param_to_interval(p, n);117i->min = val;118i->max = val;119i->integer = 1;120}121}122123static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n)124{125if (param_is_interval(n)) {126struct snd_interval *i = param_to_interval(p, n);127if (i->integer)128return i->max;129}130return 0;131}132133static void param_init(struct snd_pcm_hw_params *p)134{135int n;136137memset(p, 0, sizeof(*p));138for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK;139n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) {140struct snd_mask *m = param_to_mask(p, n);141m->bits[0] = ~0;142m->bits[1] = ~0;143}144for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;145n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) {146struct snd_interval *i = param_to_interval(p, n);147i->min = 0;148i->max = ~0;149}150p->rmask = ~0U;151p->cmask = 0;152p->info = ~0U;153}154155#define PCM_ERROR_MAX 128156157struct pcm {158int fd;159unsigned int flags;160int running:1;161int prepared:1;162int underruns;163unsigned int buffer_size;164unsigned int boundary;165char error[PCM_ERROR_MAX];166struct pcm_config config;167struct snd_pcm_mmap_status *mmap_status;168struct snd_pcm_mmap_control *mmap_control;169struct snd_pcm_sync_ptr *sync_ptr;170void *mmap_buffer;171unsigned int noirq_frames_per_msec;172};173174unsigned int pcm_get_buffer_size(struct pcm *pcm)175{176return pcm->buffer_size;177}178179const char* pcm_get_error(struct pcm *pcm)180{181return pcm->error;182}183184static int oops(struct pcm *pcm, int e, const char *fmt, ...)185{186va_list ap;187int sz;188189va_start(ap, fmt);190vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap);191va_end(ap);192sz = strlen(pcm->error);193194if (errno)195snprintf(pcm->error + sz, PCM_ERROR_MAX - sz,196": %s", strerror(e));197return -1;198}199200static unsigned int pcm_format_to_alsa(enum pcm_format format)201{202switch (format) {203case PCM_FORMAT_S32_LE:204return SNDRV_PCM_FORMAT_S32_LE;205case PCM_FORMAT_S8:206return SNDRV_PCM_FORMAT_S8;207case PCM_FORMAT_S24_LE:208return SNDRV_PCM_FORMAT_S24_LE;209default:210case PCM_FORMAT_S16_LE:211return SNDRV_PCM_FORMAT_S16_LE;212};213}214215unsigned int pcm_format_to_bits(enum pcm_format format)216{217switch (format) {218case PCM_FORMAT_S32_LE:219case PCM_FORMAT_S24_LE:220return 32;221default:222case PCM_FORMAT_S16_LE:223return 16;224};225}226227unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes)228{229return bytes / (pcm->config.channels *230(pcm_format_to_bits(pcm->config.format) >> 3));231}232233unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames)234{235return frames * pcm->config.channels *236(pcm_format_to_bits(pcm->config.format) >> 3);237}238239static int pcm_sync_ptr(struct pcm *pcm, int flags) {240if (pcm->sync_ptr) {241pcm->sync_ptr->flags = flags;242if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0)243return -1;244}245return 0;246}247248static int pcm_hw_mmap_status(struct pcm *pcm) {249250if (pcm->sync_ptr)251return 0;252253int page_size = sysconf(_SC_PAGE_SIZE);254pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED,255pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS);256if (pcm->mmap_status == MAP_FAILED)257pcm->mmap_status = NULL;258if (!pcm->mmap_status)259goto mmap_error;260261pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE,262MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);263if (pcm->mmap_control == MAP_FAILED)264pcm->mmap_control = NULL;265if (!pcm->mmap_control) {266munmap(pcm->mmap_status, page_size);267pcm->mmap_status = NULL;268goto mmap_error;269}270pcm->mmap_control->avail_min = 1;271272return 0;273274mmap_error:275276pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr));277if (!pcm->sync_ptr)278return -ENOMEM;279pcm->mmap_status = &pcm->sync_ptr->s.status;280pcm->mmap_control = &pcm->sync_ptr->c.control;281pcm->mmap_control->avail_min = 1;282pcm_sync_ptr(pcm, 0);283284return 0;285}286287static void pcm_hw_munmap_status(struct pcm *pcm) {288if (pcm->sync_ptr) {289free(pcm->sync_ptr);290pcm->sync_ptr = NULL;291} else {292int page_size = sysconf(_SC_PAGE_SIZE);293if (pcm->mmap_status)294munmap(pcm->mmap_status, page_size);295if (pcm->mmap_control)296munmap(pcm->mmap_control, page_size);297}298pcm->mmap_status = NULL;299pcm->mmap_control = NULL;300}301302static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,303char *buf, unsigned int src_offset,304unsigned int frames)305{306int size_bytes = pcm_frames_to_bytes(pcm, frames);307int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset);308int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset);309310/* interleaved only atm */311if (pcm->flags & PCM_IN)312memcpy(buf + src_offset_bytes,313(char*)pcm->mmap_buffer + pcm_offset_bytes,314size_bytes);315else316memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,317buf + src_offset_bytes,318size_bytes);319return 0;320}321322static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf,323unsigned int offset, unsigned int size)324{325void *pcm_areas;326int commit;327unsigned int pcm_offset, frames, count = 0;328329while (size > 0) {330frames = size;331pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);332pcm_areas_copy(pcm, pcm_offset, buf, offset, frames);333commit = pcm_mmap_commit(pcm, pcm_offset, frames);334if (commit < 0) {335oops(pcm, commit, "failed to commit %d frames\n", frames);336return commit;337}338339offset += commit;340count += commit;341size -= commit;342}343return count;344}345346int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,347struct timespec *tstamp)348{349int frames;350int rc;351snd_pcm_uframes_t hw_ptr;352353if (!pcm_is_ready(pcm))354return -1;355356rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC);357if (rc < 0)358return -1;359360if ((pcm->mmap_status->state != PCM_STATE_RUNNING) &&361(pcm->mmap_status->state != PCM_STATE_DRAINING))362return -1;363364*tstamp = pcm->mmap_status->tstamp;365if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0)366return -1;367368hw_ptr = pcm->mmap_status->hw_ptr;369if (pcm->flags & PCM_IN)370frames = hw_ptr - pcm->mmap_control->appl_ptr;371else372frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;373374if (frames < 0)375return -1;376377*avail = (unsigned int)frames;378379return 0;380}381382int pcm_write(struct pcm *pcm, const void *data, unsigned int count)383{384struct snd_xferi x;385386if (pcm->flags & PCM_IN)387return -EINVAL;388389x.buf = (void*)data;390x.frames = count / (pcm->config.channels *391pcm_format_to_bits(pcm->config.format) / 8);392393for (;;) {394if (!pcm->running) {395int prepare_error = pcm_prepare(pcm);396if (prepare_error)397return prepare_error;398if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {399printf("[%s]-->line:%d,errno:%d\n", __func__, __LINE__, errno);400return oops(pcm, errno, "cannot write initial data");401}402pcm->running = 1;403return 0;404}405if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {406pcm->prepared = 0;407pcm->running = 0;408printf("[%s]-->line:%d,errno:%d\n", __func__, __LINE__, errno);409if (errno == EPIPE) {410/* we failed to make our window -- try to restart if we are411* allowed to do so. Otherwise, simply allow the EPIPE error to412* propagate up to the app level */413pcm->underruns++;414if (pcm->flags & PCM_NORESTART)415return -EPIPE;416continue;417}418return oops(pcm, errno, "cannot write stream data");419}420return 0;421}422}423424int pcm_read(struct pcm *pcm, void *data, unsigned int count)425{426struct snd_xferi x;427428if (!(pcm->flags & PCM_IN))429return -EINVAL;430431x.buf = data;432x.frames = count / (pcm->config.channels *433pcm_format_to_bits(pcm->config.format) / 8);434435for (;;) {436if (!pcm->running) {437if (pcm_start(pcm) < 0) {438fprintf(stderr, "start error");439return -errno;440}441}442if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {443pcm->prepared = 0;444pcm->running = 0;445printf("[%s]-->line:%d,errno:%d\n", __func__, __LINE__, errno);446if (errno == EPIPE) {447/* we failed to make our window -- try to restart */448pcm->underruns++;449continue;450}451return oops(pcm, errno, "cannot read stream data");452}453return 0;454}455}456457static struct pcm bad_pcm = {458.fd = -1,459};460461struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,462unsigned int flags)463{464struct snd_pcm_hw_params *params;465char fn[256];466int fd;467468snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,469flags & PCM_IN ? 'c' : 'p');470471fd = open(fn, O_RDWR);472if (fd < 0) {473fprintf(stderr, "cannot open device '%s'\n", fn);474goto err_open;475}476477params = calloc(1, sizeof(struct snd_pcm_hw_params));478if (!params)479goto err_calloc;480481param_init(params);482if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) {483fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno);484goto err_hw_refine;485}486487close(fd);488489return (struct pcm_params *)params;490491err_hw_refine:492free(params);493err_calloc:494close(fd);495err_open:496return NULL;497}498499void pcm_params_free(struct pcm_params *pcm_params)500{501struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;502503if (params)504free(params);505}506507static int pcm_param_to_alsa(enum pcm_param param)508{509switch (param) {510case PCM_PARAM_ACCESS:511return SNDRV_PCM_HW_PARAM_ACCESS;512case PCM_PARAM_FORMAT:513return SNDRV_PCM_HW_PARAM_FORMAT;514case PCM_PARAM_SUBFORMAT:515return SNDRV_PCM_HW_PARAM_SUBFORMAT;516case PCM_PARAM_SAMPLE_BITS:517return SNDRV_PCM_HW_PARAM_SAMPLE_BITS;518break;519case PCM_PARAM_FRAME_BITS:520return SNDRV_PCM_HW_PARAM_FRAME_BITS;521break;522case PCM_PARAM_CHANNELS:523return SNDRV_PCM_HW_PARAM_CHANNELS;524break;525case PCM_PARAM_RATE:526return SNDRV_PCM_HW_PARAM_RATE;527break;528case PCM_PARAM_PERIOD_TIME:529return SNDRV_PCM_HW_PARAM_PERIOD_TIME;530break;531case PCM_PARAM_PERIOD_SIZE:532return SNDRV_PCM_HW_PARAM_PERIOD_SIZE;533break;534case PCM_PARAM_PERIOD_BYTES:535return SNDRV_PCM_HW_PARAM_PERIOD_BYTES;536break;537case PCM_PARAM_PERIODS:538return SNDRV_PCM_HW_PARAM_PERIODS;539break;540case PCM_PARAM_BUFFER_TIME:541return SNDRV_PCM_HW_PARAM_BUFFER_TIME;542break;543case PCM_PARAM_BUFFER_SIZE:544return SNDRV_PCM_HW_PARAM_BUFFER_SIZE;545break;546case PCM_PARAM_BUFFER_BYTES:547return SNDRV_PCM_HW_PARAM_BUFFER_BYTES;548break;549case PCM_PARAM_TICK_TIME:550return SNDRV_PCM_HW_PARAM_TICK_TIME;551break;552553default:554return -1;555}556}557558struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params,559enum pcm_param param)560{561int p;562struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;563if (params == NULL) {564return NULL;565}566567p = pcm_param_to_alsa(param);568if (p < 0 || !param_is_mask(p)) {569return NULL;570}571572return (struct pcm_mask *)param_to_mask(params, p);573}574575unsigned int pcm_params_get_min(struct pcm_params *pcm_params,576enum pcm_param param)577{578struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;579int p;580581if (!params)582return 0;583584p = pcm_param_to_alsa(param);585if (p < 0)586return 0;587588return param_get_min(params, p);589}590591unsigned int pcm_params_get_max(struct pcm_params *pcm_params,592enum pcm_param param)593{594struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;595int p;596597if (!params)598return 0;599600p = pcm_param_to_alsa(param);601if (p < 0)602return 0;603604return param_get_max(params, p);605}606607int pcm_close(struct pcm *pcm)608{609if (pcm == &bad_pcm)610return 0;611612pcm_hw_munmap_status(pcm);613614if (pcm->flags & PCM_MMAP) {615pcm_stop(pcm);616munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));617}618619if (pcm->fd >= 0)620close(pcm->fd);621pcm->prepared = 0;622pcm->running = 0;623pcm->buffer_size = 0;624pcm->fd = -1;625free(pcm);626return 0;627}628629struct pcm *pcm_open(unsigned int card, unsigned int device,630unsigned int flags, struct pcm_config *config)631{632struct pcm *pcm;633struct snd_pcm_info info;634struct snd_pcm_hw_params params;635struct snd_pcm_sw_params sparams;636char fn[256];637int rc;638639pcm = calloc(1, sizeof(struct pcm));640if (!pcm || !config)641return &bad_pcm; /* TODO: could support default config here */642643pcm->config = *config;644645snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,646flags & PCM_IN ? 'c' : 'p');647648pcm->flags = flags;649pcm->fd = open(fn, O_RDWR);650if (pcm->fd < 0) {651oops(pcm, errno, "cannot open device '%s'", fn);652return pcm;653}654if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {655oops(pcm, errno, "cannot get info");656goto fail_close;657}658659param_init(¶ms);660param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,661pcm_format_to_alsa(config->format));662param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT,663SNDRV_PCM_SUBFORMAT_STD);664param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,665pcm_format_to_bits(config->format));666param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS,667pcm_format_to_bits(config->format) * config->channels);668param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS,669config->channels);670param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);671param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate);672673if (flags & PCM_NOIRQ) {674675if (!(flags & PCM_MMAP)) {676oops(pcm, -EINVAL, "noirq only currently supported with mmap().");677goto fail;678}679680params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;681pcm->noirq_frames_per_msec = config->rate / 1000;682}683684if (flags & PCM_MMAP)685param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,686SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);687else688param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,689SNDRV_PCM_ACCESS_RW_INTERLEAVED);690691if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {692oops(pcm, errno, "cannot set hw params");693goto fail_close;694}695696/* get our refined hw_params */697config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);698config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS);699pcm->buffer_size = config->period_count * config->period_size;700701if (flags & PCM_MMAP) {702pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),703PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);704if (pcm->mmap_buffer == MAP_FAILED) {705oops(pcm, -errno, "failed to mmap buffer %d bytes\n",706pcm_frames_to_bytes(pcm, pcm->buffer_size));707goto fail_close;708}709}710711712memset(&sparams, 0, sizeof(sparams));713sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;714sparams.period_step = 1;715sparams.avail_min = 1;716717if (!config->start_threshold) {718if (pcm->flags & PCM_IN)719pcm->config.start_threshold = sparams.start_threshold = 1;720else721pcm->config.start_threshold = sparams.start_threshold =722config->period_count * config->period_size / 2;723} else724sparams.start_threshold = config->start_threshold;725726/* pick a high stop threshold - todo: does this need further tuning */727if (!config->stop_threshold) {728if (pcm->flags & PCM_IN)729pcm->config.stop_threshold = sparams.stop_threshold =730config->period_count * config->period_size * 10;731else732pcm->config.stop_threshold = sparams.stop_threshold =733config->period_count * config->period_size;734}735else736sparams.stop_threshold = config->stop_threshold;737738sparams.xfer_align = config->period_size / 2; /* needed for old kernels */739sparams.silence_size = 0;740sparams.silence_threshold = config->silence_threshold;741pcm->boundary = sparams.boundary = pcm->buffer_size;742743while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)744pcm->boundary *= 2;745746if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {747oops(pcm, errno, "cannot set sw params");748goto fail;749}750751rc = pcm_hw_mmap_status(pcm);752if (rc < 0) {753oops(pcm, rc, "mmap status failed");754goto fail;755}756757#ifdef SNDRV_PCM_IOCTL_TTSTAMP758if (pcm->flags & PCM_MONOTONIC) {759int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;760rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg);761if (rc < 0) {762oops(pcm, rc, "cannot set timestamp type");763goto fail;764}765}766#endif767768pcm->underruns = 0;769return pcm;770771fail:772if (flags & PCM_MMAP)773munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));774fail_close:775close(pcm->fd);776pcm->fd = -1;777return pcm;778}779780int pcm_is_ready(struct pcm *pcm)781{782return pcm->fd >= 0;783}784785int pcm_prepare(struct pcm *pcm)786{787if (pcm->prepared)788return 0;789790if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0)791return oops(pcm, errno, "cannot prepare channel");792793pcm->prepared = 1;794return 0;795}796797int pcm_start(struct pcm *pcm)798{799int prepare_error = pcm_prepare(pcm);800if (prepare_error)801return prepare_error;802803if (pcm->flags & PCM_MMAP)804pcm_sync_ptr(pcm, 0);805806if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0)807return oops(pcm, errno, "cannot start channel");808809pcm->running = 1;810return 0;811}812813int pcm_stop(struct pcm *pcm)814{815if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0)816return oops(pcm, errno, "cannot stop channel");817818pcm->prepared = 0;819pcm->running = 0;820return 0;821}822823static inline int pcm_mmap_playback_avail(struct pcm *pcm)824{825int avail;826827avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;828829if (avail < 0)830avail += pcm->boundary;831else if (avail > (int)pcm->boundary)832avail -= pcm->boundary;833834return avail;835}836837static inline int pcm_mmap_capture_avail(struct pcm *pcm)838{839int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr;840if (avail < 0)841avail += pcm->boundary;842return avail;843}844845static inline int pcm_mmap_avail(struct pcm *pcm)846{847pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);848if (pcm->flags & PCM_IN)849return pcm_mmap_capture_avail(pcm);850else851return pcm_mmap_playback_avail(pcm);852}853854static void pcm_mmap_appl_forward(struct pcm *pcm, int frames)855{856unsigned int appl_ptr = pcm->mmap_control->appl_ptr;857appl_ptr += frames;858859/* check for boundary wrap */860if (appl_ptr > pcm->boundary)861appl_ptr -= pcm->boundary;862pcm->mmap_control->appl_ptr = appl_ptr;863}864865int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset,866unsigned int *frames)867{868unsigned int continuous, copy_frames, avail;869870/* return the mmap buffer */871*areas = pcm->mmap_buffer;872873/* and the application offset in frames */874*offset = pcm->mmap_control->appl_ptr % pcm->buffer_size;875876avail = pcm_mmap_avail(pcm);877if (avail > pcm->buffer_size)878avail = pcm->buffer_size;879continuous = pcm->buffer_size - *offset;880881/* we can only copy frames if the are availabale and continuos */882copy_frames = *frames;883if (copy_frames > avail)884copy_frames = avail;885if (copy_frames > continuous)886copy_frames = continuous;887*frames = copy_frames;888889return 0;890}891892int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames)893{894/* update the application pointer in userspace and kernel */895pcm_mmap_appl_forward(pcm, frames);896pcm_sync_ptr(pcm, 0);897898return frames;899}900901int pcm_avail_update(struct pcm *pcm)902{903pcm_sync_ptr(pcm, 0);904return pcm_mmap_avail(pcm);905}906907int pcm_state(struct pcm *pcm)908{909int err = pcm_sync_ptr(pcm, 0);910if (err < 0)911return err;912913return pcm->mmap_status->state;914}915916int pcm_wait(struct pcm *pcm, int timeout)917{918struct pollfd pfd;919int err;920921pfd.fd = pcm->fd;922pfd.events = POLLOUT | POLLERR | POLLNVAL;923924do {925/* let's wait for avail or timeout */926err = poll(&pfd, 1, timeout);927if (err < 0)928return -errno;929930/* timeout ? */931if (err == 0)932return 0;933934/* have we been interrupted ? */935if (errno == -EINTR)936continue;937938/* check for any errors */939if (pfd.revents & (POLLERR | POLLNVAL)) {940switch (pcm_state(pcm)) {941case PCM_STATE_XRUN:942return -EPIPE;943case PCM_STATE_SUSPENDED:944return -ESTRPIPE;945case PCM_STATE_DISCONNECTED:946return -ENODEV;947default:948return -EIO;949}950}951/* poll again if fd not ready for IO */952} while (!(pfd.revents & (POLLIN | POLLOUT)));953954return 1;955}956957int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes)958{959int err = 0, frames, avail;960unsigned int offset = 0, count;961962if (bytes == 0)963return 0;964965count = pcm_bytes_to_frames(pcm, bytes);966967while (count > 0) {968969/* get the available space for writing new frames */970avail = pcm_avail_update(pcm);971if (avail < 0) {972fprintf(stderr, "cannot determine available mmap frames");973return err;974}975976/* start the audio if we reach the threshold */977if (!pcm->running &&978(pcm->buffer_size - avail) >= pcm->config.start_threshold) {979if (pcm_start(pcm) < 0) {980fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n",981(unsigned int)pcm->mmap_status->hw_ptr,982(unsigned int)pcm->mmap_control->appl_ptr,983avail);984return -errno;985}986}987988/* sleep until we have space to write new frames */989if (pcm->running &&990(unsigned int)avail < pcm->mmap_control->avail_min) {991int time = -1;992993if (pcm->flags & PCM_NOIRQ)994time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min)995/ pcm->noirq_frames_per_msec;996997err = pcm_wait(pcm, time);998if (err < 0) {999pcm->prepared = 0;1000pcm->running = 0;1001fprintf(stderr, "wait error: hw 0x%x app 0x%x avail 0x%x\n",1002(unsigned int)pcm->mmap_status->hw_ptr,1003(unsigned int)pcm->mmap_control->appl_ptr,1004avail);1005pcm->mmap_control->appl_ptr = 0;1006return err;1007}1008continue;1009}10101011frames = count;1012if (frames > avail)1013frames = avail;10141015if (!frames)1016break;10171018/* copy frames from buffer */1019frames = pcm_mmap_transfer_areas(pcm, (void *)buffer, offset, frames);1020if (frames < 0) {1021fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n",1022(unsigned int)pcm->mmap_status->hw_ptr,1023(unsigned int)pcm->mmap_control->appl_ptr,1024avail);1025return frames;1026}10271028offset += frames;1029count -= frames;1030}10311032return 0;1033}10341035int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count)1036{1037if ((~pcm->flags) & (PCM_OUT | PCM_MMAP))1038return -ENOSYS;10391040return pcm_mmap_transfer(pcm, (void *)data, count);1041}10421043int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count)1044{1045if ((~pcm->flags) & (PCM_IN | PCM_MMAP))1046return -ENOSYS;10471048return pcm_mmap_transfer(pcm, data, count);1049}105010511052