diff options
Diffstat (limited to 'audio/out/ao_sndio.c')
-rw-r--r-- | audio/out/ao_sndio.c | 330 |
1 files changed, 167 insertions, 163 deletions
diff --git a/audio/out/ao_sndio.c b/audio/out/ao_sndio.c index e5938b7d26..309ea4ab74 100644 --- a/audio/out/ao_sndio.c +++ b/audio/out/ao_sndio.c @@ -1,6 +1,8 @@ /* * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org> * Copyright (c) 2013 Christian Neukirchen <chneukirchen@gmail.com> + * Copyright (c) 2020 Rozhuk Ivan <rozhuk.im@gmail.com> + * Copyright (c) 2021 Andrew Krasavin <noiseless-ak@yandex.ru> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,13 +17,13 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "config.h" - #include <sys/types.h> #include <poll.h> #include <errno.h> #include <sndio.h> +#include "config.h" + #include "options/m_option.h" #include "common/msg.h" @@ -36,78 +38,63 @@ struct priv { bool playing; 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) + +static const struct mp_chmap sndio_layouts[] = { + {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. + * NOTE: MP_SPEAKER_ID_NA could be used to add padding channels. */ +}; + +static void uninit(struct ao *ao); + + +/* Make libsndio call movecb(). */ +static void process_events(struct ao *ao) { 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; + int n = sio_pollfd(p->hdl, p->pfd, POLLOUT); + while (poll(p->pfd, n, 0) < 0 && errno == EINTR); + + sio_revents(p->hdl, p->pfd); } -/* - * call-back invoked to notify of the hardware position - */ +/* Call-back invoked to notify of the hardware position. */ static void movecb(void *addr, int delta) { - struct priv *p = addr; + struct ao *ao = addr; + struct priv *p = ao->priv; + p->delay -= delta; } -/* - * call-back invoked to notify about volume changes - */ +/* Call-back invoked to notify about volume changes. */ static void volcb(void *addr, unsigned newvol) { - struct priv *p = addr; + struct ao *ao = addr; + struct priv *p = ao->priv; + 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. - NOTE: MP_SPEAKER_ID_NA could be used to add padding channels. */ -}; - -/* - * open device and setup parameters - * return: 0=success -1=fail - */ static int init(struct ao *ao) { struct priv *p = ao->priv; - + struct mp_chmap_sel sel = {0}; + size_t i; struct af_to_par { int format, bits, sig; }; @@ -117,59 +104,62 @@ static int init(struct ao *ao) {AF_FORMAT_S32, 32, 1}, }; const struct af_to_par *ap; - int i; + const char *device = ((ao->device) ? ao->device : SIO_DEVANY); - p->hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0); + /* Opening device. */ + MP_VERBOSE(ao, "Using '%s' audio device.\n", device); + p->hdl = sio_open(device, SIO_PLAY, 0); if (p->hdl == NULL) { - MP_ERR(ao, "can't open sndio %s\n", SIO_DEVANY); - goto error; + MP_ERR(ao, "Can't open audio device %s.\n", device); + goto err_out; } + sio_initpar(&p->par); + + /* Selecting sound format. */ ao->format = af_fmt_from_planar(ao->format); - 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; - } + p->par.bits = 16; + p->par.sig = 1; + p->par.le = SIO_LE_NATIVE; + for (i = 0; i < MP_ARRAY_SIZE(af_to_par); i++) { + ap = &af_to_par[i]; if (ap->format == ao->format) { p->par.bits = ap->bits; p->par.sig = ap->sig; - if (ap->bits > 8) - p->par.le = SIO_LE_NATIVE; - 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]); + p->par.rate = ao->samplerate; + /* Channels count. */ + for (i = 0; i < MP_ARRAY_SIZE(sndio_layouts); i++) { + mp_chmap_sel_add_map(&sel, &sndio_layouts[i]); + } if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) - goto error; + goto err_out; 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; + goto err_out; } + + /* Get current sound params. */ if (!sio_getpar(p->hdl, &p->par)) { MP_ERR(ao, "couldn't get params\n"); - goto error; + goto err_out; } if (p->par.bps > 1 && p->par.le != SIO_LE_NATIVE) { MP_ERR(ao, "swapped endian output not supported\n"); - goto error; + goto err_out; } + + /* Update sound params. */ if (p->par.bits == 8 && p->par.bps == 1 && !p->par.sig) { ao->format = AF_FORMAT_U8; } else if (p->par.bits == 16 && p->par.bps == 2 && p->par.sig) { @@ -178,142 +168,156 @@ static int init(struct ao *ao) ao->format = AF_FORMAT_S32; } else { MP_ERR(ao, "couldn't set format\n"); - goto error; + goto err_out; } - p->havevol = sio_onvol(p->hdl, volcb, p); - sio_onmove(p->hdl, movecb, p); - if (!sio_start(p->hdl)) - MP_ERR(ao, "init: couldn't start\n"); + p->havevol = sio_onvol(p->hdl, volcb, ao); + sio_onmove(p->hdl, movecb, ao); - p->pfd = calloc (sio_nfds(p->hdl), sizeof (struct pollfd)); + p->pfd = talloc_array_ptrtype(p, p->pfd, sio_nfds(p->hdl)); if (!p->pfd) - goto error; + goto err_out; - ao->period_size = p->par.round; + ao->device_buffer = p->par.bufsz; + MP_VERBOSE(ao, "bufsz = %i, appbufsz = %i, round = %i\n", + p->par.bufsz, p->par.appbufsz, p->par.round); - return 0; + p->delay = 0; + p->playing = false; + if (!sio_start(p->hdl)) { + MP_ERR(ao, "start: sio_start() fail.\n"); + goto err_out; + } -error: - if (p->hdl) - sio_close(p->hdl); + return 0; +err_out: + uninit(ao); return -1; } -/* - * close device - */ static void uninit(struct ao *ao) { struct priv *p = ao->priv; - if (p->hdl) + if (p->hdl) { sio_close(p->hdl); + p->hdl = NULL; + } + p->pfd = NULL; + p->playing = false; +} + +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct priv *p = ao->priv; + float *vol = arg; - free(p->pfd); + switch (cmd) { + case AOCONTROL_GET_VOLUME: + if (!p->havevol) + return CONTROL_FALSE; + *vol = p->vol * 100 / SIO_MAXVOL; + break; + case AOCONTROL_SET_VOLUME: + if (!p->havevol) + return CONTROL_FALSE; + sio_setvol(p->hdl, *vol * SIO_MAXVOL / 100); + break; + default: + return CONTROL_UNKNOWN; + } + return CONTROL_OK; } -/* - * stop playing and empty buffers (for seeking/pause) - */ static void reset(struct ao *ao) { struct priv *p = ao->priv; if (p->playing) { - MP_WARN(ao, "Blocking until remaining audio is played... (sndio design bug).\n"); - p->playing = false; - if (!sio_stop(p->hdl)) - MP_ERR(ao, "reset: couldn't stop\n"); +#if HAVE_SNDIO_1_9 + if (!sio_flush(p->hdl)) { + MP_ERR(ao, "reset: couldn't sio_flush()\n"); +#else + if (!sio_stop(p->hdl)) { + MP_ERR(ao, "reset: couldn't sio_stop()\n"); +#endif + } p->delay = 0; - if (!sio_start(p->hdl)) - MP_ERR(ao, "reset: couldn't start\n"); + if (!sio_start(p->hdl)) { + MP_ERR(ao, "reset: sio_start() fail.\n"); + } } } -/* - * play given number of samples until sio_write() blocks - */ -static int play(struct ao *ao, void **data, int samples, int flags) +static void start(struct ao *ao) { struct priv *p = ao->priv; - int n; - n = sio_write(p->hdl, data[0], samples * ao->sstride) / ao->sstride; - p->delay += n; p->playing = true; - /* on AOPLAY_FINAL_CHUNK, just let it underrun */ - return n; -} - -/* - * make libsndio call movecb() - */ -static void update(struct ao *ao) -{ - struct priv *p = ao->priv; - int n = sio_pollfd(p->hdl, p->pfd, POLLOUT); - while (poll(p->pfd, n, 0) < 0 && errno == EINTR) {} - sio_revents(p->hdl, p->pfd); + process_events(ao); } -/* - * how many samples can be played without blocking - */ -static int get_space(struct ao *ao) +static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *p = ao->priv; + const size_t size = (samples * ao->sstride); + size_t rc; + + rc = sio_write(p->hdl, data[0], size); + if (rc != size) { + MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n", + size, rc); + reset(ao); + p->playing = false; + return false; + } + p->delay += samples; - update(ao); - - int samples = p->par.bufsz - p->delay; - return samples / p->par.round * p->par.round; + return true; } -/* - * return: delay in seconds between first and last sample in buffer - */ -static double get_delay(struct ao *ao) +static void get_state(struct ao *ao, struct mp_pcm_state *state) { struct priv *p = ao->priv; - update(ao); - - return p->delay / (double)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) -{ - return; + process_events(ao); + + /* how many samples we can play without blocking */ + state->free_samples = ao->device_buffer - p->delay; + state->free_samples = state->free_samples / p->par.round * p->par.round; + /* how many samples are already in the buffer to be played */ + state->queued_samples = p->delay; + /* delay in seconds between first and last sample in buffer */ + state->delay = p->delay / (double)p->par.rate; + + /* report unexpected EOF / underrun */ + if ((state->queued_samples && + (state->queued_samples < state->free_samples) && + p->playing) || sio_eof(p->hdl)) + { + MP_VERBOSE(ao, "get_state: EOF/underrun detected.\n"); + MP_VERBOSE(ao, "get_state: free: %d, queued: %d, delay: %lf\n", \ + state->free_samples, state->queued_samples, state->delay); + p->playing = false; + state->playing = p->playing; + ao_wakeup(ao); + } else { + state->playing = p->playing; + } } -#define OPT_BASE_STRUCT struct priv - const struct ao_driver audio_out_sndio = { - .description = "sndio audio output", .name = "sndio", + .description = "sndio audio output", .init = init, .uninit = uninit, .control = control, - .get_space = get_space, - .play = play, - .get_delay = get_delay, - .pause = audio_pause, - .resume = audio_resume, .reset = reset, + .start = start, + .write = audio_write, + .get_state = get_state, .priv_size = sizeof(struct priv), }; |