From 32894736780ea63e5fea456de27650fc27ec57c6 Mon Sep 17 00:00:00 2001 From: Christian Neukirchen Date: Sat, 28 Sep 2013 18:01:12 +0200 Subject: audio/out: add sndio support Based on an earlier patch for mplayer by Alexandre Ratchov --- audio/out/ao.c | 4 + audio/out/ao_sndio.c | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 audio/out/ao_sndio.c (limited to 'audio') diff --git a/audio/out/ao.c b/audio/out/ao.c index 1c7cfcbcae..566ab0fea9 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -35,6 +35,7 @@ extern const struct ao_driver audio_out_oss; extern const struct ao_driver audio_out_coreaudio; extern const struct ao_driver audio_out_rsound; +extern const struct ao_driver audio_out_sndio; extern const struct ao_driver audio_out_pulse; extern const struct ao_driver audio_out_jack; extern const struct ao_driver audio_out_openal; @@ -55,6 +56,9 @@ static const struct ao_driver * const audio_out_drivers[] = { #ifdef CONFIG_PULSE &audio_out_pulse, #endif +#ifdef CONFIG_SNDIO + &audio_out_sndio, +#endif #ifdef CONFIG_ALSA &audio_out_alsa, #endif diff --git a/audio/out/ao_sndio.c b/audio/out/ao_sndio.c new file mode 100644 index 0000000000..0a1f9f3038 --- /dev/null +++ b/audio/out/ao_sndio.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2008 Alexandre Ratchov + * Copyright (c) 2013 Christian Neukirchen + * + * 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. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "mpvcore/m_option.h" +#include "mpvcore/mp_msg.h" + +#include "audio/format.h" +#include "ao.h" + +struct priv { + struct sio_hdl *hdl; + struct sio_par par; + int delay; + int vol; + int havevol; +#define SILENCE_NMAX 0x1000 + char silence[SILENCE_NMAX]; + struct pollfd *pfd; + char *dev; +}; + +/* + * misc parameters (volume, etc...) + */ +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct priv *p = ao->priv; + ao_control_vol_t *vol = arg; + + switch (cmd) { + case AOCONTROL_GET_VOLUME: + if (!p->havevol) + return CONTROL_FALSE; + vol->left = vol->right = p->vol * 100 / SIO_MAXVOL; + break; + case AOCONTROL_SET_VOLUME: + if (!p->havevol) + return CONTROL_FALSE; + sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100); + break; + default: + return CONTROL_UNKNOWN; + } + return CONTROL_OK; +} + +/* + * call-back invoked to notify of the hardware position + */ +static void movecb(void *addr, int delta) +{ + struct priv *p = addr; + p->delay -= delta * (int)(p->par.bps * p->par.pchan); +} + +/* + * call-back invoked to notify about volume changes + */ +static void volcb(void *addr, unsigned newvol) +{ + struct priv *p = addr; + p->vol = newvol; +} + +static const struct mp_chmap sndio_layouts[MP_NUM_CHANNELS + 1] = { + {0}, // empty + {1, {MP_SPEAKER_ID_FL}}, // mono + MP_CHMAP2(FL, FR), // stereo + {0}, // 2.1 + MP_CHMAP4(FL, FR, BL, BR), // 4.0 + {0}, // 5.0 + MP_CHMAP6(FL, FR, BL, BR, FC, LFE), // 5.1 + {0}, // 6.1 + MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), // 7.1 + /* above is the fixed channel assignment for sndio, since we need to fill + all channels and cannot insert silence, not all layouts are supported. */ +}; + +/* + * open device and setup parameters + * return: 0=success -1=fail + */ +static int init(struct ao *ao) +{ + struct priv *p = ao->priv; + + struct af_to_par { + int format, bits, sig, le; + } static const af_to_par[] = { + {AF_FORMAT_U8, 8, 0, 0}, + {AF_FORMAT_S8, 8, 1, 0}, + {AF_FORMAT_U16_LE, 16, 0, 1}, + {AF_FORMAT_U16_BE, 16, 0, 0}, + {AF_FORMAT_S16_LE, 16, 1, 1}, + {AF_FORMAT_S16_BE, 16, 1, 0}, + {AF_FORMAT_U24_LE, 16, 0, 1}, + {AF_FORMAT_U24_BE, 24, 0, 0}, + {AF_FORMAT_S24_LE, 24, 1, 1}, + {AF_FORMAT_S24_BE, 24, 1, 0}, + {AF_FORMAT_U32_LE, 32, 0, 1}, + {AF_FORMAT_U32_BE, 32, 0, 0}, + {AF_FORMAT_S32_LE, 32, 1, 1}, + {AF_FORMAT_S32_BE, 32, 1, 0} + }, *ap; + int i; + + p->hdl = sio_open(p->dev, SIO_PLAY, 0); + if (p->hdl == NULL) { + MP_ERR(ao, "can't open sndio %s\n", p->dev); + goto error; + } + sio_initpar(&p->par); + for (i = 0, ap = af_to_par;; i++, ap++) { + if (i == sizeof(af_to_par) / sizeof(struct af_to_par)) { + MP_VERBOSE(ao, "unsupported format\n"); + p->par.bits = 16; + p->par.sig = 1; + p->par.le = SIO_LE_NATIVE; + break; + } + if (ap->format == ao->format) { + p->par.bits = ap->bits; + p->par.sig = ap->sig; + if (ap->bits > 8) + p->par.le = ap->le; + if (ap->bits != SIO_BPS(ap->bits)) + p->par.bps = ap->bits / 8; + break; + } + } + p->par.rate = ao->samplerate; + + struct mp_chmap_sel sel = {0}; + for (int n = 0; n < MP_NUM_CHANNELS+1; n++) + mp_chmap_sel_add_map(&sel, &sndio_layouts[n]); + + if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) + goto error; + + p->par.pchan = ao->channels.num; + p->par.appbufsz = p->par.rate * 250 / 1000; /* 250ms buffer */ + p->par.round = p->par.rate * 10 / 1000; /* 10ms block size */ + if (!sio_setpar(p->hdl, &p->par)) { + MP_ERR(ao, "couldn't set params\n"); + goto error; + } + if (!sio_getpar(p->hdl, &p->par)) { + MP_ERR(ao, "couldn't get params\n"); + goto error; + } + if (p->par.bits == 8 && p->par.bps == 1) { + ao->format = p->par.sig ? AF_FORMAT_S8 : AF_FORMAT_U8; + } else if (p->par.bits == 16 && p->par.bps == 2) { + ao->format = p->par.sig ? + (p->par.le ? AF_FORMAT_S16_LE : AF_FORMAT_S16_BE) : + (p->par.le ? AF_FORMAT_U16_LE : AF_FORMAT_U16_BE); + } else if ((p->par.bits == 24 || p->par.msb) && p->par.bps == 3) { + ao->format = p->par.sig ? + (p->par.le ? AF_FORMAT_S24_LE : AF_FORMAT_S24_BE) : + (p->par.le ? AF_FORMAT_U24_LE : AF_FORMAT_U24_BE); + } else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4) { + ao->format = p->par.sig ? + (p->par.le ? AF_FORMAT_S32_LE : AF_FORMAT_S32_BE) : + (p->par.le ? AF_FORMAT_U32_LE : AF_FORMAT_U32_BE); + } else { + MP_ERR(ao, "couldn't set format\n"); + goto error; + } + + ao->bps = p->par.bps * p->par.pchan * p->par.rate; + ao->no_persistent_volume = true; + p->havevol = sio_onvol(p->hdl, volcb, p); + sio_onmove(p->hdl, movecb, p); + p->delay = 0; + if (!sio_start(p->hdl)) + MP_ERR(ao, "init: couldn't start\n"); + + p->pfd = calloc (sio_nfds(p->hdl), sizeof (struct pollfd)); + if (!p->pfd) + goto error; + + return 0; + +error: + if (p->hdl) + sio_close(p->hdl); + + return -1; +} + +/* + * close device + */ +static void uninit(struct ao *ao, bool immed) +{ + struct priv *p = ao->priv; + + if (p->hdl) + sio_close(p->hdl); + + free(p->pfd); +} + +/* + * stop playing and empty buffers (for seeking/pause) + */ +static void reset(struct ao *ao) +{ + struct priv *p = ao->priv; + + if (!sio_stop(p->hdl)) + MP_ERR(ao, "reset: couldn't stop\n"); + p->delay = 0; + if (!sio_start(p->hdl)) + MP_ERR(ao, "reset: couldn't start\n"); +} + +/* + * play given number of bytes until sio_write() blocks + */ +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *p = ao->priv; + int n; + + n = sio_write(p->hdl, data, len); + p->delay += n; + if (flags & AOPLAY_FINAL_CHUNK) + reset(ao); + return n; +} + +/* + * how many bytes can be played without blocking + */ +static int get_space(struct ao *ao) +{ + struct priv *p = ao->priv; + int n; + + /* + * call poll() and sio_revents(), so the + * delay counter is updated + */ + n = sio_pollfd(p->hdl, p->pfd, POLLOUT); + while (poll(p->pfd, n, 0) < 0 && errno == EINTR) + ; /* nothing */ + sio_revents(p->hdl, p->pfd); + + return p->par.bufsz * p->par.pchan * p->par.bps - p->delay; +} + +/* + * return: delay in seconds between first and last sample in buffer + */ +static float get_delay(struct ao *ao) +{ + struct priv *p = ao->priv; + return (float)p->delay / (p->par.bps * p->par.pchan * p->par.rate); +} + +/* + * stop playing, keep buffers (for pause) + */ +static void audio_pause(struct ao *ao) +{ + reset(ao); +} + +/* + * resume playing, after audio_pause() + */ +static void audio_resume(struct ao *ao) +{ + struct priv *p = ao->priv; + int n, count, todo; + + /* + * we want to start with buffers full, because mplayer uses + * get_space() pointer as clock, which would cause video to + * accelerate while buffers are filled. + */ + todo = p->par.bufsz * p->par.pchan * p->par.bps; + while (todo > 0) { + count = todo; + if (count > SILENCE_NMAX) + count = SILENCE_NMAX; + n = sio_write(p->hdl, p->silence, count); + if (n == 0) + break; + todo -= n; + p->delay += n; + } +} + +#define OPT_BASE_STRUCT struct priv + +const struct ao_driver audio_out_sndio = { + .info = &(const struct ao_info) { + "sndio audio output", + "sndio", + "Alexandre Ratchov , Christian Neukirchen ", + "under development" + }, + .init = init, + .uninit = uninit, + .control = control, + .get_space = get_space, + .play = play, + .get_delay = get_delay, + .pause = audio_pause, + .resume = audio_resume, + .reset = reset, + .priv_size = sizeof(struct priv), + .options = (const struct m_option[]) { + OPT_STRING("device", dev, 0, OPTDEF_STR(SIO_DEVANY)), + {0} + }, +}; -- cgit v1.2.3