/* $OpenBSD: sun.c,v 1.37 2010/05/25 06:49:13 ratchov Exp $ */ /* * Copyright (c) 2010 Jacob Meuser * Copyright (c) 2008 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * TODO: * * call hdl->cb_pos() from sun_read() and sun_write(), or better: * implement generic blocking sio_read() and sio_write() with poll(2) * and use non-blocking sio_ops only */ #ifdef USE_ALSA #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sndio_priv.h" struct alsa_hdl { struct sio_hdl sio; snd_pcm_t *out_pcm; snd_pcm_t *in_pcm; snd_pcm_hw_params_t *out_hwp; snd_pcm_sw_params_t *out_swp; snd_pcm_hw_params_t *in_hwp; snd_pcm_sw_params_t *in_swp; int filling; unsigned ibpf, obpf; /* bytes per frame */ unsigned ihfr, ohfr; /* frames the hw transfered */ unsigned isfr, osfr; /* frames the sw transfered */ unsigned ierr, oerr; /* frames the hw dropped */ int offset; /* frames play is ahead of record */ int idelta, odelta; /* position reported to client */ int infds, onfds; }; static void alsa_close(struct sio_hdl *); static int alsa_start(struct sio_hdl *); static int alsa_stop(struct sio_hdl *); static int alsa_setpar(struct sio_hdl *, struct sio_par *); static int alsa_getpar(struct sio_hdl *, struct sio_par *); static int alsa_getcap(struct sio_hdl *, struct sio_cap *); static size_t alsa_read(struct sio_hdl *, void *, size_t); static size_t alsa_write(struct sio_hdl *, const void *, size_t); static int alsa_pollfd(struct sio_hdl *, struct pollfd *, int); static int alsa_revents(struct sio_hdl *, struct pollfd *); static int alsa_setvol(struct sio_hdl *, unsigned); static void alsa_getvol(struct sio_hdl *); static struct sio_ops alsa_ops = { alsa_close, alsa_setpar, alsa_getpar, alsa_getcap, alsa_write, alsa_read, alsa_start, alsa_stop, alsa_pollfd, alsa_revents, alsa_setvol, alsa_getvol }; /* * convert ALSA format to sio_par encoding */ static int alsa_fmttopar(struct alsa_hdl *hdl, snd_pcm_format_t fmt, struct sio_par *par) { switch (fmt) { case SND_PCM_FORMAT_U8: par->bits = 8; par->sig = 0; break; case SND_PCM_FORMAT_S8: par->bits = 8; par->sig = 1; break; case SND_PCM_FORMAT_S16_LE: par->bits = 16; par->sig = 1; par->le = 1; break; case SND_PCM_FORMAT_S16_BE: par->bits = 16; par->sig = 1; par->le = 0; break; case SND_PCM_FORMAT_U16_LE: par->bits = 16; par->sig = 0; par->le = 1; break; case SND_PCM_FORMAT_U16_BE: par->bits = 16; par->sig = 0; par->le = 0; break; case SND_PCM_FORMAT_S24_LE: par->bits = 24; par->sig = 1; par->le = 1; break; case SND_PCM_FORMAT_S24_BE: par->bits = 24; par->sig = 1; par->le = 0; break; case SND_PCM_FORMAT_U24_LE: par->bits = 24; par->sig = 0; par->le = 1; break; case SND_PCM_FORMAT_U24_BE: par->bits = 24; par->sig = 0; par->le = 0; break; default: DPRINTF("alsa_fmttopar: unsupported format\n"); hdl->sio.eof = 1; return 0; } par->msb = 1; par->bps = SIO_BPS(par->bits); return 1; } /* * convert sio_par encoding to ALSA format */ static void alsa_enctofmt(struct alsa_hdl *hdl, snd_pcm_format_t *rfmt, struct sio_enc *enc) { if (enc->bits == 8) { if (enc->sig == ~0U || !enc->sig) *rfmt = SND_PCM_FORMAT_U8; else *rfmt = SND_PCM_FORMAT_S8; } else if (enc->bits == 16) { if (enc->sig == ~0U || enc->sig) { if (enc->le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_S16_BE; } else if (enc->le) *rfmt = SND_PCM_FORMAT_S16_LE; else *rfmt = SND_PCM_FORMAT_S16_BE; } else { if (enc->le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_U16_LE : SND_PCM_FORMAT_U16_BE; } else if (enc->le) *rfmt = SND_PCM_FORMAT_U16_LE; else *rfmt = SND_PCM_FORMAT_U16_BE; } } else if (enc->bits == 24) { if (enc->sig == ~0U || enc->sig) { if (enc->le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_S24_LE : SND_PCM_FORMAT_S24_BE; } else if (enc->le) *rfmt = SND_PCM_FORMAT_S24_LE; else *rfmt = SND_PCM_FORMAT_S24_BE; } else { if (enc->le == ~0U) { *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_U24_LE : SND_PCM_FORMAT_U24_BE; } else if (enc->le) *rfmt = SND_PCM_FORMAT_U24_LE; else *rfmt = SND_PCM_FORMAT_U24_BE; } } else { /* XXX */ *rfmt = SIO_LE_NATIVE ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_S16_BE; } } /* * try to set the device to the given parameters and check that the * device can use them; return 1 on success, 0 on failure or error */ #if 0 static int alsa_tryinfo(struct alsa_hdl *hdl, struct sio_enc *enc, unsigned pchan, unsigned rchan, unsigned rate) { snd_pcm_format_t fmt; alsa_enctofmt(hdl, &fmt, enc); if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_test_format(hdl->out_pcm, hdl->out_hwp, fmt) < 0) return 0; } if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_test_format(hdl->in_pcm, hdl->in_hwp, fmt) < 0) return 0; } if (pchan && (hdl->sio.mode & SIO_PLAY)) { if (snd_pcm_hw_params_test_channels(hdl->out_pcm, hdl->out_hwp, pchan) < 0) return 0; } if (rchan && (hdl->sio.mode & SIO_REC)) { if (snd_pcm_hw_params_test_channels(hdl->in_pcm, hdl->in_hwp, rchan) < 0) return 0; } if (rate && (hdl->sio.mode & SIO_PLAY)) { if (snd_pcm_hw_params_test_rate(hdl->out_pcm, hdl->out_hwp, rate, 0) < 0) return 0; } if (rate && (hdl->sio.mode & SIO_REC)) { if (snd_pcm_hw_params_test_rate(hdl->in_pcm, hdl->in_hwp, rate, 0) < 0) return 0; } return 1; } #endif /* * guess device capabilities */ static int alsa_getcap(struct sio_hdl *sh, struct sio_cap *cap) { #if 0 #define NCHANS (sizeof(chans) / sizeof(chans[0])) #define NRATES (sizeof(rates) / sizeof(rates[0])) static unsigned chans[] = { 1, 2, 4, 6, 8, 10, 12 }; static unsigned rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000 }; struct alsa_hdl *hdl = (struct alsa_hdl *)sh; struct sio_par savepar; unsigned nenc = 0, nconf = 0; unsigned enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map; unsigned i, j, conf; unsigned fmt; if (!alsa_getpar(&hdl->sio, &savepar)) return 0; /* * fill encoding list */ for (ae.index = 0; nenc < SIO_NENC; ae.index++) { if (ioctl(hdl->fd, AUDIO_GETENC, &ae) < 0) { if (errno == EINVAL) break; DPERROR("alsa_getcap: getenc"); hdl->sio.eof = 1; return 0; } if (ae.flags & AUDIO_ENCODINGFLAG_EMULATED) continue; if (ae.encoding == AUDIO_ENCODING_SLINEAR_LE) { cap->enc[nenc].le = 1; cap->enc[nenc].sig = 1; } else if (ae.encoding == AUDIO_ENCODING_SLINEAR_BE) { cap->enc[nenc].le = 0; cap->enc[nenc].sig = 1; } else if (ae.encoding == AUDIO_ENCODING_ULINEAR_LE) { cap->enc[nenc].le = 1; cap->enc[nenc].sig = 0; } else if (ae.encoding == AUDIO_ENCODING_ULINEAR_BE) { cap->enc[nenc].le = 0; cap->enc[nenc].sig = 0; } else if (ae.encoding == AUDIO_ENCODING_SLINEAR) { cap->enc[nenc].le = SIO_LE_NATIVE; cap->enc[nenc].sig = 1; } else if (ae.encoding == AUDIO_ENCODING_ULINEAR) { cap->enc[nenc].le = SIO_LE_NATIVE; cap->enc[nenc].sig = 0; } else { /* unsipported encoding */ continue; } cap->enc[nenc].bits = ae.precision; cap->enc[nenc].bps = SIO_BPS(ae.precision); cap->enc[nenc].msb = 1; enc_map |= (1 << nenc); nenc++; } /* * fill channels * * for now we're lucky: all kernel devices assume that the * number of channels and the encoding are independent so we can * use the current encoding and try various channels. */ if (hdl->sio.mode & SIO_PLAY) { memcpy(&cap->pchan, chans, NCHANS * sizeof(unsigned)); for (i = 0; i < NCHANS; i++) { if (alsa_tryinfo(hdl, NULL, chans[i], 0, 0)) pchan_map |= (1 << i); } } if (hdl->sio.mode & SIO_REC) { memcpy(&cap->rchan, chans, NCHANS * sizeof(unsigned)); for (i = 0; i < NCHANS; i++) { if (alsa_tryinfo(hdl, NULL, 0, chans[i], 0)) rchan_map |= (1 << i); } } /* * fill rates * * rates are not independent from other parameters (eg. on * uaudio devices), so certain rates may not be allowed with * certain encodings. We have to check rates for all encodings */ memcpy(&cap->rate, rates, NRATES * sizeof(unsigned)); for (j = 0; j < nenc; j++) { rate_map = 0; for (i = 0; i < NRATES; i++) { if (alsa_tryinfo(hdl, &cap->enc[j], 0, 0, rates[i])) rate_map |= (1 << i); } for (conf = 0; conf < nconf; conf++) { if (cap->confs[conf].rate == rate_map) { cap->confs[conf].enc |= (1 << j); break; } } if (conf == nconf) { if (nconf == SIO_NCONF) break; cap->confs[nconf].enc = (1 << j); cap->confs[nconf].pchan = pchan_map; cap->confs[nconf].rchan = rchan_map; cap->confs[nconf].rate = rate_map; nconf++; } } cap->nconf = nconf; if (!alsa_setpar(&hdl->sio, &savepar)) return 0; return 1; #undef NCHANS #undef NRATES #endif return 1; } static void alsa_getvol(struct sio_hdl *sh) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; sio_onvol_cb(&hdl->sio, SIO_MAXVOL); } int alsa_setvol(struct sio_hdl *sh, unsigned vol) { return 1; } struct sio_hdl * sio_open_alsa(const char *str, unsigned mode, int nbio) { struct alsa_hdl *hdl; char path[PATH_MAX]; hdl = malloc(sizeof(struct alsa_hdl)); if (hdl == NULL) return NULL; sio_create(&hdl->sio, &alsa_ops, mode, nbio); snprintf(path, sizeof(path), "hw:%s", str); if (mode & SIO_PLAY) { if (snd_pcm_open(&hdl->out_pcm, path, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) { DPERROR(path); goto bad_free; } snd_pcm_nonblock(hdl->out_pcm, 1); } if (mode & SIO_REC) { if (snd_pcm_open(&hdl->in_pcm, path, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) < 0) { DPERROR(path); goto bad_free; } snd_pcm_nonblock(hdl->in_pcm, 1); } if (hdl->out_pcm) { if (snd_pcm_hw_params_malloc(&hdl->out_hwp) < 0) { DPERROR("could not alloc out_hwp"); goto bad_free; } if (snd_pcm_sw_params_malloc(&hdl->out_swp) < 0) { DPERROR("could not alloc out_swp"); goto bad_free; } } if (hdl->in_pcm) { if (snd_pcm_hw_params_malloc(&hdl->in_hwp) < 0) { DPERROR("could not alloc in_hwp"); goto bad_free; } if (snd_pcm_sw_params_malloc(&hdl->in_swp) < 0) { DPERROR("could not alloc in_swp"); goto bad_free; } } /* * Default parameters may not be compatible with libsndio (eg. mulaw * encodings, different playback and recording parameters, etc...), so * set parameters to a random value. If the requested parameters are * not supported by the device, then sio_setpar() will pick supported * ones. */ if (mode & SIO_PLAY) { if (snd_pcm_hw_params_any(hdl->out_pcm, hdl->out_hwp) < 0) { DPERROR("no playback configurations available"); goto bad_free; } if (snd_pcm_hw_params_set_access(hdl->out_pcm, hdl->out_hwp, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { DPERROR("cannot use interleaved samples for playback"); goto bad_free; } } if (mode & SIO_REC) { if (snd_pcm_hw_params_any(hdl->in_pcm, hdl->in_hwp) < 0) { DPERROR("no recording configurations available"); goto bad_free; } if (snd_pcm_hw_params_set_access(hdl->in_pcm, hdl->in_hwp, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { DPERROR("cannot use interleaved samples for recording"); goto bad_free; } } return (struct sio_hdl *)hdl; bad_free: free(hdl); return NULL; } static void alsa_close(struct sio_hdl *sh) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; if (hdl->sio.mode & SIO_PLAY) { snd_pcm_close(hdl->out_pcm); snd_pcm_hw_params_free(hdl->out_hwp); snd_pcm_sw_params_free(hdl->out_swp); } if (hdl->sio.mode & SIO_REC) { snd_pcm_close(hdl->in_pcm); snd_pcm_hw_params_free(hdl->in_hwp); snd_pcm_sw_params_free(hdl->in_swp); } free(hdl); } static int alsa_start(struct sio_hdl *sh) { struct sio_par par; struct alsa_hdl *hdl = (struct alsa_hdl *)sh; if (!sio_getpar(&hdl->sio, &par)) return 0; hdl->ibpf = par.rchan * par.bps; hdl->obpf = par.pchan * par.bps; hdl->ihfr = 0; hdl->ohfr = 0; hdl->isfr = 0; hdl->osfr = 0; hdl->ierr = 0; hdl->oerr = 0; hdl->offset = 0; hdl->idelta = 0; hdl->odelta = 0; hdl->infds = 0; hdl->onfds = 0; /* prepare */ if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_prepare(hdl->out_pcm) < 0) { DPERROR("alsa_start: prepare playback failed"); hdl->sio.eof = 1; return 0; } hdl->onfds = snd_pcm_poll_descriptors_count(hdl->out_pcm); } if (hdl->sio.mode & SIO_REC) { if (snd_pcm_prepare(hdl->in_pcm) < 0) { DPERROR("alsa_start: prepare record failed"); hdl->sio.eof = 1; return 0; } hdl->infds = snd_pcm_poll_descriptors_count(hdl->in_pcm); } hdl->filling = 0; if (hdl->sio.mode & SIO_PLAY) { hdl->filling = 1; } if (hdl->sio.mode & SIO_REC) { if (snd_pcm_start(hdl->in_pcm) < 0) { DPERROR("alsa_start: start record failed"); hdl->sio.eof = 1; return 0; } } return 1; } static int alsa_stop(struct sio_hdl *sh) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_drop(hdl->out_pcm) < 0) { DPERROR("alsa_stop: drop/close playback failed"); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode & SIO_REC) { if (snd_pcm_drop(hdl->in_pcm) < 0) { DPERROR("alsa_stop: drop/close record failed"); hdl->sio.eof = 1; return 0; } } return 1; } static int alsa_setpar(struct sio_hdl *sh, struct sio_par *par) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; unsigned bufsz, round; unsigned irate, orate, req_rate; unsigned ich, och; snd_pcm_format_t ifmt, ofmt; snd_pcm_uframes_t infr, onfr; int fmt_err, nround; unsigned inp, onp; struct sio_enc enc; /* bits, bps, sig, le, msb */ enc.bits = par->bits; enc.bps = par->bps; enc.sig = par->sig; enc.le = par->le; enc.msb = par->msb; alsa_enctofmt(hdl, &ofmt, &enc); ifmt = ofmt; fmt_err = 0; if (hdl->sio.mode & SIO_PLAY) { play_again: /* SOUND_PCM_FORMAT_FLOAT_LE is the highest enum we can * support, SND_PCM_FORMAT_S16_LE is the lowest. */ while (ofmt < SND_PCM_FORMAT_FLOAT_LE) { if (snd_pcm_hw_params_set_format(hdl->out_pcm, hdl->out_hwp, ofmt) < 0) { if (!fmt_err) { /* skip 8-bit formats */ ofmt = SND_PCM_FORMAT_S16_LE - 1; } fmt_err++; ofmt++; } else { break; } } if (ofmt >= SND_PCM_FORMAT_FLOAT_LE) { DPERROR("could not set linear play format"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_format(hdl->out_hwp, &ofmt) < 0) { DPERROR("get play format failed"); hdl->sio.eof = 1; return 0; } if (hdl->sio.mode & SIO_REC) { ifmt = ofmt; if (snd_pcm_hw_params_set_format(hdl->in_pcm, hdl->in_hwp, ifmt) < 0) { ifmt++; ofmt = ifmt; goto play_again; } } } else { while (ifmt < SND_PCM_FORMAT_FLOAT_LE) { if (snd_pcm_hw_params_set_format(hdl->in_pcm, hdl->in_hwp, ifmt) < 0) { if (!fmt_err) { /* skip 8-bit formats */ ifmt = SND_PCM_FORMAT_S16_LE - 1; } fmt_err++; ifmt++; } else { break; } } if (ifmt >= SND_PCM_FORMAT_FLOAT_LE) { DPERROR("could not set linear record format"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_format(hdl->in_hwp, &ifmt) < 0) { DPERROR("get record format failed"); hdl->sio.eof = 1; return 0; } } alsa_fmttopar(hdl, (hdl->sio.mode & SIO_PLAY) ? ofmt : ifmt, par); /* rate */ /* set a rate so we can get 1 rate back, otherwise _get_rate() fails */ if (!par->rate || par->rate == ~0U) par->rate = 44100; req_rate = orate = irate = par->rate; if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_set_rate_near(hdl->out_pcm, hdl->out_hwp, &orate, NULL) < 0) { DPERROR("set play rate failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_rate(hdl->out_hwp, &orate, 0) < 0) { DPERROR("get play rate failed"); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_set_rate_near(hdl->in_pcm, hdl->in_hwp, &irate, NULL) < 0) { DPERROR("set rec rate failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_rate(hdl->in_hwp, &irate, 0) < 0) { DPERROR("get record rate failed"); hdl->sio.eof = 1; return 0; } } if ((hdl->sio.mode == (SIO_PLAY|SIO_REC)) && (orate != irate)) { DPERROR("could not get matching play/record rate"); hdl->sio.eof = 1; return 0; } par->rate = (hdl->sio.mode & SIO_PLAY) ? orate : irate; /* pchan, rchan */ och = par->pchan; ich = par->rchan; if (hdl->sio.mode & SIO_PLAY) { if (!och || och == ~0U) och = 2; if (snd_pcm_hw_params_set_channels_near(hdl->out_pcm, hdl->out_hwp, &och) < 0) { DPERROR("set play channel count failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_channels(hdl->out_hwp, &och) < 0) { DPERROR("get play channel count failed"); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode & SIO_REC) { if (!ich || ich == ~0U) ich = 2; if (snd_pcm_hw_params_set_channels_near(hdl->in_pcm, hdl->in_hwp, &ich) < 0) { DPERROR("set record channel count failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_channels(hdl->in_hwp, &ich) < 0) { DPERROR("get record channel count failed"); hdl->sio.eof = 1; return 0; } } par->pchan = och; par->rchan = ich; /* round */ /* * if block size and buffer size are not both set then * set the blocksize to 1/4 the buffer size */ /* * If the rate that the hardware is using is different than * the requested rate, scale buffer sizes so they will be the * same time duration as what was requested. */ bufsz = par->appbufsz; round = par->round; if (bufsz && bufsz != ~0U) { bufsz = bufsz * par->rate / req_rate; if (round == ~0U) round = (bufsz + 1) / 4; else round = round * par->rate / req_rate; } else if (round && round != ~0U) { round = round * par->rate / req_rate; bufsz = round * 4; } else { round = par->rate / 20; bufsz = round * 4; } infr = onfr = round; if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_set_period_size_near(hdl->out_pcm, hdl->out_hwp, &onfr, NULL) < 0) { DPERROR("set play period size failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_period_size(hdl->out_hwp, &onfr, NULL) < 0) { DPERROR("get play period size failed"); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode == (SIO_PLAY|SIO_REC)) infr = onfr; if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_set_period_size_near(hdl->in_pcm, hdl->in_hwp, &infr, NULL) < 0) { DPERROR("set record period size failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_period_size(hdl->in_hwp, &infr, NULL) < 0) { DPERROR("get record period size failed"); hdl->sio.eof = 1; return 0; } } if ((hdl->sio.mode == (SIO_PLAY|SIO_REC)) && (infr != onfr)) { DPERROR("could not get matching play/record period size"); hdl->sio.eof = 1; return 0; } par->round = (hdl->sio.mode & SIO_PLAY) ? onfr : infr; /* appbufsz */ nround = (bufsz + round - 1) / round; inp = onp = nround; if (hdl->sio.mode & SIO_PLAY) { snd_pcm_hw_params_set_periods_min(hdl->out_pcm, hdl->out_hwp, &onp, NULL); /* if above returned smaller than wanted, bump it back up. * if larger was returned, it's the min and we have to use it. */ if (onp < nround) onp = nround; if (snd_pcm_hw_params_set_periods_near(hdl->out_pcm, hdl->out_hwp, &onp, NULL) < 0) { DPERROR("set play periods failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_periods(hdl->out_hwp, &onp, NULL) < 0) { DPERROR("get play periods failed"); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode == (SIO_PLAY|SIO_REC)) inp = onp; if (hdl->sio.mode & SIO_REC) { snd_pcm_hw_params_set_periods_min(hdl->in_pcm, hdl->in_hwp, &inp, NULL); /* if above returned smaller than wanted, bump it back up. * if larger was returned, it's the min and we have to use it. */ if (inp < nround) inp = nround; if (snd_pcm_hw_params_set_periods_near(hdl->in_pcm, hdl->in_hwp, &inp, NULL) < 0) { DPERROR("set record periods failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_hw_params_get_periods(hdl->in_hwp, &inp, NULL) < 0) { DPERROR("get record periods failed"); hdl->sio.eof = 1; return 0; } } if ((hdl->sio.mode == (SIO_PLAY|SIO_REC)) && (inp != onp)) { DPERROR("could not get matching play/record period count"); hdl->sio.eof = 1; return 0; } if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_set_buffer_size(hdl->out_pcm, hdl->out_hwp, onp * onfr) < 0) { DPERROR("set play buffer size failed"); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_set_buffer_size(hdl->in_pcm, hdl->in_hwp, inp * infr) < 0) { DPERROR("set record buffer size failed"); hdl->sio.eof = 1; return 0; } } par->appbufsz = (hdl->sio.mode & SIO_PLAY) ? onfr * onp : infr * inp; par->bufsz = par->appbufsz; DPRINTF("alsa_setpar: pchan=%d rchan=%d rate=%d round=%d bufsz=%d\n", par->pchan, par->rchan, par->rate, par->round, par->appbufsz); /* commit hardware params */ if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params(hdl->out_pcm, hdl->out_hwp) < 0) { DPERROR("commit play params failed"); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params(hdl->in_pcm, hdl->in_hwp) < 0) { DPERROR("commit record params failed"); hdl->sio.eof = 1; return 0; } } /* software params */ if (hdl->sio.mode & SIO_PLAY) { snd_pcm_sw_params_current(hdl->out_pcm, hdl->out_swp); if (snd_pcm_sw_params_set_start_threshold(hdl->out_pcm, hdl->out_swp, UINT_MAX) < 0) { DPERROR("set play start threshold failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_sw_params_set_stop_threshold(hdl->out_pcm, hdl->out_swp, onfr * onp) < 0) { DPERROR("set play stop threshold failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_sw_params_set_avail_min(hdl->out_pcm, hdl->out_swp, onfr) < 0) { DPERROR("set play min avail failed"); hdl->sio.eof = 1; return 0; } } if (hdl->sio.mode & SIO_REC) { snd_pcm_sw_params_current(hdl->in_pcm, hdl->in_swp); if (snd_pcm_sw_params_set_start_threshold(hdl->in_pcm, hdl->in_swp, UINT_MAX) < 0) { DPERROR("set record start threshold failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_sw_params_set_stop_threshold(hdl->in_pcm, hdl->in_swp, infr * inp) < 0) { DPERROR("set record stop threshold failed"); hdl->sio.eof = 1; return 0; } if (snd_pcm_sw_params_set_avail_min(hdl->in_pcm, hdl->in_swp, infr) < 0) { DPERROR("set record min avail failed"); hdl->sio.eof = 1; return 0; } } return 1; } static int alsa_getpar(struct sio_hdl *sh, struct sio_par *par) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; snd_pcm_format_t fmt; snd_pcm_uframes_t nfr; uint nch, rate; int dir; /* bit, sig, le, msb */ if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_get_format(hdl->out_hwp, &fmt) < 0) { DPERROR("alsa_getpar: get play hw format"); hdl->sio.eof = 1; return 0; } } else if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_get_format(hdl->in_hwp, &fmt) < 0) { DPERROR("alsa_getpar: get rec hw format"); hdl->sio.eof = 1; return 0; } } alsa_fmttopar(hdl, fmt, par); /* pchan, rchan */ if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_get_channels(hdl->out_hwp, &nch) < 0) { DPERROR("alsa_getpar: get play hw channels"); hdl->sio.eof = 1; return 0; } par->pchan = nch; } if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_get_channels(hdl->in_hwp, &nch) < 0) { DPERROR("alsa_getpar: get rec hw channels"); hdl->sio.eof = 1; return 0; } par->rchan = nch; } /* rate */ if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_get_rate(hdl->out_hwp, &rate, &dir) < 0) { DPERROR("alsa_getpar: get play hw rate"); hdl->sio.eof = 1; return 0; } } else if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_get_rate(hdl->in_hwp, &rate, &dir) < 0) { DPERROR("alsa_getpar: get rec hw rate"); hdl->sio.eof = 1; return 0; } } par->rate = rate; /* round */ if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_get_period_size(hdl->out_hwp, &nfr, &dir) < 0) { DPERROR("alsa_getpar: get play hw round"); hdl->sio.eof = 1; return 0; } } else if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_get_period_size(hdl->in_hwp, &nfr, &dir) < 0) { DPERROR("alsa_getpar: get rec hw round"); hdl->sio.eof = 1; return 0; } } par->round = nfr; /* appbufsz */ if (hdl->sio.mode & SIO_PLAY) { if (snd_pcm_hw_params_get_buffer_size(hdl->out_hwp, &nfr) < 0) { DPERROR("alsa_getpar: get play hw bufsz"); hdl->sio.eof = 1; return 0; } } else if (hdl->sio.mode & SIO_REC) { if (snd_pcm_hw_params_get_buffer_size(hdl->in_hwp, &nfr) < 0) { DPERROR("alsa_getpar: get rec hw bufsz"); hdl->sio.eof = 1; return 0; } } par->appbufsz = nfr; par->bufsz = par->appbufsz; return 1; } /* * drop recorded samples to compensate xruns */ static int alsa_rdrop(struct alsa_hdl *hdl) { #define DROP_NMAX 0x1000 static char dropbuf[DROP_NMAX]; snd_pcm_sframes_t n; snd_pcm_uframes_t todo; int drop_nmax = DROP_NMAX / hdl->ibpf; while (hdl->offset > 0) { todo = hdl->offset; if (todo > drop_nmax) todo = drop_nmax; while ((n = snd_pcm_readi(hdl->in_pcm, dropbuf, todo)) < 0) { if (n == -ESTRPIPE) continue; DPERROR("alsa_rdrop: readi"); hdl->sio.eof = 1; return 0; } if (n == 0) { DPRINTF("alsa_rdrop: eof\n"); hdl->sio.eof = 1; return 0; } hdl->offset -= (int)n; //hdl->isfr += (int)n; DPRINTF("alsa_rdrop: dropped %ld/%ld frames\n", n, todo); } return 1; } static size_t alsa_read(struct sio_hdl *sh, void *buf, size_t len) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; snd_pcm_sframes_t n; snd_pcm_uframes_t todo; if (!alsa_rdrop(hdl)) return 0; todo = len / hdl->ibpf; while ((n = snd_pcm_readi(hdl->in_pcm, buf, todo)) < 0) { if (n == -ESTRPIPE) continue; DPERROR("alsa_read: read"); hdl->sio.eof = 1; return 0; } if (n == 0) { DPRINTF("alsa_read: eof\n"); hdl->sio.eof = 1; return 0; } hdl->isfr += n; n *= hdl->ibpf; return n; } static size_t alsa_autostart(struct alsa_hdl *hdl) { struct pollfd *pfd; int ret, state; unsigned short revents; pfd = malloc(sizeof(struct pollfd) * hdl->onfds); if (pfd == NULL) { DPERROR("alsa_autostart: allocate pfd"); hdl->sio.eof = 1; return 0; } ret = snd_pcm_poll_descriptors(hdl->out_pcm, pfd, hdl->onfds); if (ret != hdl->onfds) { DPERROR("alsa_autostart: snd_pcm_poll_descriptors"); hdl->sio.eof = 1; goto bad_free; } pfd->events = POLLOUT; while (poll(pfd, hdl->onfds, 0) < 0) { if (errno == EINTR) continue; DPERROR("alsa_autostart: poll"); hdl->sio.eof = 1; goto bad_free; } if (snd_pcm_poll_descriptors_revents(hdl->out_pcm, pfd, hdl->onfds, &revents) < 0) { DPERROR("alsa_autostart: snd_pcm_poll_descriptors"); goto bad_free; } if (!(revents & POLLOUT)) { hdl->filling = 0; if (hdl->sio.mode & SIO_PLAY) { state = snd_pcm_state(hdl->out_pcm); if (state == SND_PCM_STATE_PREPARED) { if (snd_pcm_start(hdl->out_pcm) < 0) { DPERROR("alsa_autostart: play failed"); goto bad_free; } } else if (state != SND_PCM_STATE_RUNNING) { DPERROR("alsa_autostart: bad play state"); goto bad_free; } } sio_onmove_cb(&hdl->sio, 0); } free(pfd); return 1; bad_free: free(pfd); return 0; } /* * insert silence to play to compensate xruns */ static int alsa_wsil(struct alsa_hdl *hdl) { #define ZERO_NMAX 0x1000 static char zero[ZERO_NMAX]; snd_pcm_uframes_t n; snd_pcm_sframes_t todo; int zero_nmax = ZERO_NMAX / hdl->obpf; while (hdl->offset < 0) { todo = (int)-hdl->offset; if (todo > zero_nmax) todo = zero_nmax; if ((n = snd_pcm_writei(hdl->out_pcm, zero, todo)) < 0) { if (errno == -EBADFD) DPERROR("alsa_wsil: PCM not in the right state"); else if (errno == -EPIPE) DPERROR("alsa_wsil: underrun"); else if (errno == -ESTRPIPE) DPERROR("alsa_wsil: stream is suspended"); hdl->sio.eof = 1; return 0; } hdl->offset += (int)n; //hdl->osfr += (int)n; DPRINTF("alsa_wsil: inserted %ld/%ld frames\n", n, todo); } return 1; } static size_t alsa_write(struct sio_hdl *sh, const void *buf, size_t len) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; const unsigned char *data = buf; ssize_t n, todo; if (!alsa_wsil(hdl)) return 0; todo = len / hdl->obpf; if ((n = snd_pcm_writei(hdl->out_pcm, data, todo)) < 0) { if (errno == -EBADFD) DPERROR("alsa_write: PCM not in the right state"); else if (errno == -EPIPE) DPERROR("alsa_write: underrun"); else if (errno == -ESTRPIPE) DPERROR("alsa_write: stream is suspended"); return 0; } if (hdl->filling) { if (!alsa_autostart(hdl)) return 0; } hdl->osfr += n; n *= hdl->obpf; return n; } static int alsa_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; int nfds, ret, i; nfds = 0; if (hdl->sio.mode & SIO_PLAY) { ret = snd_pcm_poll_descriptors(hdl->out_pcm, &pfd[0], hdl->onfds); if (ret != hdl->onfds) { DPERROR("alsa_pollfd: play snd_pcm_poll_descriptors"); hdl->sio.eof = 1; return 0; } nfds += ret; } if (hdl->sio.mode & SIO_REC) { ret = snd_pcm_poll_descriptors(hdl->in_pcm, &pfd[nfds], hdl->infds); if (ret != hdl->infds) { DPERROR("alsa_pollfd: record snd_pcm_poll_descriptors"); hdl->sio.eof = 1; return 0; } nfds += ret; } for (i = 0; i < nfds; i++) { /* user events */ pfd[i].events |= events; /* ALSA doesn't set POLLERR in some versions ? */ pfd[i].events |= POLLERR; } return nfds; } int alsa_revents(struct sio_hdl *sh, struct pollfd *pfd) { struct alsa_hdl *hdl = (struct alsa_hdl *)sh; snd_pcm_sframes_t idelay, odelay; snd_pcm_state_t istate, ostate; int hw_ptr, nfds; unsigned short revents, all_revents; all_revents = nfds = 0; if (hdl->sio.mode & SIO_PLAY) { ostate = snd_pcm_state(hdl->out_pcm); if (ostate == SND_PCM_STATE_XRUN) { printf("alsa_revents: play xrun\n"); } if (snd_pcm_poll_descriptors_revents(hdl->out_pcm, &pfd[0], hdl->onfds, &revents) < 0) { DPERROR("alsa_revents: snd_pcm_poll_descriptors"); hdl->sio.eof = 1; return POLLHUP; } if (revents & POLLERR) DPERROR("alsa_revents: play xrun?"); all_revents |= revents; nfds += hdl->onfds; } if (hdl->sio.mode & SIO_REC) { istate = snd_pcm_state(hdl->in_pcm); if (istate == SND_PCM_STATE_XRUN) { printf("alsa_revents: record xrun\n"); } if (snd_pcm_poll_descriptors_revents(hdl->in_pcm, &pfd[nfds], hdl->infds, &revents) < 0) { DPERROR("alsa_revents: snd_pcm_poll_descriptors"); hdl->sio.eof = 1; return POLLHUP; } if (revents & POLLERR) DPERROR("alsa_revents: record xrun?"); all_revents |= revents; nfds += hdl->infds; } revents = all_revents; if ((revents & POLLOUT) && (hdl->sio.mode & SIO_PLAY) && (ostate == SND_PCM_STATE_RUNNING || ostate == SND_PCM_STATE_PREPARED)) { if (snd_pcm_avail_update(hdl->out_pcm) < 0) { DPERROR("alsa_revents: play snd_pcm_avail_update"); hdl->sio.eof = 1; return POLLHUP; } if (snd_pcm_delay(hdl->out_pcm, &odelay) < 0) { DPERROR("alsa_revents: play snd_pcm_delay"); hdl->sio.eof = 1; return POLLHUP; } if (odelay < 0) { printf("alsa_revents: play xrun (delay)\n"); } hw_ptr = hdl->osfr - odelay; hdl->odelta += hw_ptr - hdl->ohfr; hdl->ohfr = hw_ptr; if (hdl->odelta > 0) { sio_onmove_cb(&hdl->sio, hdl->odelta); hdl->odelta = 0; } } if ((revents & POLLIN) && !(hdl->sio.mode & SIO_PLAY) && (istate == SND_PCM_STATE_RUNNING || istate == SND_PCM_STATE_PREPARED)) { if (snd_pcm_avail_update(hdl->in_pcm) < 0) { DPERROR("alsa_revents: record snd_pcm_avail_update"); hdl->sio.eof = 1; return POLLHUP; } if (snd_pcm_delay(hdl->in_pcm, &idelay) < 0) { DPERROR("alsa_revents: record snd_pcm_delay"); hdl->sio.eof = 1; return POLLHUP; } if (idelay < 0) { printf("alsa_revents: record xrun (delay)\n"); } hw_ptr = hdl->isfr + idelay; hdl->idelta += hw_ptr - hdl->ihfr; hdl->ihfr = hw_ptr; if (hdl->idelta > 0) { sio_onmove_cb(&hdl->sio, hdl->idelta); hdl->idelta = 0; } } /* * drop recorded samples or insert silence to play * right now to adjust revents, and avoid busy loops * programs */ if (hdl->sio.started) { if (hdl->filling) revents |= POLLOUT; if ((hdl->sio.mode & SIO_PLAY) && !alsa_wsil(hdl)) revents &= ~POLLOUT; if ((hdl->sio.mode & SIO_REC) && !alsa_rdrop(hdl)) revents &= ~POLLIN; } return revents; } #endif /* defined USE_ALSA */